1

I want to build a truly upgradable contract. I want the child contract to only implement state and then forward all functional requests to a main implementation contract. So the scheme should look like this:

pragma solidity 0.6.2;

contract Master {
    mapping(address => uint) public balances;
    address public implementation;

    function setBalance(address _address, uint _balance) public {
        balances[_address] = _balance;
    }

    function getBalance(address _address) public view returns (uint) {
        return balances[_address];
    }


}

contract Child {
    mapping(address => uint) public balances;
    address public implementation;

    function setImplementation(address _address) public {
        implementation = _address;
    }

    fallback() external payable {
        // call implementation with function
    }
}

As can be seen in this very simple example the state representation is the same which is the requirement for the delegatecall method. The main functions are all implemented in the Master and the child simply contains the state and a function to update the implementation. (Disregard the security issues here).

In this example I want to be able to call Child.setBalance. Is it even possible to forward all function requests to the master implementation?

I can't find any information on this in the documentation.


My reasoning for this:

I am currently building a collection of contracts that are rather expensive to deploy. So I want to design a system where the user only deploys the minimal Child implementation and then chooses which version of the current implementation he wants to use. This allows truly upgradable contracts that can introduce new functionality on the same state after some time. So this is different from using libraries.


Solution:

For anyone interested. The solution was to use the OpenZeppelin Proxy contract. I updated it to fit 0.6.x solidity compiler versions:

pragma solidity 0.6.2;

contract Master {
    mapping(address => uint) public balances;
    address public implementation;

    function setBalance(address _address, uint _balance) public {
        balances[_address] = _balance;
    }

    function getBalance(address _balance) public view returns (uint) {
        return balances[_balance];
    }


}

contract Child {
    mapping(address => uint) public balances;
    address public implementation;

    function setImplementation(address _address) public {
        implementation = _address;
    }


    fallback () payable external {
        _fallback();
    }

    function _fallback() internal {
        _delegate(implementation);
    }


   function _delegate(address _implementation) internal {
    assembly {
      // Copy msg.data. We take full control of memory in this inline assembly
      // block because it will not return to Solidity code. We overwrite the
      // Solidity scratch pad at memory position 0.
      calldatacopy(0, 0, calldatasize())

      // Call the implementation.
      // out and outsize are 0 because we don't know the size yet.
      let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)

      // Copy the returned data.
      returndatacopy(0, 0, returndatasize())

      switch result
      // delegatecall returns 0 on error.
      case 0 { revert(0, returndatasize()) }
      default { return(0, returndatasize()) }
    }
  }

}
1

You can simply view the code of upgradable contracts already deployed to mainnet:

| improve this answer | |
  • Just checked and they all use previous solidity versions. I fixed it by turning calldatasize to calldatasize(). Same for gas and returndatasize(). – Chris Mar 29 at 12:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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