Ethereum contracts are immutable — once deployed to the blockchain they cannot be updated, yet the need to change their logic with time is ultimately necessary. This article presents one way to implement upgradeable Ethereum contracts.
During a contract upgrade the following factors need to be considered:
- Block gas limit (4712388 for Homestead)Upgrade transactions tend to be large due to the amount of processing they have to complete e.g. deploy a contract, move data, move references.
- Inter-contract dependencies — when a contract is compiled, all of its imports are compiled into the contract thus leading to a ripple effect when you want to swap out a contract which is being referenced by other contracts.
These two are related, as having more dependencies affects the size of your deployed contracts and the overall transaction size of the upgrade. The implementation patterns below work to minimise the upgrade gas costs as well as loosening the coupling of contracts without breaking Solidity type safety.
Note that for the sake of simplifying the examples, we have omitted the implementation of security and permissions.
Avoid large data copy operations
Storing data is expensive (SSTORE operation costs 5000 or 20000 gas, http://gavwood.com/Paper.pdf) and upgrading contracts containing large storage variables runs the chance of hitting the transaction gas limit during the copying of its data. You may therefore want to isolate your datastore from the rest of your code, and make it as flexible as possible, so that it is unlikely to need to be upgraded.
Depending on your circumstances, how large of a datastore you need and whether you expect its structure to change often, you may choose a strict definition or a loosely typed flat store. Below is an example of the latter which implements support for storing a sha3 key and value pairs. It is the more flexible and extensible option. This ensures data schema changes can be implemented without requiring upgrades to the storage contract.
For upgrades you can then just switch the upgraded contract to point to the new EternalStorage contract instance without having to copy any of its data.
Use libraries to encapsulate logic
Libraries are a special form of contracts that are singletons and not allowed any storage variables. http://solidity.readthedocs.io/en/latest/contracts.html#libraries
The advantage of libraries in the context of upgrades is that they allow encapsulation of business logic or data management logic (which more frequently change) into singleton instances that cost only upgrading one and not many contracts.
Example below shows a library used for adding a Proposal to storage.
Sample library used for adding Proposals to storage
Under the cover, library functions are called using delegatecall from the calling contract which has the advantage of passing the msg.sender and msg.valueseamlessly. You can therefore write your library code as if it were just part of your contract, without having to worry about the sender or value changing.
The example below shows a sample Organisation contract using ProposalsLibrary to interact with data storage.
Sample Organisation contract calling library functions
With libraries, there is a slight gas overhead on each call. However, it makes deploying a new contract much cheaper. In the case of Organisation, deployment cost has dropped by 10% :
(before / after) library implementation creating an Organisation : 1179032 / 1048206 creating a Proposal : 71964 / 72599
Use ‘interfaces’ to decouple inter-contract communication
Abstract contract implementation behind an interface that only defines its function signatures.
This is a well known pattern in object oriented programming so if you’ve done any .NET or Java you’ll be at home with this concept of abstraction. This is best shown by an example, consider the following use of :
Organisation contract working with a TokenLedger interface
Here instead of importing the entire TokenLedger.sol contract, we use an interface containing just the function signatures. This eases any possible upgrades to TokenLedger which don’t affect its interface, too, which with this model can be implemented without redeploying the Organisation (calling) contract.
The guiding principle in this refactoring was the Ethereum white paper ‘DAO’ section https://github.com/ethereum/wiki/wiki/White-Paper#decentralized-autonomous-organizations, which writes
Although code is theoretically immutable, one can easily get around this and have de-facto mutability by having chunks of the code in separate contracts, and having the address of which contracts to call stored in the modifiable storage.
Implementing permanent storage, encapsulating logic in library functions and abstracting via an interface are all in line with this principle where we use address pointers in the calling Organisation contract for interacting with storage and business logic. Full code samples of Organisation and its Parentcontract responsible for managing it can be found below.
The Parent contract is responsible for creating and upgrading Organisations.