スマートコントラクト:「コードが契約を執行する」という発想
合意した内容を人や仲介者ではなくプログラムが自動的に実行する。スマートコントラクトは、ブロックチェーンを「価値の記録」から「価値を動かすアプリの基盤」へと押し上げた技術です。
スマートコントラクトとは
スマートコントラクトとは、ブロックチェーン上にデプロイされ、あらかじめ定めた条件が満たされると自動的に実行されるプログラムのことです。「AがBに送金したら、CがAにトークンを発行する」といったルールをコードとして書いておくと、誰の裁量も挟まず、そのとおりに執行されます。紙の契約書が「守られることを期待する」ものだとすれば、スマートコントラクトは「破りようがない」形で合意を強制する仕組みです。
概念自体は1990年代に法学者ニック・サボが提唱していましたが、それを現実にしたのが2015年登場のEthereumです。単なる送金記録ではなく任意のプログラムを実行できる「世界規模の分散コンピュータ」として設計されました。
動作の仕組み:EVM・ガス・状態遷移
スマートコントラクトは、各ノードが備える仮想マシン(Ethereumの場合はEVM: Ethereum Virtual Machine)の上で実行されます。トランザクションが関数を呼び出すと、全ノードが同じコードを実行し同じ結果に到達しなければなりません。この決定論的実行は必須です。乱数や現在時刻、外部通信のような「答えが変わる操作」を直接使えないのはこのためです。
実行にはコストが伴います。処理のステップごとにガスという単位で手数料が課され、利用者は暗号資産で支払います。資源消費の対価であると同時に、無限ループなどでネットワークを止める攻撃を防ぐ役割もあります。ガスが尽きれば処理は打ち切られ状態変更は巻き戻されますが、消費済みのガス代は戻りません。目安の消費量は次のとおりです(概算値)。
| 処理 | ガス消費量の目安 | 備考 |
|---|---|---|
| 単純なETH送金 | 21,000 | 基本コスト |
| 新規ストレージ書き込み(SSTORE、0→非0) | 20,000 | 状態を新設する操作は特に高コスト |
| 既存ストレージ更新(SSTORE、非0→非0) | 2,900 | ウォームアクセス時 |
| ストレージ読み取り(SLOAD、コールド) | 2,100 | 初回アクセスのみ。以降は安価 |
「状態を書き換える」操作ほど高コストなのがポイントです。全ノードが保持し続けるデータ量を増やす行為だからです。逆に状態を変更しない読み取り専用のview関数は、ノードへローカルに問い合わせるだけで済むためガスを消費しません。
コントラクトの実行は、ブロックチェーンの状態遷移そのものです。「誰がどのトークンをいくつ持つか」といった状態が、トランザクションによって決定論的に次の状態へ移り、その正しさを全ノードが合意(分散合意参照)することで確定します。
デプロイから実行までの流れ
スマートコントラクトが動き出すまでには、いくつかの決まったステップを踏みます。
- 記述: Solidity(最も普及)やVyperなどでロジックを書く。
- コンパイル: ソースコードを、EVMが解釈できるバイトコードと、外部から関数を呼ぶためのインターフェース定義ABIに変換する。
- デプロイトランザクション送信: 宛先を指定せず、バイトコードをdataに載せて送信する。
- アドレス決定: 新アドレスは送信者アドレスと当時のnonceから
keccak256で決定論的に計算される(CREATE2ならsaltで事前確定も可能)。 - コンストラクタ実行: デプロイ時に一度だけ初期化コードが走り、初期状態が書き込まれる。
- 呼び出しの受付: 以降、ABIエンコードされたcalldataを含むトランザクションが届くたびに対応関数が実行される。
Solidityコード例:最小限の入出金コントラクト
誰でもETHを預け入れ、自分の残高の範囲内でのみ引き出せる、最小限のコントラクト例です。
contract Vault { mapping(address => uint256) public balances; function deposit() external payable { balances[msg.sender] += msg.value; } function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount, "insufficient balance"); balances[msg.sender] -= amount; (bool ok, ) = msg.sender.call{value: amount}(""); require(ok, "transfer failed"); } }
mapping(address => uint256) balances: アドレスごとの残高を保持する状態変数。全ノードが共有するブロックチェーンの状態そのもの。external payable: 外部から呼び出し可能で、ETHを同時に受け取れることを示す修飾子。require(条件, "メッセージ"): 条件を満たさなければ処理全体を即座に巻き戻す。入力検証とアクセス制御の基本手段。balances[...] -= amountを外部呼び出し(call)より先に実行する順序が重要です。逆にすると次章で説明する再入攻撃の隙になります(Checks-Effects-Interactionsパターン)。
代表的なユースケース
- トークン(ERC-20 / NFT): 「残高の表」と「送金関数」だけの単純なコントラクトがERC-20規格の代替通貨になります。一点物を表すNFT(ERC-721)も所有者を記録するコントラクトにすぎません。共通規格のおかげで、あらゆるウォレットや取引所が同じように扱えます。
- DeFi(分散型金融): 中央機関なしにコントラクトが自動で取引を仲介します。DEXは流動性プールで誰とでもトークンを交換でき、レンディングでは担保を預けて借りられます。ルールがコードで公開・自動執行される点が特徴です。
- エスクロー・自動決済: マルチシグや条件付き送金を組み合わせ、仲介者なしのエスクローを実装できます。
脆弱性・実際の事件と防御の教訓
「コードが確実に実行される」とは、裏を返せばバグも確実に実行されるということです。公開・不可逆であるがゆえに、脆弱性が致命的な被害に直結します。
- 再入攻撃(リエントランシー): 外部呼び出しの隙を突き、状態更新前に同じ関数を再帰的に呼び資金を抜き取る手口。2016年のThe DAO事件では約360万ETHが流出し、Ethereumのハードフォークで Ethereum Classic に分岐しました。防御はCheck-Effects-Interactionsパターンと
ReentrancyGuardが定石です。 - 整数オーバーフロー: 数値が上限を超えて桁あふれし残高計算が破綻する古典的バグ。Solidity 0.8以降は算術演算を自動チェックしますが、それ以前は
SafeMathライブラリの導入漏れが実被害を生みました。 - アクセス制御の不備: 「誰がこの関数を呼べるか」のチェック漏れです。2021年のPoly Networkハッキングでは権限検証の不備を突かれ、複数チェーンから6億ドル超が流出しました(大部分は後日返還)。
onlyOwnerの網羅的な付与が対策です。 - アップグレード不能性: デプロイ済みコードは原則書き換え不可。修正にプロキシパターンが使われますが、新たな複雑さと権限集中を生みます。
- オラクル問題: 決定論的実行のため、コントラクトは外部の現実世界の情報を自力取得できません。橋渡しするオラクルが誤ったデータを流せば、正しいコードも誤動作します。
対策の要は、監査済みで実績あるコードを使い、権限管理をマルチシグなどで分散させることです。コントラクトウォレット(Safeなど)はその代表例です。
監査とテストの実務
価値を扱うコントラクトほど、デプロイ前の検証を何段階も重ねるのが実務の標準です。
- 静的解析: SlitherやMythrilで、再入やオーバーフローなど既知の脆弱性パターンを自動検出する一次スクリーニング。
- ユニットテスト・ファジング: FoundryやHardhatでテストスイートを整備し境界値を検証。ランダム入力で不変条件を壊すinvariant testingも使われる。
- 第三者監査: OpenZeppelin、Trail of Bits等の専門会社によるコードレビュー。監査済みでも脆弱性ゼロの証明にはならず、監査後の変更で保証は失われます。
- フォーマル検証: 数学的にコードが仕様どおり動くことを証明する手法。Certoraなどが大規模DeFiプロトコルで採用。
- バグバウンティ: Immunefi等で脆弱性発見者に報奨金を支払い、攻撃者より先にホワイトハットが見つける経済的インセンティブを作る。
- 段階的デプロイ: テストネットで動作確認したのちメインネットへ展開する。
L2・ロールアップでの実行
Ethereumメインネット(L1)はスループットに限りがあり、需要が集中するとガス代が高騰します。実行の大部分をL1の外側(L2)に移すロールアップが主流の解決策です。EVM互換のロールアップでは、バイトコードはL1とほぼ同一のまま動作します。
- Optimistic Rollup: 提出された取引をひとまず正しいものとして即座に反映し、疑わしければチャレンジ期間(多くは1週間前後)内に不正証明(fraud proof)で覆せる仕組み。ArbitrumやOptimismが採用。
- ZK Rollup: バッチの状態遷移が正しいことを検証可能証明(zk-SNARK等)として即座に生成・提出する仕組み。L1は証明を検証するだけでよく、チャレンジ期間を待つ必要がありません。zkSyncやStarknetなどが実装を競っています。
- 共通する設計: 実行はオフチェーンで行い、圧縮データや証明だけをL1に投稿してガス代を削減します。最終的なセキュリティはL1の分散合意に依存する点は変わりません。
従来型契約との比較
| 観点 | 従来型の契約 | スマートコントラクト |
|---|---|---|
| 執行主体 | 当事者の履行、または裁判所・仲裁機関 | プログラムが条件成立と同時に自動執行 |
| 改変・破棄 | 合意すれば契約書を修正・解除できる | 原則デプロイ後は不変(プロキシ等が必要) |
| 仲介者 | 弁護士・エスクロー業者などが介在しうる | 仲介者なしでコードが判定と実行を担う |
| 紛争解決 | 交渉・訴訟・仲裁で解決する | 解釈の余地は乏しいがバグが致命的 |
| 透明性 | 当事者間のみが内容を把握 | コードも履歴も原則公開・検証可能 |
| 執行コスト | 弁護士費用・手続き費用など | ガス代のみで完結 |
スマートコントラクトは、契約の「解釈」と「執行」を人間の裁量から切り離し、コードという単一の真実に委ねる試みです。その厳密さは強みであると同時に、コードの品質がそのまま信頼性の上限になる裏返しでもあります。監査・テスト・段階的デプロイという地道な工程が、この技術を実用に足るものにしています。