# [EIP4762] Verkleツリー対応に向けた新ガスモデルの仕組みを理解しよう！

> EIP4762は、Ethereumにおけるガススケジュールの変更を提案するものです。主な目的は、「witness（証明データ）」の生成にかかる計算コストをガスコストに正確に反映させることです。

- URL: https://cardene.dev/articles/e057fa94905ac9ff0a4f
- 公開日: 2025-07-09
- 更新日: 2025-07-09
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、Ethereumの次世代データ構造であるVerkleツリーに対応するため、ストレージやコードへのアクセスにかかるガスコストの仕組みを見直し、より効率的で安全なステート管理を可能にする仕組みを提案している**EIP4762**についてまとめていきます！

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

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

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

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

## 概要

**EIP4762**は、Ethereumにおけるガススケジュールの変更を提案するものです。
主な目的は、「**witness（証明データ）**」の生成にかかる計算コストをガスコストに正確に反映させることです。
この変更を適用するためには、Ethereumクライアントがデータベースの構造を更新する必要があります。
これにより、不適切な構造のままのクライアントがDoS攻撃のリスクにさらされることを防ぎます。

## 動機

EthereumにVerkleツリーを導入するためには、ネットワーク全体で大きな基盤的変更が必要です。
**EIP4762**は、Verkleツリー導入直前のハードフォークに向けた準備段階として提案されており、Dapp開発者やクライアント開発者が新しいストレージモデルに適応するための十分な時間を確保することを目的としています。

Dapp開発者に対しては、新しいストレージ構造の採用を促すことで、Verkleツリー導入後の対応を円滑に進められるようにし、またクライアント開発者に対しては事前にデータベース形式を移行させることで、Verkleツリーへの移行時に大きな混乱やセキュリティリスクが生じないようにします。

## 仕様

### 補助関数 `get_storage_slot_tree_keys`

```py
def get_storage_slot_tree_keys(storage_key: int) -> [int, int]:
    if storage_key < (CODE_OFFSET - HEADER_STORAGE_OFFSET):
        pos = HEADER_STORAGE_OFFSET + storage_key
    else:
        pos = MAIN_STORAGE_OFFSET + storage_key
    return (
        pos // 256,
        pos % 256
    )
```

Verkleツリーにおけるストレージスロットの位置を決定するための補助的な関数です。
Ethereumの新しいステート構造であるVerkleツリーでは、ストレージを効率的に管理するために、「**スロット番号**」を2次元のインデックス（ツリーキーとサブキー）に変換する必要があります。
その変換処理を行うのがこの関数です。

関数の目的は、単一の `storage_key`（ストレージスロット番号）を受け取り、それを `(tree_key, sub_key)` の形式に変換することです。
この形式にすることで、Verkleツリー上の正確なノードの位置を特定することができます。

#### 処理の流れ

まず、与えられた `storage_key` が「**ヘッダーストレージ領域**」に属するか、「**メインストレージ領域**」に属するかを判定します。

```python
if storage_key < (CODE_OFFSET - HEADER_STORAGE_OFFSET):
    pos = HEADER_STORAGE_OFFSET + storage_key
else:
    pos = MAIN_STORAGE_OFFSET + storage_key
```

この条件式は、ストレージスロットがヘッダー領域内かどうかを判断しています。

* `HEADER_STORAGE_OFFSET` はヘッダー領域の開始位置です。
* `MAIN_STORAGE_OFFSET` は通常のストレージ領域の開始位置です。
* `CODE_OFFSET` はコードチャンクとストレージ領域が重ならないようにするための区切り値です。

つまり、`storage_key` が比較的小さい場合は「**ヘッダー領域**」に含めて、そうでない場合は「**メイン領域**」に含めるという分類です。

分類されたあと、次のように位置情報が2つの整数 `(tree_key, sub_key)` に分解されます。

```python
return (
    pos // 256,
    pos % 256
)
```

* `tree_key` はVerkleツリーにおける「**親ノード**」のインデックスを表します。
* `sub_key` はその中にある「**子ノード（リーフ）**」の位置を表します（0〜255の範囲）。

この分割により、256個のスロット単位で構成されるVerkleノードの中で、目的のストレージスロットがどこにあるかを効率よく特定することができます。

Verkleツリーでは、従来のハッシュマップ型のストレージ構造とは異なり、「**チャンク化された木構造**」でステートを管理します。
そのため、ストレージスロットへのアクセスを効率的かつ正確に行うには、このように物理的な位置を計算して対応する必要があります。

この関数はその要となるロジックであり、正確な位置の決定はwitness（証明）生成やアクセスイベントの整合性に直結します。
したがって、Verkleツリーを前提としたステートアクセスでは必須の仕組みといえます。

### アクセスイベント

**EIP4762**では、EVM実行時にステートを読み取る操作が発生したときに、「**アクセスイベント**」として明示的に記録される仕組みが導入されています。
アクセスイベントは、Verkleツリーにおいて証明（witness）に含めるべき情報を正確に把握するために必要です。

アクセスイベントは以下の形式を取ります。

```
(address, sub_key, leaf_key)
```

* `address`
    * アクセス対象のアカウントやコントラクトアドレス。
* `sub_key`
    * 固定値またはチャンク内インデックス。
* `leaf_key`
    * アカウント情報、コード、ストレージなどに対応するキー。

この仕組みは、ステートの種類ごとに分類されています。

#### アカウントヘッダーへのアクセスイベント

アカウントヘッダーとは、そのアドレスが持つ基本情報（残高、`nonce`、コードハッシュ）を指します。
以下の条件に該当する場合、対象アドレスに対して以下のアクセスイベントが発生します。

```
(address, 0, BASIC_DATA_LEAF_KEY)
```

これは、そのアドレスの基本情報が読み取られたことを意味します。

条件は以下す。

* 対象がプレコンパイルやシステムコントラクト以外であり、`CALL`, `CALLCODE`, `SELFDESTRUCT`, `EXTCODESIZE`, `EXTCODECOPY` のいずれかが使われたとき。
* コントラクト作成時に、`initcode` の実行対象となるアドレス。
* `BALANCE` オペコードによって任意のアドレスの残高を読み取るとき。
* デプロイ済みコントラクトが `CODECOPY` を使って自身のコードを読み込むとき。

ただし、対象がプレコンパイルまたはシステムコントラクトであり、かつ `CALL`, `CALLCODE`, `SELFDESTRUCT` が `wei` を送らない（value=0） 場合は、このイベントは発生しません。
これらのアドレスはステートが固定であり、witnessに含める必要がないためです。

また、もし `CALL`, `CALLCODE`, `SELFDESTRUCT` によって `wei` が送信された場合には、呼び出し元（送信者）のアカウントについても以下のアクセスイベントが追加で発生します。

```
(caller, 0, BASIC_DATA_LEAF_KEY)
```

このようにして送信元の残高や `nonce` に対するステートアクセスが明示されます。

なお、コントラクトの存在確認にあたっては、Verkleツリーにおける拡張ノードの存在（extension-and-suffix tree）が基準となり、`CODEHASH_LEAF_KEY` の有無は使用されません。

#### コードハッシュへのアクセスイベント

`EXTCODEHASH` オペコードによって、あるアドレスにデプロイされたコードのハッシュが取得されると、以下のアクセスイベントが発生します。

```
(address, 0, CODEHASH_LEAF_KEY)
```

これは、対象アドレスのコードハッシュにアクセスがあったことを示します。
ただし、プレコンパイルやシステムコントラクトについてはコードハッシュがクライアントに既知の値として埋め込まれているため、これらのアドレスに対してはイベントは発生しません。

#### コントラクト作成時のアクセスイベント

新しいコントラクトが作成された場合、そのアドレスに対して以下の2つのアクセスイベントが同時に発生します。

```
(contract_address, 0, BASIC_DATA_LEAF_KEY)
(contract_address, 0, CODEHASH_LEAF_KEY)
```

これは、新規作成されたコントラクトアカウントの基本情報とコード状態が初期化対象となるためです。

#### ストレージへのアクセスイベント

`SLOAD` や `SSTORE` によって、ストレージスロットが読み取られる、あるいは書き込まれると、以下の形式のアクセスイベントが発生します。

```
(address, tree_key, sub_key)
```

ここでの `tree_key` と `sub_key` は、Verkleツリーのノード位置を特定するためのインデックスであり、`get_storage_slot_tree_keys(address, key)` によって計算されます。
このインデックス化により、ストレージスロットへのアクセスを効率的に処理できます。

#### コードへのアクセスイベント

コード（バイトコード）に対するアクセスもチャンク単位で記録されます。
コードは `31` バイト単位で分割されており、特定のチャンクにアクセスが発生した場合には以下の形式でイベントが発生します。

```
(address, (chunk_id + 128) // 256, (chunk_id + 128) % 256)
```

これは、チャンクIDにオフセット128を加えることで、ストレージとコードの領域を明確に分離し、Verkleツリー内の位置を区別できるようにするためのものです。

#### アクセスが発生する主な条件

* 実行中の命令位置（プログラムカウンター `PC`）がコード長未満であれば、その `PC` が属するチャンクにアクセスされる。
* `JUMP`, `JUMPI`（成功した場合）のジャンプ先チャンクにアクセスが発生。
* `PUSHn` 命令により、複数バイトの即値を読む範囲内のチャンクがすべてアクセスされる。
* `CODECOPY`, `EXTCODECOPY` によって、バイトコードの任意の範囲がコピーされた場合、その範囲に対応するチャンクすべてがアクセスされる。

#### アクセスが発生しないケース

* ジャンプ先がコード外である場合（`destination >= len(code)`）。
* `JUMPI` が false を返した場合（ジャンプが発生しない）。
* `JUMP` 実行時にガスが足りず処理が停止した場合。
* コード末尾を越えて実行された場合（`PC == len(code)`）。
* 読み取りサイズが0の `CODECOPY`, `EXTCODECOPY`。
* `CODESIZE`, `EXTCODESIZE`, `EXTCODEHASH` はチャンクアクセスを発生させない（チャンクそのものは参照されないため）。

#### コントラクト作成時の特例

コントラクトが作成された際、そのコード全体が参照されたとみなされます。
そのため、コードの長さ `len(code)` に基づいて、以下の範囲のチャンクすべてにアクセスイベントが発生します。

```
chunk_id = 0 ... (len(code) + 30) // 31
```

これは、初期デプロイ時にコード全体を登録し、後の実行に備えるための設計です。

### 書き込みイベント

**EIP4762**では、EVMの実行中にステートが変更される場面を明示的に「**書き込みイベント**」として記録することが定義されています。
書き込みイベントは、そのデータが実際に更新されたことを意味し、形式はアクセスイベントと同様に次の3要素で構成されます。

```
(address, sub_key, leaf_key)
```

このイベントは「**どのアドレスの**」、「**どのサブキーに位置する**」、「**どの種類のデータ**」が書き込まれたかを示すもので、アクセスイベントの一種でもあります。
したがって、書き込みが発生した場合、対応するアクセスイベントも必ず同時に発生します。

#### アカウントヘッダーへの書き込みイベント

アカウントの残高や `nonce`（トランザクションカウント）が変化するような操作では、そのアカウントの基本情報（`BASIC_DATA_LEAF_KEY`）が更新されるため、書き込みイベントが発生します。

具体的には、以下のような状況で書き込みイベントが発生します。

#### `CALL` / `CALLCODE` / `SELFDESTRUCT` による送金

これらの命令で非ゼロの `wei` が送金された場合、送信者（caller）と受信者（callee）に対して以下のイベントが発生します。

```
(caller, 0, BASIC_DATA_LEAF_KEY)
(callee, 0, BASIC_DATA_LEAF_KEY)
```

もし、送信先アカウントがこれまで存在していなかった場合は、新たにコード情報（空のコード）も初期化されるため、以下の書き込みイベントも追加されます。

```
(callee, 0, CODEHASH_LEAF_KEY)
```

#### コントラクトの作成時

新しくコントラクトが作成されると、そのアカウントには初期状態（バランス、`nonce`、コード）が設定されるため、以下の2つの書き込みイベントが発生します。

```
(contract_address, 0, BASIC_DATA_LEAF_KEY)
(contract_address, 0, CODEHASH_LEAF_KEY)
```

#### ストレージへの書き込みイベント

スマートコントラクト内で状態変数の変更が行われるとき、つまり `SSTORE` 命令が実行されると、ストレージの該当スロットに対して書き込みイベントが発生します。

書き込みイベントは以下の形式で記録されます。

```
(address, tree_key, sub_key)
```

ここで `tree_key` と `sub_key` は、Verkleツリー内の正確なノード位置を特定するために使用されるインデックスです。
これらは `get_storage_slot_tree_keys(address, key)` によって計算されます。
これにより、256スロットごとのチャンク構造を持つVerkleツリーの中で、更新対象となる正確なノードが特定されます。

#### コードへの書き込みイベント

新しいコントラクトが作成される際、そのバイトコードがVerkleツリーのコード領域に格納されます。
このときコードは31バイトずつのチャンクに分割され、それぞれに対して書き込みイベントが発生します。

書き込みイベントの形式は以下のとおりです。

```
(
  address,
  (CODE_OFFSET + i) // VERKLE_NODE_WIDTH,
  (CODE_OFFSET + i) % VERKLE_NODE_WIDTH
)
```

* `address` はコントラクトアドレス。
* `i` は 0 から `(len(code) + 30) // 31` までの整数で、チャンク単位のインデックス。
* `CODE_OFFSET` はコード領域の開始位置を示す定数。
* `VERKLE_NODE_WIDTH` はVerkleノードが持つスロット数（通常256）。

このイベントにより、コードがツリーのどのノードに分割されて格納されたかを明示できます。

**EIP4762**以前はコードアクセスに関するアクセスリスト（warm/coldの区別）は存在していなかったため、コードへの書き込みに関連するウォームコスト（事前アクセスによるガス節約）は発生しません。
これは設計上の互換性を保つためです。

### トランザクションイベント

Ethereumにおいて、トランザクションが送信されると、その送信者と受信者に関する状態データが読み込まれたり書き換えられたりします。
**EIP4762**では、そのようなトランザクション実行に伴うアクセスイベントと書き込みイベントを明示的に定義しています。
これにより、Verkleツリー上でどの状態が証明対象になるのかを正確に把握できるようになります。

#### アクセスイベント

トランザクションが実行されると、以下の4つのアクセスイベントが発生します。

```
(tx.origin, 0, BASIC_DATA_LEAF_KEY)
(tx.origin, 0, CODEHASH_LEAF_KEY)
(tx.target, 0, BASIC_DATA_LEAF_KEY)
(tx.target, 0, CODEHASH_LEAF_KEY)
```

ここで `tx.origin` はトランザクションの発信元（EOA: 外部所有アカウント）であり、`tx.target` はトランザクションの呼び出し先アドレス（通常はコントラクトアカウント）を表します。

* `BASIC_DATA_LEAF_KEY` へのアクセスは、アカウントの基本情報（残高・nonceなど）を読み取るために必要です。
* `CODEHASH_LEAF_KEY` へのアクセスは、コードの存在を確認するために必要です（例：コントラクトの呼び出し先が実際にコードを持っているかどうかの検証など）。

このように、トランザクション処理では、送信者・受信者のアカウント情報とコードの有無が必ず参照されるため、それに対応するアクセスイベントが自動的に発生します。

#### 書き込みイベント

トランザクションによってアカウント状態が更新される場面では、以下のような書き込みイベントが発生します。

```
(tx.origin, 0, BASIC_DATA_LEAF_KEY)
```

これは、トランザクションを送信したアカウントの `nonce` がインクリメントされるためです。
`nonce` の更新は常に発生するため、この書き込みイベントは必ず発生します。

また、トランザクションに `value`（送金額）が設定されている場合、つまり送金が行われた場合には、送金先（`tx.target`）の残高も変化するため、以下の書き込みイベントが追加されます。

```
(tx.target, 0, BASIC_DATA_LEAF_KEY)
```

このように、トランザクションに関係するすべてのアカウント状態の更新が、明確な書き込みイベントとして定義されることで、ステート変更の追跡や証明構築がより厳密になります。

### Witnessガスコスト

**EIP4762**では、Verkleツリー導入に先立って、新たなガスコストモデル「**witness-based gas cost（証明ベースのガス課金）**」を導入します。
これは、トランザクションやEVM実行に伴って「**アクセスされた状態（witness）**」のデータ構造に基づいてガスが課金される仕組みです。
従来のストレージアクセスやコードサイズに起因するガスモデルを整理し、より実態に即した課金体系へ移行することを目的としています。

#### 削除される従来のガスコスト

以下の従来ガスコストはすべて削除されます。

* 送金（value > 0）を伴う `CALL` の追加ガスコスト。
* **EIP2200**で定義されていた `SSTORE` 関連のガスコスト（ただし `SLOAD_GAS` は除外）。
* コントラクトコード1バイトあたりの 200 ガスコスト。
* 送金を伴う `CALLCODE` の追加ガスコスト。

これにより、ストレージやコードのサイズ・変化そのものに対してではなく、それらの存在や変更を証明するために必要な構造へのアクセス・更新コストに基づいてガスが課金されるようになります。

#### 削減されるガスコスト

以下の操作は、ガスコストが引き下げられます。

* `CREATE` および `CREATE2` のガスコストが従来よりも軽くなり、1000に固定されます

#### 新しいガスコスト定数

以下のコストが新たに導入され、それぞれのアクセスや更新操作に応じて課金されます：

| 項目                    | コスト  |
| --------------------- | ---- |
| `WITNESS_BRANCH_COST` | 1900 |
| `WITNESS_CHUNK_COST`  | 200  |
| `SUBTREE_EDIT_COST`   | 3000 |
| `CHUNK_EDIT_COST`     | 500  |
| `CHUNK_FILL_COST`     | 6200 |

これらは、Verkleツリーにおける状態の読み取り・更新の構造的コストを反映したものです。

#### 実行中に管理されるセット

トランザクションの実行中には、以下の4種類のセットが維持されます。

* `accessed_subtrees`
    * 読み取られたサブツリー（例：ストレージチャンクの親ノード）。
* `accessed_leaves`
    * 読み取られた具体的なリーフノード（例：アカウント残高やコードハッシュなど）。
* `edited_subtrees`
    * 書き換えが発生したサブツリー。
* `edited_leaves`
    * 書き換えが発生したリーフノード。

これらのセットに基づき、ガス課金の対象を管理します。

#### アクセスイベントに対するガス課金

ステートアクセスイベント（`(address, sub_key, leaf_key)`）が発生したとき、以下のチェックと課金が行われます。

* トランザクション発行時の初期アクセスイベントでない場合に限り処理を行う。
* `(address, sub_key)` が `accessed_subtrees` に含まれていなければ、`WITNESS_BRANCH_COST` を課金してそのタプルを追加。
* `leaf_key` が存在し、かつ `(address, sub_key, leaf_key)` が `accessed_leaves` に含まれていなければ、`WITNESS_CHUNK_COST` を課金して追加する。

#### 書き込みイベントに対するガス課金

書き込みイベント（`(address, sub_key, leaf_key)`）が発生したとき、以下の処理が行われます：

* トランザクションによる初期的な書き込みイベントであればスキップ。
* `(address, sub_key)` が `edited_subtrees` に含まれていなければ、`SUBTREE_EDIT_COST` を課金し追加。
* `leaf_key` が存在し、かつ `(address, sub_key, leaf_key)` が `edited_leaves` に含まれていなければ、`CHUNK_EDIT_COST` を課金し追加。
* その位置に以前値が存在していなかった（`None`）場合、`CHUNK_FILL_COST` を追加で課金。

重要なのは、ツリー内のスロットに一度でも値（0を含む）が書き込まれると、そのスロットは「空（None）」には戻せないというルールです。
つまり、削除はできず、0と未使用は区別されます。

#### ガス不足への対応

witnessに関係するイベント（アクセス・書き込み）を追加するために必要なガスが不足している場合、その時点で残りのガスはすべて消費され、処理が中断されます。
これは、ステート証明が不完全な状態での実行継続を防ぐための設計です。

#### 1/64ガス予約の扱い

トランザクション中に `CALL` 系や `CREATE` 系の命令を実行する際、EVMは「**ネストされた呼び出し**」に対して実行前にガスを制限する仕組み（1/64ルール）を持ちます。

**EIP4762**ではwitness課金との整合性のため、以下のようにタイミングが調整されます。

* `CALL`、`CALLCODE`、`DELEGATECALL`、`STATICCALL` の場合
    * witness課金のあとで1/64ガス制限のチェックを行う。
* `CREATE`、`CREATE2` の場合
    * 1/64ガス予約を**先に引いてから**witness課金を行う。

これにより、旧来のアクセスリストによる処理と同等の安全性と予測可能性を維持します。

### ブロックレベルでの操作におけるWitnessの扱い

**EIP4762**では、トランザクション実行時に参照・変更されるステートに応じて「**witness（証明データ）**」を構築する仕組みを導入しています。
ただし、一部の特殊なアカウントやオペレーションについては例外的な扱いがあります。
これらの特例処理は、ガス課金やwitness構築の整合性を保つために必要です。

### トランザクション開始時のウォーム状態にないアカウント

トランザクションが開始される時点では、以下に該当するアカウントやスロットはすべて「**ウォーム状態（事前アクセス済）**」ではなく、coldとして扱われます。

* プレコンパイルアカウント（例：0x01〜0x09 など）
* システムコントラクトアカウントと、そのストレージスロット
* コインベースアカウント（マイナー報酬の受け取り先）
* Withdrawal（引き出し）専用アカウント

つまり、これらのアカウントに初めてアクセスした場合は、通常のwitnessコストが課される対象となります。

### システムコントラクトに対する扱い

システムコントラクトとは、クライアント側で特別に処理される特殊なコードを持つアカウントを指します。
例えば、Ethereum Shanghai以降に導入された `0x500...` 系のシステムコントラクトや、EVMが内部的に呼び出すコードなどが該当します。

以下のような状況でシステムコントラクトが呼び出された場合そのシステムコントラクトのコードチャンクやアカウントヘッダーへのアクセスはwitnessに含める必要はありません。

* 通常のシステムコール（システムレベルから明示的に呼び出された場合）
* プレコンパイルや特殊オペコードの処理を解決するために内部的に呼び出された場合

理由は、これらの情報はクライアント側であらかじめキャッシュされており、すでに信頼された既知の状態として扱えるためです。

ただし、以下のような場合にはwitnessに含める必要があります。

* システムコントラクトによるストレージアクセスやストレージ書き込み。
* それ以外のすべての非コード関連のアクセスや書き込み。

つまり、コードとアカウントヘッダーの読み取りは除外されるが、それ以外はすべてwitnessの対象になります。

さらに、プレコンパイルの処理や特殊なオペコードの解決時に発生するwitnessコストは課金対象となる一方で、純粋なシステムコール中に発生したアクセスについては、コストは課されません。
これはオペコード解決とシステムコール処理の目的が異なるためです。

### アカウント抽象化との整合性

アカウント抽象化（Account Abstraction）に関しては、**EIP4762**に完全に組み込まれるにはまだ議論が残っています。
現在、以下の2つの提案が検討中であり、どちらを正式に採用するかが未決定の状態です。

* **EIP7702**
    * 一時的な署名者スロットをEOAに導入する提案。
* **EIP3074**
    * `AUTH`/`AUTHCALL`命令を用いたEOAの権限移譲モデル。

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

[\[EIP7702\] EOAをコントラクトアカウントに変換する仕組みを理解しよう！](/articles/13a828c97786aab953e4)

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

[\[EIP3074\] 2つの新しい命令を追加しコントラクトにEOAの制御を委任する仕組みを理解しよう！](/articles/f55a6d2df6d13030e20e)

そのため、**EIP4762**ではアカウント抽象化に関するwitness処理についての最終仕様はまだ定義されていません。
どちらの提案が採用されるかによって、アクセスや署名処理の対象が変わるため、witness構築やガス課金の設計もそれに応じて変わる可能性があります。

## 補足

**EIP4762**は、EthereumにVerkleツリーを導入するための前段階として、ガスコストモデルを再設計するものです。
主な目的は、従来のMerkle Patricia Trie（MPT）ではなく、Verkleツリーに最適化されたガス課金体系を導入し、より現実的な処理コストを反映させることです。

### ガスモデルの再設計

新しい設計では、ステート証明（witness）の構成に基づいて、ストレージやコードへのアクセスコストが正確に計算されるようになっています。
特に以下の点が特徴です。

* `WITNESS_CHUNK_COST`（コードチャンクの証明）では、1バイトあたり約6.25ガス（31バイトあたり200ガス）を課金する設計になっています。
* `WITNESS_BRANCH_COST`（ブランチノードの証明）では、平均的な証明長（144バイト）に基づき、1バイトあたり約13.2ガスを課金します。
    * 最悪なケースでも約2.5ガス／バイトとなるように設計されています。

これにより、証明のサイズが大きくなるようなステートアクセス（例えば、攻撃者が意図的に長い証明を発生させるキー配置をする場合）にも対応できます。

### 従来のBerlinガスモデルとの違い

新しいガス体系では、以下のような変更があります。

* コードへのアクセスが、31バイト単位ごとに200ガス課金される。
    * これにより、平均的なガス使用量は6〜12%程度増加する見込みです。
    * ガス単価を350にした場合、10〜20%の増加と推定されています。
* 隣接するストレージスロット（例：`key1 // 256 == key2 // 256`）へのアクセスコストは、最初のスロットを除き2100ガスから200ガスへと大幅に削減されます。
* スロット0〜63番のストレージスロットは、最初のスロットを含めてすべて200ガスに統一されます。
    * これにより、多くの既存コントラクトのパフォーマンスが向上すると期待されます。

ストレージアクセスコストの削減は、コードアクセスコストの増加を相殺する効果が見込まれており、将来的にSolidityなどのコンパイラがこの仕様に適応すれば、より最適化されたガス使用が可能になります。

### アクセスイベント仕様の意義

**EIP4762**で定義されたアクセスイベントのタイミングと精度は、Verkleツリーにおける「**period 1 tree**」（短期証明用ツリー）にどのデータを含める必要があるかを正確に特定するために不可欠です。
これがガス再設計における技術的な複雑さの主因です。

## 互換性

**EIP4762**は、コンセンサスルールの変更を含むため、ハードフォークを必要とします。
そのため、以前のルールに依存していたアプリケーションとの互換性が失われる可能性があります。

特に影響が大きいのは、コードチャンクへのアクセスコストの上昇です。
一部のアプリケーションではガス使用量が増加し、トランザクションがブロックガスリミットに収まらなくなる可能性があります。

これに対する現実的な対策として、**EIP4762**の導入と同時にブロックのガス上限を引き上げることが推奨されます。
これにより、影響を受けるアプリケーションが動作不能になるリスクを最小限に抑えることができます。

## セキュリティ

**EIP4762**では、Verkleツリー構造に基づき、同一ステム内の複数のキーに対する読み書きコストが安くなります。
つまり、あるアドレスの連続するストレージ領域へのアクセスが高速化されるため、従来よりも高密度なデータ処理が可能になります。

しかし、この利点を活用するには、クライアント側のデータベース設計の見直しが必要です。
現状のように1スロットごとに個別保存する構造を保ったままだと、Verkle構造の意味を活かせず、むしろDoS攻撃に対する脆弱性（高頻度の証明要求による負荷）を生む可能性があります。

#### 必要な対応

* コミットメントスキームとデータ保存を論理的に分離し、検索のたびにツリー全体をたどるような設計は避ける必要があります。
* 具体的には、同一ステム（256スロット分）のデータをひとまとまりの8KB BLOBとして保存することが推奨されます。
    * これにより、読み書きのI/O効率が高まり、Verkleツリーへの最適化が達成されます。

このように、データベースの物理設計をVerkleベースの証明構造に適合させることで、パフォーマンスとセキュリティの両立が可能になります。

## 引用

Guillaume Ballet (@gballet), Vitalik Buterin (@vbuterin), Dankrad Feist (@dankrad), Ignacio Hagopian (@jsign), Tanishq Jasoria (@tanishqjasoria), Gajinder Singh (@g11tech), "EIP-4762: Statelessness gas cost changes [DRAFT]," Ethereum Improvement Proposals, no. 4762, February 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4762.

## 最後に

今回は「Ethereumの次世代データ構造であるVerkleツリーに対応するため、ストレージやコードへのアクセスにかかるガスコストの仕組みを見直し、より効率的で安全なステート管理を可能にする仕組みを提案している**EIP4762**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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
