mcpField
U
upgrade-stellar-contracts

upgrade-stellar-contracts

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

关于 upgrade-stellar-contracts

使用 OpenZeppelin 可升级模块规划和实现 Soroban 合约升级的技能,讲解授权、可选存储迁移、原子升级与迁移模式,以及保持状态兼容的检查方法。

主要功能

  1. 01Soroban 原生升级模型
  2. 02仅升级与升级迁移模式
  3. 03授权实现指导
  4. 04原子升级与迁移模式
  5. 05存储兼容性与测试检查

适用场景

  1. 01设计 Soroban 合约升级路径
  2. 02为代码升级添加存储迁移
  3. 03审查升级授权控制
  4. 04上线前测试状态兼容性

安装

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

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

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

详细介绍

Stellar 升级

目录

Soroban 升级模型

Soroban 合约默认可变。可变性是指智能合约修改自身 WASM 字节码的能力,这会改变其函数接口、执行逻辑或元数据。Soroban 提供了内置的协议级机制来升级合约,无需使用代理模式。

如果合约经过明确设计,就可以自行升级。反之,只要不提供升级函数,合约就会变为不可变。这与 EVM 代理模式有本质区别:

SorobanEVM(代理模式)Starknet
机制原生 WASM 字节码替换代理通过 delegatecall 调用实现合约replace_class_syscall 原地替换类哈希
是否需要代理合约不需要——合约自行升级需要——代理位于实现合约之前不需要——合约自行升级
存储位置直接归属于合约存储在代理中,通过 delegatecall 访问直接归属于合约
选择不可变性不公开升级函数不部署代理不调用系统调用

与需要代理合约和 delegatecall 转发的平台相比,协议级可升级性的一个优势是显著降低了风险面。

新实现只有在当前调用完成之后才会生效。这意味着,如果新实现中定义了迁移逻辑,该逻辑无法在执行升级的同一次调用中运行。可以使用辅助 Upgrader 合约封装这两个调用,以实现原子性(见下文)。

使用 OpenZeppelin 可升级模块

OpenZeppelin Stellar Soroban Contracts 在 contract-utils 包中提供了 upgradeable 模块,包含两个主要组件:

组件适用场景
Upgradeable只需更新 WASM 二进制文件,无需迁移存储
UpgradeableMigratable升级期间需要修改 WASM 二进制文件和特定存储条目

推荐通过派生宏使用这些组件:#[derive(Upgradeable)] 和 #[derive(UpgradeableMigratable)]。这些宏会实现必要的函数,并将 Cargo.toml 中的 crate 版本设置为 WASM 元数据中的二进制版本,以符合 SEP-49 指南。

仅升级

在合约结构体上派生 Upgradeable,然后实现 UpgradeableInternal,并提供一个必需方法:

  • _require_auth(e: &Env, operator: &Address) — 验证操作员是否有权执行升级(例如,检查是否与存储的所有者地址相符)

operator 参数是升级函数的调用者,可用于基于角色的访问控制。

升级并迁移

在合约结构体上派生 UpgradeableMigratable,然后实现 UpgradeableMigratableInternal,并提供:

  • 一个关联的 MigrationData 类型,用于定义传递给迁移函数的数据
  • _require_auth(e, operator) — 与上述相同的授权检查
  • _migrate(e: &Env, data: &Self::MigrationData) — 使用提供的迁移数据修改存储

派生宏可确保只能在成功升级之后调用迁移,从而防止状态不一致和存储损坏。

原子升级与迁移

由于新实现只有在当前调用完成后才会生效,新合约中的迁移逻辑无法与升级在同一次调用中运行。辅助 Upgrader 合约会将两个调用封装为一个原子操作:

use soroban_sdk::{contract, contractimpl, symbol_short, Address, BytesN, Env, Val};
use stellar_contract_utils::upgradeable::UpgradeableClient;

#[contract]
pub struct Upgrader;

#[contractimpl]
impl Upgrader {
    pub fn upgrade_and_migrate(
        env: Env,
        contract_address: Address,
        operator: Address,
        wasm_hash: BytesN<32>,
        migration_data: soroban_sdk::Vec<Val>,
    ) {
        operator.require_auth();
        let contract_client = UpgradeableClient::new(&env, &contract_address);
        contract_client.upgrade(&wasm_hash, &operator);
        env.invoke_contract::<()>(
            &contract_address,
            &symbol_short!("migrate"),
            migration_data,
        );
    }
}

请为 upgrade_and_migrate 设置适当的访问控制(例如,使用 access 包中的 Ownable,并添加 #[only_owner])。

如果需要回滚,可以将合约升级到一个定义了回滚专用逻辑的新版本,并将其作为迁移来执行。

示例: 请参阅 stellar-contracts repository 的 examples/ 目录,查看 Upgradeable 和 UpgradeableMigratable 的完整集成示例,其中包括 Upgrader 模式。

访问控制

upgradeable 模块有意不内置访问控制。你必须在 UpgradeableInternal 或 UpgradeableMigratableInternal 的 _require_auth 方法中定义授权逻辑。若忘记这一点,任何人都可以替换你的合约代码。

常见的访问控制选项:

  • Ownable — 单一所有者,最简单的模式(在 access 包中提供)
  • AccessControl / RBAC — 基于角色,粒度更细(在 access 包中提供)
  • 多重签名或治理 — 适用于管理重大资产的生产合约

升级安全性

注意事项

该框架会组织升级流程,但不会执行更深入的检查:

  • 新合约的构造函数不会被调用——任何初始化都必须通过迁移或单独的调用完成
  • 不会自动检查新合约是否包含升级机制——如果升级到不包含升级机制的合约,将永久失去升级能力
  • 不会验证存储一致性——新合约可能会无意中引入存储不匹配
存储兼容性

替换 WASM 二进制文件时,现有存储会由新代码重新解释。不兼容的更改会破坏状态:

  • 不要删除或重命名现有存储键
  • 不要更改现有键下存储值的类型
  • 添加新的存储键是安全的
  • Soroban 存储使用显式字符串键(例如 symbol_short!("OWNER")),因此键名至关重要——与 EVM 的顺序槽位不同,这里不存在顺序依赖
版本跟踪

派生宏会自动从 Cargo.toml 中提取 crate 版本,并依据 SEP-49 将其作为二进制版本嵌入 WASM 元数据。这支持链上版本跟踪,也可用于协调升级路径。

测试升级路径

升级生产合约前:

  • 在本地 Soroban 测试网上部署 V1(例如,使用 stellar-cli 和本地网络)
  • 使用 V1 写入状态,升级到 V2,并验证所有现有状态均可正确读取
  • 验证升级后新功能能否按预期运行
  • 确认访问控制有效——只有经过授权的调用方才能调用 upgrade
  • 检查 V2 是否包含升级机制——否则将永久失去升级能力
  • 验证存储键兼容性——确保没有删除、重命名现有键或更改其类型
  • 如果需要迁移,请使用 Upgrader 模式测试原子升级与迁移
  • 手动审查——Soroban 没有自动的存储兼容性验证;请使用派生宏安全地搭建升级流程,并依靠测试网测试

评论

支持 Markdown