智能合约 -透明可升级合约[ hardhat、openzeppelin 、ethers ]的演示 demo

智能合约 -透明可升级合约[ hardhat、openzeppelin 、ethers ]的演示 demo 文章目录前言智能合约 -透明可升级合约[ hardhat、openzeppelin 、ethers ]的演示 demo1. 测试内容2. 合约源码3. 脚本部署测试3.1. 执行代理合约[逻辑合约BoxV1]的部署脚本3.2. 执行代理合约[逻辑合约BoxV2]的升级脚本:前言如果您觉得有用的话记得给博主点个赞评论收藏一键三连啊写作不易啊^ _ ^。而且听说点赞的人每天的运气都不会太差实在白嫖的话那欢迎常来啊!!!智能合约 -透明可升级合约[ hardhat、openzeppelin 、ethers ]的演示 demo版本:solidity ^0.8.28、hardhat 2.28.4、openzeppelin 5.6.1 ethers 6.16.01. 测试内容测试内容: BoxV2合约升级内容为覆盖BoxV1合约的call()函数。注意的是升级实现合约时不需要重新部署代理合约代理合约地址始终不变仅需将代理指向新的实现合约地址即可。所以针对 TPUProxy 这类可升级代理必须写两个核心脚本初次部署脚本代理 实现合约 V1 的绑定初始化和升级脚本部署实现合约 V2 让代理指向新实现。2. 合约源码下面是两个逻辑合约和代理合约的源码:BoxV1 合约:// 声明代码遵循 MIT 开源许可证 // SPDX-License-Identifier: MIT pragma solidity ^0.8.28;importopenzeppelin/contracts/proxy/utils/Initializable.sol;contract BoxV1 is Initializable{uint public x;// initialize 替代constructorfunctioninitialize(uint _val)external initializer{x_val;}functioncall()external{xx1;}// 逻辑合约的初始化数据:供代理合约使用functionshowInvoke()external pure returns(bytes memory){returnabi.encodeWithSelector(this.initialize.selector,1);}}BoxV2 合约:// 声明代码遵循 MIT 开源许可证 // SPDX-License-Identifier: MITimportopenzeppelin/contracts/proxy/utils/Initializable.sol;pragma solidity ^0.8.28;contract BoxV2 is Initializable{uint public x;// initialize 替代constructorfunctioninitialize(uint _val)external initializer{x_val;}functioncall()external{xx2;}// 逻辑合约的初始化数据:供代理合约使用functionshowInvoke()external pure returns(bytes memory){returnabi.encodeWithSelector(this.initialize.selector,1);}}TPUProxy 自定义透明可升级代理合约:// 声明代码遵循 MIT 开源许可证SPDX 标准许可证标识 // SPDX-License-Identifier: MIT // 透明可升级代理合约继承 OpenZeppelin 官方透明代理基础合约 pragma solidity ^0.8.28;// 限定 Solidity 编译器版本为0.8.28 及兼容版本 // 引入 OpenZeppelin 官方透明可升级代理核心合约提供代理底层逻辑importopenzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol;// 自定义透明可升级代理合约继承 TransparentUpgradeableProxy 实现功能扩展 contract TPUProxy is TransparentUpgradeableProxy{/** * dev 构造函数继承并调用父类 TransparentUpgradeableProxy 构造函数 * param _logic 实现合约地址代理指向的业务逻辑合约 * param initialOwner 代理合约初始管理员地址-区块链账户地址拥有代理升级、管理权限 * param _data 部署时传递给实现合约的初始化数据通常是初始化函数的编码数据 */ constructor(address _logic, address initialOwner, bytes memory _data)payable TransparentUpgradeableProxy(_logic, initialOwner, _data){// 无自定义逻辑仅透传参数调用父类构造函数完成代理初始化}/** * dev 外部只读方法查询代理合约的管理员地址 * return address 代理管理员地址拥有 proxyAdmin 权限的账户 * 封装父类内部的 _proxyAdmin()方法对外提供查询接口 */functionproxyAdmin()external view returns(address){return_proxyAdmin();}/** * dev 外部只读方法查询代理当前指向的实现合约地址 * return address 业务逻辑实现合约的当前地址 * 封装父类内部的 _implementation()方法对外提供查询接口 */functiongetImplementation()external view returns(address){return_implementation();}receive()external payable{revert(TPUProxy: do not send ETH directly);// 回滚交易并抛出明确错误信息}}3. 脚本部署测试3.1. 执行代理合约[逻辑合约BoxV1]的部署脚本源码:// Hardhat 和 ethers.js 的「桥梁」负责注册插件让两者能协同工作importhre fromhardhat;importnomicfoundation/hardhat-ethers;importopenzeppelin/hardhat-upgrades;const CONTRACT_NAMEBoxV1;// 部署合约 asyncfunctioncontract(){console.log(开始部署 ${CONTRACT_NAME}代理合约...);// 获取合约工厂 const factoryawait hre.ethers.getContractFactory(BoxV1);const proxyawait hre.upgrades.deployProxy(factory,[1],{initializer:initialize});// 获取代理合约地址方式 const contractAddressawait proxy.getAddress();return{contractInstance: proxy, contractAddress};// 同时返回实例和地址方便后续调用方法}asyncfunctiondeploy(){const{contractInstance, contractAddress}await contract();console.log(${CONTRACT_NAME}部署完成);console.log(代理合约地址: ${contractAddress});console.log(验证: 调用代理合约-${CONTRACT_NAME}的 call 方法);try{await contractInstance.call();console.log(验证: x:, await contractInstance.x());}catch(error){console.error(代理合约-${CONTRACT_NAME}的 方法调用失败:, error);process.exit(1);}}// 启动 本地网络 npx hardhatnodedeploy().then(()process.exit(0)).catch((error){console.error(部署失败:, error);process.exit(1);});执行脚本。npx hardhat run scripts/upgradable/deploy-BoxV1Proxy.ts --network localhost0x0165878A594ca255338adfa4d48449f69242Eb8F 是合约代理地址记下来这个会在升级脚本的时候会用到。下面是call合约的方法:functioncall()external{xx1;}初始化的x为1调用call函数后加1最后得出x为2正确。3.2. 执行代理合约[逻辑合约BoxV2]的升级脚本:源码// Hardhat 和 ethers.js 的「桥梁」负责注册插件让两者能协同工作importhre fromhardhat;importnomicfoundation/hardhat-ethers;importopenzeppelin/hardhat-upgrades;const CONTRACT_NAMEBoxV2;const PROXY_ADDRESS0x0165878A594ca255338adfa4d48449f69242Eb8F;// 部署合约 asyncfunctioncontract(){console.log(开始升级 ${CONTRACT_NAME}代理合约...);// 获取合约工厂 const factoryawait hre.ethers.getContractFactory(BoxV2);const proxyawait hre.upgrades.upgradeProxy(PROXY_ADDRESS,factory);// 获取代理合约地址方式 const contractAddressawait proxy.getAddress();return{contractInstance: proxy, contractAddress};// 同时返回实例和地址方便后续调用方法}asyncfunctiondeploy(){const{contractInstance, contractAddress}await contract();console.log(${CONTRACT_NAME}部署完成);console.log(代理合约地址: ${contractAddress});console.log(验证: 调用代理合约-${CONTRACT_NAME}的 call 方法);try{await contractInstance.call();console.log(验证: x:, await contractInstance.x());}catch(error){console.error(代理合约-${CONTRACT_NAME}的 方法调用失败:, error);process.exit(1);}}// 启动 本地网络 npx hardhatnodedeploy().then(()process.exit(0)).catch((error){console.error(部署失败:, error);process.exit(1);});执行脚本:npx hardhat run scripts/upgradable/upgrade-TPUProxy.ts --network localhost因为原来的x2,看一下BoxV2合约的call方法:functioncall()external{xx2;}调用call()函数后加2最后得出x4,验证通过。