id:BSN_2021
公众号:BSN研习社

背景:在开发或保护solidity语言的智能合约时,常常会因为业务逻辑变动而变动合约内的逻辑,这就要思考在不影响以前智能合约中已上链的数据的同时,批改或扩大新的业务逻辑,所以合约第一次开发时就须要思考其自身反对可降级性能

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

实用对象:实用于BSN凋谢联盟链武汉链(基于ETH),也可实用于其余反对solidity语言的链框架

如何应用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-s...
[2] Using with Upgrades - OpenZeppelin Docs: https://docs.openzeppelin.com...