使用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_box.js脚本。
在本文中,我们还没有initialize函数,因此将使用store 函数来初始化状态。
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' });
};
使用Rinkeby网络运行truffle migration进行部署。 我们可以看到3 个合约:Box.sol、ProxyAdmin 和 代理合约AdminUpgradeabilityProxy。
truffle migrate --network rinkeby
发布验证合约
truffle run verify Box --network rinkeby
我们可以使用Truffle控制台(truffle console)与我们的合约进行交互。
注意: Box.deployed() 是我们的代理合约的地址。
truffle console --network rinkeby
当前代理的管理员(可以执行升级)是ProxyAdmin合约。 只有ProxyAdmin的所有者可以升级代理。 警告:ProxyAdmin 所有权转移时请确保转到我们控制的地址上。
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
实现一个新的升级版本
一段时间后,我们决定要向合约添加功能。 在本文中,我们将添加一个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);
}
}
部署新的升级版本
一旦测试了新的实现,就可以准备升级。 这将验证并部署新合约。 注意:我们仅是准备升级。。
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
进入truffle控制台
truffle console --network rinkeby
升级合约
执行ProxyAdmin合约的upgrade方法
然后需要在MetaMask(或你正使用的钱包)中签署交易。
现在,我们可以与升级后的合约进行交互。 我们需要使用代理地址与BoxV2进行交互。 然后,我们可以调用新的“增量”功能,观察到整个升级过程中都保持了状态。
进入truffle控制台
truffle console --network rinkeby