写点什么

如何使用 hardhat 进行合约 uups 模式升级

作者:BSN研习社
  • 2022 年 4 月 20 日
  • 本文字数:3085 字

    阅读完需:约 10 分钟

背景:在开发或维护 solidity 语言的智能合约时,经常会因为业务逻辑变动而变动合约内的逻辑,这就要考虑在不影响以前智能合约中已上链的数据的同时,修改或扩展新的业务逻辑,所以合约第一次开发时就需要考虑其本身支持可升级功能

目的:本篇文章是为了让读者快速上手使用 hardhat 并搭配 openZeppelin 的 uups 升级模式对合约进行可升级适配以及指导后续如何进行合约升级

适用对象:适用于 BSN 开放联盟链武汉链(基于 ETH),也可适用于其他支持 solidity 语言的链框架


id:BSN_2021

公众号:BSN 研习社

如何使用 hardhat 进行合约 uups 模式升级

  • 安装 hardhat

  • 初始化项目

  • 编写合约

  • 编写测试文件

  • 编辑配置文件

  • 执行项目

  • 参考

安装 hardhat

1.创建空的文件夹 demo-uups 并进入文件夹里面,执行 npm init

2.执行安装命令 npm install --save-dev hardhat

3.安装依赖 npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers

4.安装 openZeppelin 的 uups 可升级合约包 npm install --save-dev @openzeppelin/contracts-upgradeable

5.安装测试工具 mocha npm install --save-dev mocha

初始化项目

执行 npx hardhat 选择 Create a basic sample project

初始化完成后的目录结构为

编写合约

在 contracts 目录下面新建两个名称分别为 MyLogicV1 和 MyLogicV2 的.sol 文件并进行代码编写。实现 openZeppelin 的 uups 升级模式必须要引入 openZeppelin 相关库文件,且必须在 constructor()方法上加 @custom:oz-upgrades-unsafe-allow constructor 字样注释


// contract/MyLogicV1.sol
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract MyLogicV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable { function initialize() initializer public { __Ownable_init(); __UUPSUpgradeable_init(); }
/// @custom:oz-upgrades-unsafe-allow constructor constructor() initializer {}
function _authorizeUpgrade(address) internal override onlyOwner {}
mapping(string => uint256) private logic;
event logicSetted(string indexed _key, uint256 _value);
function SetLogic(string memory _key, uint256 _value) external { logic[_key] = _value; emit logicSetted(_key, _value); }
function GetLogic(string memory _key) public view returns (uint256){ return logic[_key]; }}
复制代码



// contract/MyLogicV2.sol
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract MyLogicV2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { function initialize() initializer public { __Ownable_init(); __UUPSUpgradeable_init(); }
/// @custom:oz-upgrades-unsafe-allow constructor constructor() initializer {}
function _authorizeUpgrade(address) internal override onlyOwner {}
mapping(string => uint256) private logic;
event logicSetted(string indexed _key, uint256 _value);
function SetLogic(string memory _key, uint256 _value) external { logic[_key] = _value; emit logicSetted(_key, _value); }
function GetLogic(string memory _key) public view returns (uint256){ return logic[_key]+100; }}
复制代码

MyLogicV1 实现了一个对于 logic 的 map 的写入方法和读取方法,MyLogicV2 只是改写了 MyLogicV1 的 GetLogic 方法中的处理逻辑,使得每次返回的值加了 100

编写测试文件在 test 目录下创建生成名称为 update-test 的.js 文件并进行代码编写


// test/update-test.tsconst { expect } = require('chai');const { ethers, upgrades } = require('hardhat');

let myLogicV1;let myLogicV2;
describe('uups mode upgrade', function () { it('deploys', async function () { const MyLogicV1 = await ethers.getContractFactory('MyLogicV1'); myLogicV1 = (await upgrades.deployProxy(MyLogicV1, {kind: 'uups'})); console.log(myLogicV1.address); }) it('myLogicV1 set', async function () { await myLogicV1.SetLogic("aa", 1); expect((await myLogicV1.GetLogic("aa")).toString()).to.equal('1'); }) it('upgrades', async function () { const MyLogicV2 = await ethers.getContractFactory('MyLogicV2'); myLogicV2 = (await upgrades.upgradeProxy(myLogicV1, MyLogicV2)); console.log(myLogicV2.address); }) it('myLogicV2 get', async function () { expect((await myLogicV2.GetLogic("aa")).toString()).to.equal('101'); })})
复制代码

测试文件步骤详解

  • deploys: 调用 upgrades 中的 deployProxy 方法以 uups 的方式部署 MyLogicV1 合约

  • myLogicV1 set:调用 MyLogicV1 合约中的 SetLogic 方法将值传入,并调用 GetLogic 方法确认返回值为 1

  • upgrades:调用 upgrades 中的 upgradeProxy 方法升级

  • myLogicV2 get:调用 MyLogicV2 合约中的 GetLogic 方法确认返回值为 101

编辑配置文件编辑 hardhat.config.js 文件


require("@nomiclabs/hardhat-waffle");require('@openzeppelin/hardhat-upgrades');
// This is a sample Hardhat task. To learn how to create your own go to// https://hardhat.org/guides/create-task.htmltask("accounts", "Prints the list of accounts", async (taskArgs, hre) => { const accounts = await hre.ethers.getSigners();
for (const account of accounts) { console.log(account.address); }});
// You need to export an object to set up your config// Go to https://hardhat.org/config/ to learn more
/** * @type import('hardhat/config').HardhatUserConfig */module.exports = { solidity: { version: "0.8.4", settings: { optimizer: { enabled: true, runs: 200 } } }, mocha: { timeout: 60000 }};
复制代码

执行项目

1.执行编译合约

npx hardhat compile

2.启动本地测试节点

npx hardhat node

3.新开一个窗口运行测试文件

npx hardhat test test/update-test.js --network localhost

测试通过,合约地址都为代理合约地址,测试说明成功升级改写了 GetLogic 中的处理逻辑,使得每次返回的值都在原来的基础上多加了 100

参考

Hardhat 官方文档:Overview | Hardhat | Ethereum development environment for professionals by Nomic Foundation[1]

OpenZeppelin 官方文档:Using with Upgrades - OpenZeppelin Docs[2]

References

[1] Overview | Hardhat | Ethereum development environment for professionals by Nomic Foundation: https://hardhat.org/getting-started/

[2] Using with Upgrades - OpenZeppelin Docs: https://docs.openzeppelin.com/contracts/4.x/upgradeable

用户头像

BSN研习社

关注

还未添加个人签名 2021.11.05 加入

还未添加个人简介

评论

发布
暂无评论
如何使用hardhat进行合约uups模式升级_区块链_BSN研习社_InfoQ写作社区