Reference:

Upgrading your Smart Contracts | A Tutorial & Introduction

The State of Smart Contract Upgrades - OpenZeppelin blog

3 ways to upgrade your smart contracts”

  1. Not Really / Parameterize: Just keep updating functions for all(or most) state variables which can be called by the owner / admin only. Some problems with this is:

  2. Social YEET / Migration method: Deploy the new contract and tell the community to consider the new one as the main one and to switch over to that. v1 ⇒ v2. This is the truest defination of immutable because here you are not giving any way of upgrading in place.

    Pros:

    Cons:

    Now, when we do this we do have to move the state of the first contract to the second contract. For eg, if you are an ERC20 you have to take all the mappings(balances, etc) to the new contract.

    The problem is that if you have like a million transfer calls, it can be very expensive(specially on the ethereum mainnet chain). Refer: https://blog.trailofbits.com/2018/10/29/how-contract-migration-works/

  3. Smart Contract Upgrades: It can update our state, keep our contract address and allow us to update any type of logic in our smart contracts in an easy way. Here, we use proxies. Now, this is also the place where you can have a lot of problems.

    Proxies use a lot of low level functionalities: Main one being the delegateCall functionality. This means if I delegate call a function in contract B from contract A, I will do contract B’s logic in contract A. Just delgate all calls through a proxy contract address to some other contract.

    Screenshot 2022-10-09 at 9.56.43 PM.png

    Screenshot 2022-10-09 at 9.58.46 PM.png

    Screenshot 2022-10-09 at 9.59.22 PM.png

    Screenshot 2022-10-09 at 9.59.47 PM.png

    Proxy terminology:

    Some points to note here:

    Using proxies also has some drawbacks:

    1. Storage clashes:

      Screenshot 2022-10-09 at 10.10.47 PM.png

      This is how storage works in solidity. So, what can happen is that:

      Screenshot 2022-10-09 at 10.11.14 PM.png

      Here, when I will call the setDValue function in contract B it will overwrite the value in contract A at location 0. This means we can only append new storage variables in new implementation contract and we can’t reorder or change the old ones. This is storage clashing.

    2. Function Selector clashes:

      When we tell contract to delegate call to one of these implementations. It uses something called the function selector to find a function.

      Screenshot 2022-10-09 at 10.14.47 PM.png

      Now, it is possible that a function in the implementation contract has the same function selector as the admin function in the proxy contract.

      Screenshot 2022-10-10 at 10.34.17 AM.png

      The above two function might look different but they have the same function selector.

    Proxy Patterns

    So, there are 3 proxy patterns:

    1. Transparent Proxy Pattern: Admin can’t call implementation contract functions, they can only call the admin functions. Admin function are the functions that govern the upgrades. Now, the normal users can only call the functions in the implementation contract and not any admin contracts. This will prevent both the clashes. The admin function will be located in the proxy contract. Now, this also mean that if you are an admin you won’t be able to participate in that protocol using your admin wallet.

    2. Universal Upgradable Proxies (UUPS): This version of upgradable contracts actually put all the logic of upgrading in the implementation itself. In this way the solidity compiler will give an error if there are any clashes.

      Screenshot 2022-10-10 at 10.41.21 AM.png

      Now, this also mean there is one less read to do which is to check who is admin in the proxy contract, this will save some gas. Also the proxy is a little bit smaller due to this. The issue is that if you deploy an implementation contract without any upgradable functionality, you are stuck.

    3. Diamond Pattern: This allows multiple implementation contracts.

      Screenshot 2022-10-10 at 10.44.15 AM.png

      This addresses a couple different issues:

      • For eg, if your contract is very big and it does not fit in one contract size, you can have multiple contracts.
      • It also allows you to make more granular update. So, you don’t always have to deploy and update your entire smart contract you can just update a little piece of it if you chunk them out.

      Now, this also mean the code will be lot more complex. you need to be really good at smart contract development to use this. And you need to make sure there are no clashes or what have you.

    Transparent Proxy Pattern Example

    All the code is in the repo:

    https://github.com/codeTIT4N/solidity-transparent-proxy-example

    Add these dependencies to the project and import them in hardhat.config.js:

    1. nomiclabs/hardhat-ethers
    2. @openzeppelin/hardhat-upgrades

    hardhat.config.js:

    Screenshot 2022-10-10 at 11.03.50 AM.png

    We need to import @openzeppelin/hardhat-upgrades to use it in tests.

    Now a test case for proxy will look like:

    Screenshot 2022-10-11 at 12.03.48 PM.png

    Screenshot 2022-10-11 at 12.08.50 PM.png

    Here, you can see on line 7 we used a initializer. This is because when working with proxies we don’t have constructors. Our implementation cannot have a constructor, because if it has a constructor then all that state will be stored in the implementation and we want it to be stored in the proxy. So, here we are saying our initializer is store function. So, here after we deploy this contract it is going to call the store function afterwards with 42 being the input.

    Now, my scripts/deploy.js will look like:

    Screenshot 2022-10-11 at 12.35.06 PM.png

    Now lets deploy this in sepolia using:

    npx hardhat run scripts/deploy.js --network sepolia

    Screenshot 2022-10-11 at 12.43.17 PM.png

    Screenshot 2022-10-11 at 12.44.30 PM.png

    Now the first one we deployed (3rd in the list above) is the implementation contract(box contract).

    Second one is the ProxyAdmin contract(middle one). If I see it on etherscan it will also show that:

    Screenshot 2022-10-11 at 12.49.13 PM.png

    It defines how to work with our proxy contracts.

    At last we have our TransparentUpgradeableProxy:

    Screenshot 2022-10-11 at 12.51.00 PM.png

    Note: In openzepplin docs you will find tutorial to use it with Genosis safe multisig wallet. Which is a better approach for more decentralization and security. But here, we are not gonna do that.

    Now, lets create a BoxV2 to upgrade to:

    Screenshot 2022-10-12 at 11.05.50 AM.png

    Here you can see we have added a new function to increment the value.

    Now, we will create a new script to upgrade to this contract in our scripts folder:

    scripts/upgrade.js:

    Screenshot 2022-10-12 at 11.19.50 AM.png

    Here, in line 4 the address is of TransparentUpgradeableProxy deployed earlier. This will upgrate the contract to BoxV2.

    Screenshot 2022-10-12 at 11.14.14 AM.png

    This will upgrade contract to BoxV2.

    And you can see there will be another 2 transactions. This is how you upgrade the contracts. Now lets try new contract out.

    Screenshot 2022-10-12 at 11.19.39 AM.png

    Now, once the transaction goes through you can see it changed the value. So, it mens this is using the v2 now.

    Screenshot 2022-10-12 at 11.20.02 AM.png

    NOTE: Don’t focus too much on the transactions. Just focus on working.