Rudimentary Approaches to Upgradeability

Here we’ll take a look at some more approachable, yet less suitable solutions to smart contract upgradeability. Though these are not optimal approaches, they are at the core of what is used today.

Registry contracts

Registry contracts are probably the simplest approach to upgradeability, but in this case, with simplicity comes some serious flaws.

It works using two contracts: the registry contract and the logic contract. The registry contract exists only to point users to the current version of the logic contract. Whenever the logic contract is upgraded, the owner of the registry contract can update the address at which the logic contract is upgraded.

Example from Consensys

This approach is disadvantageous because anytime a user wants to use the contract, they must first look up the current address. Failing to do so could potentially lead to fund loss. It is also very difficult to migrate data to the new contract, and thus this process must be carefully considered to avoid failure.

Proxy contracts

Proxy contracts are used to forward data and calls to the logic contract. With a proxy contract, users can always call the same contract address, and it will simply be forwarded to the current logic contract.

This approach works by making use of the DELEGATECALL opcode. DELEGATECALL is an opcode provided by the EVM for use in assembly. It works just like a normal call, except that the code at the target address is executed in the context of the calling contract. This means that values like `msg.sender` and `msg.value` are preserved. Essentially, DELEGATECALL allows the target contract to make calls on behalf of the callee.

Example from Consensys

Though this approach avoids the problems involved with registry contracts, it has problems of its own. For example, data storage can easily fail if managed improperly. If new contracts have a different storage layout from the previous contract, the data may be corrupted. This implementation also prevents you from receiving return values from functions, limiting its use cases.

Storage contracts

Like the previous approaches, this approach requires your logic contract, along with a secondary contract. In this case, the second contract is an eternal storage contract. This technique works by separating the logic and data. The logic contract can be upgraded anytime, and since the data storage is external, your data is protected.

Of course, this approach is also fundamentally flawed. If a bug or exploit is found in the storage contract, it cannot be upgraded without corrupting current data stores. The other issue with this approach is that the logic contract needs to make an external call to view or modify data, using extra gas.