首页 > 其他分享 >使用OpenZeppelin的Upgrades插件开发可升级的智能合约

使用OpenZeppelin的Upgrades插件开发可升级的智能合约

时间:2024-06-20 18:01:14浏览次数:11  
标签:Box 插件 const hardhat await value Upgrades OpenZeppelin 合约

一、原理

https://docs.openzeppelin.com/learn/upgrading-smart-contracts#how-upgrades-work

当创建一个可升级合约的时候,OpenZeppelin Upgrades Plugins实际部署了3个合约:

  1. 原始业务逻辑合约,也叫实现合约
  2. ProxyAdmin合约
  3. 提供给用户进行交互的Proxy合约,它是原始业务逻辑合约的代理,外部交互的地址和方法都由它提供。

代理合约负责委托调用实现合约。委托调用(delegate call)类似于常规调用,不同之处在于所有代码都在调用者的上下文中执行,而不是被调用者的上下文中。因此,实现合约代码中的转账实际上会转移代理合约的余额,任何对合约存储的读写操作都会从代理合约自身的存储中读取或写入。

这使我们能够将合约的状态和代码解耦:代理合约持有状态,而实现合约提供代码。这也使我们可以通过让代理合约委托给不同的实现合约来更改代码。

一次升级涉及以下步骤:

  1. 部署新的实现合约。

  2. 向代理合约发送一笔交易,将其实现地址更新为新的地址。

智能合约的任何用户始终与代理合约交互,代理合约的地址永远不会改变。这使你能够推出升级或修复漏洞,而无需请求用户在他们的端更改任何内容——他们只需继续与一如既往的地址进行交互。

你可以有多个代理合约来代理同一个实现合约,因此如果你计划部署多个相同合约的副本,使用这种模式可以节省Gas费用。

注:应该是一般实现合约的代码多,而代理合约结构更简单而代码少,所以可以节省部署时候的Gas费。

二、开发实践
cd upgrades-contract
npx hardhat init
npm install --save-dev @openzeppelin/hardhat-upgrades  //安装可升级插件
//hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
//require('@nomiclabs/hardhat-ethers');
require('@openzeppelin/hardhat-upgrades');
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.24",
};
(1)编写合约Box
// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Box {
    uint256 private _value;

    // Emitted when the stored value changes
    event ValueChanged(uint256 value);

    // Stores a new value in the contract
    function store(uint256 value) public {
        _value = value;
        emit ValueChanged(value);
    }

    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return _value;
    }
}

测试一下

const main = async () => {
    const [owner, randomPerson] = await hre.ethers.getSigners();

    const contract = await hre.ethers.deployContract("Box");
    //const contract = await contractFactory.deploy();
    //await contract.deployed();
    console.log("合约部署到如下地址:", await contract.getAddress());
    console.log("合约部署人:", owner.address);

    let txn;
    let value = 42;

    txn = await contract.store(value);
    await txn.wait();
  
    value = await contract.retrieve();
    console.log("合约内存储的值是:", value);

};

const runMain = async () => {
    try{
        await main();
        process.exit(0);
    }catch(error){
        console.log(error);
        process.exit(1);
    }
};

runMain();

npx hardhat compile编译后,执行npx hardhat run run.js输出:

npx hardhat run .\ignition\modules\run.js
合约部署到如下地址: 0x5FbDB2315678afecb367f032d93F642f64180aa3
合约部署人: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
合约内存储的值是: 42n
(2)把Box合约变成可升级合约
只需要在部署的时候用upgrades插件进行deployProxy部署,并在后续升级的时候用这个插件将新版本合约代码upgradeProxy升级上去即可。

使用upgrades.deployProxy部署Box合约:

const { ethers, upgrades } = require('hardhat');

async function main () {
  const Box = await hre.ethers.getContractFactory('Box');
  console.log('Deploying Box...');
  const box = await upgrades.deployProxy(Box, [42], { initializer: 'store' });
  //await box.deployed();
  console.log('Box deployed to:', await box.getAddress());
}

main();

npx hardhat run .\ignition\modules\deploy.js --network localhost
Deploying Box...
Box deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512

在这之前要先把本地网络启动起来, npx hardhat node

编写v2版本合约:

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BoxV2 {
    uint256 private _value;

    // Emitted when the stored value changes
    event ValueChanged(uint256 value);

    // Stores a new value in the contract
    function store(uint256 value) public {
        _value = value;
        emit ValueChanged(value);
    }

    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return _value;
    }

    function increment() public {
        _value = _value + 1;
        emit ValueChanged(_value);
    }
}

把本地网络里的合约用v2版本进行升级:

const { ethers, upgrades } = require('hardhat');

async function main () {
  const BoxV2 = await ethers.getContractFactory('BoxV2');
  console.log('Upgrading Box...');
  await upgrades.upgradeProxy('0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512', BoxV2);
  console.log('Box upgraded');
}

main();

npx hardhat run .\ignition\modules\upgradeV2.js --network localhost
Compiled 1 Solidity file successfully (evm target: paris).
Upgrading Box...
Box upgraded

然后再次进行测试:这里直接使用npx hardhat console进入命令行交互,直接写脚本。

npx hardhat console --network localhost
Welcome to Node.js v20.9.0.
Type ".help" for more information.
> const BoxV2 = await ethers.getContractFactory('BoxV2');
undefined

> const box = await BoxV2.attach('0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512');
undefined

> await box.increment();
ContractTransactionResponse {
  provider: HardhatEthersProvider {
    _hardhatProvider: LazyInitializationProviderAdapter {
      _providerFactory: [AsyncFunction (anonymous)],
      _emitter: [EventEmitter],
      _initializingPromise: [Promise],
      provider: [BackwardsCompatibilityProviderAdapter]
    },
    _networkName: 'localhost',
    _blockListeners: [],
    _transactionHashListeners: Map(0) {},
    _eventListeners: []
  },
  blockNumber: 5,
  blockHash: '0x53cde3207dbcc1762000a0d2e7457b7d6e21439dbfb57274473d4c644afede67',
  index: undefined,
  hash: '0xe4f86449d85b0314025e96ae6565c1c0080c33422808cae9a0014689656720ba',
  type: 2,
  to: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  nonce: 4,
  gasLimit: 30000000n,
  gasPrice: 655623046n,
  maxPriorityFeePerGas: 157200340n,
  maxFeePerGas: 655623046n,
  maxFeePerBlobGas: null,
  data: '0xd09de08a',
  value: 0n,
  chainId: 31337n,
  signature: Signature { r: "0x57c07d987a9df1231142836b7e9d2eccd01f30e1561a4ee61d39b1f4e4fc4965", s: "0x447d9f942add1c47bf97f67ca8e129e2aba5f2f026c0d64f617f40ce963918af", yParity: 0, networkV: null },
  accessList: [],
  blobVersionedHashes: null
}


> (await box.retrieve()).toString();
'43'

可以看到,0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512这个合约保留了合约地址和状态变量的值,事实上合约的balance也会保留。

@问题:

好像还是对hardhat里边的一些内置对象使用不是很清楚,比如:

const { ethers, upgrades } = require('hardhat');

async function main () {
  const Box = await ethers.getContractFactory('Box');
  console.log('Deploying Box...');
  const box = await upgrades.deployProxy(Box, [42], { initializer: 'store' });
  await box.deployed();
  console.log('Box deployed to:', box.address);
}

main();

ethers, upgrades这俩对象怎么require出来的,然后await box.deployed()为啥报错提示deployed()方法不存在。

是不是跟依赖安装冲突有关, hardhat-ethers 这个插件我没装进去,npm进行安装的时候以及hardhat.config.js里边没有import

参考

https://docs.openzeppelin.com/learn/upgrading-smart-contracts

https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable

https://hardhat.org/tutorial/testing-contracts

标签:Box,插件,const,hardhat,await,value,Upgrades,OpenZeppelin,合约
From: https://www.cnblogs.com/lyhero11/p/18259193

相关文章

  • VueJs导出Excel,插件 xlsx
    1.安装组件:cnpminstallxlsx--save2.新建一个辅助ts、js文件我的excelUtil.ts注意:新版本的插件导入写法变了:import*asXLSXfrom'xlsx'import{timestampToDateString}from'@/common/dateUtil'import*asXLSXfrom'xlsx'//headerColumns:属性列表//header......
  • Stable Diffusion【应用篇】【艺术写真】:一个让影楼倒闭的SD插件,使用InstantID插件一
    大家好,我是AIGC阿道夫。最近收到不少网友关于AI写真方面的留言,希望能写一些关于AI写真方面的文章。个人关注AI写真有一段时间了,网上也看到了不少写的文章教程和案例,作为一个AI绘画的狂热爱好者,自然也少不了去操作实践一下,关于AI写真,后面我会作为专题来写一系列的文章,把自己......
  • IDEA~安装spring-javaformat插件
    相关文章springboot~代码风格统计spring-javaformat-maven-plugin插件springboot~spring-javaformat插件惹的祸说明在项目中,我们可以通过安装maven插件,spring-javaformat来达到代码统一的目的,而在idea中,为了与maven插件保持一致,我们最好也安装一个这个插件插件地址https:/......
  • 借助浏览器实现一个录屏插件?
    说在前面......
  • 【效率提升】倍速插件Global Speed
    globalspeed插件可以控制网页在线视频,能够应用在Edge和Google浏览器中,只需要在插件商店中下载并配置即可。这款插件的配置选项有很多,支持视频倍速(最低0.25倍速,最高16倍速),固定标签页,设置标记点,跳转标记点,循环播放等。除此之外,还有更多可以选择的配置。一般情况下,我都是打......
  • 仅6M,WebTab(插件)内置30+神级功能的“开挂”神器!
    曾几何时,hao123、2345这些导航站是我们上网的常用门户。对于大多数人来说,浏览新闻和资讯时,它们非常方便。不过,如果你有点洁癖或者喜欢整洁的界面,那这些导航页面可能就不那么称心了:满屏的广告、五花八门的推广,真的挺让人头疼的。​俗话说得好,需求催生市场。为了迎合大家......
  • 探索Semantic Kernel内置插件:深入了解ConversationSummaryPlugin的应用
    前言经过前几章的学习我们已经熟悉了SemanticKernel插件的概念,以及基于Prompts构造的SemanticPlugins和基于本地方法构建的NativePlugins。本章我们来讲解一下在SemanticKernel中内置的一些插件,让我们避免重复造轮子。内置插件SemanticKernel有非常多的预定义插件,作为......
  • 使用 Apache JMeter Flexible File Writer 插件的详细指南
    简介ApacheJMeter是一个强大的开源工具,广泛用于性能测试和负载测试。为了更好地记录和分析测试结果,JMeter提供了多个监听器(Listener)来收集数据。FlexibleFileWriter是一个非常有用的插件,它允许用户以自定义格式将测试结果写入文件中。本指南将详细介绍如何安装、配置和使用......
  • MyBatisX插件生成代码
    MyBatisX插件MyBatisPlus提供了一个IDEA插件——MybatisX,使用它可根据数据库快速生成Entity、Mapper、Mapper.xml、Service、ServiceImpl等代码,使用户更专注于业务。下面演示具体用法安装插件在IDEA插件市场搜索MyBatisX,进行在线安装配置数据库连接在IDEA中配置数据......
  • vscode使用tabnine ide插件,ai插件
    安装安装上面这个插件后,会提示登录,可以使用github在线地址登录。代码提示代码指令使用侧边栏功能总结tabninevscode插件就是一款代码ai自动补全的插件,侧边栏还可以进行代码解释、修正等功能。......