How to make an updatable smart contract
March 12th, 2023

In this article, I will explain how we can make updateable smart contracts.

The smart contracts are immutable

It's essential to know that smart contracts are immutable, which means you cannot change them once a smart contract is deployed. Immutability guarantees that the contract always is fulfilled. But what happens if we explicitly want that, given a rule, we could update the logic of a contract? For instance, a DAO may wish to change the logic to approve motions, or the owners of an ERC20 want to implement a fee in each transfer. We wish to update a smart contract in many use cases. Is there not any way to update a contract?

Use a contract as an interface and migrate storage

The truth is that we cannot update a contract, but we can do a trick. We can put a fixed smart contract on top of the smart contract that redirects all the calls to the logic smart contract. We will share with all the services the address of the top contract, not the address of the implementation. It is a pattern known in Computer Science, and his name is proxy. From now on, we will refer to the smart contract top as a smart contract proxy.

So, when an EOA or a smart contract interacts with the proxy smart contract, this will redirect the call to the second contract, and when we want to change the logic of the contract, we only have to change the address to which the proxy is forwarding the calls.

But there is a problem. What happens with the state of the previous smart contract? With this implementation, we would have to implement a logic for migrating the state of Smartcontract V1 to Smartcontract V2. It's a task tedious and expensive in gas if the data to migrate is big. And there are other problems. The msg.sender, which the logic smart contract is receiving, is the address of the proxy smart contract.

The next question is: Is there any way to solve this problem? The answer is yes.

Save state in the Proxy smart contract

The solution to this problem is the function: delegateCall. This function allows us to call from a smart contract A to a smart contract B. But with some useful features. The msg.sender and msg.value that smart contract B will receive are the same as that which the smart contract A is receiving. The other exciting feature is that contract B will use the state of contract A.

Using this function, we can save the storage in the smart contract proxy, and when we update our smart contract, the state will be the same.

But we still have to solve a problem. If we want to know why there still is a problem, we have to know that in solidity, each variable inside a smart contract has a slot, and it depends on the variable position on the smart contract.

The problem is that the proxy smart contract has to save two addresses. The logic contract address to forward the calls, and the admin address, which can update the logic smart contract. The problem is that these variables will use slot 0 and slot 1 of the state, so there will be a storage collision with slot 0 and slot 1 of the logic smart contract.

|Proxy                   |Implementation    |
|------------------------|------------------|
|address _implementation |address _owner    | <=== Storage collision
|address _admin          |mapping _balances |
|                        |uint256 _supply   |
|                        |...               |

ERC1967

How can we solve it? We could create two dummy variables in one of the smart contracts and only use them in one of them. It's a very ugly solution.

The best solution is to save this variable in two slots of memory known in a very big and random position. So, a storage collision would be impossible (practically impossible).

|Proxy                   |Implementation     |
|------------------------|-------------------|
|...                     |address _owner     |
|...                     |mapping _balances  |
|...                     |uint256 _supply    |
|...                     |...                |
|...                     |                   |
|...                     |                   |
|...                     |                   |
|...                     |                   |
|address _implementation |                   | <=== Randomized slot.
|...                     |                   |
|...                     |                   |

It's the way to solve this problem. ERC1967 defines a fixed slot in which the logic contract address and the admin address will be saved. A fixed slot to save this variable is good because we want to build common tools that act upon this information. If we don't have a fixed position for this, for example, the block explorer won't be able to read, which is the smart contract logic for showing it.

To access a slot of memory, we have to use an assembly code like this:

assembly {
    r.slot := slot
}

But It's easier if you use directly use the StorageSlot Contract from openzeppelin.

Now we have the method to have a smart contract proxy. But with this implementation, we still are limited. We would have to implement a particular proxy each time that we want to create an updateable contract because we have to implement the interface of the logic contract in the proxy contract to forward the calls, and in addition, we cannot modify the interface of our logic contract, because if the proxy is fixed, the proxy no forward the calls of the new functions.

Use a fallback in the proxy

The last step to have an upgradeable contract is to change the interface of our logic contract and has a generic proxy to reuse each time we want to develop an updateable contract. How can we achieve it? Using the special function fallback. The fallback is a particular function called when the function we are trying to call doesn't match any implemented function in the smart contract. We can have a fallback in the proxy contract to handle all the calls instead of implementing a method for each function. With this solution, we can change the interface of our logic contract because the proxy contract's fallback function will always be called.

But the proxy contract has to have functions implemented, for instance, the method to change the logic contract or a method to change the admin. What happens if the logic contract implements a function with the same name that a proxy contract function? This function won't be forwarded, so we must solve it.

Function signature collision

To solve this, we have two options. The first option would be restricting some functions' names in the smart contract logic. It's an ugly solution. Or we can limit the proxy functions only to be called by the admin. If an address, not admin, call it, the proxy contract has to forward the call to the logic contract.

Conclusion

And this the way to has an updateable smart contract. We must apply many tricks, but finally, we have a good solution. In the article, I have explained all the logic to arrive at a good solution, but in a production environment, the best solution is to use a proxy contract already audited, like the TransparentUpgradeableProxy from Openzeppelin.

I hope that this article has been helpful to you and that now you understand how to make an updateable contract.

Subscribe to gabrielmartinez
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from gabrielmartinez

Skeleton

Skeleton

Skeleton