Deploy OpenZeppelin upgradable contracts

Contracts deployed using the OpenZeppelin upgrade plugin are upgradeable: they can be upgraded to modify their code, while preserving their addresses, state, and balances. New features can be added to the project iteratively, or any bugs that may be found in the live version can be fixed.

Configure the 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 the Truffle upgrade plugin

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

Create upgradable contracts

Note that upgradable contracts use 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 the contract to the public network

We will use the Truffle migration to deploy the Box contract. The Truffle upgrade plugin provides a deployProxy function to deploy upgradeable contracts. It will deploy the contract we implemented, ProxyAdmin will act as the project proxy and proxy administrator, and call the initialization function.

Create the following 2_deploy_contracts.js script in the migrations directory.

In this article, we don't have an initialize function yet, so the store function will be used to initialize the state.

2_deploy_contracts.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' });
};

Run truffle migrate with Rinkeby network to deploy. We can see 3 contracts: Box.sol, ProxyAdmin and the proxy contract TransparentUpgradeableProxy.

truffle migrate --network rinkeby

...
2_deploy_contracts.js
===============

   Deploying 'Box'
   ---------------
   > transaction hash:    0x1e5a61c2d4560d6ffe5cc60d7badbfef6d5e420708eebff6dc580bb3f9f9f3e1
   > Blocks: 1            Seconds: 14
   > contract address:    0x7f7dc11961fCD81f53e9F45D9DfBb745832c0657
...

   Deploying 'ProxyAdmin'
   ----------------------
   > transaction hash:    0x298b429391c5a98701bf79df00f4f5526c61570f3091b3d6693e3a4f12a88409
   > Blocks: 1            Seconds: 14
   > contract address:    0x7Bd40e62aEe2c5e232152351f57068038761E20F
...

   Deploying 'TransparentUpgradeableProxy'
   ---------------------------------------
   > transaction hash:    0x7a0043dbe9a35ab9eab8cf0eac1856418cd0c359e330448df016150d293e6716
   > Blocks: 2            Seconds: 26
   > contract address:    0xc2ea7DE43F194bB397761a30a05CEDcF28835F24
...

Publish the verification contract

truffle run verify Box --network rinkeby

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

Note: Box.deployed() is the address of our proxy 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 proxy (who can perform upgrades) is the ProxyAdmin contract. Only the owner of the ProxyAdmin can upgrade the proxy. Warning: Please make sure to go to an address we control when ProxyAdmin ownership is transferred.

Create the following 3_transfer_ownership.js script in the migrations directory.

3_transfer_ownership.js

// migrations/3_transfer_ownership.js
const { admin } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer, network) {
  // 使用你的 钱包 地址
  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 the migration on the Rinkeby network

truffle migrate --network rinkeby

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

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

Implement a new upgrade version

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

Note: We cannot change the storage layout of previous contract implementations, see Upgrades for more details on technical limitations.

Create a new implementation BoxV2.sol in your contracts directory with the following Solidity code.

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 the new upgrade version

Once the new implementation has been tested, the upgrade is ready. This will validate and deploy the new contract. NOTE: We are only preparing to upgrade.

Create the following 4_prepare_upgrade_boxv2.js script in the migrations directory.

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 migrations on the Rinkeby network to deploy new contract implementations

truffle migrate --network rinkeby

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

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

Post-deployment address 

 Enter the truffle 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

Execute the upgrade method of the ProxyAdmin contract

proxy: the address of the TransparentUpgradeableProxy contract

implementation: the address of the BoxV2 contract

Then the transaction needs to be signed 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. We can then call the new "incremental" function and observe that the state is maintained throughout the upgrade.

Enter the truffle 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'

Guess you like

Origin blog.csdn.net/watson2017/article/details/122745389