upgrade-stylus-contracts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Stylus 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.
StylusSolidity
Proxy mechanismSame —
delegatecall
to implementation contract
delegatecall
to implementation contract
Storage layout
#[storage]
fields map to the same EVM slots as equivalent Solidity structs
Sequential slot allocation per Solidity rules
EIP standardsERC-1967 storage slots, ERC-1822 proxiable UUIDSame
Context detection
logic_flag
boolean in a unique storage slot (no
immutable
support)
address(this)
stored as
immutable
InitializationTwo-step: constructor sets
logic_flag
, then
set_version()
via proxy
Constructor + initializer via proxy
ReactivationWASM contracts must be reactivated every 365 days or after a Stylus protocol upgradeNot applicable
Existing Solidity contracts can upgrade to a Stylus (Rust) implementation via proxy patterns. The
#[storage]
macro lays out fields in the EVM state trie identically to Solidity, so storage slots line up when type definitions match.
Stylus合约作为WebAssembly(WASM)程序与EVM一同运行在Arbitrum上。它们与Solidity合约共享相同的状态树、存储模型和账户系统。因此,EVM代理模式在Stylus上的工作方式完全相同——Solidity代理可以委托给Stylus实现合约,反之亦然。
特性StylusSolidity
代理机制相同——通过
delegatecall
调用实现合约
通过
delegatecall
调用实现合约
存储布局
#[storage]
字段映射到与等效Solidity结构体相同的EVM存储槽
按照Solidity规则顺序分配存储槽
EIP标准ERC-1967存储槽、ERC-1822可代理UUID相同
上下文检测在唯一存储槽中使用
logic_flag
布尔值(不支持
immutable
关键字)
address(this)
存储为
immutable
变量
初始化两步流程:构造函数设置
logic_flag
,然后通过代理调用
set_version()
构造函数 + 通过代理调用初始化方法
重新激活WASM合约必须每365天或在Stylus协议升级后重新激活不适用
现有Solidity合约可以通过代理模式升级为Stylus(Rust)实现。
#[storage]
宏在EVM状态树中的字段布局与Solidity完全一致,因此当类型定义匹配时,存储槽会对齐。

Proxy Patterns

代理模式

OpenZeppelin Contracts for Stylus provides three proxy patterns:
PatternKey typesBest for
UUPS
UUPSUpgradeable
,
IErc1822Proxiable
,
Erc1967Proxy
Most projects — upgrade logic in the implementation, lighter proxy
Beacon
BeaconProxy
,
UpgradeableBeacon
Multiple proxies sharing one implementation — updating the beacon upgrades all proxies atomically
Basic Proxy
Erc1967Proxy
,
Erc1967Utils
Low-level building block for custom proxy patterns
OpenZeppelin为Stylus提供的合约包含三种代理模式:
模式核心类型适用场景
UUPS
UUPSUpgradeable
IErc1822Proxiable
Erc1967Proxy
大多数项目——升级逻辑在实现合约中,代理更轻量化
Beacon
BeaconProxy
UpgradeableBeacon
多个代理共享同一实现合约——更新Beacon可一次性升级所有代理
基础代理
Erc1967Proxy
Erc1967Utils
用于自定义代理模式的底层构建模块

UUPS

UUPS

The implementation contract composes
UUPSUpgradeable
in its
#[storage]
struct alongside access control (e.g.,
Ownable
). Integration requires:
  1. Add
    UUPSUpgradeable
    (and access control) as fields in the
    #[storage]
    struct
  2. Call
    self.uups.constructor()
    and initialize access control in the constructor
  3. Expose
    initialize
    calling
    self.uups.set_version()
    — invoked via proxy after deployment
  4. Implement
    IUUPSUpgradeable
    upgrade_to_and_call
    guarded by access control,
    upgrade_interface_version
    delegating to
    self.uups
  5. Implement
    IErc1822Proxiable
    proxiable_uuid
    delegating to
    self.uups
The proxy contract is a thin
Erc1967Proxy
with a constructor that takes the implementation address and initialization data, and a
#[fallback]
handler that delegates all calls.
Deploy the proxy with
set_version
as the initialization call data. Use
cargo stylus deploy
or a deployer contract. The initialization data is the ABI-encoded
setVersion
call:
rust
let data = MyContractAbi::setVersionCall {}.abi_encode();
// Pass `data` as the proxy constructor's second argument at deployment time.
实现合约需要在其
#[storage]
结构体中组合
UUPSUpgradeable
与访问控制组件(如
Ownable
)。集成步骤如下:
  1. #[storage]
    结构体中添加
    UUPSUpgradeable
    (及访问控制)作为字段
  2. 在构造函数中调用
    self.uups.constructor()
    并初始化访问控制
  3. 暴露
    initialize
    方法,该方法调用
    self.uups.set_version()
    ——在部署后通过代理调用
  4. 实现
    IUUPSUpgradeable
    接口——
    upgrade_to_and_call
    需由访问控制保护,
    upgrade_interface_version
    委托给
    self.uups
  5. 实现
    IErc1822Proxiable
    接口——
    proxiable_uuid
    委托给
    self.uups
代理合约是一个轻量的
Erc1967Proxy
,其构造函数接收实现合约地址和初始化数据,并通过
#[fallback]
处理函数委托所有调用。
部署代理时,将
set_version
作为初始化调用数据。可使用
cargo stylus deploy
或部署合约。初始化数据是经过ABI编码的
setVersion
调用:
rust
let data = MyContractAbi::setVersionCall {}.abi_encode();
// Pass `data` as the proxy constructor's second argument at deployment time.

Beacon

Beacon

Multiple
BeaconProxy
contracts point to a single
UpgradeableBeacon
that stores the current implementation address. Updating the beacon upgrades all proxies in one transaction.
多个
BeaconProxy
合约指向同一个
UpgradeableBeacon
,该Beacon存储当前实现合约的地址。更新Beacon可在一笔交易中完成所有代理的升级。

Context detection (Stylus-specific)

上下文检测(Stylus专属)

Stylus does not support the
immutable
keyword. Instead of storing
__self = address(this)
,
UUPSUpgradeable
uses a
logic_flag
boolean in a unique storage slot:
  • The implementation's constructor sets
    logic_flag = true
    in its own storage.
  • When code runs via a proxy (
    delegatecall
    ), the proxy's storage does not contain this flag, so it reads as
    false
    .
  • only_proxy()
    checks this flag to ensure upgrade functions can only be called through the proxy, not directly on the implementation.
only_proxy()
also verifies that the ERC-1967 implementation slot is non-zero and that the proxy-stored version matches the implementation's
VERSION_NUMBER
.
Examples: See the
examples/
directory of the rust-contracts-stylus repository for full working integration examples of UUPS, Beacon, and related patterns.
Stylus不支持
immutable
关键字。
UUPSUpgradeable
并未存储
__self = address(this)
,而是在一个唯一的存储槽中使用
logic_flag
布尔值:
  • 实现合约的构造函数会在自身存储中设置
    logic_flag = true
  • 当代码通过代理(
    delegatecall
    )运行时,代理的存储中不存在该标志,因此读取值为
    false
  • only_proxy()
    会检查此标志,确保升级函数只能通过代理调用,而不能直接调用实现合约。
only_proxy()
还会验证ERC-1967实现槽是否非零,以及代理存储的版本是否与实现合约的
VERSION_NUMBER
匹配。
示例: 可查看rust-contracts-stylus仓库
examples/
目录,获取UUPS、Beacon及相关模式的完整可运行集成示例。

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_call
:
rust
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_call
中添加:
rust
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
#[storage]
fields are laid out in the EVM state trie identically to Solidity. The same storage layout rules apply when upgrading:
  • 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
#[storage]
(e.g., composing
Erc20
,
Ownable
,
UUPSUpgradeable
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.
Stylus的
#[storage]
字段在EVM状态树中的布局与Solidity完全一致。升级时需遵循相同的存储布局规则:
  • 切勿重新排序、删除或修改现有存储字段的类型
  • 切勿在现有字段之前插入新字段
  • 在结构体末尾追加新字段
  • ERC-1967代理存储槽位于高位标准化位置——不会与实现合约的存储冲突
与Solidity的一个区别:Stylus
#[storage]
中的嵌套结构体(如将
Erc20
Ownable
UUPSUpgradeable
作为字段组合)的布局为每个嵌套结构体从自身的确定性存储槽开始。这与Solidity中常规的结构体嵌套一致,但不同于Solidity基于继承的扁平布局(所有继承变量共享一个连续的存储槽范围)。

Initialization safety

初始化安全性

  • The implementation constructor sets
    logic_flag
    and any implementation-only state. It runs once at implementation deployment.
  • set_version()
    must be called via the proxy (during deployment or via
    upgrade_to_and_call
    ) to write the
    VERSION_NUMBER
    into the proxy's storage.
  • If additional initialization is needed (ownership, token supply), expose a protected initialization function and include
    set_version()
    in it.
  • 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:
  1. Access control — restrict
    upgrade_to_and_call
    (e.g.,
    self.ownable.only_owner()
    )
  2. Proxy context enforcement
    only_proxy()
    reverts if the call is not via
    delegatecall
  3. Proxiable UUID validation
    proxiable_uuid()
    must return the ERC-1967 implementation slot, confirming UUPS compatibility
UUPS实现会强制执行三项安全检查:
  1. 访问控制 — 限制
    upgrade_to_and_call
    的调用权限(如
    self.ownable.only_owner()
  2. 代理上下文验证 — 如果调用不是通过
    delegatecall
    进行,
    only_proxy()
    会回滚交易
  3. 可代理UUID验证
    proxiable_uuid()
    必须返回ERC-1967实现槽,以确认UUPS兼容性

Reactivation

重新激活

Stylus WASM contracts must be reactivated once per year (365 days) or after any Stylus protocol upgrade. Reactivation can be done using
cargo-stylus
or the
ArbWasm
precompile. If a contract is not reactivated, it becomes uncallable. This is orthogonal to proxy upgrades but must be factored into maintenance planning.
Stylus WASM合约必须每年重新激活一次(365天),或在每次Stylus协议升级后重新激活。可使用
cargo-stylus
ArbWasm
预编译合约进行重新激活。如果合约未被重新激活,将无法被调用。这与代理升级无关,但必须纳入维护计划中。

Testing 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
    upgrade_to_and_call
    , and verify that all existing state reads correctly
  • 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
    VERSION_NUMBER
    is incremented in the new implementation
  • 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写入状态,通过
    upgrade_to_and_call
    升级到V2,并验证所有现有状态读取正确
  • 验证升级后新功能是否按预期工作
  • 确认访问控制——仅授权调用者可触发
    upgrade_to_and_call
  • 检查存储布局——确保现有字段未被重新排序、删除或修改类型
  • 验证新实现合约中的
    VERSION_NUMBER
    已递增
  • 测试重新激活——确保升级后的合约可被重新激活
  • 人工审核——目前没有针对Stylus Rust合约的自动化存储布局验证工具;需依赖结构体对比和测试网测试