使用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自带的升级安全检查,请执行:
-
安装node.js
-
在
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 插件,我们部署了三个合约:
-
Logic 合约:也叫 Implementation 合约,即LogicV1。
-
Proxy 合约:透明代理合约,即TransparentUpgradeableProxy。
-
ProxyAdmin 合约:代理管理员合约,ProxyAdmin。
对应地址:
-
https://repo.sourcify.dev/11155111/0xF401a5f6E69bDC1c4BDa51e9Be7719338dB61096
-
https://repo.sourcify.dev/11155111/0xDDDb96d38096Bc2acB1a43a0c5A29d4162ac0CB7
-
https://repo.sourcify.dev/11155111/0xCF5a9750169b0DA9408AEa6757912211692F1FCf
这三个合约之间的关系如下:

管理员只能通过 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调用更新了逻辑合约地址:


浙公网安备 33010602011771号