upgrade-stylus-contracts
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStylus Upgrades
Stylus合约升级
Contents
目录
Stylus Upgrade Model
Stylus升级模型
Stylus contracts run on Arbitrum as WebAssembly (WASM) programs alongside the EVM. They share the same state trie, storage model, and account system as Solidity contracts. Because of this, EVM proxy patterns work identically for Stylus — a Solidity proxy can delegate to a Stylus implementation and vice versa.
| Stylus | Solidity | |
|---|---|---|
| Proxy mechanism | Same — | |
| Storage layout | | Sequential slot allocation per Solidity rules |
| EIP standards | ERC-1967 storage slots, ERC-1822 proxiable UUID | Same |
| Context detection | | |
| Initialization | Two-step: constructor sets | Constructor + initializer via proxy |
| Reactivation | WASM contracts must be reactivated every 365 days or after a Stylus protocol upgrade | Not applicable |
Existing Solidity contracts can upgrade to a Stylus (Rust) implementation via proxy patterns. The macro lays out fields in the EVM state trie identically to Solidity, so storage slots line up when type definitions match.
#[storage]Stylus合约作为WebAssembly(WASM)程序与EVM一同运行在Arbitrum上。它们与Solidity合约共享相同的状态树、存储模型和账户系统。因此,EVM代理模式在Stylus上的工作方式完全相同——Solidity代理可以委托给Stylus实现合约,反之亦然。
| 特性 | Stylus | Solidity |
|---|---|---|
| 代理机制 | 相同——通过 | 通过 |
| 存储布局 | | 按照Solidity规则顺序分配存储槽 |
| EIP标准 | ERC-1967存储槽、ERC-1822可代理UUID | 相同 |
| 上下文检测 | 在唯一存储槽中使用 | 将 |
| 初始化 | 两步流程:构造函数设置 | 构造函数 + 通过代理调用初始化方法 |
| 重新激活 | WASM合约必须每365天或在Stylus协议升级后重新激活 | 不适用 |
现有Solidity合约可以通过代理模式升级为Stylus(Rust)实现。宏在EVM状态树中的字段布局与Solidity完全一致,因此当类型定义匹配时,存储槽会对齐。
#[storage]Proxy Patterns
代理模式
OpenZeppelin Contracts for Stylus provides three proxy patterns:
| Pattern | Key types | Best for |
|---|---|---|
| UUPS | | Most projects — upgrade logic in the implementation, lighter proxy |
| Beacon | | Multiple proxies sharing one implementation — updating the beacon upgrades all proxies atomically |
| Basic Proxy | | Low-level building block for custom proxy patterns |
OpenZeppelin为Stylus提供的合约包含三种代理模式:
| 模式 | 核心类型 | 适用场景 |
|---|---|---|
| UUPS | | 大多数项目——升级逻辑在实现合约中,代理更轻量化 |
| Beacon | | 多个代理共享同一实现合约——更新Beacon可一次性升级所有代理 |
| 基础代理 | | 用于自定义代理模式的底层构建模块 |
UUPS
UUPS
The implementation contract composes in its struct alongside access control (e.g., ). Integration requires:
UUPSUpgradeable#[storage]Ownable- Add (and access control) as fields in the
UUPSUpgradeablestruct#[storage] - Call and initialize access control in the constructor
self.uups.constructor() - Expose calling
initialize— invoked via proxy after deploymentself.uups.set_version() - Implement —
IUUPSUpgradeableguarded by access control,upgrade_to_and_calldelegating toupgrade_interface_versionself.uups - Implement —
IErc1822Proxiabledelegating toproxiable_uuidself.uups
The proxy contract is a thin with a constructor that takes the implementation address and initialization data, and a handler that delegates all calls.
Erc1967Proxy#[fallback]Deploy the proxy with as the initialization call data. Use or a deployer contract. The initialization data is the ABI-encoded call:
set_versioncargo stylus deploysetVersionrust
let data = MyContractAbi::setVersionCall {}.abi_encode();
// Pass `data` as the proxy constructor's second argument at deployment time.实现合约需要在其结构体中组合与访问控制组件(如)。集成步骤如下:
#[storage]UUPSUpgradeableOwnable- 在结构体中添加
#[storage](及访问控制)作为字段UUPSUpgradeable - 在构造函数中调用并初始化访问控制
self.uups.constructor() - 暴露方法,该方法调用
initialize——在部署后通过代理调用self.uups.set_version() - 实现接口——
IUUPSUpgradeable需由访问控制保护,upgrade_to_and_call委托给upgrade_interface_versionself.uups - 实现接口——
IErc1822Proxiable委托给proxiable_uuidself.uups
代理合约是一个轻量的,其构造函数接收实现合约地址和初始化数据,并通过处理函数委托所有调用。
Erc1967Proxy#[fallback]部署代理时,将作为初始化调用数据。可使用或部署合约。初始化数据是经过ABI编码的调用:
set_versioncargo stylus deploysetVersionrust
let data = MyContractAbi::setVersionCall {}.abi_encode();
// Pass `data` as the proxy constructor's second argument at deployment time.Beacon
Beacon
Multiple contracts point to a single that stores the current implementation address. Updating the beacon upgrades all proxies in one transaction.
BeaconProxyUpgradeableBeacon多个合约指向同一个,该Beacon存储当前实现合约的地址。更新Beacon可在一笔交易中完成所有代理的升级。
BeaconProxyUpgradeableBeaconContext detection (Stylus-specific)
上下文检测(Stylus专属)
Stylus does not support the keyword. Instead of storing , uses a boolean in a unique storage slot:
immutable__self = address(this)UUPSUpgradeablelogic_flag- The implementation's constructor sets in its own storage.
logic_flag = true - When code runs via a proxy (), the proxy's storage does not contain this flag, so it reads as
delegatecall.false - checks this flag to ensure upgrade functions can only be called through the proxy, not directly on the implementation.
only_proxy()
only_proxy()VERSION_NUMBERExamples: See thedirectory of the rust-contracts-stylus repository for full working integration examples of UUPS, Beacon, and related patterns.examples/
Stylus不支持关键字。并未存储,而是在一个唯一的存储槽中使用布尔值:
immutableUUPSUpgradeable__self = address(this)logic_flag- 实现合约的构造函数会在自身存储中设置。
logic_flag = true - 当代码通过代理()运行时,代理的存储中不存在该标志,因此读取值为
delegatecall。false - 会检查此标志,确保升级函数只能通过代理调用,而不能直接调用实现合约。
only_proxy()
only_proxy()VERSION_NUMBER示例: 可查看rust-contracts-stylus仓库的目录,获取UUPS、Beacon及相关模式的完整可运行集成示例。examples/
Access Control
访问控制
Upgrade functions must be guarded with access control. OpenZeppelin's Stylus contracts do not embed access control into the upgrade logic itself — you must add it in :
upgrade_to_and_callrust
fn upgrade_to_and_call(&mut self, new_implementation: Address, data: Bytes) -> Result<(), Vec<u8>> {
self.ownable.only_owner()?; // or any access control check
self.uups.upgrade_to_and_call(new_implementation, data)?;
Ok(())
}Common options:
- Ownable — single owner, simplest pattern
- AccessControl / RBAC — role-based, finer granularity
- Multisig or governance — for production contracts managing significant value
升级函数必须通过访问控制进行保护。OpenZeppelin的Stylus合约并未将访问控制嵌入到升级逻辑中——你需要在中添加:
upgrade_to_and_callrust
fn upgrade_to_and_call(&mut self, new_implementation: Address, data: Bytes) -> Result<(), Vec<u8>> {
self.ownable.only_owner()?; // or any access control check
self.uups.upgrade_to_and_call(new_implementation, data)?;
Ok(())
}常见的访问控制选项:
- Ownable — 单一所有者,最简单的模式
- AccessControl / RBAC — 基于角色,粒度更细
- 多签或治理 — 适用于管理大额资产的生产级合约
Upgrade Safety
升级安全性
Storage compatibility
存储兼容性
Stylus fields are laid out in the EVM state trie identically to Solidity. The same storage layout rules apply when upgrading:
#[storage]- Never reorder, remove, or change the type of existing storage fields
- Never insert new fields before existing ones
- Only append new fields at the end of the struct
- ERC-1967 proxy storage slots are in high, standardized locations — they will not collide with implementation storage
One difference from Solidity: nested structs in Stylus (e.g., composing , , as fields) are laid out with each nested struct starting at its own deterministic slot. This is consistent with regular struct nesting in Solidity, but not with Solidity's inheritance-based flat layout where all inherited variables share a single sequential slot range.
#[storage]Erc20OwnableUUPSUpgradeableStylus的字段在EVM状态树中的布局与Solidity完全一致。升级时需遵循相同的存储布局规则:
#[storage]- 切勿重新排序、删除或修改现有存储字段的类型
- 切勿在现有字段之前插入新字段
- 仅在结构体末尾追加新字段
- ERC-1967代理存储槽位于高位标准化位置——不会与实现合约的存储冲突
与Solidity的一个区别:Stylus 中的嵌套结构体(如将、、作为字段组合)的布局为每个嵌套结构体从自身的确定性存储槽开始。这与Solidity中常规的结构体嵌套一致,但不同于Solidity基于继承的扁平布局(所有继承变量共享一个连续的存储槽范围)。
#[storage]Erc20OwnableUUPSUpgradeableInitialization safety
初始化安全性
- The implementation constructor sets and any implementation-only state. It runs once at implementation deployment.
logic_flag - must be called via the proxy (during deployment or via
set_version()) to write theupgrade_to_and_callinto the proxy's storage.VERSION_NUMBER - If additional initialization is needed (ownership, token supply), expose a protected initialization function and include in it.
set_version() - Failing to initialize properly can result in orphaned contracts with no owner, uninitialized state, or denied future upgrades.
- 实现合约的构造函数设置及所有仅属于实现合约的状态。它仅在实现合约部署时运行一次。
logic_flag - 必须通过代理调用(部署期间或通过
set_version()),将upgrade_to_and_call写入代理的存储。VERSION_NUMBER - 如果需要额外的初始化(如所有权、代币供应量),需暴露一个受保护的初始化函数,并在其中包含。
set_version() - 初始化不当可能导致合约成为无主状态、状态未初始化或无法进行后续升级。
UUPS upgrade checks
UUPS升级检查
The UUPS implementation enforces three safety checks:
- Access control — restrict (e.g.,
upgrade_to_and_call)self.ownable.only_owner() - Proxy context enforcement — reverts if the call is not via
only_proxy()delegatecall - Proxiable UUID validation — must return the ERC-1967 implementation slot, confirming UUPS compatibility
proxiable_uuid()
UUPS实现会强制执行三项安全检查:
- 访问控制 — 限制的调用权限(如
upgrade_to_and_call)self.ownable.only_owner() - 代理上下文验证 — 如果调用不是通过进行,
delegatecall会回滚交易only_proxy() - 可代理UUID验证 — 必须返回ERC-1967实现槽,以确认UUPS兼容性
proxiable_uuid()
Reactivation
重新激活
Stylus WASM contracts must be reactivated once per year (365 days) or after any Stylus protocol upgrade. Reactivation can be done using or the precompile. If a contract is not reactivated, it becomes uncallable. This is orthogonal to proxy upgrades but must be factored into maintenance planning.
cargo-stylusArbWasmStylus WASM合约必须每年重新激活一次(365天),或在每次Stylus协议升级后重新激活。可使用或预编译合约进行重新激活。如果合约未被重新激活,将无法被调用。这与代理升级无关,但必须纳入维护计划中。
cargo-stylusArbWasmTesting upgrade paths
测试升级路径
Before upgrading a production contract:
- Deploy V1 implementation and proxy on a local Arbitrum devnet
- Write state with V1, upgrade to V2 via , and verify that all existing state reads correctly
upgrade_to_and_call - Verify new functionality works as expected after the upgrade
- Confirm access control — only authorized callers can invoke
upgrade_to_and_call - Check storage layout — ensure no reordering, removal, or type changes to existing fields
- Verify is incremented in the new implementation
VERSION_NUMBER - Test reactivation — ensure the upgraded contract can be reactivated
- Manual review — there is no automated storage layout validation for Stylus Rust contracts; rely on struct comparison and devnet testing
升级生产合约前,请完成以下步骤:
- 在本地Arbitrum测试网部署V1实现合约和代理
- 使用V1写入状态,通过升级到V2,并验证所有现有状态读取正确
upgrade_to_and_call - 验证升级后新功能是否按预期工作
- 确认访问控制——仅授权调用者可触发
upgrade_to_and_call - 检查存储布局——确保现有字段未被重新排序、删除或修改类型
- 验证新实现合约中的已递增
VERSION_NUMBER - 测试重新激活——确保升级后的合约可被重新激活
- 人工审核——目前没有针对Stylus Rust合约的自动化存储布局验证工具;需依赖结构体对比和测试网测试