upgrade-stylus-contracts について
Arbitrum Stylus コントラクトをアップグレードする開発者向けガイドです。プロキシパターン、アクセス制御、ストレージ互換性、初期化、WASM コントラクトの再アクティベーションを扱います。
主な機能
- 01Stylus と Solidity のプロキシパターンの解説
- 02UUPS、Beacon、基本プロキシの紹介
- 03アップグレード関数へのアクセス制御の適用
- 04ストレージレイアウトと初期化の安全性確認
- 05アップグレードとコントラクト再アクティベーションのテスト
活用シーン
- 01Stylus コントラクトのプロキシパターン選択
- 02プロキシ経由での Solidity または Stylus 実装の更新
- 03アップグレード前後のストレージレイアウト確認
- 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 の実装へ、またその逆へ委譲できます。
| Stylus | Solidity | |
|---|---|---|
| プロキシの仕組み | 同じ — 実装コントラクトへの 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 つのプロキシパターンを提供します。
| パターン | 主な型 | 適した用途 |
|---|---|---|
| UUPS | UUPSUpgradeable、IErc1822Proxiable、Erc1967Proxy | ほとんどのプロジェクト — アップグレードロジックを実装に置き、プロキシを軽量化します |
| Beacon | BeaconProxy、UpgradeableBeacon | 複数のプロキシで 1 つの実装を共有する場合 — ビーコンを更新すると、すべてのプロキシがアトミックにアップグレードされます |
| Basic Proxy | Erc1967Proxy、Erc1967Utils | カスタムプロキシパターンを構築するための低レベルな構成要素 |
UUPS
実装コントラクトでは、アクセス制御(例:Ownable)とともに、#[storage] 構造体に UUPSUpgradeable を組み込みます。統合には次の作業が必要です。
#[storage]構造体のフィールドとしてUUPSUpgradeable(およびアクセス制御)を追加します- コンストラクターで
self.uups.constructor()を呼び出し、アクセス制御を初期化します self.uups.set_version()を呼び出すinitializeを公開します — デプロイ後、プロキシ経由で呼び出しますIUUPSUpgradeableを実装します —upgrade_to_and_callはアクセス制御で保護し、upgrade_interface_versionはself.uupsに委譲します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 つの安全チェックが適用されます。
- アクセス制御 —
upgrade_to_and_callを制限します(例:self.ownable.only_owner()) - プロキシコンテキストの強制 — 呼び出しが
delegatecall経由でない場合、only_proxy()はリバートします - 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 コントラクトにはストレージレイアウトの自動検証がありません。構造体の比較と開発ネットでのテストに頼ってください