闲社服务运行正常AI智能体自动化平台
u

升级Solidity合约

技能标识:upgrade-solidity-contracts

Upgrade Solidity smart contracts using OpenZeppelin proxy patterns. Use when users need to: (1) make contracts upgradeable with UUPS, Transparent, or Beacon proxies, (2) write initializers instead of constructors, (3) use the Hardhat or Foundry upgrades plugins, (4) understand storage layout rules and ERC-7201 namespaced storage, (5) validate upgrade safety, (6) manage proxy deployments and upgrades, or (7) understand upgrade restrictions between OpenZeppelin Contracts major versions.

作者:admin | 来源记录:ClawHub
登记来源
ClawHub
版本
V 1.0.1
检测标记
后台标记通过
免费
免费获取
0
收藏
来源与检测标记为平台登记信息,并不代表已展示可复核的检测报告。使用前请核对版本、依赖和所需权限,建议先在隔离环境中运行。
概述
安装方式
版本历史

升级Solidity合约

Solidity 升级

目录

代理模式概述

模式升级逻辑所在位置最佳适用场景
UUPS (UUPSUpgradeable)实现合约(重写 _authorizeUpgrade)大多数项目 — 代理更轻量,部署 gas 更低
透明代理
独立的 ProxyAdmin 合约 | 管理员/用户调用分离至关重要时 — 管理员无法意外调用实现函数 | | 信标 | 共享的信标合约 | 多个代理共享同一实现 — 原子升级信标即可升级所有代理 |

三者均使用 EIP-1967 存储槽存储实现地址、管理员和信标。

透明代理 — v5 构造函数变更: 在 v5 中,TransparentUpgradeableProxy 自动部署自己的 ProxyAdmin 合约,并将管理员地址存储在不可变变量中(在构造时设置,不可更改)。第二个构造函数参数是该自动部署的 ProxyAdmin 的所有者地址 — 请不要在此处传入现有的 ProxyAdmin 合约地址。升级能力的转移完全通过 ProxyAdmin 所有权处理。这与 v4 不同,v4 中 ProxyAdmin 是单独部署的,其地址被传递给代理构造函数。

主版本升级限制(v4 → v5)

不支持将代理的实现从使用 OpenZeppelin Contracts v4 升级到使用 v5。

v4 使用顺序存储(按声明顺序的槽位);v5 使用命名空间存储(ERC-7201,在确定性槽位上的结构体)。v5 实现无法安全读取 v4 实现写入的状态。理论上可以进行手动数据迁移,但通常不可行 — mapping 条目无法枚举,因此在任意键下写入的值无法重新定位。

推荐方法: 使用 v5 实现部署新代理,并将用户迁移到新地址 — 不要升级当前指向 v4 实现的代理。

鼓励将代码库更新到 v5。 上述限制仅适用于已部署的代理。基于 v5 的新部署以及同一主版本内的升级完全支持。

编写可升级合约

使用初始化器替代构造函数

代理合约通过 delegatecall 调用实现。构造函数仅在实现本身部署时运行,而非创建代理时。用初始化函数替代构造函数:

solidity
import {Initializable} from @openzeppelin/contracts/proxy/utils/Initializable.sol;

contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers(); // 锁定实现
}

function initialize(address initialOwner) public initializer {
ERC20_init(MyToken, MTK);
Ownable_init(initialOwner);
}
}

关键规则:

  • - 顶层 initialize 使用 initializer 修饰符
  • 父级初始化函数(Xinit)内部使用 onlyInitializing — 显式调用它们,编译器不会像构造函数那样自动线性化初始化器
  • 始终在构造函数中调用 disableInitializers(),防止攻击者直接初始化实现
  • 不要在字段声明中设置初始值(例如 uint256 x = 42)— 这些会编译到构造函数中,不会为代理执行。constant 是安全的(编译时内联)。immutable 值存储在字节码中,在所有代理间共享 — 插件默认将其标记为不安全;当有意共享值时,使用 /// @custom:oz-upgrades-unsafe-allow state-variable-immutable 选择加入

使用可升级包

从 @openzeppelin/contracts-upgradeable 导入基础合约(例如 ERC20Upgradeable、OwnableUpgradeable)。从 @openzeppelin/contracts 导入接口和库。在 v5.5+ 中,Initializable 和 UUPSUpgradeable 也应直接从 @openzeppelin/contracts 导入 — 可升级包中的别名将在下一个主版本中移除。

v5.5+ 的完整导入块示例:

solidity
// 来自 @openzeppelin/contracts(非可升级包)
import {Initializable} from @openzeppelin/contracts/proxy/utils/Initializable.sol;
import {UUPSUpgradeable} from @openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol;

// 来自 @openzeppelin/contracts-upgradeable(可升级基础合约)
import {ERC20Upgradeable} from @openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol;
import {OwnableUpgradeable} from @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol;

存储布局规则

升级时,新实现必须与旧实现存储兼容:

  • - 切勿重新排序、删除或更改现有状态变量的类型
  • 切勿在现有变量之前插入新变量
  • 只能在末尾追加新变量
  • 切勿更改基础合约的继承顺序

命名空间存储(ERC-7201)

现代方法 — 所有 @openzeppelin/contracts-upgradeable 合约(v5+)都使用此方法。状态变量被分组到确定性存储槽的结构体中,隔离每个合约的存储,无需存储间隙。推荐用于所有可能作为基础合约导入的合约。

solidity
/// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 value;
mapping(address => uint256) balances;
}

// keccak256(abi.encode(uint256(keccak256(example.main)) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant MAINSTORAGELOCATION = 0x...;

function _getMainStorage() private pure returns (MainStorage storage $) {
assembly { $.slot := MAINSTORAGELOCATION }
}

从命名空间存储中使用变量:
solidity
function _getBalance(address account) internal view returns (uint256) {
MainStorage storage $ = _getMainStorage();
return $.balances[account];
}

相比传统存储间隙的优势:安全地向基础合约添加变量,继承顺序更改不会破坏布局,每个合约的存储完全隔离。

升级时,切勿通过从继承链中删除命名空间来移除它。插件将删除的命名空间标记为错误 — 存储在该命名空间中的状态成为孤儿:数据仍在链上,但新实现无法读取或写入。如果命名空间不再活跃使用,请将旧合约保留在继承链中。未使用的命名空间不会增加运行时成本,也不会导致存储冲突。没有针对性的标志来抑制此错误;唯一的绕过方式是 unsafeSkipStorageCheck,它会禁用所有存储布局兼容性检查,是危险的最后手段。

计算 ERC-7201 存储位置

生成命名空间存储代码时,始终计算实际的 STORAGE_LOCATION 常量。使用 Bash 工具运行以下命令,传入实际的命名空间 ID,并将计算出的值直接嵌入生成的代码中。切勿保留像 0x... 这样的占位值。

公式为:keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff)),其中 id 是命名空间字符串(例如 example.main)。

Node.js 与 ethers:

bash
node -e const{keccak256,toUtf8Bytes,zeroPadValue,toBeHex}=require(ethers);const id=process.argv[1];const h=BigInt(keccak256(toUtf8Bytes(id)))-1n;console.log(toBeHex(BigInt(keccak256(zeroPadValue(toBeHex(h),32)))&~0xffn,32)) example.main

将 example.main 替换为实际的命名空间 ID,运行命令,并将输出用作常量值。

不安全操作

  • - 无 selfdestruct — 在 Dencun 之前的链上,会销毁实现并使所有代理失效。Dencun 之后(EIP-6780),selfdestruct 仅在创建同一交易中调用时销毁代码,但插件仍将其标记为不安全
  • 无对不可信合约的 delegatecall — 恶意目标可能 selfdestruct 或破坏存储

此外,避免在可升级合约内部

标签

skill ai

通过对话安装

以下为平台配置的接入选项,并非逐项实测通过。能否安装取决于客户端支持、技能来源和运行环境:

OpenClaw WorkBuddy QClaw Kimi Claude

方式一:安装 SkillHub 和技能

帮我安装 SkillHub 和 upgrade-solidity-contracts-1776077601 技能

方式二:设置 SkillHub 为优先技能安装源

设置 SkillHub 为我的优先技能安装源,然后帮我安装 upgrade-solidity-contracts-1776077601 技能

通过命令行安装

skillhub install upgrade-solidity-contracts-1776077601

下载

⬇ 下载 upgrade-solidity-contracts v1.0.1(免费)

文件大小: 6.47 KB | 发布时间: 2026-4-15 14:48

v1.0.1 最新 2026-4-15 14:48
Fix display name from probe to proper title
返回顶部