Hardhat工具包2--可升级合约示例

上一篇文章介绍了Hardhat工具包如何自动化部署合约并发起调用, 本章介绍如何对已经部署的合约升级,需要使用 OpenZeppelin的 Upgrades插件。提纲如下:
(1)原有合约使用Upgrades工具部署,得到代理合约地址
(2)修改合约,生成V2版本
(3)部署V2版本合约,而代理合约地址不变。
项目示例代码: 
本机虚拟机 Ubuntu18 :    root@ubuntu:/opt/uups/demo1
1 安装openzeppelin库
# 安装脚手架工具
npm install --save-dev @openzeppelin/hardhat-upgrades
npm install @nomiclabs/hardhat-waffle
# 安装 openzepplein 可升级合约库, 我们在开发合约时会引用这里的合约文件
npm install @openzeppelin/contracts-upgradeable
2 编写新合约V1
必须重新编写原有的合约,继承基类 Initializable,以后才能使用代理模式进行升级。
注意: 代理模式导致实现合约的构造函数无效,必须改写成initialize函数进行初始化。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
// 引用@openzeppelin/contracts-upgradeable 可升级合约库
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
// 我们的合约需要继承Initializable
contract MyContractV1 is Initializable {
    int storageValue;
    // modifier(修饰器) initializer 可以确保initialize只会被调用一次
    function initialize(int initValue) public initializer {
        storageValue = initValue;
    }
    function setValue(int newValue) public {
        storageValue = newValue + 1;
    }
    function getValue() public view returns (int) {
        return storageValue;
    }
}
3 V1版本编译、部署、调试
root@ubuntu:/opt/uups/demo1# npx hardhat compile
Compiled 6 Solidity files successfully
root@ubuntu:/opt/uups/demo1#
提示成功编译了6个文件,因为继承的父合约也要编译。
修改注册可升级插件
修改hardhat.config.js文件,添加@openzeppelin/hardhat-upgrades的引用
require("@nomicfoundation/hardhat-toolbox");
//新增引用
require("@nomiclabs/hardhat-waffle");
require('@openzeppelin/hardhat-upgrades');
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.9",
};
修改部署脚本文件
修改 deploy_V1.js 脚本文件
// 引用 可升级插件
const { ethers, upgrades } = require("hardhat");
async function main() {
  // 获取 MyContract合约
  const MyContract = await ethers.getContractFactory("MyContractV1");
  // 部署, 传入初始化 storageValue 的值
  const myContract = await upgrades.deployProxy(
    MyContract, // 要部署的合约
    [666],  // 初始化参数
    { initializer: 'initialize' } // 指定初始化函数名称
  );
  // 等待 MyContract合约部署完成
  await myContract.deployed();
  // 输出 MyContract合约地址
  console.log("MyContractV1 deployed to:", myContract.address);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
部署
我们还是部署到本地节点,确保本地节点启动了,如果没有输入npx hardhat node命令启动,并新开控制台程序操作部署:
npx hardhat run --network localhost scripts/deploy.js
MyContract deployed to: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
合约部署成功!
但这里我们只得到了一个地址0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9,但前面我们说可升级合约有逻辑合约和代理合约,为什么这里只有一个合约地址? 下面我们来解密。
真实的合约地址
打开项目目录找到 .openzeppelin目录下的unknown-31337.json文件
这里我对unknown-31337.json里的数据进行了标注,大家可以打开图片查看细节
这里可以看到有三个地址,可我前面只说到了两个,因为在我们实践操作前来说管理员(admin)合约会比较抽象,所以现在再说会比较合适
管理员(admin)合约,这里的管理员其实主要指的是管理代理合约与逻辑合约的关系,当我们操作合约升级时其实是修改代理合约中逻辑合约的实例指向新版本逻辑合约
现在我们能看到三个地址:
管理员合约:0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
代理合约:0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
逻辑合约:0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 MyContract合约的真实地址
合约交互
root@ubuntu:/opt/uups/demo1/ npx hardhat console --network localhost                  
Welcome to Node.js v16.15.1.
Type ".help" for more information.
> const MyContract = await ethers.getContractFactory("MyContract")
undefined
> const myContract = await MyContract.attach("0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9")
undefined
> await myContract.getValue()
BigNumber { value: "666" }
> await myContract.setValue(111)
{
  hash: '0x806e46660e1eacf6c967fa1d6a75414bae230b34a208ae4f855e420d47961319',
  type: 2,
  accessList: [],
  blockHash: '0x731b09b68c08232cc9bc2ed0d18a336ec1c6bf6f9c964c9ad97717000d94418e',
  blockNumber: 6,
  transactionIndex: 0,
  confirmations: 1,
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  gasPrice: BigNumber { value: "455945547" },
  maxPriorityFeePerGas: BigNumber { value: "0" },
  maxFeePerGas: BigNumber { value: "577056082" },
  gasLimit: BigNumber { value: "34472" },
  to: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9',
  value: BigNumber { value: "0" },
  nonce: 5,
  data: '0x5093dc7d000000000000000000000000000000000000000000000000000000000000006f',
  r: '0xf31ab81921058ccb193d6c39165c185febdf0c4437117bc6cad4f33f5c27ab1d',
  s: '0x1c53f8eea9967c097e2f9fa4aa02d8ab94107350a7bfb0751c11c981bdf0f123',
  v: 0,
  creates: null,
  chainId: 31337,
  wait: [Function (anonymous)]
}
> await myContract.getValue()
BigNumber { value: "112" }
4 V2版本合约代码
我们在contracts目录下新建MyContractV2.sol合约文件,把MyContract.sol的代码拷贝过来,修改合约名,修复错误代码,并添加一个新的string型的状态变量和操作方法,
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
// 我们的合约需要继承Initializable
contract MyContractV2 is Initializable {
    int storageValue;
    // 新的状态变量
    string storageStr;
    function initialize(int initValue) public initializer {
        storageValue = initValue;
    }
    function setValue(int newValue) public {
        storageValue = newValue;
    }
    function getValue() public view returns (int) {
        return storageValue;
    }
    // 设置 storageStr
    function setStr(string memory newStr) public {
        storageStr = newStr;
    }
    // 查询 storageStr
    function getStr() public view returns (string memory) {
        return storageStr;
    }
}
编译
npx hardhat compile
修改部署脚本文件
我们还需要在scripts目录下新建upgrade.js升级脚本文件,并添加下面的代码:
const { ethers, upgrades } = require("hardhat");
// 代理合约地址
const myContractProxyAddr = "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9"
async function main() {
    const MyContractV2 = await ethers.getContractFactory("MyContractV2");
    // 升级
    const myContractV2 = await upgrades.upgradeProxy(myContractProxyAddr, MyContractV2);
    console.log("myContractV2 upgraded");
}
main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });
部署
# 运行 pgrade-MyContract.js
root@ubuntu:/opt/uups/demo1# npx hardhat run --network localhost scripts/upgrade.js
Compiled 2 Solidity files successfully
myContractV2 upgraded
合约地址
再次打开项目目录找到 .openzeppelin目录下的unknown-31337.json文件,我们可以看到impls里多了一个合约地址。
这里的 0x0165878A594ca255338adfa4d48449f69242Eb8F 就是刚刚成功部署的逻辑合约(MyContractV2)
合约交互
操作跟前面一样,但这次我们几个点:
1. 合约升级后,以前的数据是否正常?
2. 升级后,bug是否已经修复
3. 新添加的string型状态变量与相关操作方法是否可以正常调用
root@ubuntu:/opt/uups/demo1/  npx hardhat console --network localhost                          
Welcome to Node.js v16.15.1.
Type ".help" for more information.
> const MyContractV2 = await ethers.getContractFactory("MyContractV2")
undefined
#
# 这里实例化合约时,我们还是只想相同的代理合约地址
> const myContractV2 = await MyContractV2.attach("0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9")
undefined
#
# 这里验证了第1点,升级后数据依然保留
> await myContractV2.getValue()
BigNumber { value: "112" }
#
# 再次调用 setValue 方法
> await myContractV2.setValue(111)
{
  hash: '0x3e40f270713a705e403e89d4436b9a058a0463290ce0d903367f37efcc103b5c',
  type: 2,
  accessList: [],
  blockHash: '0xccb9f43f973204a8ebf96e563269bc89a2d9f37131653a1ad5f22bfc78d649f1',
  blockNumber: 9,
  transactionIndex: 0,
  confirmations: 1,
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  gasPrice: BigNumber { value: "307113676" },
  maxPriorityFeePerGas: BigNumber { value: "0" },
  maxFeePerGas: BigNumber { value: "388690746" },
  gasLimit: BigNumber { value: "34239" },
  to: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9',
  value: BigNumber { value: "0" },
  nonce: 8,
  data: '0x5093dc7d000000000000000000000000000000000000000000000000000000000000006f',
  r: '0xd074afac4ae37c931d2c270e9123e02e9291e0cf514d1640e67ce13d077628a1',
  s: '0x3c47a9528e95e67a325ffb79c6a52da3523b2268bc3dbff0e3589a0f843b293f',
  v: 1,
  creates: null,
  chainId: 31337,
  wait: [Function (anonymous)]
}
#
# 验证了第2点,之前的bug已经修复
> await myContractV2.getValue()
BigNumber { value: "111" }
> await myContractV2.getStr()
''
#
# 调用新版本添加的方法 setStr
> await myContractV2.setStr("i am lyon")
{
  hash: '0x017db24bdc29a1b56bc02a197f7347ae2186508aed8eeec206c810c52f2d358c',
  type: 2,
  accessList: [],
  blockHash: '0x5d66d6259200a092eea01a448fbbbf43b000773433aa6f75658cf75210960a09',
  blockNumber: 10,
  transactionIndex: 0,
  confirmations: 1,
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  gasPrice: BigNumber { value: "268811416" },
  maxPriorityFeePerGas: BigNumber { value: "0" },
  maxFeePerGas: BigNumber { value: "340214448" },
  gasLimit: BigNumber { value: "52779" },
  to: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9',
  value: BigNumber { value: "0" },
  nonce: 9,
  data: '0x191347df000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096920616d206c796f6e0000000000000000000000000000000000000000000000',
  r: '0x2519d536c6a0fb26e2fca71738247aedb9f9e14cf6b66f9cea67b3fe999f9837',
  s: '0x2aad3b415db1d9078d8766bb79f6f0fc98eb8ce5d8d4c9688c5e63016183d899',
  v: 0,
  creates: null,
  chainId: 31337,
  wait: [Function (anonymous)]
}
#
# 这里验证了第3点,新添加的方法和状态变量都访问正常
> await myContractV2.getStr()
'i am lyon'
到这里我们已经成功的完成了合约升级, 并且通过控制台验证的方式验证了升级后的合约符合预期。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值