How to write upgradable smart contracts in solidity.

How to write upgradable smart contracts in solidity.

How to write upgradable smart contracts in solidity.

How to write upgradable smart contracts in solidity.

How to write upgradable smart contracts in solidity.

Table Of Content
Read Time: 8 minutes

While working on the smart contracts security audits platform QuillAudits at QuillHash, we are giving most of the time to researching best security practices in smart contracts. QuillAudits considers the following distinct and crucial facets of the smart contract code: Whether the code is secure. Whether the code corresponds to the documentation (including white paper). Whether the code meets best practices, inefficient use of gas, code readability, etc. An approach to upgrade contracts must be in the armour to prevent damage made by programming bugs after the contract got deployed.

The Topic of upgradeable contracts is not very new to the world of ethereum. There are some different approaches to upgrading smart contracts. Here we talk about that How to write upgradable smart contracts in solidity.

Some approaches we considered in development are:-

  1. Separate logic and data.
  2. Partially upgradable smart contracts system.
  3. Separate logic and data in key-value pairs.
  4. Eternal storage with proxy contract

With the first three approaches, the contract can be updated by pointing users to use the new logic contract (through a resolver such as ENS) and updating the data contract permissions to allow the new logic contract to execute the setters. In the fourth approach, we don’t need to do this redirection, and it is a very flexible approach to updating smart contracts. We found that eternal storage with a proxy contract approach is flawless till now.

Readers are most welcome to comment if you know of any flaw in this approach. It will be very helpful for the developer community.

There is a good reasons for and against being able to update smart contracts. The good reason is all the recent hacks were based on programming error and could be fixed very easily if it was possible to upgrade those contracts.

However, the ability to upgrade smart contracts after deployment is somewhat against the ethics and immutability of the blockchain. People need to trust that you are a good boy. One thing that might make sense would be to have multi-sig upgrades, where the “OK” from multiple people is necessary before a new contract is deployed and can access the storage. I think that it is storage records which need to be immutable in the blockchain. Logic must be improved with time, as in all software engineering practices.No one can guarantee to develop bug-free software in the first version. So Upgradeable smart contracts with some upgrading governance mechanism can save many hacks.

In this post, I will touch on the upgrade mechanism, and in a follow-up post I will try to come up with the best contract upgrading governance mechanism.

So let’s start with the implementation approach !!

  • The most important thing to consider when upgrading contracts are how to preserve the state of the original contract in the upgraded contract.
  • The state of the contract can be separated from the functionality of the contract. This approach allows multiple contracts to share the same state.
  • In this approach, a proxy contract will act as an immutable storage contract, and a delegate contract will contain the functionality.
  • The storage structure of both these contracts must be similar.
  • To upgrade the logic of contract we need to inform the proxy contract the address of new delegate contract.
  • When a transaction is sent to the proxy contract, it does not know the specified transaction function.
  • The proxy contract will proxy the transaction to what we’ll refer to as a “delegate” contract (Which contains the functionality logic). This is done using the native EVM code, delegate call.

With a delegate call, a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract; only the code is taken from the called address.
When a proxy contract uses a delegate contract’s functionality, state modifications will happen on the proxy contract. This means that the two contracts need to define the same storage memory. The order that storage is defined in memory needs to match the two contracts.

We will deploy these contracts initially:-

  1. key Storage Contract (Contains the shared state)
  2. Delegate contractV1 and Delegate contractV2
  3. Proxy Contract (Contains the delegate call functionality)

Key Storage contract

It contains common storage for all storage state variables which will be shared among all versions of the smart contract. It also contains getter and setter functions to update and get the value of state from delegate contract.

Key Storage contract can be consumed by any delegate contract via proxy contract once deployed. We cannot create new getter and setters once key storage got deployed so we need to consider this while designing the initial version of the smart contract.

The best approach is to make mappings for every type of field in key storage contract. Where the key of mapping will be the name of the key simply in bytes and value will of the type declared in mapping.

For ex:- mapping(bytes32 => uint)

Now we can use this mapping to set and get an integer value from the delegate contract by calling key storage getter and setter function for the uint type. For example, we can set the total supply with the key “totalSupply” and any uint value.

But wait something is missing, Now anyone can call our key storage contract getter and setter function and update the state of storage which is getting used by our delegate contract. So to prevent this unauthorized state change we can use the address of proxy contract as the key of mapping.

mapping(address => mapping(bytes32 => uint)) uintStorage
In our setter function:
function setUintStorage(bytes32 keyField, uint value) public {
uintStorage[msg.sender][keyField] = value
}

Now as we are using msg.sender address in setter function and only this state change will be reflected in the proxy contract state when it uses the getter function to get the state. Similarly, we can create other state mappings along with getter and setter functions as shown in the code below:-

Delegate contract

Delegate contract contains the actual functionality of dApp.It also contains a local copy of KeyStorage contract. In our dApp , if we include a certain functionality and later we found a bug in deployed contract, in that case, we can create a new version of delegate contract.

In the code below, Delegate contract version 1 (“DelegateV1.sol”) is deployed.

After deploying DelegateV1 we noticed the number of owners can be set by any user. So now we want to upgrade the smart contract so that only the owner of the contract can set a number of owners.

We cannot change the code of the already deployed contracts in ethereum. So the obvious solution is to create a new contract, which will contain a local copy of the Key Value contract. Here we are creating DelegateV2.sol contract with onlyOwner modifier added.

We have created a new contract, but the storage of the previous contract is not available in the new version. So we can include a reference to the actual keyStorage contract in every version of the delegate contract. In this way, every version of the delegate contract shares the same storage. But one thing is not desirable here: we need to tell every user about the updated version of the contract address so that they can use the updated contract. It sounds stupid. So we will not store an actual copy of the key storage contract in every version of the delegate contract. Getting a shared storage proxy contract comes to the rescue; let’s move on to the proxy contract.

Proxy Contract

A proxy contract uses the delegatecall opcode to forward function calls to a target contract which can be updated. As delegatecall retains the state of the function call, the target contract’s logic can be updated, and the state will remain in the proxy contract for the updated target contract’s logic to use. As with delegatecall, the msg.sender will remain that of the caller of the proxy contract.

A delegate call can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address.
SO we just need to pass the address of new version of the contract to proxy contract via upgradeTo function.


The code of proxy contract is quite complicated in fallback function as here low-level delegate call assembly code is used.

Let break it down simply what is getting done in assembly code:-

delegatecall(gas, _impl, add(data, 0x20), mload(data), 0, 0);

In above function delegate call is calling code at “_impl” address with the input “add(data,0x20)” and with input memory size “mload(data)”,delegate call will return 0 on error and 1 on success and result of the fallback function is whatever will be returned by the called contract function.

In the proxy contract, we are extending StorageState contract which will contain a global variable to store the address of keyStorage contract.

The order of extending the storage state contract before the ownable contract is important here. This storage state contract will be extended by our delegate contracts, and all the functions logic executed in the delegate contract will be from the context of proxy contract. The order of storage structure of Proxy contract and Delegate contract must be the same.

Now the user will always interact with dapp via the same address of proxy contract, and the state of the key storage contract seems to be shared among all versions of the contract but in actually only proxy, the contract contains the reference to the actual keyStorage contract. Delegate contracts contain the local copy of keyStorage contract to get the getter and setter functions logic and to have a similar storage structure like the proxy contract. Still, actual storage changes are getting done from the context of proxy contracts only.

Deploying and testing it together:-

Here the output of test cases will be: 10 10 and 20

We are calling getNumberOfOwners() three times in test case. First to get the state change by DelegateV1 contract.S econd time to get the state modified by DelegateV1 from DelegateV2 contract and we successfully managed to retain the state modified by DelegateV1 and third time to get the state modification done by DelegateV2 contract.

Note here that we are calling getNumberOfOwners() every time from the same address of proxy contract. So we successfully managed to update the functionality of our contract without losing the previous state.

If we call setNumberOfOwners() from any other address except account[2], which is the contract owner address, it will throw a revert error.

At Quillhash we are developing a platform Quillplay to create secure and customised smart contracts templates for users with no programming experience.
https://quillhash.com/quillplay
I will try to come up with some upgrading governance mechanism in next post.
You can see the complete code here:-
https://github.com/Quillhash/upradeableToken.git

Launch your blockchain project with Quillhash: https://quillhash.typeform.com/to/KQ5Hhm

Thanks for reading. Also, do check out our earlier blog posts.

3,979 Views

Blockchain for dog nose wrinkles' Ponzi makes off ~$127M🐶

Project promised up to 150% returns on investment in 100 days, raising about 166.4 billion South Korean won — or about $127 million — from 22,000 people.

Latest blogs for this week

Understanding Fuzzing and Fuzz Testing: A Vital Tool in Web3 Security

Read Time: 5 minutes When it comes to smart contracts, ensuring the robustness and security of code is paramount. Many techniques are employed to safeguard these contracts against vulnerabilities
Read More

How EigenLayer’s Restaking Enhances Security and Rewards in DeFi

Read Time: 7 minutes Decentralized finance (DeFi) relies on Ethereum staking to secure the blockchain and maintain consensus. Restaking allows liquid staking tokens to be staked with validators in
Read More

ERC 404 Standard: Everything You Need to Know

Read Time: 7 minutes Introduction Ethereum has significantly shaped the crypto world with its introduction of smart contracts and decentralized applications (DApps). This has led to innovative developments in
Read More

DNS Attacks:  Cascading Effects and Mitigation Strategies

Read Time: 8 minutes Introduction DNS security is vital for a safe online space. DNS translates domain names to IP addresses, crucial for internet functionality. DNS ensures unique name-value
Read More

EIP-4844 Explained: The Key to Ethereum’s Scalability with Protodanksharding

Read Time: 7 minutes Introduction  Ethereum, the driving force behind dApps, has struggled with scalability. High fees and slow processing have limited its potential. They have kept it from
Read More

QuillAudits Powers Supermoon at ETH Denver!

Read Time: 4 minutes Calling all the brightest minds and leaders in the crypto world! Are you ready to build, connect, and innovate at the hottest event during ETH
Read More

Decoding the Role of Artificial Intelligence in Metaverse and Web3

Read Time: 7 minutes Introduction  Experts predict a transformative shift in global software, driven by AI and ML, marking the dawn of a new era. PwC predicts AI will
Read More

Transforming Assets: Unlocking Real-World Asset Tokenization

Read Time: 7 minutes In the blockchain, real-world assets (RWAs) are digital tokens that stand for tangible and conventional financial assets, including money, raw materials, stocks, and bonds. As
Read More
Scroll to Top

Become a Quiffiliate!
Join our mission to safeguard web3

Sounds Interesting, Right? All you have to do is:

1

Refer QuillAudits to Web3 projects for audits.

2

Earn rewards as we conclude the audits.

3

Thereby help us Secure web3 ecosystem.

Total Rewards Shared Out: $200K+