Contract Migration

In attacks where private keys are compromised, it may be impossible to fix the deployed smart contract, even if the contract has an upgradability mechanism. A new instance of the contract will need to be deployed and properly initialized to restore functionality to your users.

Hence, smart contract developers should integrate a migration procedure during the contract design phase.

A migration has two steps:

  1. Recovering the data to migrate

  2. Writing the data to the new contract

Step 1: Data Recovery

Retrieval methods by data type

  • Public variables - use public getter functions

  • Private variables - use events, or compute the location in memory of the variable, then retrieve its value using: await provider.getStorageAt(address, slot)

  • Arrays - number of elements is known; use methods above

  • Mappings - keys are not stored, but are required to access mapping values. To simplify off-chain tracking, emit events when a value is stored in a mapping.

Holders and Balances

  • For token contracts, often we can find the list of all the holders by tracking the addresses of the Transfer events.

  • Once holder addresses are obtained, query the balanceOf function to recover the balance associated to each holder.

Step 2: Data Writing

For simple variables, set the values through the constructor of the contract.

If there is a large amount of data that cannot be sent with one transaction, the migration has to be split into several. To do this, add an initialisation state to the contract where only the owner can change the state variables, and users can’t take any action.

The initialization state can be implemented with a Pausable feature and a boolean indicating the initialization state.

To reduce the cost, the migration of the balances can be implemented with a batch transfer function that lets you set multiple accounts in a single transaction.

General Recommendations

  • Favoring contract migration over upgradeability. Migration system have many of the same advantages than upgradeable, without their drawbacks.

  • Using the data separation pattern over the delegatecallproxy one. If your project has a clear abstraction separation, upgradeability using data separation will necessitate only a few adjustments. The delegatecallproxy requires EVM expertise and is highly error-prone.

  • Document the migration/upgrade procedure before the deployment. If you have to react under stress without any guidelines, you will make mistakes. Write the procedure to follow ahead of time. It should include:

    • The calls that initiate the new contracts

    • Where are stored the keys and how to access them

    • How to check the deployment! Develop and test a post-deployment script.

References:

https://blog.trailofbits.com/2018/10/29/how-contract-migration-works/

Last updated