upgrade-cairo-contracts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cairo Upgrades

Cairo 合约升级

Contents

目录

Starknet Upgrade Model

Starknet 升级模型

Starknet separates contract instances from contract classes. A class is the compiled program (identified by its class hash); a contract is a deployed instance pointing to a class. Multiple contracts can share the same class.
Upgrading a contract means replacing its class hash so it points to a new class. The contract keeps its address, storage, and nonce — only the code changes. This is fundamentally different from EVM proxy patterns:
StarknetEVM (proxy pattern)
Mechanism
replace_class_syscall
swaps the class hash in-place
Proxy
delegatecall
s to a separate implementation contract
Proxy contract neededNo — the contract upgrades itselfYes — a proxy sits in front of the implementation
Storage locationBelongs to the contract directlyLives in the proxy, accessed via delegatecall
Fallback routingNot applicable — no fallback/catch-all mechanism in CairoProxy forwards all calls via fallback function
The
replace_class_syscall
is a native Starknet syscall. When called, it atomically replaces the calling contract's class hash with the provided one. The new class must already be declared on-chain. After the syscall, the current execution frame continues with the old code, but subsequent calls to the contract — whether via
call_contract_syscall
later in the same transaction or in future transactions — execute the new code.
Starknet 将合约实例合约类分离。类是编译后的程序(通过类哈希标识);合约是指向某个类的已部署实例。多个合约可以共享同一个类。
升级合约意味着替换其类哈希,使其指向新的类。合约会保留其地址、存储和随机数——只有代码会改变。这与EVM代理模式有着本质区别:
StarknetEVM(代理模式)
实现机制
replace_class_syscall
原地替换类哈希
代理通过
delegatecall
调用独立的实现合约
是否需要代理合约不需要——合约自行完成升级需要——代理合约位于实现合约前端
存储位置直接属于合约本身存储在代理中,通过delegatecall访问
回退路由不适用——Cairo中没有回退/兜底机制代理通过回退函数转发所有调用
replace_class_syscall
是Starknet的原生系统调用。调用时,它会原子性地将调用合约的类哈希替换为提供的新哈希。新类必须已在链上声明。系统调用后,当前执行帧会继续使用旧代码,但后续对该合约的调用——无论是同一交易中后续的
call_contract_syscall
还是未来的交易——都会执行新代码。

Using the OpenZeppelin Upgradeable Component

使用 OpenZeppelin 可升级组件

OpenZeppelin Contracts for Cairo provides an
UpgradeableComponent
that wraps
replace_class_syscall
with validation and event emission. Integrate it as follows:
  1. Declare the component alongside an access control component (e.g.,
    OwnableComponent
    )
  2. Add both to storage and events using
    #[substorage(v0)]
    and
    #[flat]
  3. Expose an
    upgrade
    function
    behind access control that calls the component's internal
    upgrade
    method — the component calls
    replace_class_syscall
    to atomically swap the class hash; always mention this syscall when explaining how Cairo upgrades work
  4. Initialize access control in the constructor
The component emits an
Upgraded
event on each class hash replacement and rejects zero class hashes.
There is also an
IUpgradeAndCall
interface variant that couples the upgrade with a function call in the new class context — useful for post-upgrade migrations or re-initialization.
OpenZeppelin Contracts for Cairo 提供了
UpgradeableComponent
,它封装了
replace_class_syscall
并添加了验证和事件发射功能。集成步骤如下:
  1. 声明组件,同时声明访问控制组件(如
    OwnableComponent
  2. 使用
    #[substorage(v0)]
    #[flat]
    将两者添加到存储和事件中
  3. 暴露
    upgrade
    函数
    ,并通过访问控制进行保护,该函数会调用组件的内部
    upgrade
    方法——组件会调用
    replace_class_syscall
    来原子性地交换类哈希;在解释Cairo合约升级原理时,务必提及此系统调用
  4. 在构造函数中初始化访问控制
该组件会在每次类哈希替换时发射
Upgraded
事件,并拒绝零值类哈希。
还有一个
IUpgradeAndCall
接口变体,可将升级操作与新类上下文下的函数调用结合——适用于升级后的迁移或重新初始化操作。

Access control

访问控制

The
UpgradeableComponent
deliberately does not embed access control itself. You must guard the external
upgrade
function with your own check (e.g.,
self.ownable.assert_only_owner()
). Forgetting this allows anyone to replace your contract's code.
Common access control options:
  • Ownable — single owner, simplest pattern
  • AccessControl / RBAC — role-based, finer granularity
  • Multisig or governance — for production contracts managing significant value
UpgradeableComponent
刻意内置访问控制。你必须自行保护外部
upgrade
函数(例如
self.ownable.assert_only_owner()
)。如果遗漏这一步,任何人都可以替换你的合约代码。
常见的访问控制选项:
  • Ownable —— 单所有者,最简单的模式
  • AccessControl / RBAC —— 基于角色,粒度更细
  • 多签或治理 —— 适用于管理大额资产的生产级合约

Upgrade Safety

升级安全性

Storage compatibility

存储兼容性

When replacing a class hash, existing storage is reinterpreted by the new class. Incompatible changes corrupt state:
  • Do not rename or remove existing storage variables — the slot is derived from the variable name, so renaming makes old data inaccessible
  • Do not change the type of existing storage variables
  • Adding new storage variables is safe
  • Component storage uses
    #[substorage(v0)]
    , which flattens component slots into the contract's storage space without automatic namespacing — follow the convention of prefixing storage variable names with the component name (e.g.,
    ERC20_balances
    ) to avoid collisions across components
Unlike Solidity's sequential storage layout, Cairo storage slots are derived from variable names via
sn_keccak
hashing (conceptually analogous to, but more fundamental than, ERC-7201 namespaced storage in Solidity). This makes ordering irrelevant but makes naming critical.
替换类哈希时,现有存储会被新类重新解析。不兼容的更改会导致状态损坏:
  • 请勿重命名或删除现有存储变量——存储槽由变量名派生而来,重命名会导致旧数据无法访问
  • 请勿更改现有存储变量的类型
  • 添加新的存储变量是安全的
  • 组件存储使用
    #[substorage(v0)]
    ,它会将组件的存储槽扁平化到合约的存储空间中,且没有自动命名空间——请遵循组件名前缀的约定(例如
    ERC20_balances
    ),以避免跨组件的存储冲突
与Solidity的顺序存储布局不同,Cairo的存储槽是通过
sn_keccak
哈希从变量名派生而来(概念上类似于但比Solidity中的ERC-7201命名空间存储更基础)。这意味着存储顺序无关紧要,但变量命名至关重要。

OpenZeppelin version upgrades

OpenZeppelin 版本升级

OpenZeppelin Contracts for Cairo follows semantic versioning for storage layout compatibility:
  • Patch updates always preserve storage layout
  • Minor updates preserve storage layout (from v1.0.0 onward)
  • Major updates may break storage layout — never upgrade a live contract across major versions without reviewing the changelog
OpenZeppelin Contracts for Cairo 遵循语义化版本控制以保证存储布局兼容性:
  • 补丁版本更新始终保留存储布局
  • 次要版本更新保留存储布局(从v1.0.0开始)
  • 主要版本更新可能会破坏存储布局——在未查看变更日志的情况下,切勿对线上合约进行跨主要版本的升级

Testing upgrade paths

测试升级路径

Before upgrading a production contract:
  • Deploy V1 and V2 classes in a local devnet (e.g.,
    starknet-devnet-rs
    or Katana)
  • Write state with V1, upgrade to V2, 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
  • Check API compatibility — changed external function signatures break existing callers and integrations
  • Review storage changes — ensure no renames, removals, or type changes to existing variables
  • Manual review — there is no automated storage layout validation for Cairo; use the MCP contract generators to discover current integration patterns and rely on devnet testing
在升级生产级合约之前:
  • 部署V1和V2版本的类到本地开发网络(如
    starknet-devnet-rs
    或Katana)
  • 用V1版本写入状态,升级到V2版本,验证所有现有状态都能被正确读取
  • 验证新功能在升级后能正常工作
  • 确认访问控制——只有授权调用者才能调用
    upgrade
    函数
  • 检查API兼容性——外部函数签名的变更会破坏现有调用者和集成
  • 审查存储变更——确保没有对现有变量进行重命名、删除或类型更改
  • 人工审查——目前Cairo没有自动化的存储布局验证工具;使用MCP合约生成器了解当前集成模式,并依赖开发网络测试