Stylus 升级

该文章详细介绍了如何在Arbitrum上使用OpenZeppelin代理模式(如UUPS和Beacon)升级Stylus智能合约。它涵盖了Stylus特有的升级机制,包括logic_flag上下文检测、WASM合约的重新激活、存储兼容性、访问控制集成以及确保升级安全性的最佳实践。

目录

Stylus 升级模型

Stylus 合约在 Arbitrum 上作为 WebAssembly (WASM) 程序与 EVM 并行运行。它们与 Solidity 合约共享相同的 state trie、存储模型和账户系统。因此,EVM 代理模式对 Stylus 的工作方式完全相同——Solidity 代理可以委托给 Stylus 实现,反之亦然。

Stylus Solidity
代理机制 相同 — delegatecall 到实现合约 delegatecall 到实现合约
存储布局 #[storage] 字段映射到与等效 Solidity 结构体相同的 EVM 槽 根据 Solidity 规则的顺序槽分配
EIP 标准 ERC-1967 存储槽, ERC-1822 proxiable UUID 相同
上下文检测 唯一存储槽中的 logic_flag 布尔值(不支持 immutable address(this) 存储为 immutable
初始化 两步:constructor 设置 logic_flag,然后通过代理 set_version() constructor + 通过代理 initializer
重新激活 WASM 合约必须每 365 天或 Stylus 协议升级后重新激活 不适用

现有的 Solidity 合约可以通过代理模式升级到 Stylus (Rust) 实现。#[storage] 宏以与 Solidity 相同的方式在 EVM state trie 中布局字段,因此当类型定义匹配时,存储槽会对齐。

代理模式

OpenZeppelin Contracts for Stylus 提供了三种代理模式:

模式 关键类型 最适合
UUPS UUPSUpgradeable, IErc1822Proxiable, Erc1967Proxy 大多数项目 — 升级逻辑在实现中,代理更轻量
Beacon BeaconProxy, UpgradeableBeacon 多个代理共享一个实现 — 更新 beacon 会原子性地升级所有代理
Basic Proxy Erc1967Proxy, Erc1967Utils 用于自定义代理模式的低级构建块

UUPS

实现合约在其 #[storage] 结构中与访问控制(例如,Ownable)一起组合 UUPSUpgradeable。集成需要:

  1. #[storage] 结构中添加 UUPSUpgradeable(和访问控制)作为字段
  2. 在 constructor 中调用 self.uups.constructor() 并初始化访问控制
  3. 公开 initialize 调用 self.uups.set_version() — 在部署后通过代理调用
  4. 实现 IUUPSUpgradeableupgrade_to_and_call 受访问控制保护,upgrade_interface_version 委托给 self.uups
  5. 实现 IErc1822Proxiableproxiable_uuid 委托给 self.uups

代理合约是一个轻量级的 Erc1967Proxy,带有一个接收实现地址和初始化数据的 constructor,以及一个委托所有调用的 #[fallback] 处理程序。

使用 set_version 作为初始化调用数据来部署代理。使用 cargo stylus deploy 或部署者合约。初始化数据是 ABI-encoded 的 setVersion 调用:

let data = MyContractAbi::setVersionCall {}.abi_encode();
// 在部署时将 `data` 作为代理 constructor 的第二个参数传入。

Beacon

多个 BeaconProxy 合约指向一个存储当前实现地址的 UpgradeableBeacon。更新 beacon 会在一次交易中升级所有代理。

上下文检测 (Stylus-specific)

Stylus 不支持 immutable 关键字。UUPSUpgradeable 不存储 __self = address(this),而是使用一个 logic_flag 布尔值在一个唯一的存储槽中:

  • 实现的 constructor 在其自己的存储中设置 logic_flag = true
  • 当代码通过代理 (delegatecall) 运行时,代理的存储不包含此标志,因此它读取为 false
  • only_proxy() 检查此标志,以确保升级函数只能通过代理调用,而不能直接在实现上调用。

only_proxy() 还验证 ERC-1967 实现槽是非零的,并且代理存储的版本与实现的 VERSION_NUMBER 匹配。

示例: 请参阅 rust-contracts-stylus repositoryexamples/ 目录,了解 UUPS、Beacon 和相关模式的完整工作集成示例。

访问控制

升级函数必须受到访问控制的保护。OpenZeppelin 的 Stylus 合约将访问控制嵌入到升级逻辑本身中 — 你必须将其添加到 upgrade_to_and_call 中:

fn upgrade_to_and_call(&mut self, new_implementation: Address, data: Bytes) -> Result<(), Vec<u8>> {
    self.ownable.only_owner()?; // 或任何访问控制检查
    self.uups.upgrade_to_and_call(new_implementation, data)?;
    Ok(())
}

常见选项:

  • Ownable — 单一所有者,最简单的模式
  • AccessControl / RBAC — 基于角色的,更细粒度
  • Multisig 或治理 — 适用于管理重要价值的生产合约

升级安全

存储兼容性

Stylus #[storage] 字段在 EVM state trie 中的布局与 Solidity 完全相同。升级时适用相同的存储布局规则:

  • 绝不重新排序、删除或更改现有存储字段的类型
  • 绝不在现有字段之前插入新字段
  • 在结构体的末尾追加新字段
  • ERC-1967 代理存储槽位于高位、标准化位置 — 它们不会与实现存储冲突

与 Solidity 的一个区别是:Stylus #[storage] 中的嵌套结构(例如,将 Erc20OwnableUUPSUpgradeable 作为字段组合)的布局是每个嵌套结构从其自己的确定性槽开始。这与 Solidity 中常规的结构体嵌套一致,但与 Solidity 基于继承的扁平布局(其中所有继承的变量共享一个单一的连续槽范围)不一致。

初始化安全

  • 实现的 constructor 设置 logic_flag 和任何仅限实现的 state。它在实现部署时运行一次。
  • set_version() 必须通过代理(在部署期间或通过 upgrade_to_and_call)调用,才能将 VERSION_NUMBER 写入代理的存储中。
  • 如果需要额外的初始化(所有权、token 供应),则公开一个受保护的初始化函数,并在其中包含 set_version()
  • 未能正确初始化可能导致孤立合约,没有所有者、未初始化的 state 或拒绝未来的升级。

UUPS 升级检查

UUPS 实现强制执行三项安全检查:

  1. 访问控制 — 限制 upgrade_to_and_call(例如,self.ownable.only_owner()
  2. 代理上下文强制 — 如果调用不是通过 delegatecallonly_proxy() 会回滚
  3. Proxiable UUID 验证proxiable_uuid() 必须返回 ERC-1967 实现槽,确认 UUPS 兼容性

重新激活

Stylus WASM 合约必须每年重新激活一次(365 天)或在任何 Stylus 协议升级后重新激活。重新激活可以使用 cargo-stylusArbWasm precompile 完成。如果合约未重新激活,它将变得不可调用。这与代理升级无关,但必须纳入维护计划。

测试升级路径

在升级生产合约之前:

  • [ ] 在本地 Arbitrum devnet 上部署 V1 实现和代理
  • [ ] 用 V1 写入 state,通过 upgrade_to_and_call 升级到 V2,并验证所有现有 state 都正确读取
  • [ ] 验证新功能在升级后按预期工作
  • [ ] 确认访问控制 — 只有授权的调用者才能调用 upgrade_to_and_call
  • [ ] 检查存储布局 — 确保没有对现有字段进行重新排序、删除或类型更改
  • [ ] 验证 VERSION_NUMBER 在新实现中已递增
  • [ ] 测试重新激活 — 确保升级后的合约可以重新激活
  • [ ] 手动审查 — Stylus Rust 合约没有自动存储布局验证;依赖结构体比较和 devnet 测试
  • 原文链接: github.com/OpenZeppelin/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。