Essential Design Considerations for Ethereum ÐApps (1): Upgradeable Smart Contracts
Coming to one of the basic principles at Ethereum that is a Smart Contract cannot be modified after deployment. This is a great feature to create trust for Ethereum Tokens. But, Ethereum Smart Contracts for ÐApps are not like Ethereum Smart Contracts for Tokens. Software will continue to evolve and will need to be updated frequently. In addition, you have to be prepared for crisis-situations such that when a bug is discovered or an attack happen.
I will short list the 5 things that you need to consider for both ÐApps that run on Public or Private Ethereum network.
Considerations for Upgradeable Smart Contracts:
All those have to be planed from the start. You will need to design your smart contract taking in consideration of the following 5 points:
1.Keep your smart contracts modular by a fairly separation of rules and logic from data structure. Such that, if you will need to change something, you will change just the related contract. Hence, there will be no need to change many or all contracts. For example, you can save the data of people at some contract, while separating the logic and rules that control who can read, insert, update and delete, people’s data, at another smart contract.
You can check in the following example how the data is saved in PeopleRepository contract, while the control of who can access the data exists at ManagableAuthorization. The modifier “canRead” is used in this sample to have cleaner code:
As you can see in the previous code, if you later decided to change how the access to data is controlled, and you need to change the “read” permissions such that a reader is granted a read-access to a specific person not to all people, you will need to change only ManagableAuthorization smart contract. On the other hand, if you needed to add another field in the Person struct say for example “passportId”, you will change only PeopleRepository.
2. You should be prepared by having an emergency stop or circuit-breaker to be able to stop all operations during any migration. Because, you do not want to be in a situation where people can still update or insert data to the old version of the smart contract while you are performing migration, and thereafter. This is implemented simply using a private Boolean variable and a custom modifier(s) that depend upon. Check this sample copied from Smart Contracts Best Practice:
3. You should previously provide the ability to read all the data from smart contract. Of course, you can do a permissioned read by restricting reading all data to the owner or any other trusted user or even another smart contract. But what you need to consider is that you may need to read from the old version of your smart contract to be able to insert in the new version, specially when there will be a change in the data structure.
This is essential, particularly when you will use for example
mapping(address => Entity). Because, if you do not provide the ability to loop through all your address using another array, you will never be able to enumerate through all your
Entityobjects (like structs, contracts or any). As an example, I recommend checking this solid answer at ethereum.stackexchange.com. Where you can find under the title: “Mapped Structs with Delete-enabled Index”, the following sample:
throw is deprecated in favour of
revert(). In the previous sample, I suggest to replace
4. You will use one of the following strategies to communicate with your changeable address of the smart contract. Because, in solidity, every time you deploy a smart contract, it will be deployed to a different address.
The following is copied from Smart Contact Best Practices:
“Upgrading Broken Contracts
There are two basic approaches that are most commonly used. The simpler of the two is to have a registry contract that holds the address of the latest version of the contract. A more seamless approach for contract users is to have a contract that forwards calls and data onto the latest version of the contract.
Example 1: Use a registry contract to store latest version of a contract
In this example, the calls aren’t forwarded, so users should fetch the current address each time before interacting with it.
There are two main disadvantages to this approach:
1. Users must always look up the current address, and anyone who fails to do so risks using an old version of the contract
2. You will need to think carefully about how to deal with the contract data when you replace the contract
The alternate approach is to have a contract forward calls and data to the latest version of the contract:
Example 2: Use a DELEGATECALL to forward data and calls
This approach avoids the previous problems but has problems of its own. You must be extremely careful with how you store data in this contract. If your new contract has a different storage layout than the first, your data may end up corrupted. Additionally, this simple version of the pattern cannot return values from functions, only forward them, which limits its applicability. (More complex implementations attempt to solve this with in-line assembly code and a registry of return sizes.)
Regardless of your approach, it is important to have some way to upgrade your contracts, or they will become unusable when the inevitable bugs are discovered in them.”
However, I also recommend checking Proxy Libraries in Solidity that is posted by Zeppelin Solutions and Aragon. There is a planning to make an industry standard for this matter.
5. You have to have a good testing strategies and tactics. You will need to test all the above at every test cycle. This means that while you are testing your core logic, you have to test for example the circuit-breaker and the ability to read all data, in order to check if it will always be working as expected.
For testing, you will consider the concepts that applies to all types of software. Additionally, consider utilizing what your development tools provide for testing. Truffle, for example, provides a very neat way for writing test scripts. Check it at: http://truffleframework.com/docs/getting_started/solidity-tests.
The above 5 points are not the only points to consider when designing your solution. But, I think that those are the minimum for “Upgradable Smart Contracts”. And considering, at least them, will most likely put you in the safe side. However, if you did not follow them, the cost of updating your smart contract can really ruin your life. You have to consider that some bugs may not be solved except by a hard-fork! And you do not need to run into such situation.
What is next
This article is the first one of a series for “Essential Design Considerations for Ethereum ÐApps”. The next one is about data structure design: Struct vs. Child Contract. It is answering the question: Which is the best to save data for DApps? One smart contract for all members OR one smart contract for every member?