mcpField
U
upgrade-cairo-contracts

upgrade-cairo-contracts

github.com/openzeppelin开发219211 次安装2 天前更新
代码仓库

关于 upgrade-cairo-contracts

面向 Starknet Cairo 合约开发者的升级指引,介绍类哈希替换、OpenZeppelin 可升级组件、访问控制和存储安全检查。

主要功能

  1. 01说明 Starknet 类哈希升级机制
  2. 02集成 OpenZeppelin UpgradeableComponent
  3. 03用访问控制保护升级入口
  4. 04检查存储兼容性与升级安全
  5. 05通过本地 devnet 测试升级路径

适用场景

  1. 01为 Cairo 合约添加升级入口
  2. 02选择 Ownable 或 RBAC 等访问控制
  3. 03审查升级前后的存储变更
  4. 04在本地 devnet 验证合约升级

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/openzeppelin/openzeppelin-skills --skill upgrade-cairo-contracts

安装后在 Claude Code 中输入/upgrade-cairo-contracts确认已加载。

详细介绍

Cairo 升级

目录

Starknet 升级模型

Starknet 将合约实例与合约类分开。类是已编译的程序(由其类哈希标识);合约是指向某个类的已部署实例。多个合约可以共享同一个类。

升级合约意味着替换其类哈希,使其指向一个新类。合约会保留其地址、存储和 nonce,只有代码会发生变化。这与 EVM 代理模式有本质区别:

StarknetEVM(代理模式)
机制replace_class_syscall 原地替换类哈希代理通过 delegatecall 调用单独的实现合约
是否需要代理合约不需要——合约自行升级需要——代理位于实现合约之前
存储位置直接属于合约存储在代理中,通过 delegatecall 访问
回退路由不适用——Cairo 中没有回退/全捕获机制代理通过回退函数转发所有调用

replace_class_syscall 是 Starknet 原生系统调用。调用时,它会以原子方式将调用合约的类哈希替换为给定的类哈希。新类必须已在链上声明。系统调用执行后,当前执行帧会继续运行旧代码,但之后对该合约的调用——无论是在同一笔交易中稍后通过 call_contract_syscall 发起,还是在后续交易中发起——都会执行新代码。

使用 OpenZeppelin 可升级组件

OpenZeppelin Contracts for Cairo 提供了一个 UpgradeableComponent,它通过验证并发出事件来封装 replace_class_syscall。按以下方式集成:

  1. 声明组件,并与访问控制组件(例如 OwnableComponent)一起使用
  2. 将两者添加到存储和事件中,使用 #[substorage(v0)] 和 #[flat]
  3. 通过访问控制保护 upgrade 函数,并在其中调用组件的内部 upgrade 方法——该组件会调用 replace_class_syscall,以原子方式替换类哈希;解释 Cairo 升级的工作方式时,务必提及此系统调用
  4. 在构造函数中初始化访问控制

每次替换类哈希时,该组件都会发出 Upgraded 事件,并拒绝零类哈希。

此外还有一个 IUpgradeAndCall 接口变体,可在新类的上下文中将升级与函数调用结合起来,适用于升级后迁移或重新初始化。

访问控制

UpgradeableComponent 有意不内置访问控制。你必须使用自己的检查来保护外部 upgrade 函数(例如 self.ownable.assert_only_owner())。如果忘记这样做,任何人都可以替换合约代码。

常见的访问控制选项:

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

升级安全性

存储兼容性

替换类哈希时,新类会重新解释现有存储。不兼容的更改会破坏状态:

  • 不要重命名或移除现有存储变量——存储槽由变量名派生,因此重命名会导致旧数据无法访问
  • 不要更改现有存储变量的类型
  • 添加新的存储变量是安全的
  • 组件存储使用 #[substorage(v0)],将组件存储槽展平到合约的存储空间中,不会自动添加命名空间——请遵循在存储变量名前加组件名前缀的约定(例如 ERC20_balances),以避免组件之间发生冲突

与 Solidity 的顺序存储布局不同,Cairo 存储槽通过 sn_keccak 哈希根据变量名派生(从概念上类似于 Solidity 中的 ERC-7201 命名空间存储,但更为基础)。因此,顺序无关紧要,但命名至关重要。

OpenZeppelin 版本升级

OpenZeppelin Contracts for Cairo 遵循语义化版本控制,以保证存储布局兼容性:

  • 补丁更新始终保留存储布局
  • 次要版本更新保留存储布局(从 v1.0.0 起)
  • 主要版本更新可能会破坏存储布局——未经审查变更日志,绝不要跨主要版本升级线上合约
测试升级路径

升级生产合约前:

  • 在本地 devnet(例如 starknet-devnet-rs 或 Katana)中部署 V1 和 V2 类
  • 使用 V1 写入状态,升级到 V2,并验证所有现有状态均可正确读取
  • 验证新功能在升级后能否按预期运行
  • 确认访问控制——只有获得授权的调用方才能调用 upgrade
  • 检查 API 兼容性——外部函数签名发生变更会破坏现有调用方和集成
  • 审查存储变更——确保现有变量没有被重命名、移除或更改类型
  • 人工审查——Cairo 没有自动化的存储布局验证;使用 npx @openzeppelin/contracts-cli 了解当前集成模式,并依赖 devnet 测试

评论

支持 Markdown