mcpField
U
upgrade-stylus-contracts

upgrade-stylus-contracts

github.com/openzeppelin開発219211 回インストール2 日前に更新
リポジトリ

upgrade-stylus-contracts について

Arbitrum Stylus コントラクトをアップグレードする開発者向けガイドです。プロキシパターン、アクセス制御、ストレージ互換性、初期化、WASM コントラクトの再アクティベーションを扱います。

主な機能

  1. 01Stylus と Solidity のプロキシパターンの解説
  2. 02UUPS、Beacon、基本プロキシの紹介
  3. 03アップグレード関数へのアクセス制御の適用
  4. 04ストレージレイアウトと初期化の安全性確認
  5. 05アップグレードとコントラクト再アクティベーションのテスト

活用シーン

  1. 01Stylus コントラクトのプロキシパターン選択
  2. 02プロキシ経由での Solidity または Stylus 実装の更新
  3. 03アップグレード前後のストレージレイアウト確認
  4. 04アップグレードテストと再アクティベーションの計画

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/upgrade-stylus-contractsを入力して読み込まれたか確認します。

詳細

Stylus のアップグレード

目次

Stylus のアップグレードモデル

Stylus コントラクトは、EVM と並行して WebAssembly(WASM)プログラムとして Arbitrum 上で実行されます。Solidity コントラクトと同じ状態トライ、ストレージモデル、アカウントシステムを共有します。このため、EVM のプロキシパターンは Stylus でも同じように機能します。Solidity のプロキシから Stylus の実装へ、またその逆へ委譲できます。

StylusSolidity
プロキシの仕組み同じ — 実装コントラクトへの delegatecall実装コントラクトへの delegatecall
ストレージレイアウト#[storage] フィールドは、対応する Solidity の構造体と同じ EVM スロットに割り当てられますSolidity の規則に従ってスロットを順次割り当てます
EIP 標準ERC-1967 ストレージスロット、ERC-1822 の proxiable UUID同じ
コンテキストの検出固有のストレージスロットにある logic_flag ブール値(immutable はサポートされません)address(this) を immutable として保存
初期化2 段階:コンストラクターで logic_flag を設定し、その後プロキシ経由で set_version() を呼び出しますコンストラクターと、プロキシ経由の初期化関数
再アクティベーションWASM コントラクトは、365 日ごと、または Stylus プロトコルのアップグレード後に再アクティベーションが必要です該当なし

既存の Solidity コントラクトは、プロキシパターンを使用して Stylus(Rust)実装にアップグレードできます。#[storage] マクロはフィールドを Solidity と同じように EVM の状態トライに配置するため、型定義が一致すればストレージスロットも一致します。

プロキシパターン

OpenZeppelin Contracts for Stylus は、3 つのプロキシパターンを提供します。

パターン主な型適した用途
UUPSUUPSUpgradeable、IErc1822Proxiable、Erc1967Proxyほとんどのプロジェクト — アップグレードロジックを実装に置き、プロキシを軽量化します
BeaconBeaconProxy、UpgradeableBeacon複数のプロキシで 1 つの実装を共有する場合 — ビーコンを更新すると、すべてのプロキシがアトミックにアップグレードされます
Basic ProxyErc1967Proxy、Erc1967Utilsカスタムプロキシパターンを構築するための低レベルな構成要素
UUPS

実装コントラクトでは、アクセス制御(例:Ownable)とともに、#[storage] 構造体に UUPSUpgradeable を組み込みます。統合には次の作業が必要です。

  1. #[storage] 構造体のフィールドとして UUPSUpgradeable(およびアクセス制御)を追加します
  2. コンストラクターで self.uups.constructor() を呼び出し、アクセス制御を初期化します
  3. self.uups.set_version() を呼び出す initialize を公開します — デプロイ後、プロキシ経由で呼び出します
  4. IUUPSUpgradeable を実装します — upgrade_to_and_call はアクセス制御で保護し、upgrade_interface_version は self.uups に委譲します
  5. IErc1822Proxiable を実装します — proxiable_uuid は self.uups に委譲します

プロキシコントラクトは、実装アドレスと初期化データを受け取るコンストラクター、およびすべての呼び出しを委譲する #[fallback] ハンドラーを持つ、薄い Erc1967Proxy です。

初期化の呼び出しデータとして set_version を指定してプロキシをデプロイします。cargo stylus deploy またはデプロイ用コントラクトを使用します。初期化データは、ABI エンコードされた setVersion の呼び出しです。

let data = MyContractAbi::setVersionCall {}.abi_encode();
// Pass `data` as the proxy constructor's second argument at deployment time.
Beacon

複数の BeaconProxy コントラクトが、現在の実装アドレスを保存する単一の UpgradeableBeacon を参照します。ビーコンを更新すると、1 回のトランザクションですべてのプロキシがアップグレードされます。

コンテキスト検出(Stylus 固有)

Stylus は immutable キーワードをサポートしていません。__self = address(this) を保存する代わりに、UUPSUpgradeable は一意のストレージスロットにある logic_flag というブール値を使用します。

  • 実装のコンストラクターは、実装自身のストレージで logic_flag = true を設定します。
  • プロキシ経由でコードが実行される場合(delegatecall)、プロキシのストレージにはこのフラグがないため、値は false として読み取られます。
  • only_proxy() はこのフラグをチェックし、アップグレード関数が実装に直接ではなく、プロキシ経由でのみ呼び出されるようにします。

only_proxy() はさらに、ERC-1967 の実装スロットがゼロ以外であることと、プロキシに保存されているバージョンが実装の VERSION_NUMBER と一致することを検証します。

例: UUPS、Beacon、および関連パターンの動作する完全な統合例については、rust-contracts-stylus リポジトリの examples/ ディレクトリを参照してください。

アクセス制御

アップグレード関数はアクセス制御で保護する必要があります。OpenZeppelin の Stylus コントラクトは、アップグレードロジック自体にアクセス制御を組み込んでいません。upgrade_to_and_call に追加する必要があります。

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 — ロールベースで、より細かく制御できます
  • マルチシグまたはガバナンス — 大きな価値を管理する本番用コントラクト向けです

アップグレードの安全性

ストレージの互換性

Stylus の #[storage] フィールドは、Solidity と同じように EVM の状態トライに配置されます。アップグレード時には同じストレージレイアウトのルールが適用されます。

  • 既存のストレージフィールドの順序、削除、型の変更は絶対に行わないでください
  • 既存のフィールドより前に新しいフィールドを挿入することは絶対に避けてください
  • 新しいフィールドは構造体の末尾にのみ追加してください
  • ERC-1967 プロキシのストレージスロットは、標準化された高位の場所にあるため、実装のストレージと衝突しません

Solidity との違いとして、Stylus の #[storage] 内のネストされた構造体(例:Erc20、Ownable、UUPSUpgradeable をフィールドとして組み合わせる場合)は、それぞれが決定的な独自のスロットから始まるように配置されます。これは Solidity の通常の構造体のネストと一貫していますが、継承されたすべての変数が単一の連続したスロット範囲を共有する、Solidity の継承ベースのフラットなレイアウトとは異なります。

初期化の安全性
  • 実装のコンストラクターは logic_flag と実装専用の状態を設定します。これは実装のデプロイ時に一度だけ実行されます。
  • set_version() は、プロキシ経由で(デプロイ時または upgrade_to_and_call 経由で)呼び出し、プロキシのストレージに VERSION_NUMBER を書き込む必要があります。
  • 追加の初期化(所有権、トークン供給量など)が必要な場合は、保護された初期化関数を公開し、その関数に set_version() を含めてください。
  • 適切に初期化できないと、所有者がいないコントラクト、未初期化の状態、または今後のアップグレードが拒否される状態になる可能性があります。
UUPS アップグレードのチェック

UUPS 実装では、次の 3 つの安全チェックが適用されます。

  1. アクセス制御 — upgrade_to_and_call を制限します(例:self.ownable.only_owner())
  2. プロキシコンテキストの強制 — 呼び出しが delegatecall 経由でない場合、only_proxy() はリバートします
  3. Proxiable UUID の検証 — proxiable_uuid() は ERC-1967 の実装スロットを返す必要があります。これにより UUPS との互換性が確認されます
再アクティベーション

Stylus の WASM コントラクトは、毎年 1 回(365 日ごと)、または Stylus プロトコルのアップグレード後に再アクティベーションする必要があります。再アクティベーションは cargo-stylus または ArbWasm プリコンパイルを使用して実行できます。コントラクトが再アクティベーションされない場合、呼び出せなくなります。これはプロキシのアップグレードとは別の事項ですが、メンテナンス計画に織り込む必要があります。

アップグレードパスのテスト

本番コントラクトをアップグレードする前に:

  • V1 実装とプロキシをデプロイし、ローカルの Arbitrum 開発ネットで動作させます
  • V1 で状態を書き込み、upgrade_to_and_call 経由で V2 にアップグレードし、既存の状態がすべて正しく読み取れることを確認します
  • アップグレード後に新機能が想定どおりに動作することを確認します
  • アクセス制御を確認します。upgrade_to_and_call を呼び出せるのは、認可された呼び出し元のみです
  • ストレージレイアウトを確認します。既存フィールドの並べ替え、削除、型変更がないことを確認します
  • 新しい実装で VERSION_NUMBER がインクリメントされていることを確認します
  • 再アクティベーションをテストし、アップグレードしたコントラクトを再アクティベートできることを確認します
  • 手動レビュー — Stylus Rust コントラクトにはストレージレイアウトの自動検証がありません。構造体の比較と開発ネットでのテストに頼ってください

コメント