Contract Upgradability Patterns in Solidity

The Blockchain Immutability

One of the major benefits of the blockchain is its immutability. Providing an immutable, timestamped, and unforgeable record of data. In the Ethereum blockchain, smart contracts, most often programmed in Solidity, are themselves stored on the blockchain, and hence are immutable. A smart contract can modify as part of a transaction its storage, with the new update contract state stored in the blockchain. However, the contract itself cannot be modified.

This immutability of smart contracts means that in contrast to other code, contracts cannot be updated and upgraded. So the common practice of software update and upgrade cannot be applied to a smart contract per se.

Naturally, bugs, improvements, and changing requirements would need some way to modify the collection of contracts comprising a blockchain application. Such a blockchain application can be either a pure blockchain application or more combined web and blockchain application commonly called a Dapp (decentralized app).

Approaches for Contract Upgrade

Three approaches for contract upgrade have been proposed in general. The article Essential Design Considerations for Ethereum ÐApps (1): Upgradeable Smart Contracts summarizes these approaches well. In practice, all these approaches are to be taken together, either with manual coding or in constructing a smart contract upgrade framework.

Modularization of Contracts

Both for upgradability and for following good software engineering practices, separate the data and the logic of a contract.

For each significant contract, split the contract logic, both the business logic and the authorization (in the form of access modifiers) into separate contracts.

Such an approach would enable a separate upgrade of the data and the logic.

Migration of Data

Pull the data from the old contract, write it into the new contract, and just forget the old contract.

Such an approach requires stopping the old contract with a circuit breaker function. However, such an approach is clearly problematic for a Dapp, like shutting an ordinary web application server, it leaves users hanging loose without the functionality.

Wrap the Contract with a Proxy

Each smart contract is wrapped by a proxy for upgrades. Thus, we always deploy two contracts. The first contract is a simple wrapper or "proxy" which users interact with directly and is in charge of forwarding transactions to and from the second contract, which contains the logic. The key concept to understand is that the logic contract can be replaced while the proxy or the access point is never changed. Both contracts are still immutable in the sense that their code cannot be changed, but the logic contract can simply be swapped by another contract. The wrapper can thus point to a different logic implementation and in doing so, the software is "upgraded".

The basic pattern take from Essential Design Considerations for Ethereum ÐApps (1): Upgradeable Smart Contracts is:

contract Relay {

    address public currentVersion;

    address public owner;

      modifier onlyOwner() {

        require(msg.sender == owner);

        _;

    }

    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() {

        require(currentVersion.delegatecall(msg.data));

    }

}

The proxy pattern has been generally accepted by the community as a generally applicable solution.

The ZeppelinOS Upgrade Pattern

ZeppelinOS is a platform to develop, deploy and operate smart contract projects on Ethereum and every other EVM and eWASM-powered blockchain.

ZeppelinOS Upgrades Pattern generalizes the proxy wrapper idea to be an integral part of a whole development framework, that includes a variation of the well-known Truffle Framework rewritten to support upgradability.

The basic idea is:

User ---- tx ---> Proxy ----------> Implementation_v0

                     |

                      ------------> Implementation_v1

                     |

                      ------------> Implementation_v2

Proxy Forwarding

Forwarding the calls from the proxy to the contract is done as:

assembly {

  let ptr := mload(0x40)



  // (1) copy incoming call data

  calldatacopy(ptr, 0, calldatasize)



  // (2) forward call to logic contract

  let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)

  let size := returndatasize



  // (3) retrieve return data

  returndatacopy(ptr, 0, size)



  // (4) forward return data back to caller

  switch result

  case 0 { revert(ptr, size) }

  default { return(ptr, size) }

}

This code can be put in the fallback function of a proxy and will forward any call to any function with any set of parameters to the logic contract without it needing to know anything in particular of the logic contract's interface.

Structuring Storage

Using a delegatecall means the call is executed in the context of the proxy contract. Hence, ZeppelinOS uses a novel unstructured storage approach to make sure the implementation contract storage does not override the proxy contract storage.

Special precautions in terms of storage layout are suggested to make sure an upgrade is possible.

The 2key Network Approach to Upgrade

2key Network is a blockchain-based referral network intended to reward referrers through smart contracts. Referrals progress through the network thanks to people sharing them through their regular browsers. Therefore, 2key generates off-chain cryptographically signed links, which propagate among users without reaching the blockchain. Upon conversion, a user submits the signed link to the smart contract. The smart contract rewards the chain of referrers as represented in the signed link. The smart contract is deployed per referral campaign by the campaign initiator, depositing a referral reward, as an amount of cryptocurrency, from which the smart contract will later pay the referrers.

Permanent vs Ephemeral Contracts

2key Network collection of smart contracts is broadly divided into two parts:

  • Permanent Contracts: These contracts manage the whole ecosystem. This includes the upcoming ICO of the 2Key Token which is to be an ERC20 type of token, as specified in the ERC20 Token Standard, and the 2key governance, to be modeled after the well-known How to build a DEMOCRACY on the blockchain guide of Ethereum. But the novel contributions of 2key Network in terms of a referral reputation model and incentive model are also smart contracts.

  • Ephemeral Contracts: The contract of a campaign is intended for the duration of the campaign and that's it. Business-wise a campaign implements a pre-agreed logic that is not going to change. However, such a contract will include references and hence calls to the permanent contracts.

Upgrade the Persistent Contracts

It may be projected how ERC20 types of contracts and governance contracts may evolve, and build special upgrade mechanisms into these. But, as 2key Network develops novel economic incentive mechanisms to be implemented in its reputation model and incentive model, it is hard to foresee the corresponding contracts are going to evolve.

Consequently, we intend to implement the proxy pattern as our mechanism for the upgrade of the persistent contracts.

Throw the Ephemeral After Use

The campaign contract is created for a campaign, and as part of the conversion process, creates an escrow contract to temporarily hold a tokenized asset purchased by the converter.

Examining the whole collection of contracts around a campaign, these are all deployed from our Dapp for a campaign. So since business-wise this collection of contract deployed per campaign cannot change, we do not have a need to upgrade this collection.

These ephemeral contracts obtain while being constructed a reference to the persistent contracts, but this will be a reference to the corresponding proxies.

Conclusion

We believe the 2key Network split into persistent and ephemeral contracts is a good design approach for Dapps in general. Especially for mature scalable Dapps. This persistent core and ephemeral parts split is a novel practice that is not encountered even in ordinary software. It is clearly distinct from the well-known core vs plugins approach.