本書で扱わなかったことを、正直に並べておきます。何を知らないかを知っているほうが、知ったつもりでいるより安全だからです。

メインネットに出さない理由 — 技術以外の話

本書は一貫して「テストネット専用」と書いてきました。理由の半分は技術的なもの(監査を受けていない、バグがあっても直せない)ですが、残りの半分は法律の話です。ここを書かずに終わるのは不誠実なので、最後に触れておきます。

筆者は法律家ではありません。 以下は論点の整理であって、法的な助言ではありません。実際に判断が必要なら弁護士に相談してください。

賭博罪の要件

日本の刑法 185 条はこうです。

賭博をした者は、五十万円以下の罰金又は科料に処する。ただし、一時の娯楽に供する物を賭けたにとどまるときは、この限りでない。

要件は ①偶然の勝敗により ②財物の得喪を争う こと。本書の対人戦は、②を明確に満たします。両者が ETH を賭け、勝者が総取りする仕組みそのものです。

問題は①です。「まるばつは完全情報の確定ゲームだから偶然性がない」と言いたくなります。しかしこの抗弁は、日本の判例では通りにくい。囲碁・将棋・麻雀・ゴルフのように技量が支配的な競技でも、賭博罪の成立は認められてきました。「勝敗が多少なりとも偶然に左右されれば足りる」という解釈が定着しています。

そして本書の対人戦には、偶然と評価されうる要素があります。先手後手の割り当て(作成者が必ず X)、相手の技量が事前に分からないこと。第 15 章で「対人戦は公平ではない」と書いた非対称が、ここでは別の意味を持ちます。

なお但書の「一時の娯楽に供する物」は、判例上その場で消費される軽微なもの(飲食物程度)を指します。金銭は原則として含まれません。

より重いのは 186 条 2 項

賭博場を開張し、又は博徒を結合して利益を図った者は、
三月以上五年以下の拘禁刑に処する。

本書の feeBps は、まさにこれに該当しうる設計です。 場を用意し、そこから手数料を取る。単に賭けに参加するより格段に重い罪です。

しかも手数料を 0 にしても、「賭博場を開張した」という評価自体は残ります。コントラクトをデプロイする行為が、場を開くことに相当しうるからです。

スマートコントラクト固有の問題

ここからが本書の主題に直結します。やり直しが効きません。

通常のサービススマートコントラクト
問題に気づいたら止める止められない
消したいと思ったら消せる消せない
誰がやったかサーバのログ次第チェーンに永久記録

具体的には、

  • デプロイしたら削除も停止もできません。 「やっぱりやめます」ができない
  • owner にあなたのアドレスが記録されます。 デプロイした取引も、その署名も永久に残る
  • 資金の流れを遡れます。 faucet や取引所を経由していれば、現実の身元に到達しうる
  • 「匿名だから大丈夫」は成り立ちません。 むしろ証拠が永久保存され、誰でも検証できる状態になります

第 5 章で「署名は本人であることの数学的な証明だ」と書きました。それは不利な場面でも同じように働くということです。

「海外にデプロイしたから」という発想も通りません。そもそもコントラクトに所在地はありませんし、日本から参加すれば日本法が及びます(オンラインカジノで実際に摘発例があります)。

だからどうするか

本書のコードで価値のやり取りを本番で試したいなら、選択肢はこうです。

  • テストネットに留める。 本書の立場です。学ぶ目的なら、これで何も失われません
  • 賭け金をゼロにする。 SoloTicTacToe と同じく、財物の得喪が無ければ賭博の要件を満たしません。ゲームとしては成立します
  • 勝敗と報酬を切り離す。 参加費なし・報酬なしなら、単なるオンチェーンのゲームです
  • どうしても価値を扱いたいなら、事前に弁護士へ相談する。 デプロイしてからでは遅い

第 15 章で「実装できることと、成立する仕組みであることは別」と書きました。あれは経済的な意味でしたが、同じ言葉が法的な意味でも成り立ちます。そしてスマートコントラクトでは、間違えたときに取り消せないという一点が、その判断の重さを何倍にもします。

デプロイは、技術的な操作であると同時に、取り消せない意思決定です。

監査を受けていない

本書のコードはテストネット専用です。監査を受けていませんし、実資金を扱う想定で書かれていません。

とはいえ、書いている最中に踏んだ・避けた落とし穴は本物です。

  • 勝者への自動送金 → 受け取り拒否で全体が凍る → pull payment に変更
  • 相手が消えると資金が凍る → 持ち時間 + 不戦勝
  • block.timestamp の操作可能性 → 仕様の粒度を分単位以上にする
  • オンチェーン乱数 → 賭けが絡む場所では使わない

これらは実際の監査でも指摘される類のものです。ただし「よくある落とし穴を避けた」ことと「安全である」ことは別です。

アップグレード可能性

本書のコントラクトは不変です。バグがあっても直せません。

実務ではプロキシパターン(UUPS、Transparent Proxy)でロジックを差し替え可能にすることがあります。しかしこれは「管理者がいつでもロジックを差し替えられる」ということでもあります。

つまり、不変性という最大の性質を手放して、修正可能性を買う取引です。どちらが正しいかは用途によります。「アップグレード可能」が無条件に良いことではない、という点だけ押さえておいてください。

アクセス制御の作り込み

onlyOwner を自前で 5 行書いただけです。実務では OpenZeppelin の Ownable2Step(誤ったアドレスへの移譲を防ぐ 2 段階の受諾)や AccessControl(ロールベース)を使います。

所有者アドレスの管理も本書では扱っていません。実際には EOA(人が秘密鍵で直接操作する普通のアドレス)ではなく、マルチシグ(Safe)にするのが定石です。単一の鍵が失われたり漏れたりすると終わりだからです。

オラクル

外部の情報(価格、天気、試合結果)をチェーンに持ち込む仕組みです。まるばつには不要でした。

しかし多くの実用的なコントラクトは、外部の事実を必要とします。そしてオラクルは攻撃対象の筆頭です。「価格オラクルを操作して不当に借り入れる」類の攻撃で、実際に数億ドル規模の被害が出ています。

MEV とフロントランニング

トランザクションは、ブロックに取り込まれる前にメンプールで公開されます。誰でも読めるので、有利なトランザクションを見つけて先回りできます。これが MEV(Maximal Extractable Value)です。

本書のゲームでフロントランニングが問題になる場面は、ほぼありません。着手は手番の人しかできないからです。ただし claimTimeout は誰でも呼べるので、理屈のうえでは「先に呼んでガス代を稼ぐ」ボットが現れ得ます。実害はありませんが、誰でも呼べる関数には常にこの視点が要ります

賭けの結果が読めるゲームなら、話は変わります。「勝ち筋が確定した瞬間に相手が撤退トランザクションを先回りさせる」といった設計は成立しうるので、そこは設計時に潰す必要があります。

ガスの本気の最適化

via_ir、アセンブリ、unchecked ブロック、SSTORE の回数削減。本書ではやっていません。

理由は、削れる額と、読みにくくなる量が釣り合わないからです。1 手 39,408 ガスから 300 ガス削るために勝敗判定を目視で追えなくするのは、学習用のコードとしても実務のコードとしても筋が悪い。

ただし、扱う金額が大きくなるほどこの計算は変わります。「読みやすさ優先」は永遠の正解ではないことも書いておきます。

形式検証

テストは「試した入力では正しい」ことしか言いません。形式検証は「すべての入力について正しい」ことを証明します。

  • Halmos — Foundry のテストをそのまま記号実行できる
  • Certora — 仕様記述言語で不変条件を書く
  • Echidna — property-based fuzzing

本書の test_HardNeverLoses は全探索でしたが、これは状態空間が 1,300 通りしかないから成立した特殊例です。一般には形式検証の道具が要ります。

静的解析

SlitherAderyn は、既知の脆弱性パターンを機械的に検出します。導入は数分です。

pip install slither-analyzer
slither .
slither . --print human-summary
slither . --print call-graph      # 呼び出し関係の図

他人のコントラクトを読むときにも有用です。call-graph を出すと構造が一目で分かります。

サブグラフ

第 17 章で触れたとおり、eth_getLogs の indexed トピックで足りる範囲に留めました。集計・join・任意フィールドでのソートが要るなら、The Graph / Ponder / Envio を検討します。

アカウント抽象化(ERC-4337)

「ガス代を誰かが肩代わりする」「ソーシャルリカバリで鍵を復旧する」「複数の操作を 1 回の承認でまとめる」といったことを可能にする仕組みです。

本書の UX の弱点はほぼここに集約されます。 1 手ごとに MetaMask の承認が要るのは、ゲームとして快適とは言えません。ガススポンサーとセッションキーを組み合わせれば、「最初に 1 回承認したら、あとは普通のゲームのように打てる」形にできます。

次に読むもの

最後の 3 つは特におすすめです。攻撃側の視点を一度通すと、なぜ pull payment なのか、なぜ checks-effects-interactions なのかが腑に落ちます。

おわりに

本書は「まるばつゲームを作る」という小さな題材で、次のことを扱いました。

  • コントラクトはクラスではなく、永久常駐するサービス + 専用 DB
  • Foundry の道具立てと、テストを Solidity で書く理由
  • L1 / L2、chain ID と RPC の違い
  • ローカル環境の落とし穴(実際に全部踏みました
  • 1 コントラクトの単位をどう決めるか
  • お金を扱うときの pull payment と checks-effects-interactions
  • 資金を凍結させないための持ち時間と不戦勝
  • ガスの料金表と、それに従った表現の選択
  • fuzz と全探索による検証
  • Solidity で書くゲーム AI と、オンチェーン乱数の危うさ
  • wagmi / viem による結線と、ABI 生成の重要性
  • サブグラフなしでどこまで読めるか、そしてハッシュの連鎖を可視化すること

どれも、まるばつという最小の題材から自然に出てきた問題です。題材が小さいほど、本質的な制約が見えやすいというのが、書き終えての実感です。

コードは動きます。ぜひ手元で壊してみてください。壊れ方から学ぶのが一番速い、というのは、anvil を落とせばチェーンごと消えるこの環境の、最大の利点です。