1

I know that smart contracts are supposed to be immutable and that's the whole point, but to expect someone to implement a logic that never changes (no upgrade or no bugs) from day 1 is also unrealistic.
Therefore, I've been reading about several methods of working around this immutable state. A popular method seems to be using delegateCall with a Relay contract, but I'm struggling with how to actually use this method as I couldn't find any example.
Would someone be kind enough to look at that simple example I created, and tell me what I'm doing incorrectly?
https://gist.github.com/fabdarice/d513d620d9355312d085c7a68e6c6118
Relay.sol

contract Relay {
  address public currentVersion;
  address public owner;
  mapping (address => uint) user_amounts;


  modifier onlyOwner() {
    if (msg.sender != owner) {
        throw;
    }
    _;
  }

  function Relay(address initAddr) {
    currentVersion = initAddr;
    owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner
  }

  function changeContract(address newVersion) public
  onlyOwner()
  {
    currentVersion = newVersion;
  }

  function() {
    if(!currentVersion.delegatecall(msg.data)) throw;
  }
}  

Donation.sol :

contract Donation {
  mapping (address => uint) user_amounts;    


  /* DOES THIS METHODS MODIFY user_amounts of the Relay contract ??? */
  function sendDonation(uint n) {
    user_amounts[msg.sender] = user_amounts[msg.sender] + n
  }
}  

DonationNew.sol :

contract DonationNew {
  mapping (address => uint) user_amounts;

  function sendDonation(uint n) {
    user_amounts[msg.sender] = user_amounts[msg.sender] + n
  }

  function cancelDonation() {
    user_amounts[msg.sender] = 0
  }
}  

app.js :

// First, deploying Relay, then deploying Donation and retrieve Donation contract address in 'donation_contract_address'

// Then, linking Relay to my first version of my contract Donation 
Relay.deployed().then(function(contractInstance) {
   contractInstance.changeContract(donation_contract_address);
})

// Then, I want to call sendDonation from the Donation contract
// !!!!! I DON'T KNOW WHAT IS THE CORRECT WAY TO CALL THIS !!!!!!
Relay.deployed().then(function(contractInstance) {
   contractInstance.sendDonation(5) ;
})
// OR 
Relay.deployed().then(function(contractInstance) {
   contractInstance.currentVersion.delegateCall(sendDonation(5)) ;
})

// Now I want to update the Donation contract to add the cancelDonation function
// First I deploy the new contract DonationNew and retrieve it's address in 'donation_new_contract_address'
Relay.deployed().then(function(contractInstance) {
   contractInstance.changeContract(donation_new_contract_address);
})

// are the state variables still available from the old contract to the new one?

// Then if I want to call the new function : 
Relay.deployed().then(function(contractInstance) {
   contractInstance.cancelDonation() ;
})

P.S.: I know that this method above allow us to create an "upgradable contract" in the case we need to update our logic (functions etc..), however it doesn't allow us to modify/add our state variables structure. Is there a workaround this as well?

Thanks a lot, happy to be part of this community !

2 Answers 2

0

This is just an answer to your P.S. , since i'm not well versed in Solidity yet to try and find what's wrong with your code .

If you want the ability to upgrade code, while keeping storage, you could think of separating storage and logic. Have a dedicated storage contract, which accepts write calls from trusted addresses (e.g. the logic contracts). All important storage should be associated with this one.You should also look into making it as flexible as possible, so that it is unlikely to need to be upgraded.

This article contains an example as well as plenty other suggestions for writing upgradable smart contracts:

https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88

0

Here's an example for upgradeable libraries.

https://github.com/kyriediculous/knuckles/tree/master/contracts/contracts

I'm currently working on cutting it down.

There's a central registry to keep track of library addresses being used. Changing these changes the address where the proxies delegate to.

The proxy is linked to the storage through an interface of the library. This constructs the calldata to send it to the proxy which then delegates it to the library.

If you want to upgrade the library you redeploy it and change the address in the central registry.

Here's a cut down example , blog post coming soon.

pragma solidity ^0.4.23;

contract Registry {
  mapping (bytes32 => address) public libraries;
  mapping (bytes32 => address) public contracts;

    function addLibrary(bytes32 _name, address _lib) external {
    require(libraries[_name] == address(0), "LIBRARY_ALREADY_EXISTS");
    require(_lib != address(0), "INSERT_VALID_LIBRARY_ADDRESS");
    libraries[_name] = _lib;
  }

  function addContract(bytes32 _name, address _contract) external {
    Enabled(_contract).setCMCAddress(address(this));
    contracts[_name] = _contract;
  }
}

interface ContractProvider {
    function libraries(bytes32 _name) external view returns (address);
    function contracts(bytes32 _name) external view returns (address);
}

contract Enabled {
  address public CMC;
  function setCMCAddress(address _CMC) external {
    if (CMC != 0x0 && msg.sender != CMC) {
        revert();
    } else {
        CMC = _CMC;
    }
  }
}

contract setXproxy is Enabled {
  function () payable public {
    address _impl =  ContractProvider(CMC).libraries('setXlib');
    require(_impl != address(0));
    assembly {
      let ptr := mload(0x40)
      calldatacopy(ptr, 0, calldatasize)
      let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
      let size := returndatasize
      returndatacopy(ptr, 0, size)
      switch result
      case 0 { revert(ptr, size) }
      default { return(ptr, size) }
    }
  }
}

library setXinterface {
    struct X {
        uint x;
    }

    function setX(X storage _X, uint _x) external;
}

library setXlib {
    function setX(setXinterface.X storage _X, uint _x) external {
        _X.x = _x;
    }
}

contract setXstorage is Enabled {
    using setXinterface for setXinterface.X;
    setXinterface.X X;
    function setX(uint _x) external {
        X.setX(_x);
    }

    function getX() external view returns (uint) {
      return X.x;
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.