Developer's Corner

Smart Contract Upgrades

Ajay Shetty
Ajay Shetty

Smart contracts are immutable by definition. Once deployed, they cannot be updated or changed. Then why would someone want to change a smart contract and for what purpose? In this blog post, we highlight the most common reasons for a smart contract upgrade, the challenges involved, and how to upgrade using a well-regarded design pattern.

Why upgrade a Smart contract?

Decentralised applications are powered by smart contracts. The basic need for smart contract upgrade is driven by the usual application evolution demands. One might need to ship features for evolving user requirements. Or there might be critical bugs that slipped past the rigorous smart contract development and testing phase, and need to be resolved.

Smart contract development is hard, upgrades are even harder, especially given the security concerns and desired immutability. DeFi Hacks such as Burgerswap have already taught us a lesson on how crucial smart contract security is and why we need to ensure that any vulnerabilities found after deployment must be fixed immediately to avoid manipulation.

How to upgrade a Smart contract?

Let’s assume we have a Robot which is controlled by a smart contract.

The smart contract enables the robot to move forward, backward, left, right and traverse the terrain happily. So far so good. Everything works perfectly. Now, what if the terrain, where this robot is supposed to move, changes suddenly? What if it requires the robot to not only move as before but also jump?

Bummer!

Our smart contract that powers the robot functions does not know how to execute this new complex move.

So what do we do? If we deploy a new smart contract, that would mean a new robot — that breaks the immutability. We certainly wouldn’t want that.

Luckily, for us, design patterns are available that can tackle exactly such a scenario.

Proxy Design Pattern

One of the characteristics of proxy contract pattern is the use of delegatecall function. This is a low-level Ethereum SmartContract function call that ensures preserving the caller context at the called execution environment.

What this means is, if a Contract ‘A’ calls a function “foo” defined by Contract ‘B’ via delegate call, then the context of Contract ‘A’ is also passed along to function “foo”. Also, if function “foo” results in changes to storage, those changes actually happen at the storage of smart contract ‘A’ and not of smart contract ‘B’, where this function is defined.

The proxy design pattern uses another key concept of smart contract known as the fallback function. The fallback function of a smart contract is called in response to an unsupported function call.

That’s it. Viola!

We can have smart contract upgrades through proxy magic!

If you are familiar with how a typical proxy works, you may have figured out already how such a proxy can utilise delegatecall primitive and fallback function feature offered by smart contract, to achieve an upgrade.

For the benefit of the others, here are the three steps outlining how this upgrade works:

1. To implement an upgradable ‘Smart contract’, you actually need two underlying smart contracts that fulfil it. One is for the proxy itself (storage), and the other contract is for implementing the logic. The new feature or upgrade is supported via another smart contract that implements the new logic.

2. The robot application invokes the proxy contract and not the logic contract directly. The proxy contract routes or delegates every function call to the requisite logic contract.

3. Lastly, the most important part is implementing the fallback function in Proxy contract so that it points to the new “jump” functionality implementation in the logic contract. For all the other functions, it can point to the previous smart logic contract.

Are there any gotchas?

Hmm. Well, it looks like we are all set with smart contract upgrades. However, there are some additional areas that one must consider while creating smart contract upgrades with proxy patterns.

1. Storage clashes : As it is a delegated call, we can not re-order or change the storage spots, otherwise it might have undesirable side effects.

2. Function selector clashes: Sometimes, the proxy and logic implementation contract can have same function selectors.

Additional information: It is highly recommended that you use design patterns by openzepplin contracts. Zeppelin has been working on several proxy patterns and you can find interesting details on inherited, eternal and unstructured storage patterns.

Now that it can jump, here is our ‘smart contract upgraded’ happy robot with a gift for you.

Do you like it?

A part of our “Engineering at Arcana” series, blogs such as this, and more coming your way, are straight from our developers — providing invaluable engineering insights and tips. Subscribe to our Medium and follow us on all our socials to stay up to date with all the latests posts.