スマートコントラクトの本番デプロイ|監査したコードとずれない手順と署名前の確認

コラム

/約14分で読めます

コラム

/約14分

スマートコントラクトの本番デプロイ|監査したコードとずれない手順と署名前の確認
目次(タップで折りたたみ)

    ある決済サービスの開発チームが、監査を終えたスマートコントラクトを本番のチェーンに出しました。ところが後から比べてみると、本番に出たコードは監査したものと一致しませんでした。ソースは同じなのに、ビルドの設定が手元とCIで少し違っていたのです。

    ずれ方は、ほかにもあります。

    • ビルドの設定の違い:同じソースでも、設定が違えば出来上がるコードが変わる
    • 承認した後の差し替え:承認の後で値を入れ直し、承認と違う中身に署名してしまう
    • 途中での停止:複数の送信に分かれた作業が途中で止まり、中途半端な状態が外から使われる

    通常のWebアプリなら、問題があれば直前の版へ戻せます。スマートコントラクトはそうはいきません。確定したトランザクションは取り消せず、コントラクトのアドレスや保存したデータ、外部のサービスとのつながりが残ります。

    だからこそ、「レビューして承認したもの」と「実際にチェーンへ送ったもの」が同じであることを、あとから確かめられるようにしておきます。どのソースを、どの設定でビルドし、どのチェーンのどこへ出すのか。それを1つの記録にまとめ、実行後はその記録と結果を突き合わせます。途中で失敗したときに止める方法と直す方法も、先に決めておきます。

    以下では、EVM系のスマートコントラクトを本番に出す流れを、上の3つのずれへの対策として追っていきます。個別のツールの操作説明ではありません。事業側の承認者は、「承認者が署名の前に確かめる5つのこと」と最後のチェックリストから読むと、判断に要るところがつかめます。

    この記事で使う言葉

    • バイトコード:ソースをビルドして出来上がる、チェーン上で動くコード
    • calldata:トランザクションで送る命令の中身
    • nonce:送信元ごとの送信の通し番号
    • マルチシグ/タイムロック:複数人の署名がないと実行できない仕組み/決めた時間が過ぎるまで実行できない仕組み
    • フォーク:本番チェーンのある時点の状態を写し取った試験環境

    リリースでは、何をひとまとめにして固定するか

    本番に出す作業は、デプロイ、初期化、設定など、複数の送信に分かれることがあります。途中で止まれば、中途半端な状態が残ります。だからリリースの単位は、1回のデプロイの送信ではなく、複数の送信とその前後の状態を含めた、確かめられる変更のまとまり(変更セット)です。このまとまりを固定する記録を、ここではリリースマニフェスト(release manifest)と呼びます。

    マニフェストに固定する値は、次の5つの領域に分かれます。それぞれ、防ぎたい食い違いが違います。

    領域固定する値防ぐ不一致
    ビルドソースのコミット、コンパイラの完全な版、オプティマイザー・EVM設定、依存関係のロックファイル、成果物のハッシュ手元とCI、監査対象と本番バイトコードの差
    対象チェーンID、RPCの照合点、既存アドレス・コードハッシュ、デプロイ用アカウント、nonceの扱い別チェーン・別コントラクトへの誤送信
    変更内容トランザクションの順序、to、value、calldata、予想アドレス、前提条件画面表示だけを信用した誤承認
    権限提案者、署名閾値、タイムロック、実行者、緊急停止者職務分離の迂回と単独実行
    受け入れ条件実行前後の不変条件、イベント、コードハッシュ、ロール、業務側の参照データトランザクション成功だけで完了とする誤判定

    承認した後で環境変数から値を入れると、何を承認したのかが決まらなくなります。だからマニフェストは、人が見る承認票であるだけでなく、CIや検証スクリプトがそのまま読める形式にします。

    秘密情報そのものは記録しません。記録するのは、公開できるアドレス、ハッシュ、チェーンID、パラメータ、想定する結果です。秘密情報は署名のときに、権限を管理した保管先から使います。

    ずれ1への対策:同じソースから同じコードが出ることを確かめる

    同じソースでも、ビルドの設定が少し違うだけで、出来上がるコードが変わることがあります。コンパイラの細かい版、最適化の設定、使うライブラリなどです(設定の一覧は後半)。

    Solidityのコントラクトのメタデータには、コンパイラの版、設定、ソースのハッシュなどが入っています。同じ入力でビルドし直せば、デプロイ済みのバイトコードと比べられます(Solidity「コントラクトのメタデータ」)。

    Gitのタグが同じでも、ビルドの設定が違えば、出来上がるコードが変わることがあります。だから、監査したソースからビルドし直して、同じ結果(ハッシュ)が出なければ先へ進まない、と決めます。

    CIでは、まっさらな状態から、ロックファイルどおりにビルドします。そして成果物のハッシュを残します。エクスプローラーでソースを検証するときにも、同じコンパイラの設定が要ります。Foundry公式のスクリプトのベストプラクティスも、本番の前にすべきことを挙げています。

    • テスト・lint(書き方の自動チェック)・フォーマットの確認
    • 正確なコンパイラの設定での検証
    • 暗号化したキーストアか、ハードウェアウォレットの利用

    (Foundry Best Practices)

    署名の前に、本番と同じ条件で試す

    テストネットで成功しても、本番では残高、ロール(権限)、nonce、オラクルの値、ほかのコントラクトの版が違います。署名の前に、本番チェーンの特定のブロックをフォークします。そして、マニフェストどおりの送信者、送信の順番、送る金額(value)で実行します。

    確かめるのは、処理が失敗しない(revertしない)ことだけではありません。次の5つを見ます。

    1. 事前の条件:対象アドレスのコードハッシュ、Implementation(Proxyが指す実装)、ロール、停止状態、残高、nonceがマニフェストと一致する。
    2. 実行の足どり:意図した呼び出し先・関数・value以外へ届かない。予期しない外部の呼び出しや、権限の変更がない。
    3. 状態の変化:残高の合計、ロール、上限、手数料、承認額(allowance)、保存データの重要な値が、期待した範囲で変わる。
    4. 周りのシステム:インデクサー、バックエンド、フロントエンド、キーパー(条件に応じてコントラクトの処理を呼び出す外部のボット)が、新しいイベント・ABI・アドレスを扱える。
    5. ガスの条件:ブロックのガス上限、マルチシグ・タイムロックを通す追加のコスト、混雑時の上限に収まる。

    試したブロック番号と、そのときの状態の照合点を残します。署名までに対象の状態が変わったら、差を評価し直します。承認待ちが長引く間に、前提が崩れることがあるからです。「今のImplementationが変わった」「nonceが使われた」といったリリースは、そのまま実行しません。シミュレーション基盤の作り方はオンチェーン取引シミュレーションの実装設計で詳しく扱っています。

    ずれ2への対策:作る人・承認する人・署名する人・実行する人を分ける

    マルチシグは、1つの鍵に頼り切る危険を減らします。それでも、署名者を何人にしても、全員が同じ間違った中身を見て承認すれば止められません。

    そこで、変更を作る人、技術的に確かめる人、業務として承認する人、署名する人、実行する人、実行後に確かめる人を分けます。少なくとも1人は、別のツールでcalldataを読み解き、マニフェストと照らし合わせます。

    それぞれの役割が兼務を避ける理由は、次のとおりです。

    役割判定・出力兼務を避ける理由
    ビルド担当成果物とハッシュ署名対象を単独で差し替えさせない
    検証担当再ビルド、シミュレーション、差分作成者の思い込みを独立して見つける
    承認者実施・延期・中止技術的な成功と事業判断を分ける
    署名者閾値署名画面上の名称でなく実データを承認する
    実行者トランザクションハッシュ・レシート未承認の変更を作れない主体にする
    実行後の確認担当受け入れ/封じ込め/前方修正送信者自身の成功宣言だけにしない

    各役割が受け取るものは、後半の「実装する人向けの詳細」にまとめています。

    ふだんの変更にはタイムロックを使います。提案のハッシュと実行する中身をチェーン上で縛っておけば、人の運用だけに頼るより抜け道が減ります。

    チェーン上に置くのは、「誰が、何を、いつから実行できるか」と、結果を確かめるのに要る情報だけです。次のような機密を含む記録は、改ざん防止とアクセス制御を備えたチェーンの外の保管先に置きます。

    • CIのログ、監査報告
    • 脆弱性の詳細、連絡網

    承認者が署名の前に確かめる5つのこと

    承認者は、次の5つが確かめられているかを見ます。

    1. 監査したものと同じか:監査したソースからビルドし直し、同じハッシュが出ている。
    2. 出す先が正しいか:対象のチェーン、アドレス、いまのコードハッシュ、ロールを、2重に確かめてある。
    3. 署名する中身が承認と同じか:calldataを別のツールで読み解き、署名者がアドレスとパラメータを確かめた。
    4. 本番の状態で試したか:本番のフォーク上で、実際の送信者・順番・valueで状態の変化を確かめた。
    5. 止まったときの手が決まっているか:影響、停止時間、採用しなかった案。有効化の上限と、止める条件と、判断する人。

    承認者が出す結論は、実施・延期・中止のどれかです。

    ずれ3への対策:途中で止まったときの状態を先に決めておく

    デプロイ、初期化、ロールの付与、依存先の設定、資金の移動、旧版の停止。これらが複数の送信に分かれると、送信ごとの途中の状態は、チェーン上で外から使えてしまいます。

    最後まで成功する前提のスクリプトでは危険です。区切りごとに、次の操作を安全に再開できるか、第三者に先回りされないかを確かめます。失敗した地点ごとの、安全な途中の状態と避ける対応は次のとおりです。

    失敗地点安全な途中状態避ける対応
    デプロイ後・初期化前第三者が初期化できず、利用経路も未接続公開メンプールで無防備に初期化を後から送る
    設定の途中機能が無効、上限0、または停止中スクリプトを先頭から確認なしに再送する
    権限移管の途中旧管理者が残るか、受領確認後に失効先に唯一の管理者権限を放棄する
    有効化後の検証失敗停止・上限・経路の切り替えで影響を限定旧バイトコードへ戻せば状態も戻ると仮定する

    メンプールは、確定前のトランザクションが並ぶ待合所です。ここに初期化の送信が見えると、第三者に先に初期化されるおそれがあります。

    やり直してよい条件も、地点ごとに決めておきます。

    • デプロイ後・初期化前:アドレスとコードハッシュが一致している
    • 設定の途中:終わった手順を、チェーン上の状態で照らし合わせた
    • 権限移管の途中:新しい管理者が操作できる
    • 有効化後の検証失敗:原因と状態の変化をつかんだ

    できるなら、デプロイと初期化を1つのトランザクションにまとめます。設定が終わるまでは、使えない状態にしておきます。やり直しの具体的な作り方は後半にまとめました。

    いっせいに切り替えず、影響を絞って使い始める

    デプロイと使い始めを分けられる作りなら、最初は障害時の影響の範囲(blast radius)を絞ります。資金の上限、許可した利用者、対象のマーケット、流量の制限などです。

    リリース計画には、チェーン上のコードだけでなく、周りの切り替えの順番も入れます。フロントエンドのアドレス、バックエンドの署名鍵、インデクサー、キーパー、オラクル、承認額です。

    1. 新しいコントラクトをデプロイし、ソース・バイトコードを検証する。
    2. 初期化の値、Owner、Role、外部の参照、不変条件を独立して確かめる。
    3. 限った経路・上限で、試験的なトランザクション(カナリア)を実行する。
    4. レシートだけでなく、イベント、残高、インデクサー、業務画面を照らし合わせる。
    5. 観測時間と停止条件を満たしたときだけ、上限か経路を広げる。

    誰でも直接呼べるコントラクトでは、Webのように利用者を割合で振り分けるカナリアリリースができないことがあります。その場合は、コントラクトが強制できる範囲で絞ります。機能フラグ、上限、別のアドレスへのはっきりした移行、フロントエンドの段階的な切り替えなどです。

    「監視しながら少しずつ」という曖昧な計画では足りません。上限値、観測時間、停止条件、承認者をマニフェストに書きます。

    失敗したら、戻すのではなく、何を選ぶか

    確定したトランザクションと、すでに変わった保存データは消せません。ProxyのImplementationを旧版へ戻せても、新版が書いたデータを旧コードが安全に読める保証はありません。

    そこで、リリースの前に次の4つを比べます。変更ごとに使えるものだけを、運用の手順書に残します。

    • 封じ込め:停止、上限の引き下げ、経路の停止で、被害の広がりを止める。
    • 前方修正(forward-fix):新しい修正版へ進み、すでに変わった状態をはっきり扱う。
    • 実装の差し戻し:保存データの互換性と外部の依存を確かめられた場合だけ、旧実装へ戻す。
    • 移行:新しいアドレスへ状態・資産・連携先を移し、旧経路を段階的に閉じる。

    失敗した後、同じスクリプトをやり直す前に、どこまでチェーンに反映されたかを確かめます。トランザクション、イベント、コードハッシュ、状態で見ます。

    本番の監視、緊急停止、証拠の保全、復旧の体制はスマートコントラクト本番運用で扱っています。Proxyの方式と保存データの互換性・権限の統治はスマートコントラクトのアップグレード設計、既存の保存データを移す手順はアップグレード可能なコントラクトのデータ移行を参照してください。

    リリースの記録を、第三者があとから再現できる形で残す

    トランザクションが成功しても、承認と違う中身だったかもしれません。だから完了の判定は、「トランザクションが成功した」ではありません。第三者があとから同じ結果を確かめられるだけの記録を、1つにまとめて残します。残す項目の一覧は後半にあります。

    OpenZeppelin Upgrades Coreは、ビルド情報からアップグレードの安全性と、保存データの並び(ストレージレイアウト)の互換性を検査できます。CIにも組み込めます(OpenZeppelin Upgrades Core & CLI)。

    監査済みでも、監査の後でビルドの設定やcalldataが変われば、別のリリースです。監査の範囲、解決していない指摘、例外の承認を記録に結びつけます。監査の依頼範囲と成果物はスマートコントラクト監査の工程と費用目安で整理しています。

    本番承認の前の最小チェックリスト

    • ソースのコミットから、まっさらな環境で同じ成果物のハッシュを作り直せる
    • 対象のチェーンID、アドレス、いまのコードハッシュ、ロール、nonceを2重に確かめた
    • マニフェストからトランザクションを作り、作った後の手修正を禁じている
    • 本番のフォーク上で、実際の送信者・順番・valueによる状態の変化を確かめた
    • calldataを別のツールで読み解き、署名者がアドレスとパラメータを確かめた
    • 複数トランザクションの途中の状態と、安全に再開できる地点を決めた
    • 有効化の上限、観測時間、停止条件、判断する人を決めた
    • 「元に戻せる」という前提を置かず、実行できる封じ込め・前方修正・移行を選んだ
    • 実行後のコードハッシュ、ロール、残高、不変条件、周りのシステムを確かめる担当がいる
    • 秘密情報や未公開の脆弱性を、チェーン上や公開する成果物に入れていない

    実装する人向けの詳細

    ここからは、ビルドやデプロイのスクリプトを実際に組む担当者向けの補足です。

    バイトコードを変えうるビルドの設定

    同じSolidityファイルでも、次のものが違えばバイトコードは変わり得ます。

    • コンパイラのパッチ版
    • オプティマイザーの実行回数、viaIR
    • ライブラリのアドレス、メタデータの設定、依存パッケージ

    マニフェストの例

    release_id: payments-v1.4.0
    source_commit: 4c1f...9a2d
    chain_id: 1
    compiler: solc-0.8.30+commit.73712a01
    settings_sha256: 8bb3...d210
    artifact_sha256: 61aa...ad90
    transactions_sha256: 77e2...a410
    current_state_block: 23123456

    CIで残すハッシュ

    まっさらなチェックアウトからビルドし、次のもののハッシュを残します。

    • 成果物、ABI(呼び出しの形式の定義)
    • ストレージレイアウト
    • デプロイするトランザクションの一覧

    各役割が受け取るもの

    • ビルド担当:承認済みのソース・ロックファイル
    • 検証担当:成果物・マニフェスト・フォーク環境
    • 承認者:影響、停止時間、採用しなかった案
    • 署名者:デコード済みのトランザクション・ハッシュ
    • 実行者:署名済みの操作
    • 実行後の確認担当:レシート・オンチェーンの状態

    シミュレーションで照らし合わせる細目

    実行の足どりでは、関数セレクター(呼び出す関数の識別子)の単位で、意図した呼び出しだけかを見ます。試したブロックについては、状態ルート相当の照合点を残します。

    途中から安全に再開するスクリプト

    スクリプトは、トランザクションハッシュだけを見て再開しません。各手順の後に期待される状態を照らし合わせ、何度実行しても同じ結果になる(冪等な)形で再開できるようにします。チェーン別のパラメータとデプロイ先のアドレスを設定ファイルへ書き戻す場合は、その設定ファイルもレビューと保管の対象です。

    リリース記録に結びつける項目

    • リリースID、マニフェストのハッシュ、ソースのコミット、成果物のハッシュ
    • 署名・タイムロックの操作、トランザクションハッシュ、ブロック
    • デプロイ先アドレス、コードハッシュ、ソース検証
    • 実行後の検査結果、例外と承認者

    XTELAができること

    私たちは、リリースマニフェスト、デプロイスクリプト、フォーク環境でのシミュレーション、権限表、段階的な有効化、実行後の検証を、貴社のコントラクトと業務要件に合わせて設計・実装します。既存のデプロイ手順を「レビューしたもの=実行したもの」と確かめられる形へ組み直す作業も、一つのリリースを題材にしたPoCから始められます。リリース工程を検証できる形へ整えたい場合はお問い合わせフォームからご連絡ください。

    主要参考資料

    資料の確認日と注意

    仕様・公式文書は2026年8月12日に確認し、FoundryとOpenZeppelin Upgrades Coreの記述は2026年9月24日に再確認しました。EVM系の技術情報にもとづく解説です。利用するチェーン、コンパイラ、ライブラリ、署名基盤、エクスプローラーの公式文書と監査範囲は、リリースごとに確認してください。

    お問い合わせ

    どんなフェーズからでも、お持ちのアイデアや企画をもとにご提案可能です。
    まずはお気軽にご相談下さい!