合约升级 (Contract Upgrade)
概念简介
合约升级是指在以太坊等区块链上,通过特定的设计模式改变智能合约的逻辑或修复漏洞,同时保留原有合约状态(数据)和地址的过程。这是区块链开发中最具挑战性的技术之一,因为它试图在不可变(Immutable)的环境中实现可变性(Mutability)。
核心矛盾:
区块链的本质:
- 代码一旦部署,永久不可更改
- 保证信任和确定性
现实需求:
- 修复安全漏洞
- 添加新功能
- 适应业务需求变化
- 优化性能和降低Gas成本
合约升级:在不可变性和灵活性之间的妥协方案
历史演进:
- 2016年 DAO 事件:暴露了无法升级合约的严重问题,最终导致以太坊硬分叉
- 2017年 Parity 多签钱包漏洞:由于无法升级,超过50万ETH被永久冻结
- 2018年后:代理模式成为行业标准,OpenZeppelin等提供升级框架
- 2020年后:UUPS、BeaconProxy、Diamond等高级模式成熟
市场规模:
据统计,截至2024年:
- 可升级合约占比:Top 100 DeFi协议中,85%+使用可升级合约
- 代理合约数量:以太坊主网上超过10万个代理合约
- 管理的资产:超过$50B的资产由可升级合约管理
- 安全事件:30%+的合约漏洞与升级机制相关
核心特性
代理模式 (Proxy Pattern)
基本原理:
代理模式是实现合约升级的核心机制,通过分离"存储"和"逻辑"来实现升级。
传统合约:
┌─────────────────────┐
│ 智能合约 │
│ ├─ 业务逻辑 │
│ └─ 状态数据 │
└─────────────────────┘
不可变:无法修改
代理模式:
┌──────────────┐ delegatecall ┌──────────────┐
│ 代理合约 │ ───────────────────> │ 逻辑合约 │
│ (Proxy) │ │ (Logic) │
│ │ │ │
│ - 存储数据 │ │ - 业务逻辑 │
│ - 资产余额 │ │ - 函数代码 │
│ - 用户交互 │ │ - 无状态 │
└──────────────┘ └──────────────┘
↓
可升级:更换逻辑合约地址
delegatecall 机制:
// Proxy Contract
contract Proxy {
address public implementation; // 逻辑合约地址
fallback() external payable {
address _impl = implementation;
assembly {
// 复制calldata
calldatacopy(0, 0, calldatasize())
// delegatecall调用逻辑合约
let result := delegatecall(
gas(),
_impl,
0,
calldatasize(),
0,
0
)
// 复制返回数据
returndatacopy(0, 0, returndatasize())
// 根据结果返回或revert
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
// Logic Contract
contract Logic {
uint256 public value; // 实际存储在Proxy的slot 0
function setValue(uint256 _value) public {
value = _value; // 修改的是Proxy的存储
}
}
关键特性:
delegatecall vs call:
call:
- 在被调用合约的上下文中执行
- msg.sender = 调用者
- 修改被调用合约的存储
delegatecall:
- 在调用者合约的上下文中执行
- msg.sender = 原始调用者(穿透代理)
- 修改调用者合约的存储
- 完美适合代理模式
存储冲突 (Storage Collision)
最严重的升级风险:
// ❌ 错误示例:存储冲突
// Proxy Contract
contract Proxy {
address public implementation; // slot 0
address public admin; // slot 1
// ... delegatecall logic
}
// Logic Contract V1
contract LogicV1 {
uint256 public value; // slot 0 ❌ 冲突!
function setValue(uint256 _value) public {
value = _value; // 实际写入 Proxy 的 slot 0
// 覆盖了 implementation 地址!
}
}
结果:
- 执行 setValue 后,implementation 被覆盖
- 代理合约永久损坏
- 资金被锁定
解决方案:ERC-1967 标准
// ✅ 正确示例:使用ERC-1967
contract Proxy {
// 使用伪随机slot,避免冲突
bytes32 private constant IMPLEMENTATION_SLOT =
bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
function _getImplementation() internal view returns (address impl) {
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
impl := sload(slot)
}
}
function _setImplementation(address newImplementation) internal {
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
sstore(slot, newImplementation)
}
}
}
// Logic Contract
contract Logic {
uint256 public value; // slot 0
// 不会冲突,因为 Proxy 使用特殊 slot
}
ERC-1967 标准slot:
Implementation Slot:
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
Admin Slot:
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
Beacon Slot:
0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50
计算公式:
bytes32(uint256(keccak256('eip1967.proxy.[类型]')) - 1)
升级模式
透明代理 (Transparent Proxy)
问题背景:
函数选择器冲突:
Proxy Contract:
- upgradeTo(address) - 升级函数
Logic Contract:
- upgradeTo(address) - 业务函数(碰巧同名)
用户调用 upgradeTo:
- 应该调用哪个?
- 如果调用错误,可能导致意外升级
解决方案:
// OpenZeppelin Transparent Proxy
contract TransparentUpgradeableProxy {
address private _admin;
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}
// 管理员调用:执行代理函数
function upgradeTo(address newImplementation) external ifAdmin {
_upgradeTo(newImplementation);
}
// 普通用户调用:转发到逻辑合约
fallback() external payable {
require(msg.sender != _getAdmin(), "Admin cannot fallback");
_fallback();
}
function _fallback() internal {
_delegate(_getImplementation());
}
}
工作原理:
角色分离:
Admin(管理员):
- 调用代理管理函数(upgradeTo、changeAdmin)
- 无法调用逻辑合约函数
- 通常是 MultiSig 或 DAO
User(普通用户):
- 调用逻辑合约函数
- 无法调用代理管理函数
- 通过 fallback 转发
示例:
Admin 调用 proxy.upgradeTo(newLogic):
→ 执行代理的升级函数
User 调用 proxy.setValue(100):
→ fallback → delegatecall → Logic.setValue(100)
优缺点:
优点:
+ 安全:彻底隔离管理和业务调用
+ 简单:用户无需关心升级机制
+ 成熟:OpenZeppelin 经过充分审计
缺点:
- Gas 高:每次调用都需检查 msg.sender
- 管理员无法调用业务函数(需要另一个地址)
UUPS代理 (Universal Upgradeable Proxy Standard)
EIP-1822 标准:
UUPS 是一种更高效的升级模式,将升级逻辑放在逻辑合约中而非代理合约。
核心思想:
传统代理:
- 升级逻辑在 Proxy 中
- Proxy 代码复杂、Gas 高
UUPS:
- 升级逻辑在 Logic 中
- Proxy 极简、Gas 低
- 通过 delegatecall 执行升级
实现:
// UUPS Proxy(极简)
contract UUPSProxy {
bytes32 private constant IMPLEMENTATION_SLOT =
bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
constructor(address _logic, bytes memory _data) {
_setImplementation(_logic);
if (_data.length > 0) {
(bool success,) = _logic.delegatecall(_data);
require(success);
}
}
fallback() external payable {
_delegate(_getImplementation());
}
function _delegate(address impl) internal {
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
function _getImplementation() internal view returns (address impl) {
bytes32 slot = IMPLEMENTATION_SLOT;
assembly { impl := sload(slot) }
}
function _setImplementation(address newImplementation) internal {
bytes32 slot = IMPLEMENTATION_SLOT;
assembly { sstore(slot, newImplementation) }
}
}
// UUPS Logic Contract(包含升级逻辑)
abstract contract UUPSUpgradeable {
bytes32 private constant IMPLEMENTATION_SLOT =
bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
event Upgraded(address indexed implementation);
// 升级函数(在逻辑合约中)
function upgradeTo(address newImplementation) public virtual {
_authorizeUpgrade(newImplementation);
_upgradeToAndCall(newImplementation, bytes(""));
}
function _upgradeToAndCall(
address newImplementation,
bytes memory data
) internal {
// 1. 更新 implementation slot
bytes32 slot = IMPLEMENTATION_SLOT;
assembly { sstore(slot, newImplementation) }
emit Upgraded(newImplementation);
// 2. 可选:调用新逻辑的初始化函数
if (data.length > 0) {
(bool success,) = newImplementation.delegatecall(data);
require(success);
}
}
// 子类必须实现权限检查
function _authorizeUpgrade(address newImplementation) internal virtual;
}
// 实际业务合约
contract MyContract is UUPSUpgradeable {
address public owner;
uint256 public value;
function initialize(address _owner) public {
require(owner == address(0), "Already initialized");
owner = _owner;
}
function setValue(uint256 _value) public {
value = _value;
}
// 实现权限检查
function _authorizeUpgrade(address) internal override {
require(msg.sender == owner, "Not owner");
}
}
升级流程:
1. 部署新逻辑合约 LogicV2
2. 调用 proxy.upgradeTo(LogicV2)
↓
3. Proxy fallback → delegatecall → LogicV1.upgradeTo()
↓
4. LogicV1.upgradeTo() 执行:
- _authorizeUpgrade(LogicV2) // 权限检查
- sstore(IMPLEMENTATION_SLOT, LogicV2) // 更新地址
↓
5. 后续调用都转发到 LogicV2
优缺点:
优点:
+ Gas 效率高:Proxy 极简,每次调用省 Gas
+ 灵活:每个逻辑合约可定制升级逻辑
+ 无管理员限制:管理员可正常调用业务函数
缺点:
- 风险高:如果逻辑合约忘记继承 UUPSUpgradeable
→ 无法升级,永久锁定
- 复杂:开发者必须正确实现升级逻辑
安全检查:
// OpenZeppelin 的 UUPS 安全检查
contract UUPSUpgradeable {
address private immutable __self = address(this);
function upgradeTo(address newImplementation) public virtual {
require(address(this) != __self, "Must be called through proxy");
// 防止直接调用逻辑合约的升级函数
_authorizeUpgrade(newImplementation);
_upgradeToAndCall(newImplementation, bytes(""));
}
}
Beacon代理 (Beacon Proxy)
应用场景:
问题:
部署了 1,000 个相同逻辑的代理合约
(例如:1,000 个用户的个人钱包合约)
升级需求:
- 修复逻辑合约的漏洞
- 需要升级所有 1,000 个代理
传统方案:
- 调用 1,000 次 upgradeTo
- Gas 成本:1,000 × 100,000 gas = 极高
- 操作复杂度:高
Beacon 方案:
- 所有代理指向同一个 Beacon
- 升级 Beacon 一次
- 所有代理自动升级
架构:
┌─────────────┐
│ BeaconProxy │ ──┐
│ #1 │ │
└─────────────┘ │
│
┌─────────────┐ │ ┌─────────────┐ ┌─────────────┐
│ BeaconProxy │ ──┼─────>│ Beacon │────> │ Logic │
│ #2 │ │ │ │ │ │
└─────────────┘ │ └─────────────┘ └─────────────┘
│
┌─────────────┐ │
│ BeaconProxy │ ──┘
│ #1000 │
└─────────────┘
升级:仅需更新 Beacon 中的 Logic 地址
实现:
// Beacon Contract
contract UpgradeableBeacon {
address private _implementation;
address private _owner;
event Upgraded(address indexed implementation);
constructor(address implementation_) {
_setImplementation(implementation_);
_owner = msg.sender;
}
// 获取当前逻辑合约地址
function implementation() public view returns (address) {
return _implementation;
}
// 升级逻辑合约
function upgradeTo(address newImplementation) public {
require(msg.sender == _owner, "Not owner");
_setImplementation(newImplementation);
}
function _setImplementation(address newImplementation) private {
require(newImplementation.code.length > 0, "Not a contract");
_implementation = newImplementation;
emit Upgraded(newImplementation);
}
}
// Beacon Proxy
contract BeaconProxy {
address private immutable _beacon;
constructor(address beacon, bytes memory data) {
_beacon = beacon;
if (data.length > 0) {
(bool success,) = _implementation().delegatecall(data);
require(success);
}
}
// 从 Beacon 获取逻辑合约地址
function _implementation() internal view returns (address) {
return IBeacon(_beacon).implementation();
}
fallback() external payable {
_delegate(_implementation());
}
function _delegate(address impl) internal {
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
使用示例:
// 1. 部署逻辑合约
Logic logic = new Logic();
// 2. 部署 Beacon
UpgradeableBeacon beacon = new UpgradeableBeacon(address(logic));
// 3. 部署多个 Beacon Proxy
for (uint i = 0; i < 1000; i++) {
new BeaconProxy(address(beacon), initData);
}
// 4. 升级:仅需一次
LogicV2 logicV2 = new LogicV2();
beacon.upgradeTo(address(logicV2));
// 所有 1000 个代理自动使用新逻辑
优缺点:
优点:
+ 批量升级:一次升级影响所有代理
+ Gas 高效:升级成本 O(1)
+ 管理简单:中心化控制
缺点:
- 单点故障:Beacon 被攻击影响所有代理
- 灵活性低:无法单独升级某个代理
- 中心化:所有代理强制统一版本
Diamond代理 (Diamond Standard / EIP-2535)
突破24KB限制:
以太坊合约大小限制:
- Spurious Dragon 升级后:24 KB
- 复杂 DeFi 协议经常超出
传统解决方案:
- 拆分多个合约,手动协调
- 复杂且易出错
Diamond 方案:
- 一个代理委托给多个 Facet(逻辑合约)
- 突破大小限制
- 模块化升级
架构:
DiamondProxy
│
┌────────────────┼────────────────┐
│ │ │
↓ ↓ ↓
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Facet A │ │ Facet B │ │ Facet C │
│ │ │ │ │ │
│ func1() │ │ func3() │ │ func5() │
│ func2() │ │ func4() │ │ func6() │
└─────────┘ └─────────┘ └─────────┘
函数选择器映射:
func1 → Facet A
func2 → Facet A
func3 → Facet B
func4 → Facet B
func5 → Facet C
func6 → Facet C
核心数据结构:
// Diamond Storage
library LibDiamond {
bytes32 constant DIAMOND_STORAGE_POSITION =
keccak256("diamond.standard.diamond.storage");
struct FacetAddressAndSelectorPosition {
address facetAddress;
uint16 selectorPosition;
}
struct DiamondStorage {
// 函数选择器 → Facet 地址映射
mapping(bytes4 => FacetAddressAndSelectorPosition) selectorToFacet;
// Facet 地址 → 函数选择器数组
mapping(address => bytes4[]) facetToSelectors;
}
function diamondStorage() internal pure returns (DiamondStorage storage ds) {
bytes32 position = DIAMOND_STORAGE_POSITION;
assembly {
ds.slot := position
}
}
}
// Diamond Proxy
contract Diamond {
constructor(address _diamondCutFacet) {
// 初始化 DiamondCut Facet(用于管理升级)
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
ds.selectorToFacet[IDiamondCut.diamondCut.selector].facetAddress = _diamondCutFacet;
}
fallback() external payable {
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
// 根据函数选择器找到对应的 Facet
address facet = ds.selectorToFacet[msg.sig].facetAddress;
require(facet != address(0), "Function does not exist");
// delegatecall 到 Facet
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
升级操作:
interface IDiamondCut {
enum FacetCutAction { Add, Replace, Remove }
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
}
// 使用示例
FacetCut[] memory cuts = new FacetCut[](3);
// 1. 添加新 Facet
cuts[0] = FacetCut({
facetAddress: address(newFacet),
action: FacetCutAction.Add,
functionSelectors: [newFunc1.selector, newFunc2.selector]
});
// 2. 替换现有函数
cuts[1] = FacetCut({
facetAddress: address(updatedFacet),
action: FacetCutAction.Replace,
functionSelectors: [existingFunc.selector]
});
// 3. 移除函数
cuts[2] = FacetCut({
facetAddress: address(0),
action: FacetCutAction.Remove,
functionSelectors: [oldFunc.selector]
});
diamond.diamondCut(cuts, address(0), "");
优缺点:
优点:
+ 突破 24KB 限制:无限扩展
+ 模块化升级:细粒度控制
+ 代码复用:Facet 可共享
+ 灵活性:添加/替换/删除函数
缺点:
- 复杂度极高:学习和维护成本
- Gas 开销:函数查找额外成本
- 安全风险:复杂性带来更多攻击面
- 采用率低:生态支持不如传统代理
初始化模式
构造函数问题
为什么不能用构造函数?
// ❌ 错误:逻辑合约使用构造函数
contract Logic {
address public owner;
constructor() {
owner = msg.sender; // ❌ 只在 Logic 合约部署时执行
// Proxy 调用时 owner 为 0
}
}
问题:
- 构造函数在合约部署时执行
- Logic 部署时,owner = Logic部署者
- Proxy 调用 Logic 时,owner 未初始化
解决方案:initialize 函数
// ✅ 正确:使用 initialize
contract Logic {
address public owner;
bool private initialized;
function initialize(address _owner) public {
require(!initialized, "Already initialized");
initialized = true;
owner = _owner;
}
}
// Proxy 部署时调用
proxy = new Proxy(logic, abi.encodeWithSelector(
Logic.initialize.selector,
msg.sender
));
Initializable 模式
OpenZeppelin 实现:
abstract contract Initializable {
uint8 private _initialized;
bool private _initializing;
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) ||
(!Address.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
}
}
// 使用
contract MyContract is Initializable {
uint256 public value;
function initialize(uint256 _value) public initializer {
value = _value;
}
// V2 升级后的重新初始化
function initializeV2(uint256 _newValue) public reinitializer(2) {
value = _newValue * 2;
}
}
存储间隔 (Storage Gap)
升级兼容性:
// ❌ 危险:升级导致存储冲突
// V1
contract LogicV1 {
uint256 public value;
// slot 0: value
}
// V2(添加新变量)
contract LogicV2 {
address public owner; // slot 0: owner ❌ 覆盖了 value!
uint256 public value; // slot 1
}
结果:
- V1 的 value 数据被解释为 address
- 数据损坏
正确做法:
// ✅ 正确:使用 storage gap
// V1
contract LogicV1 {
uint256 public value;
// 预留 50 个 slot
uint256[49] private __gap;
}
// V2(安全添加新变量)
contract LogicV2 {
uint256 public value; // slot 0
address public owner; // slot 1(使用 gap 空间)
// 减少 gap
uint256[48] private __gap; // 49 - 1 = 48
}
计算 gap 大小:
目标:未来可能添加的最大变量数量
例如:
- 预留 50 个 slot
- 当前使用 5 个
- gap = 50 - 5 = 45
每次添加 N 个变量:
- gap = gap - N
安全考虑
函数选择器冲突
问题:
// Proxy 和 Logic 同名函数
contract Proxy {
function admin() public view returns (address);
}
contract Logic {
function admin() public view returns (address); // 冲突!
}
// 用户调用 proxy.admin()
// 应该执行哪个?
透明代理解决:
- 管理员调用 Proxy 版本
- 普通用户调用 Logic 版本
UUPS 解决:
- 升级函数在 Logic 中,避免冲突
Delegatecall 安全
selfdestruct 攻击:
// ❌ 危险逻辑合约
contract MaliciousLogic {
function destroy() public {
selfdestruct(payable(msg.sender));
// 通过 delegatecall 执行
// → 销毁 Proxy 合约!
}
}
防护:
- 永远不要在逻辑合约中使用 selfdestruct
- 审计升级的新逻辑
构造函数陷阱:
// ❌ 逻辑合约的构造函数不会被 Proxy 执行
contract Logic {
uint256 public constant IMPORTANT = 100;
constructor() {
// 这里的代码只在 Logic 部署时执行
// Proxy 调用时不会执行!
}
}
时间锁 (Timelock)
延迟升级:
contract TimelockProxy {
address public pendingImplementation;
uint256 public upgradeTime;
uint256 public constant DELAY = 2 days;
// 提出升级
function proposeUpgrade(address newImpl) public onlyAdmin {
pendingImplementation = newImpl;
upgradeTime = block.timestamp + DELAY;
emit UpgradeProposed(newImpl, upgradeTime);
}
// 执行升级(必须等待)
function executeUpgrade() public {
require(block.timestamp >= upgradeTime, "Too early");
require(pendingImplementation != address(0), "No pending upgrade");
_setImplementation(pendingImplementation);
pendingImplementation = address(0);
emit Upgraded(pendingImplementation);
}
// 取消升级
function cancelUpgrade() public onlyAdmin {
pendingImplementation = address(0);
emit UpgradeCancelled();
}
}
Compound Timelock 示例:
// Compound 的 Timelock
contract Timelock {
uint public constant GRACE_PERIOD = 14 days;
uint public constant MINIMUM_DELAY = 2 days;
uint public constant MAXIMUM_DELAY = 30 days;
mapping (bytes32 => bool) public queuedTransactions;
function queueTransaction(
address target,
uint value,
string memory signature,
bytes memory data,
uint eta
) public returns (bytes32) {
require(msg.sender == admin, "Unauthorized");
require(eta >= getBlockTimestamp() + delay, "ETA must exceed delay");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
function executeTransaction(
address target,
uint value,
string memory signature,
bytes memory data,
uint eta
) public payable returns (bytes memory) {
require(msg.sender == admin, "Unauthorized");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "Not queued");
require(getBlockTimestamp() >= eta, "Not yet");
require(getBlockTimestamp() <= eta + GRACE_PERIOD, "Expired");
queuedTransactions[txHash] = false;
bytes memory callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
(bool success, bytes memory returnData) = target.call{value: value}(callData);
require(success, "Execution failed");
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
}
最佳实践
开发流程
1. 设计阶段:
决策树:
是否需要升级?
│
├─ 否 → 不可升级合约
│ 优点:简单、安全、Gas 低
│ 适用:简单工具合约、Token
│
└─ 是 → 选择升级模式
│
├─ 单个合约 → Transparent 或 UUPS
│ 推荐:UUPS(Gas 低)
│
├─ 批量合约 → Beacon Proxy
│ 例如:用户钱包、NFT
│
└─ 超大合约 → Diamond
例如:复杂 DeFi 协议
2. 实现阶段:
// 推荐结构
// 1. 导入 OpenZeppelin
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
// 2. 继承顺序很重要
contract MyContract is
Initializable, // 第一个
OwnableUpgradeable,
UUPSUpgradeable // 最后一个
{
// 3. 状态变量
uint256 public value;
// 4. Storage Gap
uint256[49] private __gap;
// 5. 禁用构造函数
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
// 6. Initialize 函数
function initialize(uint256 _value) public initializer {
__Ownable_init();
__UUPSUpgradeable_init();
value = _value;
}
// 7. 业务逻辑
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
// 8. 升级授权
function _authorizeUpgrade(address newImplementation)
internal
override
onlyOwner
{}
}
3. 测试阶段:
// Hardhat 测试示例
const { ethers, upgrades } = require("hardhat");
describe("Upgrade", function () {
it("Should upgrade correctly", async function () {
// 部署 V1
const V1 = await ethers.getContractFactory("MyContractV1");
const proxy = await upgrades.deployProxy(V1, [100], {
kind: "uups"
});
// 验证初始状态
expect(await proxy.value()).to.equal(100);
// 升级到 V2
const V2 = await ethers.getContractFactory("MyContractV2");
const upgraded = await upgrades.upgradeProxy(proxy.address, V2);
// 验证状态保留
expect(await upgraded.value()).to.equal(100);
// 验证新功能
await upgraded.newFunction();
});
});
工具支持
Hardhat Upgrades Plugin:
// hardhat.config.js
require("@openzeppelin/hardhat-upgrades");
module.exports = {
solidity: "0.8.20",
};
// 部署脚本
async function main() {
const MyContract = await ethers.getContractFactory("MyContract");
// 自动处理代理部署
const proxy = await upgrades.deployProxy(MyContract, [100], {
initializer: "initialize",
kind: "uups" // 或 "transparent", "beacon"
});
console.log("Proxy deployed to:", proxy.address);
// 验证升级安全性
await upgrades.validateImplementation(MyContract);
}
Foundry 升级测试:
// test/Upgrade.t.sol
contract UpgradeTest is Test {
Proxy proxy;
LogicV1 logicV1;
LogicV2 logicV2;
function setUp() public {
logicV1 = new LogicV1();
proxy = new Proxy(address(logicV1), abi.encodeWithSelector(
LogicV1.initialize.selector,
100
));
}
function testUpgrade() public {
LogicV1 proxyAsV1 = LogicV1(address(proxy));
assertEq(proxyAsV1.value(), 100);
// 升级
logicV2 = new LogicV2();
proxyAsV1.upgradeTo(address(logicV2));
// 转换接口
LogicV2 proxyAsV2 = LogicV2(address(proxy));
// 验证状态保留
assertEq(proxyAsV2.value(), 100);
// 测试新功能
proxyAsV2.newFunction();
}
}
推荐阅读
- OpenZeppelin Upgrades Plugins - OpenZeppelin 升级工具文档
- OpenZeppelin Proxy Pattern - 代理模式深度解析
- EIP-1967: Standard Proxy Storage Slots - 代理存储槽标准
- EIP-1822: UUPS - UUPS 标准
- EIP-2535: Diamond Standard - Diamond 标准
- Consensys: Proxy Patterns - 代理模式最佳实践
- Trail of Bits: Contract Upgrade Anti-Patterns - 升级反模式分析