[EIP7907] コントラクトサイズ上限256KBの仕組みを理解しよう！

# はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、Ethereumのコントラクトコードサイズ上限を24KBから256KBに拡張する仕組みを提案している**EIP7907**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-7907

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

**EIP7907**では、これまでEthereumで導入されていた「コントラクトコードサイズの上限（24KB）」を256KBまで引き上げる提案がされています。
同時に、これに伴って初期化コード（`initcode`）のサイズ上限も、**EIP3860**で定められていた**48KBから512KBまで拡張**されます。

:::note info
**EIP3860**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/df8d2a331482e6b7c66b

ただし、サイズ制限を単純に緩和するだけではネットワークがDoS（サービス妨害攻撃）に晒されるリスクがあるため、**24KBを超える部分に対しては、32バイトごとに2ガスの追加コスト**が課される仕組みも導入されます。これにより、リソース消費に応じたガス支払いを義務付け、Ethereumの「使った分だけガスを払う」という設計思想を維持しています。

# 動機

この提案の主な動機は、**開発者の利便性を大きく向上させること**です。

現在の24KBというコードサイズ上限は、元々はネットワーク保護のためにEIP-170で導入されたものでした。大きなコントラクトはディスクアクセスやMerkle証明生成などにおいてリソースを大量に消費し、その分のガスが十分に徴収されない設計だったため、悪用のリスクがあったのです。

しかしこの制限のせいで、本来単一のコントラクトで完結できるロジックを複数のコントラクトに分割しなければならないという課題が生じています。
そのため開発者は、プロキシパターンや`delegatecall`ベースの間接処理、あるいは「Diamond Standard」などの高度な設計手法を取る必要があります。
これにより以下の問題が発生しています。

* コード構造が複雑になる
* 複数コントラクト呼び出しが増えガスコストが増加する
* セキュリティ監査の範囲が広がりコストが増える
* 初心者が参入しにくくなる

**EIP7907**は、開発者がより直感的かつシンプルかつ安全にスマートコントラクトを構築できる環境を提供することを目的としています。
コードを1つの大きなコントラクトに集約できれば、可読性も高まりテストや保守もしやすくなります。
特に初心者にとっては、複雑な設計パターンを学ぶ前に「動くスマートコントラクト」を書けるようになるという大きな利点があります。

# 仕様

## コントラクトコードサイズ上限の拡張

**EIP170**では、1つのスマートコントラクトのバイトコードを24 KB（0x6000 バイト）に制限していました。
**EIP7907**ではこの上限を256 KB（0x40000 バイト）へ大幅に引き上げます。
これにより、大規模なロジックを1つのコントラクトにまとめたままデプロイできるようになります。

:::note info
**EIP170**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/eef7d90d07aae21dfe6f

## コード読み込みオペコードのガス調整

`CALL` 系（`CALL`, `STATICCALL`, `DELEGATECALL`, `CALLCODE`）および `EXTCODECOPY` の5つのオペコードは、実行時に対象コントラクトのバイトコードを読み込みます。
サイズが24KBを超えるコードを読み込む場合、追加リソース分を補うために追加でガスコストが課されます。

```js
excess_contract_size = max(0, contract_size − 0x6000)       // 余分なバイト数
largeContractCost    = ceil32(excess_contract_size) / 32 * 2 // 32 バイト語あたり 2 ガス
```

`ceil32` は32バイト単位に切り上げる関数です。
結果として「余分な32Bごとに2ガス」が上乗せされます。

## コールドコードとウォームコード

**EIP2929**で導入されたアクセスリストの概念に従い、コントラクトのコードには「ウォーム（既に読み込まれた）」と「コールド（まだ読み込まれていない）」の2種類の状態があります。
**EIP7907**ではコードがウォームであってもアカウントがコールドという中間パターンも定義し、以下の表のようにガスコストを変化させます。

:::note info
**EIP2929**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/ca94d8fd0159bb3cc9fb

| 状態                | 追加ガス                       | 主な条件                  |
| ----------------- | -------------------------- | --------------------- |
| コールドアカウント・コールドコード | `2600 + largeContractCost` | アクセスリスト外で初アクセス        |
| ウォームアカウント・コールドコード | `100 + largeContractCost`  | 残高やストレージは既に触ったがコードは未読 |
| ウォームアカウント・ウォームコード | `100`（変更なし）                | 既にコードも読み込み済み          |

* 基本値2600ガスは `COLD_ACCOUNT_ACCESS_COST`、100ガスは `WARM_STORAGE_READ_COST`（ともに**EIP2929**定義）です。
* ウォームコードは同一トランザクション内で一度読み込めば、その後は追加のガスコストを払う必要がありません。

## `initcode` サイズ上限の拡張

コントラクト作成時に実行される `initcode` についても、**EIP3860**の48 KB（`0xc000` バイト）制限を512 KB（0x80000 バイト）に引き上げます。
`initcode` はデプロイ後にチェーン上へ保持されませんが、デプロイ時にEVMが実行するため、そのサイズにも上限が必要です。

# 補足

## ガスコストの根拠

**EIP7907**では、24KBを超えるコントラクトに対して32バイトあたり2ガスの追加コストが課されます。
この「2ガス」という値は、**EIP3860**と整合性を持たせたものです。

* コードサイズが大きくなるほどディスクI/Oが増加し、読み込みにかかる負荷が上がる
* 実行前にEVMが行う `JUMPDEST` 解析（ジャンプ先の命令位置を特定する処理）の計算量が増える
* ブロック内に大規模コントラクトを含むことで、Merkle証明のサイズが増加してブロック証明（witness）コストが上昇する

## なぜ24KBを閾値にしたか

本来、ガスコストは単純に「`ceil32(contract_size) / 32 * 2`」のような形で計算してもよいはずです。
しかし、既存の24KB未満のコントラクトに対してガスコストが下がってしまう可能性を避けるため、式に「**24KB超過分のみ追加ガス**」という下限値（フロア）を設けています。
これにより、新ルールの導入が従来のコントラクトに影響を与えないよう配慮されています。

## EXTCODECOPY も対象に含めた理由

理論的には、`EXTCODECOPY`（他コントラクトのバイトコードを読み取る命令）は必要な範囲だけ部分的に読み込むことができるため、追加コストから除外してもよいように思えます。

しかし、Ethereumのプロトコル上ではフルコードが必要です。
例えばブロックの検証には、コントラクトの完全なコードを含む証明（witness）が要求されます。
そのため現時点では、`EXTCODECOPY`も他のコード読み込み系命令と同様に追加コストの対象となります。
将来的にプロトコル側が部分読み込みに対応すれば、除外が再検討される可能性はあります。

## コントラクトサイズ上限を256KBにした理由

新しいコードサイズ上限は256KBに設定されていますが、これは現在のブロックガス上限（約35Mガス）を考慮すると実質170KB程度までしか利用できない状況です。
それでも上限を256KBにしたのは、将来的にブロックガスリミットが引き上げられた場合にも、データベースやP2Pネットワーク（devp2p）層への影響を抑えるための予防措置です。

例えば、P2P通信プロトコルであるdevp2pにおける最大パケットサイズは10MB、Snap Sync における最大パケットサイズは現時点で512KBと制限されています。
このような制約の中でも、想定外の挙動が起きないように256KBという現実的な上限が設定されています。

## `initcode` サイズ上限を512KBにした理由

`initcode`（コントラクト生成時に実行されるコード）はステートに保存されないため、理論的には無制限でも問題ないように見えます。
しかし、実行時にEVMが処理する負荷や、メモリ使用量、検証時の証明サイズなど、未知の影響が残る可能性があります。

そのため、**EIP3860**で採用された「`initcode` サイズはruntimeコードサイズの2倍まで」という保守的な指針に従い、**EIP7907**でも `initcode` 上限を512KBに引き上げるにとどめています。
これにより、安全性を保ちつつ、柔軟性を確保するバランスが取られています。

# 互換性

**EIP7907**は、既存のコントラクトと互換性があります。。

# セキュリティ

**EIP7907**を導入するにあたり、クライアント側（GethやNethermindなど）にはコードサイズの取得処理に関する注意点が生じます。

コードサイズが24KBを超える場合、追加のガス（`largeContractCost`）が課されるため、EVMはそのガスを事前に確保できているかどうかを確認する必要があります。
ところが、もしEVMがガスの残量チェックより先にコードを完全に読み込む処理を行ってしまうと、ガスが足りない状況でも読み込みが進行し、クライアントのリソースを過剰に消費してしまうリスクがあります。

この問題を避けるために、クライアント実装では以下のような対策が推奨されます。

* コードサイズ情報を、実際のコードとは別のテーブルに保存しておく（キーは`code hash`）
* 読み込み前にそのサイズ情報だけを軽量に取得し、ガス残量で支払えるかどうかを先に判断する

これにより、不正なガス残量で大きなコードを読み込ませるようなDoS攻撃を未然に防ぐことができます。

このセキュリティ対策は、あくまでクライアント実装の効率性と健全性を保つための推奨事項であり、EIP自体の仕様に含まれるものではありません。

# 引用

Charles Cooper (@charles-cooper), Qi Zhou (@qizhou), "EIP-7907: Meter Contract Code Size And Increase Limit [DRAFT]," Ethereum Improvement Proposals, no. 7907, March 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7907.

# 最後に

今回は「Ethereumのコントラクトコードサイズ上限を24KBから256KBに拡張する仕組みを提案している**EIP7907**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4