Upgradable Solidity Contract Design

Eternal Storage + Hub and Spoke Topology = ♥

Ethereum contracts are an amazing invention; autonomous immutable code that can act as judge, jury and executioner for a plethora of different services. Once deployed to the Ethereum blockchain though, they are essentially set in stone. This means if a serious bug or issue appears and your contracts aren’t designed in a way that will allow them to be upgraded in your Dapp seamlessly, you might be glad you bought that 5 pack of brown underpants.


260 Million Reasons

There’s a treasure trove of reasons, hacks and careless coding that has led to the loss of over $260 million worth of ether in recent years. Two of the most prominent were the DAO hack which lost approximately $60 million and the much more recent Parity Multisig Wallet hack that resulted in approximately $162 million worth of ether and tokens being stuck in limbo for a possible eternity on the blockchain.

Not all of these incidents could have been prevented by making the contracts upgradable as some of them were only detected after the damage had been done. The biggest one though and most recent, the Parity Multisig Wallet hack, certainly could have been fixed in no time had it not been for this hard coded library contract address in the multisig wallet at line 451

// FIELDS
address constant _walletLibrary = 0x863df6bfa4469f3ead0be8f9f2aae51c91a907b4;

Yeouch! Without any way to modify the address of that library contract, all other contracts that rely on it are now effected by any issue with it… and that’s exactly what happened.

address _walletLibrary = 0x863df6bfa4469f3ead0be8f9f2aae51c91a907b4;
/// @dev Set a new wallet library contract address
function setLibraryAddress(address _newAddress) external onlyOwner {
_walletLibrary = _newAddress;
}

Adding just one extra function to the multisig wallet that lets the owner of the wallet update the address of that library contract would have made the issue just a mere squeak instead of the lions roar it turned into.


Our Approach

While designing and building Rocket Pool a complex multi-contract Dapp, there were two main design choices that were made early on.

All main contracts must be upgradable.
Have a flexible, yet simple way to store data permanently.

The first one is fairly self-explanatory, all contracts must be upgradable within the network. If we have an issue with one contract, its address needs to be editable so that we can replace the bugged contract with a new copy of it sans bugs and all other contracts in the Dapp must now communicate with the new version. Ok that sounds great! But what a lot of new developers don’t realise is that any data that bugged contract was storing on itself is now gone…

Imagine if you had a contract called Users.sol that maintains a list of all the users that sign up to your Dapp, but it turns out Users.sol has a major bug, your developer Harry rushes to replace that bugged contract with a new one and in doing so, he deploys a new fixed Users.sol and updates the address of it so that all contracts in your Dapp now talk to the fixed contract. Harry is super happy, proud he fixed it so quick and soon to be wishing he also bought a 5 pack of brown underpants.

As what Harry is soon to realise is that contract storage cannot be so easily replaced, he’s just fixed the bug in Users.sol code and all contracts in your Dapp now talk to the new fixed contract, but all records of their users in the Dapp are gone, instead stuck in the old contracts storage… oops.

The second goal is then to create a way to isolate your datastore from your contracts and make it as flexible as possible so that it is unlikely to ever warrant upgrading.


Eternal Storage

While researching various storage techniques for the Ethereum blockchain, I came across an article by Elena from Colony on Eternal Storage. This is a relatively old article now, but this article details building a simple single contract that stores all permanent data that your Dapp needs. Many of you may recognise this storage technique as key / value pair storage, a simple and extensible way to store any kind of data from simple values, to arrays and complex object type data.

When Elena wrote this article, it was not possible for another contract to use a few of the methods in her storage contract, such as:

function setStringValue(bytes32 record, string value)

This was due to the fact that sending data with a variable length (strings, arrays) was not possible at the time between contracts. With the most recent hardfork that occurred just recently in October as phase one of the Metropolis upgrade to the Ethereum network, this is now possible.


Rocket Storage

Below is an updated version of the original Eternal Storage that is now used for storing any persistent data in Rocket Pool.

Rocket Pools Eternal Storage Contract

To keep things simple, there is a single modifier that restricts access to the set and delete methods. It will only allow the owner of the storage contract to access these methods directly to set some initial contract addresses during deployment, after deployment their access is removed to ensure only visible contract methods can write to storage. From then on, only registered contracts within the Rocket Pool network can write to storage.

You could modify this to suit whatever needs you have with restricting access to who/what can write or delete from storage. If you were to add more than one though, I would look at adding an access content layer contract that contained the required permissions so that the storage contract remains as simple as possible, it should after all, never need upgrading.

Isolating this to its own simple contract means that you can upgrade any other contract in your Dapp and retain any storage you need by storing it in your datastore contract.


Hub and Spoke Topology Design

Alright, we’ve now got a dedicated persistent storage contract, woohoo! The next step is to use that storage contract as a hub in our Dapp. If you’ve ever been in the networking world, you will no doubt be familiar with the hub and spoke topology. This is a technique where all communications are routed through a single entity. Since our storage contract will never need upgrading, we always know where it will be, so let’s use that as our Dapps contract hub.

All contract to contract communications are done through the main storage hub.

Now let’s say we have a Dapp with 3 contracts, Users.sol, Organisations.sol and Storage.sol. The Users.sol contract will now store the Dapps users on Storage.sol, not itself like in the example with poor Harry. This means if it is upgraded, all current users will remain in the Dapp and not be lost on the buggy contract.

Users.sol will also need to access the Organisations.sol contract periodically to find out what organisation a particular user belongs to. Instead of the User.sol contract communicating directly with the Organisation.sol contract in a standard approach, it will now ask the Storage.sol (Hub) contract for the address of the Organisation.sol contract, when it receives this address, it then communicates with the Organisation.sol contract using the address provided by Storage.sol.

What this does is it allows us to replace the address of the Organisation.sol contracts address any time it needs upgrading, when the new upgraded contract is deployed, we simply update the address in the Storage.sol (Hub) with the new address of the upgraded Organisation.sol and the next time the User.sol contract needs to communicate with Organisation.sol, it will get the address of the newly upgraded contract. This allows for a seamless upgrade, no brown underpants needed.


How to store and read contract addresses in the hub

It’s quite simple to store a contract address in the hub, it’s just a storage contract after all. When Rocket Pool is deployed through truffle, it automatically registers all contract addresses in the network with the hub upon deployment. Below is an example contract of how to register a contract name and address with the hub + how to read the address of another contract from the hub before communicating with that contract.

Example Solidity contract for setting a contract address and reading one from storage.

Benefits

There’s many benefits to creating a storage and hub system like this for your Dapp, the two biggest benefits in my eyes are:

  • Flexibility. You can add whole new contracts to your Dapp with various data types and structures that can all be stored in the storage contract without ever needing to upgrade it.
  • Security / Upgrades. If bugs are found, you can whip out the old contract, deploy a new one without losing any persistent storage and that new contract will automatically be used instantly by every contract in your Dapp.
  • Underpants. You might not need to buy anymore 5 packs of brown underpants for deploying / upgrading Dapps. Unless you really like brown underpants, then knock yourself out.

Drawbacks

Of course no solution is perfect when it comes to these types of new age platforms. The main drawbacks we’ve encountered so far are:

  • Contract size on deployment will increase due to hashing of data required by the storage contract. This will be an issue when deploying contracts, especially really big contracts that are already near the gas block limit.
  • Extra calls. Since inter-contract communication is now done through the hub, this results in an extra call. Same for storing and retrieving general data from the storage contract.
  • Complexity. It will add some extra complexity to your Dapp.

I never intended to make a graphic of a 5 pack of underpants, and yet here we are…

Conclusion

There are loads of ways to create upgradable contracts in Ethereum, this is just our favourite and a combination of two existing techniques that we’ve found through our experience to be really great at providing a simple, flexible and upgradable path for any Dapp. We’ve only touched on the surface here of whats possible, for a much more thorough view, please checkout the Rocket Pool Github.