使用OpenZeppelin编写可升级智能合约(代理)

开发环境:VSCode + Foundry

一、创建项目

执行$forge init ProxyDemo 创建Foundry项目,并创建2个版本的逻辑合约:

  • LogicV1.sol

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.30;
    
    import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
    
    contract LogicV1 is Initializable {
        uint256 public number;
    
        function initialize(uint256 initialValue) public initializer {
            number = initialValue;
        }
    
        function changeNumber() public {
            number++;
        }
    }
    
  • LogicV2.sol

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.30;
    
    import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
    
    contract LogicV2 is Initializable {
        uint256 public number;
    
        function setNumber(uint256 newValue) public {
            number = newValue;
        }
    
        function changeNumber() public {
            number += 10;
        }
    }
    

普通合约和可升级合约的最大区别在于,可升级合约没有constructor()

二、使用OpenZeppelin Upgrades 插件部署LogicV1

2.1 安装库

安装OpenZeppelin可升级合约库:

forge install OpenZeppelin/openzeppelin-foundry-upgrades
forge install OpenZeppelin/openzeppelin-contracts-upgradeable

然后更新remappings.txt文件:forge remappings > remappings.txt

如果你想在升级或部署时,运行OpenZeppelin自带的升级安全检查,请执行:

  1. 安装node.js

  2. foundry.toml里配置ffi、 ast、 build info和storage layout:

[profile.default]
ffi = true
ast = true
build_info = true
extra_output = ["storageLayout"]

2.2 编写部署脚本

script/LogicV1.s.sol 中编写 LogicV1 合约的部署脚本

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

import {Script} from "forge-std/Script.sol";
import {LogicV1} from "../src/LogicV1.sol";
import {console} from "forge-std/console.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";

contract LogicV1Script is Script {
    function setUp() public {}

    function run() public {
        // 部署的账户私钥
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

        vm.startBroadcast(deployerPrivateKey);

        // Deploy the upgradeable proxy and its first implementation
        address proxy = Upgrades.deployTransparentProxy(
            "LogicV1.sol",
            vm.envAddress("ADMIN_ADDRESS"),
            abi.encodeCall(LogicV1.initialize, (22))
        );

        console.log("Proxy deployed at:", proxy);
        console.log("Implementation deployed at:", Upgrades.getImplementationAddress(proxy));
        console.log("Admin address at:", Upgrades.getAdminAddress(proxy));

        vm.stopBroadcast();
    }
}

执行 forge script 进行部署:

$ forge script script/LogicV1.s.sol --rpc-url https://sepolia.gateway.tenderly.co --broadcast --verify

[⠒] Compiling...
No files changed, compilation skipped
Script ran successfully.

== Logs ==
  Proxy deployed at: 0xDDDb96d38096Bc2acB1a43a0c5A29d4162ac0CB7
  Implementation deployed at: 0xF401a5f6E69bDC1c4BDa51e9Be7719338dB61096
  Admin address at: 0xCF5a9750169b0DA9408AEa6757912211692F1FCf

通过OpenZeppelin Upgrades 插件,我们部署了三个合约:

  1. Logic 合约:也叫 Implementation 合约,即LogicV1

  2. Proxy 合约:透明代理合约,即TransparentUpgradeableProxy

  3. ProxyAdmin 合约:代理管理员合约,ProxyAdmin

对应地址:

  1. https://repo.sourcify.dev/11155111/0xF401a5f6E69bDC1c4BDa51e9Be7719338dB61096

  2. https://repo.sourcify.dev/11155111/0xDDDb96d38096Bc2acB1a43a0c5A29d4162ac0CB7

  3. https://repo.sourcify.dev/11155111/0xCF5a9750169b0DA9408AEa6757912211692F1FCf

这三个合约之间的关系如下:
image

管理员只能通过 ProxyAdmin 合约去调用 Proxy 合约的 upgradeToAndCall() 函数进行升级。

三、使用OpenZeppelin Upgrades 插件升级LogicV2

script/LogicV2.s.sol 中编写 LogicV1 -> LogicV2 的升级脚本:

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

import {Script} from "forge-std/Script.sol";
import {LogicV2} from "../src/LogicV2.sol";
import {console} from "forge-std/console.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {Options} from "openzeppelin-foundry-upgrades/Options.sol";

contract LogicV2Script is Script {
    function setUp() public {}

    function run() public {
        // 部署的账户私钥
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        // 代理合约地址
        address proxyAddress = vm.envAddress("PROXY_ADDRESS");
        // 升级选项,指定旧合约
        Options memory opts;
        opts.referenceContract = "LogicV1.sol";

        vm.startBroadcast(deployerPrivateKey);

        // 升级代理合约到 LogicV2,并调用 setNumber 函数设置新的值
        Upgrades.upgradeProxy(
            proxyAddress,
            "LogicV2.sol", // 要升级的合约
            abi.encodeCall(LogicV2.setNumber, (50)), // 调用新合约的函数
            opts
        );

        console.log("Proxy deployed at:", proxyAddress);
        console.log("New implementation deployed at:", Upgrades.getImplementationAddress(proxyAddress));

        vm.stopBroadcast();
    }
}

执行 forge script 进行升级:

$ forge script script/LogicV2.s.sol --rpc-url https://sepolia.gateway.tenderly.co --broadcast --verify

[⠒] Compiling...
No files changed, compilation skipped
Script ran successfully.

== Logs ==
  Proxy deployed at: 0xDDDb96d38096Bc2acB1a43a0c5A29d4162ac0CB7
  New implementation deployed at: 0x964D52fd3c94CA379Ab1ED3411f01EeFF8e99Bf5

新部署的LogicV2合约地址:https://repo.sourcify.dev/11155111/0x964D52fd3c94CA379Ab1ED3411f01EeFF8e99Bf5

查看Proxy合约的日志,可以看到有一次Upgraded调用更新了逻辑合约地址:
image

posted @ 2025-11-23 20:27  songlee  阅读(16)  评论(0)    收藏  举报