How to write upgradable smart contracts in solidity !

While working on quillplay platform (https://quillhash.com/quillplay) at quillhash we are giving most of the time to research about best security practices in smart contracts.An approach to upgrade contracts must be in the armor to prevent damage made by programming bugs after contract got deployed.

The Topic of upgradeable contracts is not very new to the world of ethereum.There are some different approaches to upgrade smart contracts.

Some approaches we considered in development are:-

  1. Separate logic and data.
  2. Partially upgradable smart contracts system.
  3. Separate logic and data in key value pairs.
  4. Eternal storage with proxy contract

With first three approaches the contract can be updated by pointing users to use the new logic contract (through a resolver such as ENS) and updating the data contract permissions to allow the new logic contract to be able to execute the setters.In forth approach we don`t need to do this redirection and its is very flexible approach to update smart contracts.We found that eternal storage with proxy contract approach is flawless till now.

”Readers are most welcome to comment if you know any flaw in this approach.It will be very helpful for developers community.”

There is a good reason for and against being able to update smart contracts.The good reason is all the recent hacks were based on programming error and could be fixed very easily if it was possible to upgrade those contracts.

However the ability to upgrade smart contracts after they got deployed is somewhat against the ethics and immuteablity of blockchain.People need to trust you that you are a good boy. One thing that might makes sense would be to have multi-sig-upgrades, where the “OK” from multiple people is necessary before a new contract is deployed and can access the storage.I think that it is storage records which needs to be immutable in blockchain.Logic must be improved with time as in all software engineering practices.No one can guarantee to develop bug free software in first version.So Upgradeable smart contracts with some upgrading governance mechanism can save many hacks.

In this post ,I will touch the upgrade mechanism and in follow up post i will try to come with best contract upgrading governance mechanism.

So lets start with implementation approach !!

  • The most important thing to consider when upgrading contracts is how to preserve the state of the original contract in the upgraded contract.
  • The state of the contract can be separated from the functionality of the contract. This approach allows multiple contracts to share the same state.
  • In this approach,a proxy contract will act as a immutable storage contract and delegate contract will contain the functionality.
  • The storage structure of both these contracts must be similar.
  • To upgrade the logic of contract we need to inform the proxy contract the address of new delegate contract .
  • When a transaction is sent to proxy contract ,it does not know about the specified function in the transaction.
  • Proxy contract will proxy the transaction to what we’ll refer to as a “delegate” contract (Which contains the functionality logic).This is done using the native EVM code,delegate call.
With delegate call,a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address.
When a proxy contract uses a delegate contract’s functionality, state modifications will happen on the proxy contract. This means that the two contracts need to define the same storage memory. The order that storage is defined in memory needs to match between the two contracts.

We will deploy these contracts initially :-

  1. key Storage Contract (Contains the shared state)
  2. Delegate contractV1 and Delegate contractV2
  3. Proxy Contract (Contains the delegate call functionality)

Key Storage contract :-

It contains a common storage for all storage state variables which will be shared among all versions of smart contract.It also contains getter and setter functions to update and get the value of state from delegate contract.

Key Storage contract can be consumed by any delegate contract via proxy contract once deployed.We cannot create new getter and setters once key storage got deployed so we need to consider this while designing the initial version of smart contract.

The best approach is to make mappings for every type of field in key storage contract.Where the key of mapping will be the the name of the key simply in bytes and value will of the type declared in mapping.

For ex:- mapping(bytes32 => uint)

Now we can use this mapping to set and get any integer value from delegate contract by calling key storage getter and setter function for uint type.For ex: we can set the total supply with the key “totalSupply” and with any uint value.

But wait something is missing,Now anyone can call our key storage contract getter and setter function and update the state of storage which is getting used by our delegate contract.So to prevent this unauthorised state change we can use the address of proxy contract as the key of mapping.

mapping(address => mapping(bytes32 => uint)) uintStorage
In our setter function:
function setUintStorage(bytes32 keyField,uint value) public {
uintStorage[msg.sender][keyField] = value
}

Now as we are using msg.sender address in setter function and only this state change will reflected in proxy contract state when it uses getter function to get the state.Similarly we can create other state mappings along with getter and setter functions as shown in the code below:-

Delegate contract :-

Delegate contract contains the actual functionality of dapp.It also contains a local copy of KeyStorage contract.In our dapp ,if we include a certain functionality and later we found a bug in deployed contract ,in that case we can create a new version of delegate contract.

In code below ,Delegate contract version 1 (“DelegateV1.sol”) is deployed.

After deploying DelegateV1 we noticed the number of owners can be set by any user .So now we want to upgrade the smart contract so that only owner of contract can set number of owners.

We cannot change the code of already deployed contract in ethereum.So obvious solution is to create a new contract and new contract too will contain a local copy of Key Value contract. Here we are creating DelegateV2.sol contract with onlyOwner modifier added.

Now we have created a new contract but the storage of previous contract is not available in new version of contract.So we can include a reference to actual keyStorage contract in every version of delegate contract .In this way every version of delegate contract shares a same storage.But one thing is not desirable here,we need to tell every user about the address of updated version of contract so that they can use updated contract.It sounds stupid.So we will not store actual copy of key storage contract in every version of delegate contract .To get a shared storage proxy contract comes in to the rescue , lets move on to proxy contract.

Proxy Contract:-

A proxy contract uses the delegatecall opcode to forward function calls to a target contract which can be updated. As delegatecall retains the state of the function call, the target contract’s logic can be updated and the state will remain in the proxy contract for the updated target contract’s logic to use. As with delegatecall, the msg.sender will remain that of the caller of the proxy contract.

A delegate can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address.
SO we just need to pass the address of new version of contract to proxy contract via upgradeTo function.

The code of proxy contract is quite complicated in fallback function as here low level delegate call assembly code is used.

Let break it down simply what is getting done in assembly code:-

delegatecall(gas, _impl, add(data, 0x20), mload(data), 0, 0);

In above function delegate call is calling code at “_impl” address with the input “add(data,0x20)” and with input memory size “mload(data)”,delegate call will return 0 on error and 1 on success and result of the fallback function is whatever will be returned by the called contract function.

In proxy contract we are extending StorageState contract which will contain a global variable to store address of keyStorage contract.

The order of extending storage state contract before ownable contract is important here.This storage state contract will be extended by our delegate contracts and all the functions logic executed in delegate contract will be from the context of proxy contract.The order of storage structure of Proxy contract and Delegate contract must be same.

Now user will always interact with dapp via same address of proxy contract and state of key storage contract seems to be shared among all versions of contract but in actual only proxy contract contains the reference to actual keyStorage contract.Delegate contracts contains local copy of keyStorage contract to get the getter ,setter functions logic and to have similar storage structure like proxy contract but actual storage changes are getting done from the context of proxy contract only.

Deploying and testing it together:-

Here output of test cases will be : 10 10 and 20

We are calling getNumberOfOwners() three times in test case.First to get the state change by DelegateV1 contract .Second time to get the state modified by DelegateV1 from DelegateV2 contract and we success fully managed to retain the state modified by DelegateV1 and third time to get the state modification done by DelegateV2 contract.

Note here that we are calling getNumberOfOwners() every time from the same address of proxy contract.So we successfully managed to update the functionality of our contract with out losing the previous state.

If we call setNumberOfOwners() from any other address except account[2] which is contract owner address,it will throw revert error.

Lets wind up the article with some diagrams:-

call upgrade to DelegateV2
At quillhash we are developing a platform quillplay to create secure and customized smart contracts templates for users with no programming experience.
https://quillhash.com/quillplay
I will try to come up with some upgrading governance mechanism in next post.

Thanks for reading :)

You can see the complete code here:-

https://github.com/Quillhash/upradeableToken.git

References:

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

https://medium.com/level-k/flexible-upgradability-for-smart-contracts-9778d80d1638

https://medium.com/cardstack/upgradable-contracts-in-solidity-d5af87f0f913

https://blog.zeppelinos.org/smart-contract-upgradeability-using-eternal-storage/

https://medium.com/rocket-pool/upgradable-solidity-contract-design-54789205276d