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

文章目录

  • 前言
    • [智能合约 -透明可升级合约[ hardhat、openzeppelin 、ethers ]的演示 demo](#智能合约 -透明可升级合约[ hardhat、openzeppelin 、ethers ]的演示 demo)
      • [1. 测试内容](#1. 测试内容)
      • [2. 合约源码](#2. 合约源码)
      • [3. 脚本部署测试](#3. 脚本部署测试)
        • [3.1. 执行代理合约[逻辑合约BoxV1]的部署脚本](#3.1. 执行代理合约[逻辑合约BoxV1]的部署脚本)
        • [3.2. 执行代理合约[逻辑合约BoxV2]的升级脚本:](#3.2. 执行代理合约[逻辑合约BoxV2]的升级脚本:)

前言

如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^。

而且听说点赞的人每天的运气都不会太差,实在白嫖的话,那欢迎常来啊!!!


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

版本:

solidity ^0.8.28、hardhat 2.28.4、openzeppelin 5.6.1 ethers 6.16.0

1. 测试内容

测试内容: BoxV2合约升级内容为覆盖BoxV1合约的call()函数。

注意的是升级实现合约时,不需要重新部署代理合约,代理合约地址始终不变,仅需将代理指向新的实现合约地址即可。

所以针对 TPUProxy 这类可升级代理,必须写两个核心脚本:初次部署脚本(代理 + 实现合约 V1 的绑定初始化)和升级脚本(部署实现合约 V2 + 让代理指向新实现)。

2. 合约源码

下面是两个逻辑合约和代理合约的源码:

BoxV1 合约:

bash 复制代码
// 声明代码遵循 MIT 开源许可证
// SPDX-License-Identifier: MIT


pragma solidity ^0.8.28;
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
contract BoxV1 is Initializable{

    uint public x;

    // initialize 替代constructor
    function initialize(uint _val) external initializer{
        x = _val;
    }

    function call() external {
        x=x+1;
    }
    // 逻辑合约的初始化数据:供代理合约使用
    function showInvoke() external pure returns (bytes memory){
     
        return abi.encodeWithSelector(this.initialize.selector, 1);
    }
}

BoxV2 合约:

bash 复制代码
// 声明代码遵循 MIT 开源许可证
// SPDX-License-Identifier: MIT
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";

pragma solidity ^0.8.28;

contract BoxV2 is Initializable{

    uint public x;
   
    // initialize 替代constructor
    function initialize(uint _val) external initializer{
        x = _val;
    }

    function call() external {
        x=x+2;
    }

    // 逻辑合约的初始化数据:供代理合约使用
    function showInvoke() external pure returns (bytes memory){
     
        return abi.encodeWithSelector(this.initialize.selector, 1);
    }
}

TPUProxy 自定义透明可升级代理合约:

bash 复制代码
// 声明代码遵循 MIT 开源许可证,SPDX 标准许可证标识
// SPDX-License-Identifier: MIT
// 透明可升级代理合约,继承 OpenZeppelin 官方透明代理基础合约
pragma solidity ^0.8.28; // 限定 Solidity 编译器版本为 0.8.28 及兼容版本
// 引入 OpenZeppelin 官方透明可升级代理核心合约,提供代理底层逻辑
import "@openzeppelin/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() 方法,对外提供查询接口
     */
    function proxyAdmin() external view returns(address) {
        return _proxyAdmin();
    }


    /**
     * @dev 外部只读方法,查询代理当前指向的实现合约地址
     * @return address 业务逻辑实现合约的当前地址
     * 封装父类内部的 _implementation() 方法,对外提供查询接口
     */
    function getImplementation() external view returns(address) {
        return _implementation();
    }
 
    receive() external payable {
        revert("TPUProxy: do not send ETH directly"); // 回滚交易并抛出明确错误信息
    }
}

3. 脚本部署测试

3.1. 执行代理合约[逻辑合约BoxV1]的部署脚本

源码:

bash 复制代码
// Hardhat 和 ethers.js 的「桥梁」,负责注册插件,让两者能协同工作;
import hre from "hardhat";
import "@nomicfoundation/hardhat-ethers";
import "@openzeppelin/hardhat-upgrades";

const CONTRACT_NAME = "BoxV1";

// 部署合约
async function contract(){
  console.log(`开始部署 ${CONTRACT_NAME} 代理合约...`);
  // 获取合约工厂
  const factory = await hre.ethers.getContractFactory("BoxV1");
  const proxy = await hre.upgrades.deployProxy(factory,[1],{
     initializer: "initialize"
  });

  // 获取代理合约地址方式
  const contractAddress = await proxy.getAddress();
  return { contractInstance: proxy, contractAddress }; // 同时返回实例和地址,方便后续调用方法

}


async function deploy() {

  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 hardhat node
deploy()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error("部署失败:", error);
    process.exit(1);
  });

执行脚本。

npx hardhat run scripts/upgradable/deploy-BoxV1Proxy.ts --network localhost

0x0165878A594ca255338adfa4d48449f69242Eb8F 是合约代理地址,记下来,这个会在升级脚本的时候会用到。

下面是call合约的方法:

bash 复制代码
function call() external {
    x=x+1;
}

初始化的x为1,调用call函数后,加1,最后得出x为2,正确。

3.2. 执行代理合约[逻辑合约BoxV2]的升级脚本:

源码:

bash 复制代码
// Hardhat 和 ethers.js 的「桥梁」,负责注册插件,让两者能协同工作;
import hre from "hardhat";
import "@nomicfoundation/hardhat-ethers";
import "@openzeppelin/hardhat-upgrades";




const CONTRACT_NAME = "BoxV2";
const PROXY_ADDRESS = "0x0165878A594ca255338adfa4d48449f69242Eb8F";
// 部署合约
async function contract(){
  console.log(`开始升级 ${CONTRACT_NAME} 代理合约...`);
  // 获取合约工厂
  const factory = await hre.ethers.getContractFactory("BoxV2");
  const proxy = await hre.upgrades.upgradeProxy(PROXY_ADDRESS,factory);

  // 获取代理合约地址方式
  const contractAddress = await proxy.getAddress();
  return { contractInstance: proxy, contractAddress }; // 同时返回实例和地址,方便后续调用方法




}


async function deploy() {

  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 hardhat node
deploy()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error("部署失败:", error);
    process.exit(1);
  });

执行脚本:

npx hardhat run scripts/upgradable/upgrade-TPUProxy.ts --network localhost

因为原来的x=2,看一下BoxV2合约的call方法:

bash 复制代码
    function call() external {
        x=x+2;
    }

调用call()函数后,加2,最后得出x=4,验证通过。

相关推荐
好多大米10 小时前
W2D3-Foundry 测试
区块链·solidity
木西18 小时前
深度复刻 Sky Protocol:基于 OpenZeppelin V5 与 Solidity 0.8.24 的工程实践
web3·智能合约·solidity
Bczheng118 小时前
四.比特币默克尔树(上)
区块链
OxYGC20 小时前
[Web3] 一文读懂区块链中的账本类型
web3·区块链
david_lv20 小时前
大A,2026年Q1总结
区块链
筱璦20 小时前
期货软件开发 - 策略编辑
前端·区块链·交易·期货
Risk Actuary2 天前
侧挂车(Sidecar)与巨灾债券(Cat Bond)
区块链
Css38RttP2 天前
springMVC-RequestMapping注解
区块链
Amos_Web2 天前
Solana开发(1)- 核心概念扫盲篇&&扫雷篇
前端·rust·区块链
OPHKVPS2 天前
GoBruteforcer(GoBrut)僵尸网络新攻势:AI 生成弱配置成“帮凶”,瞄准加密货币及区块链数据库
网络·人工智能·区块链