upgrade-stellar-contracts について
OpenZeppelin の upgradeable モジュールを使った Soroban コントラクトのアップグレードを支援するスキルです。認可、ストレージ移行、原子的な更新手順、状態互換性の確認方法を解説します。
主な機能
- 01Soroban のネイティブな更新モデル
- 02更新のみ・移行付き更新のパターン
- 03認可の実装ガイダンス
- 04原子的な更新と移行のパターン
- 05ストレージ互換性とテスト項目
活用シーン
- 01Soroban コントラクトの更新経路の設計
- 02コード更新へのストレージ移行の追加
- 03更新権限の制御のレビュー
- 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 のプロキシパターンとは根本的に異なります。
| Soroban | EVM(プロキシパターン) | Starknet | |
|---|---|---|---|
| 仕組み | ネイティブな WASM バイトコードの置き換え | プロキシが実装コントラクトに delegatecall する | replace_class_syscall がクラスハッシュをその場で置き換える |
| プロキシコントラクトが必要か | いいえ — コントラクト自身がアップグレードする | はい — 実装の前段にプロキシを配置する | いいえ — コントラクト自身がアップグレードする |
| ストレージの場所 | コントラクトに直接属する | プロキシに保存され、delegatecall 経由でアクセスされる | コントラクトに直接属する |
| 不変性を選択する方法 | アップグレード関数を公開しない | プロキシをデプロイしない | syscall を呼び出さない |
プロトコルレベルのアップグレード可能性の利点の一つは、プロキシコントラクトと delegatecall による転送が必要なプラットフォームと比べて、リスク範囲を大幅に縮小できることです。
新しい実装が有効になるのは、現在の呼び出しが完了した後です。つまり、新しい実装でマイグレーションロジックを定義しても、アップグレードと同じ呼び出し内では実行できません。補助的な Upgrader コントラクトで両方の呼び出しをラップすれば、アトミックに実行できます(下記参照)。
OpenZeppelin Upgradeable モジュールの使用
OpenZeppelin Stellar Soroban Contracts は、contract-utils パッケージで upgradeable モジュールを提供しています。このモジュールには、主に次の 2 つのコンポーネントがあります。
| コンポーネント | 使用する状況 |
|---|---|
Upgradeable | WASM バイナリの更新のみが必要で、ストレージのマイグレーションは不要な場合 |
UpgradeableMigratable | アップグレード中に WASM バイナリと特定のストレージエントリーを変更する必要がある場合 |
これらを使用する方法として、derive マクロの #[derive(Upgradeable)] と #[derive(UpgradeableMigratable)] が推奨されています。これらのマクロは必要な関数の実装を処理し、Cargo.toml のクレートバージョンを WASM メタデータのバイナリバージョンとして設定して、SEP-49 のガイドラインに準拠させます。
アップグレードのみ
コントラクトの構造体に Upgradeable を derive し、次に UpgradeableInternal を実装します。実装する必要があるメソッドは次の 1 つです。
_require_auth(e: &Env, operator: &Address)— アップグレードを実行する権限がオペレーターにあることを検証します(例:保存済みのオーナーアドレスと照合します)
operator パラメーターはアップグレード関数の呼び出し元であり、ロールベースのアクセス制御に使用できます。
アップグレードとマイグレーション
コントラクトの構造体に UpgradeableMigratable を derive し、次の要素を含む UpgradeableMigratableInternal を実装します。
- マイグレーション関数に渡すデータを定義する関連型
MigrationData _require_auth(e, operator)— 上記と同じ認証チェック_migrate(e: &Env, data: &Self::MigrationData)— 渡されたマイグレーションデータを使用してストレージを変更します
derive マクロにより、マイグレーションを実行できるのはアップグレードの成功後のみとなり、状態の不整合やストレージの破損を防止できます。
アトミックなアップグレードと移行
新しい実装が有効になるのは現在の呼び出しが完了した後であるため、新しいコントラクトの移行ロジックをアップグレードと同じ呼び出しで実行することはできません。補助的な 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])を設定してください。
ロールバックが必要な場合は、ロールバック専用のロジックを定義し、移行として実行する新しいバージョンにコントラクトをアップグレードできます。
例:
UpgradeableとUpgradeableMigratableの両方について、Upgraderパターンを含む、動作する統合例の全体は、stellar-contracts repository のexamples/ディレクトリを参照してください。
アクセス制御
upgradeable モジュールには、意図的にアクセス制御が組み込まれていません。UpgradeableInternal または UpgradeableMigratableInternal の _require_auth メソッドで認証を定義する必要があります。これを忘れると、誰でもコントラクトのコードを置き換えられるようになります。
一般的なアクセス制御の選択肢:
- Ownable — 単一のオーナーを使用する最もシンプルなパターン(
accessパッケージで利用可能) - AccessControl / RBAC — ロールベースで、より細かな制御が可能(
accessパッケージで利用可能) - マルチシグまたはガバナンス — 多額の価値を管理する本番コントラクト向け
アップグレードの安全性
注意事項
フレームワークはアップグレードの流れを構成しますが、詳細な検証は行いません。
- 新しいコントラクトのコンストラクターは呼び出されません。初期化は移行または別の呼び出しで行う必要があります
- 新しいコントラクトにアップグレード機構が含まれているかどうかの自動チェックはありません。アップグレード機構のないコントラクトにアップグレードすると、アップグレード可能性が永久に失われます
- ストレージの整合性は検証されません。新しいコントラクトによって、意図せずストレージの不整合が生じる可能性があります
ストレージの互換性
WASM バイナリを置き換えると、新しいコードによって既存のストレージが再解釈されます。互換性のない変更を行うと、状態が破損します。
- 既存のストレージキーを削除または名前変更しないでください
- 既存のキーに保存されている値の型を変更しないでください
- 新しいストレージキーの追加は安全です
- Soroban ストレージでは明示的な文字列キー(例:
symbol_short!("OWNER"))を使用するため、キーの命名が重要です。EVM の連続スロットとは異なり、順序への依存はありません
バージョン管理
derive マクロは Cargo.toml からクレートのバージョンを自動的に取得し、SEP-49 に従って WASM メタデータにバイナリのバージョンとして埋め込みます。これにより、オンチェーンでのバージョン管理が可能になり、アップグレードパスの調整に利用できます。
アップグレードパスのテスト
本番コントラクトをアップグレードする前に、次の項目を確認してください。
- ローカル Soroban テストネット(例:ローカルネットワークでの
stellar-cli)に V1 をデプロイする - V1 で状態を書き込み、V2 にアップグレードして、既存の状態がすべて正しく読み取れることを確認する
- アップグレード後に新機能が期待どおりに動作することを確認する
- アクセス制御を確認する。認証された呼び出し元のみが
upgradeを呼び出せることを確認する - V2 にアップグレード機構が含まれていることを確認する。含まれていない場合、アップグレード可能性は永久に失われます
- ストレージキーの互換性を確認する。既存のキーが削除、名前変更、または型変更されていないことを確認する
- 移行が必要な場合は、
Upgraderパターンを使用してアップグレードと移行をアトミックに実行するテストを行う - 手動で確認する。Soroban にはストレージ互換性を自動検証する仕組みがありません。安全なアップグレードの土台として derive マクロを使用し、テストネットでのテストも行ってください