在代理合约与升级中,我们了解到升级运行的原理,理解代理模式面临函数选择器冲突的问题:当代理合约和实现合约有相同函数名时,会产生调用歧义。透明代理模式正是为了解决这个问题而诞生的。
本文将详细介绍透明代理的实现原理,并提供在 Foundry 中的完整实战指南。
在基础篇中,我们看到了这样的问题:
contract Proxy {
address public implementation;
// 升级函数
function upgrade(address newImpl) external {
implementation = newImpl;
}
fallback() external payable {
// 转发到实现合约
}
}
contract Implementation {
// 业务函数:恰好也叫 upgrade,或者是函数选择器一样
function upgrade(address user) external {
// 业务逻辑:升级用户等级
}
}
问题:
proxy.upgrade(newImpl) → 调用代理的升级函数 ✓proxy.upgrade(userAddress) → 也会调用代理的升级函数,但会因为权限检查失败 ✗用户永远无法调用到实现合约中的 upgrade 函数!
透明代理的核心思想:通过调用者身份来区分调用目标。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// OpenZeppelin 的 StorageSlot 库
library StorageSlot {
struct AddressSlot {
address value;
}
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r.slot := slot
}
}
}
contract TransparentProxy {
// EIP-1967 标准存储槽
bytes32 private constant IMPLEMENTATION_SLOT =
bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);
bytes32 private constant ADMIN_SLOT =
bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1);
constructor(address _implementation, address _admin) {
_setImplementation(_implementation);
_setAdmin(_admin);
}
// 核心:delegatecall 转发
function _delegate(address _implementation) internal virtual {
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
// fallback:关键的身份检查逻辑
fallback() external payable {
if (msg.sender == _getAdmin()) {
// 管理员调用:在 fallback 中处理管理操作
// 注意:代理合约不能有显式的管理函数,否则会产生函数选择器冲突
bytes4 selector = bytes4(msg.data);
// upgradeTo(address)
if (selector == 0x3659cfe6) {
address newImplementation = abi.decode(msg.data[4:], (address));
_setImplementation(newImplementation);
return;
}
// changeAdmin(address)
else if (selector == 0x8f283970) {
address newAdmin = abi.decode(msg.data[4:], (address));
_setAdmin(newAdmin);
return;
}
// 其他调用:管理员不能访问实现合约
else {
revert("Admin cannot call implementation");
}
} else {
// 普通用户:转发到实现合约
_delegate(_getImplementation());
}
}
receive() external payable {
if (msg.sender == _getAdmin()) {
revert("Admin cannot call implementation");
} else {
_delegate(_getImplementation());
}
}
// 读取实现合约地址
function _getImplementation() private view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
// 读取管理员地址
function _getAdmin() private view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
// 设置实现合约地址
function _setImplementation(address _implementation) private {
require(_implementation.code.length > 0, "Implementation is not a contract");
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = _implementation;
}
// 设置管理员地址
function _setAdmin(address _admin) private {
require(_admin != address(0), "Admin cannot be zero address");
StorageSlot.getAddressSlot(ADMIN_SLOT).value = _admin;
}
}
1. EIP-1967 存储槽
bytes32 private constant IMPLEMENTATION_SLOT =
bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);
bytes32 private constant ADMIN_SLOT =
bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1);
2. StorageSlot 库
library StorageSlot {
struct AddressSlot {
address value;
}
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r.slot := slot
}
}
}
3. 核心:Fallback 中的身份检查和操作处理
fallback() external payable {
if (msg.sender == _getAdmin()) {
// 管理员调用:在 fallback 中处理管理操作
bytes4 selector = bytes4(msg.data);
if (selector == 0x3659cfe6) { // upgradeTo(address)
address newImplementation = abi.decode(msg.data[4:], (address));
_setImplementation(newImplementation);
return;
}
// ... 其他管理操作
} else {
// 普通用户:转发到实现合约
_delegate(_getImplementation());
}
}
这是透明代理的核心机制!
关键设计原则:代理合约不能有显式函数
function upgradeTo(address) 这样的显式函数upgradeTo 函数,实现合约也有 upgradeTo 函数,就会产生冲突为什么叫"透明"?
透明代理之所以叫"透明",是因为对于普通用户来说,代理机制是完全透明的:
现在让我们看如何编写实现合约并使用透明代理。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 实现合约 V1
contract CounterV1 {
// 业务状态变量(从 slot 0 开始)
uint public count;
function increment() public {
count += 1;
}
function getCount() public view returns (uint) {
return count;
}
}
关键点:
implementation 和 admin 变量// 升级后的实现合约 V2
contract CounterV2 {
// 存储布局必须与 V1 保持一致
uint public count; // slot 0 - 保持不变
// V1 的功能
function increment() public {
count += 1;
}
function getCount() public view returns (uint) {
return count;
}
// V2 新增功能:减少计数
function decrement() public {
require(count > 0, "Count is already zero");
count -= 1;
}
// V2 新增功能:重置计数
function reset() public {
count = 0;
}
}
// 1. 部署实现合约 V1
CounterV1 implV1 = new CounterV1();
// 2. 部署透明代理(使用前面定义的 TransparentProxy)
TransparentProxy proxy = new TransparentProxy(address(implV1));
// 3. 普通用户调用业务函数
address user = address(0x1234);
vm.prank(user);
CounterV1(address(proxy)).increment();
vm.prank(user);
uint count = CounterV1(address(proxy)).getCount();
console.log("Count:", count); // 输出:1
// 4. 管理员升级合约
CounterV2 implV2 = new CounterV2();
// 管理员通过 fallback 调用 upgrade
address admin = proxy.admin();
vm.prank(admin);
(bool success,) = address(proxy).call(
abi.encodeWithSignature("upgrade(address)", address(implV2))
);
require(success, "Upgrade failed");
// 5. 升级后,数据保留
vm.prank(user);
uint countAfterUpgrade = CounterV2(address(proxy)).getCount();
console.log("Count after upgrade:", countAfterUpgrade); // 输出:1(数据保留!)
// 6. 使用 V2 的新功能
vm.prank(user);
CounterV2(address(proxy)).decrement();
vm.prank(user);
console.log("Count after decrement:", CounterV2(address(proxy)).getCount()); // 输出:0
// 7. 管理员无法调用业务函数
vm.prank(admin);
try CounterV2(address(proxy)).increment() {
revert("Should not reach here");
} catch {
console.log("Admin cannot call implementation functions");
}
简单示例用来帮助我们理解升级是如何运作的,在实际开发中,我们使用 OpenZeppelin 的 Foundry Upgrades 插件来部署和升级透明代理。
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
forge install OpenZeppelin/openzeppelin-foundry-upgrades
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
ffi = true
ast = true
build_info = true
extra_output = ["storageLayout"]
配置说明:
ffi = true:允许插件调用外部程序进行验证ast = true:生成抽象语法树,用于分析合约结构build_info = true:保存构建信息extra_output = ["storageLayout"]:输出存储布局信息,用于升级时的兼容性检查// src/BoxV1.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract BoxV1 is Initializable, OwnableUpgradeable {
uint256 private value;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
}
function store(uint256 newValue) public {
value = newValue;
}
function retrieve() public view returns (uint256) {
return value;
}
}
重要说明:
Initializable 代替构造函数initialize 函数进行初始化_disableInitializers() 防止实现合约被直接初始化// src/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract BoxV2 is Initializable, OwnableUpgradeable {
uint256 private value;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
}
function store(uint256 newValue) public {
value = newValue;
}
function retrieve() public view returns (uint256) {
return value;
}
// 新功能:增加值
function increment() public {
value = value + 1;
}
}
// script/DeployBox.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import "../src/BoxV1.sol";
contract DeployBox is Script {
function run() external {
address owner = vm.envAddress("OWNER_ADDRESS");
vm.startBroadcast();
// 使用插件部署透明代理
address proxy = Upgrades.deployTransparentProxy(
"BoxV1.sol",
owner,
abi.encodeCall(BoxV1.initialize, owner)
);
vm.stopBroadcast();
console.log("Proxy deployed at:", proxy);
}
}
插件做了什么:
selfdestruct、delegatecall 到用户输入的地址等)BoxV1 实现合约TransparentUpgradeableProxy 合约ProxyAdmin 合约管理代理initialize 函数// script/UpgradeBox.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
contract UpgradeBox is Script {
function run() external {
address proxy = vm.envAddress("PROXY_ADDRESS");
vm.startBroadcast();
// 使用插件升级
Upgrades.upgradeProxy(
proxy,
"BoxV2.sol",
""
);
vm.stopBroadcast();
console.log("Proxy upgraded to BoxV2");
}
}
插件做了什么:
验证存储布局兼容性:
BoxV1 和 BoxV2 的存储布局验证升级安全性:
_disableInitializers())部署新实现合约:自动编译并部署 BoxV2
执行升级:
ProxyAdmin 调用代理的升级函数BoxV2可选的迁移调用:如果提供了第三个参数(初始化数据),会在升级后调用迁移函数
# 部署
forge script script/DeployBox.s.sol:DeployBox \
--rpc-url $RPC_URL \
--broadcast \
--verify
# 升级
forge script script/UpgradeBox.s.sol:UpgradeBox \
--rpc-url $RPC_URL \
--broadcast \
--verify
透明代理模式是最常用的合约升级方案之一, 核心特点: