A well tested guide to upgradeable proxy Ethereum smart contracts

Jack Tanner
Indorse
Published in
4 min readApr 13, 2018

--

At Indorse, we are delighted to have spent the time investigating upgradeable contracts to contribute this guide to the Ethereum space.

Our research started by surveying and summarising the different ways in which an upgradeable smart contract could be done. Three different 100% upgradeable strategies are possible and we chose to dive into the upgradeable proxy pattern to see its advantages and limitations, which you can see below.

Our next and most recent piece of work has been to conduct a thorough and comprehensive test suite of this upgrade pattern. We conducted over 90 tests, each one upgrading a simple smart contract in a different subtle way, to get intimate 💕 and confident 😎 with how it works. We hope that our test repository can be used by the community:

  • As a summary of what can and cannot be done in an upgrade (all upgrade tests are summarised in README.md).
  • As a set of upgrade examples for developers.
  • As a test to check that a version of a Solidity compiler does not break the upgradeable proxy pattern.

The repository: https://github.com/jackandtheblockstalk/upgradeable-proxy

Conclusions

  1. The upgradeable proxy pattern allows contracts to upgrade their function logic and add state storage 😁.
  2. The pattern requires very few changes to current contracts to work with this upgrade pattern.
  3. When creating an iteration (new version) of a contract you cannot change the order of previously defined state variables. This includes changing the orders of fields in a struct, adding new slots to a fixed sized array, or converting from a fixed to a dynamic sized array.
  4. New storage can be added to the upgraded contracts by defining new state variables after any previously defined state variables (appended). You do not need to use hashed key-value storage “Eternal storage” to allow for upgradeable storage as some projects seem to be doing. If you are one of these project then please comment/share why are you doing this.
  5. State variable initialisation with the contract’s constructor does not function as normal. A special initialize() function has been created to fulfil this feature.
  6. Using an inherited contract of the previous contract version is the simple and safest way to create an upgrade. See Section 3.2.3 of repository README.md.

To see the full list of what can and cannot be done with an upgradeable contract upgrade, check out the repository page here.

Let’s create an Upgradeable Contract

To get a better idea of what’s going on, let’s walk through how we deploy and use a simple upgradeable contract. To get a solid explanation of how the upgrade mechanism actually works check out this blog or the resources at the bottom here. The contract we are going to upgrade is called UintSimpleV1.

contract UintSimpleV1 is Upgradeable {
uint value;
function getValue() view public returns (uint) { return value; }
function setValue(uint _value) public { value = _value; }
}

Deploy the Upgradeable contract

1. Deploy UintSimpleV1.
UintSimpleV1.new()

2. Deploy Proxy with the address of the deployed UintSimpleV1 passed as a constructor argument.
Proxy.new(UintSimpleV1.address)

3. Now tell your application that UintSimpleV1 is at the address of the proxy.
UintSimpleV1.at(Proxy.address)

You can now make function calls to the upgradeable proxy contract.

UintSimpleV1.at(Proxy.address).setValue(10)
UintSimpleV1.at(Proxy.address).getValue()
> 10
Call logic for contract call and upgrade

Upgrade the contract

We will deploy a simple upgrade that will update the logic of setValue()

contract UintSimpleV2 is UintSimpleV1 {
function setValue(uint _value) public { value = 2*_value; }
}

1. Deploy the new upgraded contract.
UintSimpleV2.new()

2. Call upgradeTo() on the Proxy.
UintSimpleV1.at(Proxy.address).upgradeTo(UintSimpleV2.address)
or
Proxy.upgradeTo(UintSimpleV2.address)
Both will do the same thing

That’s it! You can now call the contract and see the new logic. The previous state is also still available.

UintSimpleV1.at(Proxy.address).getValue()
> 10
UintSimpleV1.at(Proxy.address).setValue(11)
UintSimpleV1.at(Proxy.address).getValue()
> 22

Yes it’s that simple. It does not require developers to restructure their contracts, but it does require developers to be aware of a few interesting abilities of upgradeable contracts, and what they can’t do.

If you are going ahead and thinking about using this pattern, please check the repository for a detailed outline of how they can be used.

The repository: https://github.com/jackandtheblockstalk/upgradeable-proxy

--

--