基于Hardhat和Openzeppelin开发可升级合约(二)

这篇具有很好参考价值的文章主要介绍了基于Hardhat和Openzeppelin开发可升级合约(二)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

基于Hardhat和Openzeppelin开发可升级合约(二)

在本章我将开始介绍和演示 基于 Openzeppelin 的可升级合约解决方案

简介

根据设计,智能合约是不可变的。但随着新的客户需求和产品设计的升级迭代,合约也需要升级。
Openzeppelin 的基础可升级合约解决方案是将合约数据与逻辑分离。

  • 代理合约(Proxy) 负责转发交易到逻辑合约,并保存 合约数据
  • 逻辑合约(Logic)负责实现功能逻辑
    升级时,只需要重新部署新版本的逻辑合约,并将代理合约中的逻辑合约实例指向新版本逻辑合约实例即可

可升级合约的原理-DelegateCall

第三方库

  • Hardhat 关于Hardhat的安装和介绍, 参考我的另一篇文章<<简介智能合约开发框架-Hardhat>>
  • Upgrades Plugins

安装 openzeppelin 库

# 安装脚手架工具npm install --save-dev @openzeppelin/hardhat-upgrades
# 安装 openzepplein 可升级合约库, 我们在开发合约时会引用这里的合约文件npm install @openzeppelin/contracts-upgradeable

编码

修改合约文件

基于上一章中的项目进行修改, 所以这里还是修改 MyContract.sol 合约文件

初始化函数改造

这里我们需要引用Openzeppelin的可升级合约库@openzeppelin/contracts-upgradeable

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

// 引用@openzeppelin/contracts-upgradeable 可升级合约库
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

// 我们的合约需要继承Initializable
contract MyContract 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;
    }
}

这里我们做了两件事:

  1. 引入可升级合约库
  2. 改造初始化函数

需要重点说明了的是,在可升级合约中,不能使用合约的构造函数 constructor()
《基于 Openzeppelin 的可升级合约解决方案的注意事项》

编译

如果刚刚按照第一章操作的读者,请按两次 Ctrl + C 退出控制台再输入编译命令编译

# 编译
➜  npx hardhat compile
Compiled 3 Solidity files successfully
# 提示成功编译了3个 solidity文件,因为MyContract 继承的父合约也要编译
# 因为我们继承了 Initializable,以及 Initializable 所继承的 AddressUpgradeable

这里编译通过,我们继续下一步 部署和交互测试

部署与调试

修改注册可升级插件

修改hardhat.config.js文件,添加@openzeppelin/hardhat-upgrades的引用

require("@nomiclabs/hardhat-waffle");
require('@openzeppelin/hardhat-upgrades');

修改部署脚本文件

还是修改 deploy.js 脚本文件

// 引用 可升级插件
const { ethers, upgrades } = require("hardhat");

async function main() {
  // 获取 MyContract合约
  const MyContract = await ethers.getContractFactory("MyContract");
  // 部署, 传入初始化 storageValue 的值
  const myContract = await upgrades.deployProxy(
    MyContract, // 要部署的合约
    [666],  // 初始化参数
    { initializer: 'initialize' } // 指定初始化函数名称
  );

  // 等待 MyContract合约部署完成
  await myContract.deployed();

  // 输出 MyContract合约地址
  console.log("MyContract 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文件
hardhat openzeppline,区块链开发,Ethereum(以太坊),以太坊,区块链,solidity,智能合约,可升级合约
这里我对unknown-31337.json里的数据进行了标注,大家可以打开图片查看细节
这里可以看到有三个地址,可我前面只说到了两个,因为在我们实践操作前来说管理员(admin)合约会比较抽象,所以现在再说会比较合适

  • 管理员(admin)合约,这里的管理员其实主要指的是管理代理合约逻辑合约的关系,当我们操作合约升级时其实是修改代理合约逻辑合约的实例指向新版本逻辑合约

现在我们能看到三个地址:

  • 管理员合约:0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
  • 代理合约:0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
  • 逻辑合约:0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 MyContract合约的真实地址

接下来我们通过控制台测试刚刚部署的合约

本地节点上的合约交互

刚上一章的步骤一样

➜ 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" }

这里可以看到,我们在实例化MyContract合约时,指定的地址是代理合约地址0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9,而不是逻辑合约(真正的MyContract合约)地址0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 ,并且交易都成功执行了。
这是因为代理合约会把我们的交易转发给MyContract合约,最后把结果返还给我们。

问题

上一章我提到过,我想通过 setValue 方法设置新的 storageValue 值,但是我们在合约里的代码多写了一个 +1(虽然是为了演示故意为之)。所以我要修复合约代码里的bug,删除这部分代码
hardhat openzeppline,区块链开发,Ethereum(以太坊),以太坊,区块链,solidity,智能合约,可升级合约
那么接下来我们着手开始编写新版本合约代码,并完成合约升级

升级新版本

新版本合约代码

我们在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                                      
Compiled 1 Solidity file successfully
# 因为上一次编译文件已经把前面写的合约编译过了,所以这次只编译新增的 MyContractV2

部署

修改部署脚本文件

部署前,我们还需要在scripts目录下新建upgrade-MyContract.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
➜ npx hardhat run --network localhost scripts/upgrade-MyContract.js 
myContractV2 upgraded

部署完成

合约地址

再次打开项目目录找到 .openzeppelin目录下的unknown-31337.json文件,我们可以看到impls里多了一个合约地址
hardhat openzeppline,区块链开发,Ethereum(以太坊),以太坊,区块链,solidity,智能合约,可升级合约
这里的0x0165878A594ca255338adfa4d48449f69242Eb8F就是刚刚成功部署的逻辑合约(MyContractV2)

合约交互

操作跟前面一样,但这次我们几个点:

  1. 合约升级后,以前的数据是否正常?
  2. 升级后,bug是否已经修复
  3. 新添加的string型状态变量与相关操作方法是否可以正常调用

那么开始

➜ 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'

到这里我们已经成功的完成了合约升级, 并且通过控制台验证的方式验证了升级后的合约符合预期。

总结

我相信刚接触合约升级的读者肯定会有很多的疑问,我会在后面的文章来继续介绍合约升级的相关知识。
可升级合约的原理-DelegateCall

有问题,或者建议请留言,谢谢。文章来源地址https://www.toymoban.com/news/detail-803988.html

到了这里,关于基于Hardhat和Openzeppelin开发可升级合约(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用Hardhat测试智能合约

    Hardhat是一个编译、部署、测试和调试以太坊应用的开发环境。它可以帮助开发人员管理和自动化构建智能合约和dApps过程中固有的重复性任务,并围绕这一工作流程轻松引入更多功能。这意味着hardhat在最核心的地方是编译、运行和测试智能合约。 Hardhat内置了Hardhat网络,这是

    2024年02月04日
    浏览(57)
  • 使用hardhat验证智能合约(goeril测试网)

    使用openzeppelin写了个简单的Erc721合约,成功部署到goerli测试网,但是在验证的时候一直报错:

    2024年02月11日
    浏览(37)
  • 8.区块链系列之hardhat框架部署合约(二)

    现在我们来实践hardhat部署合约中的其他更多技术要点 1. 代码方式验证合约 注册 https://etherscan.io/ , 如下图添加拷贝API_KEY 在.env文件中新增 ETHERSCAN_API_KEY hardhat.config.js中新增配置 覆盖deploy.js 验证合约 如果用的使用了clash代理的话开启Tun模式,否则可能会报Connect Timeout Error 2.

    2024年01月22日
    浏览(34)
  • 如何在vscode、remix中结合hardhat编译部署合约

    首先创建 npm 空项目,注意这里要选择合约项目对应的文件目录,比如这里的合约项目是 suchas 接着安装 hardhat 环境,这里安装的版本 2.11.1 接着创建 hardhat 工程,选择你要创建的工程类型,这里我选的 TS 一般简单的测试学习我们可以用 remix,更多时候是用专业的 vscode IDE 编写

    2024年02月06日
    浏览(35)
  • 10.区块链系列之hardhat部署抵押赎回Fund合约

    本文继续通过笔者学习到的抵押赎回智能合约Fund来进一步学习solidity语言,加深对开发的理解,其中通过storage节省gas是需要重点实践的,毕竟涉及到资产 代码已提交至https://gitee.com/SJshenjian/blockchain/tree/master/hardhat-fund-me-fcc 1. 依赖安装 若出现如下错误 在yarn.lock中ethereumjs-abi指

    2023年04月09日
    浏览(30)
  • 基于openzeppelin编写solidity可升级的智能合约

            现代软件的设计原则是“敏捷开发,迅速迭代”,功能升级或bug修复是所有软件系统都要面对的问题。甚至可以说软件质量在很大程度上依赖于升级和修补源代码的能力。当然Dapp(去中心化应用)也不例外,尤其Dapp一切都是透明的,这使得任何级别的bug都会被成

    2024年01月18日
    浏览(48)
  • Hardhat 开发框架 - Solidity开发教程连载

    Decert.me 要连载教程了, 《Solidity 开发教程》 力求 系统 、 深入 的介绍 Solidity 开发, 同时这是一套交互式教程,你可以实时的修改教程里的合约代码并运行。 本教程来自贡献者 @Tiny熊,让我们正式开始学习吧。 如果你已经是 Hardhat 的使用者,可以直接跳到文末,参与挑战

    2024年02月03日
    浏览(32)
  • hardhat开发dapp初始化操作

    入门的话可以通过 Remix 开发工具完成solidity项目的编写、编译、部署等操作。专业点的开发工具有 Truffle 和 Hardhat ,先看一看hardhat的简介: Hardhat is a development environment for Ethereum software. It consists of different components for editing, compiling, debugging and deploying your smart contracts and dApps,

    2023年04月09日
    浏览(43)
  • 部署OpenZeppelin可升级合约

    使用OpenZeppelin升级插件部署的合约具备可升级的特性:可以升级以修改其代码,同时保留其地址,状态和余额。 可以迭代地向项目中添加新功能,或修复在线上版本中可能发现的任何错误。 创建一个新的npm项目 安装并初始化Truffle 安装Truffle升级插件 注意,可升级合约使用

    2023年04月08日
    浏览(34)
  • DAPP开发【09】NFT交易市场开发(hardhat测试)

    测试文件下新建market.js文件 扁平化,将所有依赖放在tmp.sol,可以去给他人使用 npx hardhat flatten tmp.sol 测试文件

    2024年01月16日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包