智能合约 -透明可升级合约[ 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,验证通过。

相关推荐
程序员李程峰12 分钟前
基础知识④链和代币之间的关系
web3·去中心化·区块链·智能合约·同态加密·共识算法·信任链
程序员李程峰3 小时前
基础知识⑤ERC-20、BEP-20 和TRC-20 这三种流行的加密代币标准
web3·去中心化·区块链·智能合约·同态加密·共识算法·信任链
m0_380167143 小时前
CoinGlass API 好用吗?(2026深度评估)
ai·区块链
长安链开源社区8 小时前
动手开发 | 如何通过k8s部署长安链
云原生·容器·kubernetes·区块链
长安链开源社区11 小时前
长安链开发大赛决赛入围名单揭晓
web3·区块链·共识算法
m0_3801671412 小时前
加密货币数据 API 怎么选?(2026 全面指南)
区块链
程序员李程峰12 小时前
基础知识——各种钱包之间的联系与区别
web3·去中心化·区块链·智能合约·同态加密·零知识证明·信任链
程序员李程峰12 小时前
基础知识①区块链钱包基础
去中心化·区块链·智能合约·同态加密·共识算法·信任链·分布式账本
程序员李程峰12 小时前
基础知识②区块链的链是什么
web3·去中心化·区块链·智能合约·同态加密·共识算法·信任链
深念Y12 小时前
当加密遇见分布式:Web3、去中心化与元宇宙的底层逻辑
分布式·web3·去中心化·区块链·元宇宙·加密·价值