使用OpenZeppelin升级插件部署的合约具备可升级的特性:可以升级以修改其代码,同时保留其地址,状态和余额。 可以迭代地向项目中添加新功能,或修复在线上版本中可能发现的任何错误。
配置开发环境创建一个新的npm项目
mkdir mycontract && cd mycontract
npm init -y
安装并初始化Truffle
npm i --save-dev truffle
npx truffle init
安装Truffle升级插件
npm i --save-dev @openzeppelin/truffle-upgrades
创建可升级合约
注意,可升级合约使用initialize函数而不是构造函数来初始化状态。
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;
}
}
将合约部署到公共网络
我们将使用Truffle迁移来部署Box合约。Truffle升级插件提供了一个 deployProxy功能来部署可升级合约。它将部署我们实现的合约,ProxyAdmin会作为项目代理和代理管理员,并调用初始化函数。
在migrations目录中创建以下2_deploy_contracts.js脚本。
在本文中,我们还没有initialize函数,因此将使用store 函数来初始化状态。
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' });
};
使用Rinkeby网络运行truffle migration进行部署。 我们可以看到3 个合约:Box.sol、ProxyAdmin 和 代理合约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 ...
发布验证合约
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.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);
}
};
在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: 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);
}
}
部署新的升级版本
一旦测试了新的实现,就可以准备升级。 这将验证并部署新合约。 注意:我们仅是准备升级。
在migrations目录中创建以下4_prepare_upgrade_boxv2.js脚本。
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 });
};
在Rinkeby网络上运行迁移,以部署新的合约实现
truffle migrate --network rinkeby
... 4_prepare_upgrade_boxv2.js ==========================
Deploying 'BoxV2' ----------------- > transaction hash: 0x078c4c4454bb15e3791bc80396975e6e8fc8efb76c6f54c321cdaa01f5b960a7 > Blocks: 1 Seconds: 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'