How to upgrade ERC20 contract when it is live on mainnet

Hello OZ Team,

I have deployed an ERC20 smart contract which is upgradable. It is live and many users are transferring tokens at the moment.

Now, I would like to upgrade the contract. Is there any restriction how I can do it when it is live ?

My doubt is what happen if upgradation is going on and at the same time user transfers the tokens ?

I’m upgrading contract using add,push and update methods.

Any help would be appreciated.

Thanks.

1 Like

Hi @Ronak,

Welcome to the community :wave:

Thanks for posting in the community forum.

The upgrade and your user transfers are separate transactions so don’t happen simultaneously. The important thing is to appropriately test your upgrade.

Before you upgrade you should:

  • Create and run automated tests to check the upgrade locally
  • Test the upgrade using a fork of mainnet. (see Testing real-world contract upgrades for concepts)
  • Communicate with your community on what changes you are making, why you are making the changes and when you are doing it.

:warning: Storage layout changes means that contracts can’t be upgraded from OpenZeppelin Contracts Ethereum Package v2.5 to OpenZeppelin Contracts Ethereum Package v3.0

Depending what version of OpenZeppelin CLI you are using, you can just use npx oz upgrade, though given network congestion and gas prices you may be better to break it down into push (to push the logic contract) and upgrade (to update the proxy to use the new logic contract) transactions. (At the time of writing standard was 40 Gwei: https://www.ethgasstation.info/)

@abcoathup Thank you for your suggestions. It really helped. I would prefer to go with oz push and oz upgrade.

1 Like

@abcoathup I tried to push first and encountered a below error:

? Pick a network mainnet
- Variable _roles (AccessControlUpgradeSafe) contains a struct or enum. These are not automatically checked for storage compatibility in the current version. See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#modifying-your-contracts for more info.
✖ Validating and deploying contract TestToken
TestToken deployment failed with error: The contract code couldn't be stored, please check your gas limit.

Even though it failed with an error, the contract is successfully deployed on mainnet.

I can see it used 58 Gwei gas price.

Below is my network.js settings for mainnet:

mainnet: {
      provider: () => new HDWalletProvider(process.env.PK, "https://mainnet.infura.io/v3/" + process.env.INFURA_API_KEY),
      network_id: "1",
      skipDryRun: true,
      confirmations: 2
    }

Due to error, it couldn’t write mainnet.json file properly and now I’m afraid to call oz upgrade.

Please provide your suggestions.

Thanks.

1 Like

Hi @Ronak,

I really recommend testing the upgrade using a fork of mainnet.

I recommend including gas and gasPrice in your network config (I assume you are using truffle https://www.trufflesuite.com/docs/truffle/reference/configuration#networks), to ensure that you configure and control your costs.

I would recommend also testing any transactions on a public testnet so that you can calculate the cost and use https://www.ethgasstation.info/ for gas prices.

You could also change skipDryRun to be false.

When I change skipDryRun to false, it always throws below error:

Migrations dry-run (simulation)
===============================
> Network name:    'development-fork'
> Network id:      1
> Block gas limit: 6721975 (0x6691b7)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > block number:        10514145
   > block timestamp:     1595486045
   > account:             0x56CC12Ee7c43218D7558B31E575169c3A63d55B3
   > balance:             99.999692996
   > gas used:            153502 (0x2579e)
   > gas price:           2 gwei
   > value sent:          0 ETH
   > total cost:          0.000307004 ETH

   -------------------------------------
   > Total cost:         0.000307004 ETH


2_deploy_testtoken.js
=====================

Error: Given network 'development-fork' is not defined in your networks.js file
    at Object.loadNetworkConfig (/Users/ronak/Developer/projects/test/testtoken_upgradable/node_modules/@openzeppelin/cli/src/models/config/NetworkConfig.ts:84:39)
    at Object.<anonymous> (/Users/ronak/Developer/projects/test/testtoken_upgradable/node_modules/@openzeppelin/cli/src/models/config/ConfigManager.ts:33:71)
    at Generator.next (<anonymous>)
    at /Users/ronak/Developer/projects/test/testtoken_upgradable/node_modules/@openzeppelin/cli/lib/models/config/ConfigManager.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/ronak/Developer/projects/test/testtoken_upgradable/node_modules/@openzeppelin/cli/lib/models/config/ConfigManager.js:4:12)
    at Object.initNetworkConfiguration (/Users/ronak/Developer/projects/test/testtoken_upgradable/node_modules/@openzeppelin/cli/lib/models/config/ConfigManager.js:33:16)
    at deployer.then (/Users/ronak/Developer/projects/test/testtoken_upgradable/migrations/2_deploy_testtoken.js:18:55)
    at /usr/local/lib/node_modules/truffle/build/webpack:/packages/deployer/index.js:64:1
    at /usr/local/lib/node_modules/truffle/build/webpack:/packages/deployer/src/deferredchain.js:20:1
    at process._tickCallback (internal/process/next_tick.js:68:7)
Truffle v5.1.31 (core: 5.1.31)
Node v10.20.1

The issue is, it tries to find the development-fork network in network.js file and can’t find. If I try to add the same in network.js, it throws below error:

/Users/ronak/Developer/projects/test/testtoken_upgradable/networks.js:16
    development-fork: {
               ^

SyntaxError: Unexpected token -

I am really surprise because I have tested the same on Ropsten Testnet and it works fine there.

I’m using Truffle and OZ together.

// Load zos scripts and truffle wrapper function
const { scripts, ConfigManager } = require('@openzeppelin/cli');
const { add, push, create } = scripts;

async function deploy(options) {
  // Register v0 of TestToken in the oz project
  add({ contractsData: [{ name: 'TestToken', alias: 'TestToken' }] });

  // Push implementation contracts to the network
  await push(options);

  // Create an instance of TestToken, setting initial values
  await create(Object.assign({ contractAlias: 'TestToken', methodName: 'initialize', methodArgs: ["Test Token","TEST",18,100000000] }, options));
}

module.exports = function(deployer, networkName, accounts) {
  deployer.then(async () => {
    const { network, txParams } = await ConfigManager.initNetworkConfiguration({ network: networkName, from: accounts[0] })
    await deploy({ network, txParams })
  })
}

Please let me know if you have any suggestions.

1 Like

Hi @Ronak,

It looks like we can’t use the Truffle dry run feature in this type of setup as it is trying to access development-fork in network.js which would be the OpenZeppelin network configuration but you are using truffle-config.js.

1 Like

Yes, that’s the reason I kept skidDryRun: true.

Is it possible to manually write mainnet.json file contents for TestToken implementation contract which is already successfully deployed but oz failed to write in mainnet.json ?

1 Like

Hi @Ronak,

Yes you can (with care) manually update mainnet.json. Use your deployment from a public testnet as a guide.

You should then test using a fork of mainnet before doing the upgrade on mainnet.

I will give a try being very careful. Thank you.

1 Like

Hi @Ronak,

Just wanted to check that you were able to upgrade?

@abcoathup Yes, I am able to upgrade it by increasing gas limit and gas price. I didn’t choose the option to manually write the file.

1 Like