使用OpenZeppelin升级插件部署的合约具备可升级的特性:可以升级以修改其代码,同时保留其地址,状态和余额。 可以迭代地向项目中添加新功能,或修复在线上版本中可能发现的任何错误。

配置开发环境

创建一个新的npm项目

mkdir mycontract && cd mycontractnpm init -y

安装并初始化Truffle

npm i --save-dev trufflenpx truffle init

安装Truffle升级插件

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

创建可升级合约

注意,可升级合约使用initialize函数而不是构造函数来初始化状态。

Box.sol

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

将合约部署到公共网络

我们将使用Truffle迁移来部署Box合约。Truffle升级插件提供了一个 deployProxy功能来部署可升级合约。它将部署我们实现的合约,ProxyAdmin会作为项目代理和代理管理员,并调用初始化函数。

在migrations目录中创建以下2_deploy_contracts.js脚本。

在本文中,我们还没有initialize函数,因此将使用store 函数来初始化状态。

2_deploy_contracts.js

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

使用Rinkeby网络运行truffle migration进行部署。 我们可以看到3 个合约:Box.sol、ProxyAdmin 和 代理合约TransparentUpgradeableProxy。

truffle migrate --network rinkeby


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

Deploying ‘Box’
—————
> transaction hash:0x1e5a61c2d4560d6ffe5cc60d7badbfef6d5e420708eebff6dc580bb3f9f9f3e1
> Blocks: 1Seconds: 14
> contract address:0x7f7dc11961fCD81f53e9F45D9DfBb745832c0657

Deploying ‘ProxyAdmin’
———————-
> transaction hash:0x298b429391c5a98701bf79df00f4f5526c61570f3091b3d6693e3a4f12a88409
> Blocks: 1Seconds: 14
> contract address:0x7Bd40e62aEe2c5e232152351f57068038761E20F

Deploying ‘TransparentUpgradeableProxy’
—————————————
> transaction hash:0x7a0043dbe9a35ab9eab8cf0eac1856418cd0c359e330448df016150d293e6716
> Blocks: 2Seconds: 26
> contract address:0xc2ea7DE43F194bB397761a30a05CEDcF28835F24

发布验证合约

truffle run verify Box --network rinkeby

我们可以使用Truffle控制台(truffle console)与我们的合约进行交互。

注意: Box.deployed() 是我们的代理合约的地址。

truffle console --network rinkeby

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

当前代理的管理员(可以执行升级)是ProxyAdmin合约。 只有ProxyAdmin的所有者可以升级代理。 警告:ProxyAdmin 所有权转移时请确保转到我们控制的地址上。

在migrations目录中创建以下3_transfer_ownership.js脚本。

3_transfer_ownership.js

// migrations/3_transfer_ownership.jsconst { admin } = require('@openzeppelin/truffle-upgrades'); module.exports = async function (deployer, network) {// 使用你的 钱包 地址const admin = '0x1c14600daeca8852BA559CC8EdB1C383B8825906'; // Don't change ProxyAdmin ownership for our test networkif (network !== 'test') {// The owner of the ProxyAdmin can upgrade our contractsawait admin.transferProxyAdminOwnership(admin);}};

在Rinkeby网络上运行迁移

truffle migrate --network rinkeby


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

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

实现一个新的升级版本

一段时间后,我们决定要向合约添加功能。 在本文中,我们将添加一个increment函数。

注意:我们无法更改之前合约实现的存储布局,有关技术限制的更多详细信息,请参阅升级。

使用以下Solidity代码在你的contracts目录中创建新的实现BoxV2.sol 。

BoxV2.sol

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

部署新的升级版本

一旦测试了新的实现,就可以准备升级。 这将验证并部署新合约。 注意:我们仅是准备升级。

在migrations目录中创建以下4_prepare_upgrade_boxv2.js脚本。

4_prepare_upgrade_boxv2.js

// migrations/4_prepare_upgrade_boxv2.jsconst 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 });};

在Rinkeby网络上运行迁移,以部署新的合约实现

truffle migrate --network rinkeby


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

Deploying ‘BoxV2’
—————–
> transaction hash:0x078c4c4454bb15e3791bc80396975e6e8fc8efb76c6f54c321cdaa01f5b960a7
> Blocks: 1Seconds: 17
> contract address:0xEc784bE1CC7F5deA6976f61f578b328E856FB72c

部署后地址

进入truffle控制台

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’

升级合约

执行ProxyAdmin合约的upgrade方法

proxy:TransparentUpgradeableProxy合约的地址

implementation:BoxV2合约的地址

然后需要在MetaMask(或你正使用的钱包)中签署交易。

现在,我们可以与升级后的合约进行交互。 我们需要使用代理地址与BoxV2进行交互。 然后,我们可以调用新的“增量”功能,观察到整个升级过程中都保持了状态。

进入truffle控制台

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’