2

I have been looking at how to make my contracts upgradeable and looking through the Zeppelin docs and source code to try to wrap my head around this.

One thing I cannot work out is:

  • How do new logic contracts share data (when they have no reference to one another and are deployed in different transactions)?
    • From a security perspective what stops some bad actor from implementing a new logic contract and hijacking my data (it seems like Zeppelin deals with this with the ifAdmin modifier)?

For example, say I have contract MyLogicV1.sol:

contract MyLogicV1 {
  uint256 public foo;
  // ...
}

I now have v2 which has to ensure its data is ordered the same and only appends new data after existing data:

contract MyLogicV2 is MyLogicv1 {
  uint256 public foo;
  uint256 public bar;
  // ...
}

In the above MyLogicv1 will have a different address to the first MyLogicV2 deployed so the data will be different.

The test examples provided in the Zeppelin SDK do not really shed any light as they are all deployed in the same test meaning the contracts have references to one another. Realistically v1 is deployed and gets an address, v2 will be deployed some arbitrary amount of time later likely in a completely different transaction with no reference to the original contract.

To sum up how is v2 which is an upgrade on v1 see v1's data and how is this secured?

1

It might be cognitively useful to think of the proxy contract importing bytecode from the logic contracts for its own use because they execute in the context of the proxy and use the proxy for storage. I realize this is misleading about implementation details.

Proxy - execution context. data goes here
  |
  |
Implementation - logic 

After an upgrade, you get something like this:

Proxy - execution context. data still goes here
  |
  +-------------------------+
                            |
                Implementation V2 - new logic

The Proxy needs to know the address of the contract to use for implementation details. That will go in a state variable or come from a Registry and special care will be (must be!) taken to ensure that the Proxy's storage requirements don't collide with the storage layout of the Implementation.

One way V2 of the Implementation can avoid storage layout conflicts with V1 is to inherit V1 and then override functions, extend the storage layout as needed, and so on. In my opinion, this clearer and more reliable and explicitly states intent which might be easier than pouring over code and compiler optimization settings, etc., to make sure the layout is not altered in any way.

contract V2 is V1 {
  // describe changes
}

It might be best to start with a minimal approach that doesn't call for big changes in the way you approach contract design. This example of an implementation registry might be helpful: https://github.com/rob-Hitchens/TrustlessUpgrades

Also, try this explainer about the same approach: https://medium.com/hackernoon/trustless-upgrades-in-solidity-bf0bd4047d28

Hope it helps.

1

Upgradability in smart contracts is nice feature that gives you the option to upgrade your logic and keep the contract data safe. Let's take for example we have 2 contracts first one is where the data is located (state contract) and the second one is where the logic is located (proxy contract). The scenario is that the proxy contract is updating the data inside the state contract and all of the conditions and logic are located inside the proxy contract. The state contract contains the data and logic to accept requests only from the proxy contract. Example: State contract:

pragma solidity 0.5.11;

contract Ownable {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address _new_owner) public onlyOwner {
        require(_new_owner != address(0));
        owner = _new_owner;
    }
}

contract StateContract is Ownable {
    uint256 public contractVariable = 3;
    address public proxy_contract;

    // modifier that allows only proxy contract to make calls
    modifier onlyApprovedProxy() {
        require(msg.sender == proxy_contract);
        _;
    }

    function changeProxyAddress(address _proxy_contract) public onlyOwner returns(bool) {
        proxy_contract = _proxy_contract;
        return true;
    }

    function editContractVariable(uint256 _conrtactVariable) external onlyApprovedProxy returns(bool) {
         contractVariable = _conrtactVariable;
         return true;
    }

    function getContractVariable() public view returns(uint256) {
        return contractVariable;
    }
}

Proxy contract:

pragma solidity 0.5.11;

contract ProxyConract {
    address public stateAddress;
    StateContract state;

    constructor(address _stateAddress) public {
        stateAddress = _stateAddress;
        state = StateContract(stateAddress);
    }

    event logSuccessfulStateEdit(uint256 _conractVariable);

    function changeStateData(uint256 _stateData) public returns(bool) {
        require(state.editContractVariable(_stateData));
        return true;
    }

    function getStateData() public view returns(uint256) {
        return state.getContractVariable();
    }
}

interface StateContract {
    function getContractVariable() external view returns(uint256);

    function editContractVariable(uint256 _conrtactVariable) external returns(bool);
}

Now after some time if I notice bug or if I wan't to change my logic and conditions of how the data is being saved inside the state contract what I will do is I will deploy new ProxyConract and I will make make a request to method changeProxyAddress inside StateContract to update the proxy address with new deployed contract. This way the old proxy contract has no permissions anymore to update my data located at the state contract, but new proxy contract has the permissions now.

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.