Upgradeable Ethereum Contracts V2

Written by Nico_Vergauwen | Published 2018/12/05
Tech Story Tags: ethereum | smart-contracts | solidity | upgradeable-contracts | ethereum-contracts-v2

TLDRvia the TL;DR App

Getting the most out of delegatecall

Looking for the code?I’m a blockchain architect and developer at Design is Dead, check us out!

A couple of months ago I wrote my first blog post about upgradeable smart contracts on the Ethereum blockchain. We explained that once a smart contract has been deployed onto the blockchain it cannot be changed.

Wait? Not being able to upgrade code after launching it? That seems like a bad idea, especially if you’re used to an agile development cycle. And while guaranteed code execution can be a benefit, when there is a bug it is the total opposite. Bugs in smart contracts have caused millions of dollars in losses and even gave birth to Ethereum as we know today, after one of the most popular smart contracts in the early days, the slock.it DAO, was found to have a vulnerability that allowed an attacker to drain all funds in the contract (re-entrancy attack).

https://medium.com/swlh/the-story-of-the-dao-its-history-and-consequences-71e6a8a551ee

First Version

In our last blog post on this subject, we explored a strategy based on the five-types-model that was conceived by Monax (now Hyperledger Burrow) before Solc was even released, so it’s pretty old in the ethereum world. This model splits storage and logic into separate contracts and has a registry to keep track of the individual parts.   This version becomes expensive to deploy, especially if we instantiate the same contract over and over again. This is similar to instantiating classes in traditional programming but contract deployment on the Ethereum blockchain costs real money, so we don’t want to spend more than necessary.

Proxy Libraries

The five-types-model was okay to start with but wasn’t optimal. With proxy libraries, our base contract is a library already deployed on the blockchain. When we create an instance of this, we will deploy a contract that forwards its calls to the address that is specified. If we want to update some function logic in the library we would redeploy the library and point our contracts to this new library. Even with this strategy, there’s quite a bit of code overhead to make it happen. Eventually, our goal was to limit the code of our instantiated contracts to only one function, a forwarding function.

Upgradeable V2

A little over a year ago Vitalik released a Reddit post regarding the subject of forwarding contracts. The issue was that pre-metropolis (when the post was written), the output size of forwarded functions could not be dynamic and was restricted to 4096 bytes. That issue was fixed in Metropolis which allows us to properly implement this type of forwarding now.   Contract calls are forwarded using a low-level version of DELEGATECALL (although in solidity 0.5.0, the high-level version also returns data now). Delegatecall forwards a call to another contract and allows that contract to change the storage of the delegator while keeping the original message object.

Delegatecall isn’t very well documented. The documentation says that it takes the context of the calling contract (message object and storage) and uses that in the forwarded call. One undocumented feature is that if the forwarder has no storage definitions of itself in the contract, it will still assume the definitions of the master contract with its own values after initialization as long as the storage order matches.

Let’s make it more clear with a code example (thanks to the guys from Origin Protocol for pointing me to this method)

Example

For starters, we have a master contract. It has an unsigned integer set in storage called count and two functions. One to increment count by one, and one to retrieve the variable.

DummyMaster.sol

Next up we have the contract that will serve as the instance of DummyMaster.sol , note that both contracts start with address impl because the contracts need to follow each other’s storage order. In the constructor of Dummy.sol we set the address of DummyMaster.sol .

Dummy.sol

After compiling, Dummy.sol has no ABI that matches DummyMaster, in order to easier interact with the contracts we can write an interface contract that is not deployed. This step isn’t necessary but it’s easier to create the calldata this way by instantiating the interface at the proxy address (see test.js).

DummyInterface.sol

test.js

Let’s test our new architecture! First deploy the master, then deploy the proxy a couple of times and pass the master address in the constructor. In our test, we deploy the proxy three times. We can test that each proxy has separate storage, the count in all instances must equal 1 if we were using the same contract everytime the count would be 3.

And that’s it, a really light method to write upgradeable smart contracts! One caveat is that transactions to these contracts are slightly more expensive, therefore it’s not a good idea to use this with a monolith contract that holds all the data, like ERC20 token contracts.

On the other hand, contracts that are per-user or between a limited set of users, like an escrow, bounty or identity claims registry would greatly benefit from this.

The full code can be found on my Github! Thanks for reading!

I’m a blockchain architect and developer at Design is Dead, check us out!

Learn smart contract development


Published by HackerNoon on 2018/12/05