Deploy OpenZeppelin upgradeable contract

The contract deployed using the OpenZeppelin upgrade plug-in has an upgradeable feature: it can be upgraded to modify its code while retaining its address, status and balance. You can iteratively add new functionality to the project or fix any errors that might be found in the online version.

Configure development environment

Create a new npm project

mkdir mycontract && cd mycontract
npm init -y

Install and initialize Truffle

npm i --save-dev truffle
npx truffle init

Install Truffle upgrade plug-in

npm i --save-dev @openzeppelin/truffle-upgrades

Create upgradeable contracts

Note that the upgradeable contract uses the initialize function instead of the constructor to initialize the state.

Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
 
contract Box {
    uint256 private value;
 
    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
 
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
 
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

Deploy contracts to public networks

We will use Truffle migration to deploy the Box contract. The Truffle upgrade plug-in provides a deployProxy function to deploy scalable contracts. It will deploy the contract we implemented, and ProxyAdmin will act as the project agent and agent administrator, and call the initialization function.

Create the following 2 in the migrations directory_ deploy_ box. JS script.

In this article, we don't have the initialize function yet, so we'll use the store function to initialize the state.

2_deploy_box.js

// migrations/2_deploy_box.js
const Box = artifacts.require('Box');
 
const { deployProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer) {
  await deployProxy(Box, [42], { deployer, initializer: 'store' });
};

Use Rinkeby network to run truss migration for deployment. We can see three contracts: box Sol, ProxyAdmin and agent contract AdminUpgradeabilityProxy.

truffle migrate --network rinkeby

...
2_deploy_box.js
===============

   Deploying 'Box'
   ---------------
   > transaction hash:    0x3263d01ce2e3eb4ba51abf882abbdd9252364b51eb972f82958719d60a8b9ebe
   > Blocks: 0            Seconds: 5
   > contract address:    0xd568071213Ea31B01AA2247BC9eC7285087cf882
...
   Deploying 'ProxyAdmin'
   ----------------------
   > transaction hash:    0xf39e8cb97c332b8bbdf0c66b13f26a9a3dc97b207d2caec73ba6df8d5bb6b211
   > Blocks: 1            Seconds: 17
   > contract address:    0x2A210B6d5EffC0A3BB47dD3791a4C26B8E31f161
...
   Deploying 'AdminUpgradeabilityProxy'
   ------------------------------------
   > transaction hash:    0x439711597b694f03b1065582ab44ac0bea5e22b0c6e3c460ae7b4536f004c355
   > Blocks: 1            Seconds: 17
   > contract address:    0xF325bB49f91445F97241Ec5C286f90215a7E3BC6
...

Release validation contract

truffle run verify Box --network rinkeby

We can use the truss console to interact with our contracts.

Note: box Deployed () is the address of our agency contract.

truffle console --network rinkeby

truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> box.address
'0xc2ea7DE43F194bB397761a30a05CEDcF28835F24'
truffle(rinkeby)> (await box.retrieve()).toString()
'42'

The administrator of the current agent (who can perform the upgrade) is ProxyAdmin contract. Only the owner of ProxyAdmin can upgrade the agent. Warning: when the ownership of ProxyAdmin is transferred, please make sure to go to the address under our control.

3_transfer_ownership.js

// migrations/3_transfer_ownership.js
const { admin } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer, network) {
  // Use your wallet address
  const admin = '0x1c14600daeca8852BA559CC8EdB1C383B8825906';
 
  // Don't change ProxyAdmin ownership for our test network
  if (network !== 'test') {
    // The owner of the ProxyAdmin can upgrade our contracts
    await admin.transferProxyAdminOwnership(admin);
  }
};

Run migration on Rinkeby network

truffle migrate --network rinkeby

...
3_transfer_ownership.js
=======================

   > Saving migration to chain.
   -------------------------------------
...

Implement a new upgraded version

After some time, we decided to add features to the contract. In this article, we will add an increment function.

Note: we cannot change the storage layout implemented by the previous contract. For more details about technical restrictions, see upgrading.

Use the following solid code to create a new implementation boxv2 in your contracts directory sol .

BoxV2.sol

// contracts/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
 
contract BoxV2 {
    uint256 private value;
 
    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
 
    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
    
    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
    
    // Increments the stored value by 1
    function increment() public {
        value = value + 1;
        emit ValueChanged(value);
    }
}

Deploy a new upgrade

Once you have tested the new implementation, you are ready to upgrade. This will validate and deploy the new contract. Note: we are only preparing to upgrade..

4_prepare_upgrade_boxv2.js

// migrations/4_prepare_upgrade_boxv2.js
const Box = artifacts.require('Box');
const BoxV2 = artifacts.require('BoxV2');
 
const { prepareUpgrade } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer) {
  const box = await Box.deployed();
  await prepareUpgrade(box.address, BoxV2, { deployer });
};

Run the migration on the Rinkeby network to deploy the new contract implementation

truffle migrate --network rinkeby

...
4_prepare_upgrade_boxv2.js
==========================

   Deploying 'BoxV2'
   -----------------
   > transaction hash:    0x078c4c4454bb15e3791bc80396975e6e8fc8efb76c6f54c321cdaa01f5b960a7
   > Blocks: 1            Seconds: 17
   > contract address:    0xEc784bE1CC7F5deA6976f61f578b328E856FB72c
...

Enter the truss console

truffle console --network rinkeby

truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> boxV2 = await BoxV2.deployed()
truffle(rinkeby)> box.address
'0xF325bB49f91445F97241Ec5C286f90215a7E3BC6'
truffle(rinkeby)> boxV2.address
'0xEc784bE1CC7F5deA6976f61f578b328E856FB72c'

Upgrade contract

upgrade method for executing ProxyAdmin contract

proxy: the address of the TransparentUpgradeableProxy contract

implementation: address of BoxV2 contract

Then you need to sign the transaction in MetaMask (or the wallet you are using).

Now we can interact with the upgraded contract. We need to use the proxy address to interact with BoxV2. Then, we can call the new "increment" function and observe that the state is maintained throughout the upgrade process.

Enter the truss console

truffle console --network rinkeby

truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> boxV2 = await BoxV2.at(box.address)
truffle(rinkeby)> (await boxV2.retrieve()).toString()
'42'
truffle(rinkeby)> await boxV2.increment()
{ tx:
...
truffle(rinkeby)> (await boxV2.retrieve()).toString()
'43'

Keywords: Blockchain Truffle

Added by netpants on Sun, 30 Jan 2022 09:51:31 +0200