【入門編】 コントラクトのアップグレードに伴うストレージ破壊 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

スマートコントラクトの「模様替え」で家が崩壊?プロキシパターンの落とし穴を徹底解説

こんにちは!セキュリティリサーチャーの視点から、今日はWeb3開発の現場で意外と見落とされがちな「スマートコントラクトのアップグレード」に潜む罠についてお話しします。

「一度デプロイしたら修正できない」と言われるブロックチェーンの世界ですが、実はプロキシ(代理人)パターンという手法を使えば、中身を入れ替える「模様替え」が可能です。しかし、この模様替え、やり方を間違えると大変な惨事を招きます。

今回は、初心者の方向けに、この「ストレージ破壊」という事故を、身近な例えを交えて紐解いていきましょう。

—

1. なぜ「模様替え」で家が崩壊するのか?

まず、プロキシパターンを「持ち家」に例えてみましょう。

  • プロキシ(窓口): あなたの家の玄関ドア。誰もがこのドアを叩いて用件を伝えます。
  • ロジックコントラクト(中身): 実際に作業をする人(職人)。玄関ドアの向こう側にいます。

あなたが「もっと良い家にしたい!」と思って職人を入れ替えるとき、「タンスやベッドの配置」を全く同じにしておかないとどうなるでしょう?

新しい職人が「ベッドはここ!」と思って古い書類を探すと、そこには金庫の暗証番号が書かれていた……なんてことになります。これがスマートコントラクトにおける「ストレージレイアウトの不一致」です。

データの保存場所は「住所」で決まっている

スマートコントラクトのデータ保存は、変数名ではなく「スロット番号(0番、1番、2番…)」という住所で管理されています。コードを修正した際に、変数の順番を入れ替えてしまうと、プログラムは「以前のデータが入っているはずの住所」から、全く別の新しいデータを取り出そうとしてしまうのです。

—

2. 実際にやってはいけない「NG例」を見てみよう

コードで具体的に見てみましょう。下の例は、アップグレードで順番を変えてしまった最悪のケースです。

// 【修正前】
contract MyBank {
    uint256 public balance;    // スロット0番
    address public owner;     // スロット1番
}

// 【修正後:ここが危ない!】
contract MyBankV2 {
    uint256 public totalUsers; // スロット0番 ← おっと!balanceだった場所がこれに!
    uint256 public balance;    // スロット1番 ← ownerだった場所がこれに!
    address public owner;      // スロット2番
}

この修正をしてしまうと、balance(残高)を見ようとしたとき、プログラムは「スロット0番」を見に行きます。そこには本来の残高ではなく、新しく追加したtotalUsersが入っています。これでは、あなたの銀行口座が他人の数字と混ざり合い、大混乱ですよね。

—

3. 安全に模様替えするための「鉄則」

一歩ずつ対策を学んでいきましょう。ストレージを壊さないための、現場で必須となるルールは以下の通りです。

ルール1:変数の順番を絶対に変えない

既存の変数は、そのままの位置に残してください。もし新しい変数を追加したい場合は、必ず「末尾」に追加しましょう。

ルール2:変数の型を勝手に変えない

uint256をuint8に変えるようなことも厳禁です。データのサイズが変わると、後の変数の住所(スロット)が全てズレてしまいます。

ルール3:OpenZeppelinの「Initializable」を使う

コンストラクタはアップグレード後には再実行されません。そのため、初期化用の関数を別途用意する必要があります。

安全な実装例:

// アップグレード可能なコントラクトの雛形
contract MyBankV2 is Initializable {
    uint256 public balance;    // 位置は動かさない!
    address public owner;      // 位置は動かさない!
    uint256 public newFeature; // 新しい変数は必ず末尾に追加する

    function initialize() public initializer {
        owner = msg.sender;
    }
}

—

4. 現場のプロが使っている「守りの盾」

最近では、人間が手作業で「住所」を計算するのはリスクが高いと考えられています。そこで、以下のようなツールを必ず使いましょう。

  • OpenZeppelin Upgrades Plugins:

HardhatやFoundryといった開発ツールと連携し、デプロイ前に「ストレージの配置が変わっていないか」を自動チェックしてくれます。

  • Storage Layoutの確認:

npx hardhat check のようなコマンドで、現在のストレージ構成を可視化し、破壊的な変更がないか事前に確認する癖をつけましょう。

—

まとめ:セキュリティは「思い込み」を捨てることから

「ちょっとコードを書き換えるだけだし、動けば大丈夫だろう」という油断が、多くのハッキング事件を引き起こしてきました。

スマートコントラクトのアップグレードは、「住んでいる家を、家具を一切動かさずに建て替える」ような精密な作業です。怖がる必要はありませんが、ルールを守れば非常に強力な武器になります。

まずは、自分の書いたコントラクトの変数の順番を再確認することから始めてみませんか?一歩ずつ着実に歩むことが、あなたのプロダクトを守る最強の鍵になりますよ!

それでは、また次のセキュリティ・ナレッジでお会いしましょう。ハッピー・コーディング!

コメント

タイトルとURLをコピーしました