# [EIP7973] 同じアドレスへの2回目以降のETH送金を安くする仕組みを理解しよう！

> EIP7973とは、アカウントへの書き込み操作に「2回目以降は安くする」仕組み（warm metering）を導入する提案です。

- URL: https://cardene.dev/articles/13928a9c2bbae0a3e7a5
- 公開日: 2026-03-23
- 更新日: 2026-03-23
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、同一トランザクション内でアカウントのフィールド（nonce、残高、コードハッシュ）を2回以上更新する時、2回目以降のガスコストを引き下げる仕組みを導入する**EIP7973**についてまとめていきます！

:::message
以下にまとめられているものを解説しながらまとめていきます。
:::

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

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

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

## 概要

**EIP7973**とは、アカウントへの書き込み操作に「**2回目以降は安くする**」仕組み（warm metering）を導入する提案です。
アカウントのフィールド（`nonce`、`value`（残高）、`codehash`）がトランザクション内で2回以上変更される場合、2回目以降の書き込みコストを削減します。

:::message
**Warm Metering（ウォームミータリング）とは**

最初のアクセス（コールドアクセス）と2回目以降のアクセス（ウォームアクセス）でガスコストを変える仕組みです。
ストレージの読み書きでは**EIP2929**で既に導入されていますが、アカウントフィールドの書き込みにはまだ適用されていません。
:::

ステートルート（Ethereumの全状態を表すハッシュツリーの根）の更新はブロック構築で最もコストが高い処理の一つですが、同じアカウントへの複数回の更新はバッチ処理できるため、2回目以降の実際のコストは1回目より低くなります。
**EIP7973**はこの実態に合わせてガスコストを調整します。

## 動機

#### ETH送金とWETH送金のコスト不公平

現在のEthereumでは、ストレージへの書き込みには**EIP2200**で導入されたネット計量（net metering）が適用されています。
同じストレージスロットへの複数回の書き込みは、最初のコールドアクセスコストが償却され、2回目以降は安くなります。

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

[スマートコントラクトの脆弱性を理解してBugハンターになろう](/articles/ff3a98d909b659)

しかし、アカウントフィールドの更新にはこのような割引がありません。
例えば、1つのトランザクション内でETHを複数のアドレスに送金する場合を考えます。

WETH（Wrapped ETH）を使ったTransferはストレージの書き込みなので「**2回目以降は安くなる**」恩恵を受けます。
一方、ネイティブETHのTransferはアカウントの`balance`フィールドの更新であり、毎回同じコストが課金されます。
同じ送金先に何度送っても割引がないのです。

この不公平は、開発者がネイティブETHの使用を避けてWETHを選ぶ動機になっており、将来の`PAY`や`GAS2ETH`のような値Transferを含む新しいオペコードにも不利に働きます。

#### ステートルート更新のコスト実態

Ethereumのアカウントは`(nonce, balance, storage_root, codehash)`の4つのフィールドからなるタプルで、Merkle Patricia Tree（MPT）のリーフとして保存されています。
アカウントの任意のフィールドを変更するとMPTのステートルート再計算が必要ですが、同じアカウントへの複数回の変更はバッチ処理できます。
つまり、2回目以降のステートルート再計算は不要であり、実際のコストは低いのです。

#### この提案による解決策

**EIP7973**はアカウント書き込みにコールド/ウォームの区別を導入します。
トランザクション内で初めてアカウントを更新する時は「**コールドライト**」（高コスト）、既に更新済みのアカウントへの追加更新は「**ウォームライト**」（低コスト）として課金されます。
これにより、ETH送金のコストが実態に合ったものになり、WETHとの不公平が解消されます。

## 仕様

#### パラメータ定義

**EIP7973**では、既存の`GAS_CALL_VALUE`と`GAS_STORAGE_UPDATE`パラメータを廃止し、以下の3つのパラメータを導入します。

| パラメータ | 値 | 説明 |
|----------|-----|------|
| `GAS_COLD_STORAGE_WRITE` | 未確定 | ストレージトライへの1回の更新コスト。 |
| `GAS_COLD_ACCOUNT_WRITE` | 未確定 | アカウントトライへの1回の更新コスト（初回更新時に課金）。 |
| `GAS_WARM_WRITE` | 未確定 | ウォーム状態のトライへの書き込みコスト（ステートルート再計算不要）。 |

:::message
**パラメータ値は未確定**

具体的なガスコスト値は、ステートフルベンチマークの結果に基づいて決定される予定です。
ベンチマークは現在開発中です。
:::

#### アカウント更新オペコードのガスコスト変更

`CREATE`、`CREATE2`、`*CALL`（`CALL`、`STATICCALL`等）のようなアカウントを更新するオペコードでは、以下のようにコストが決まります。

```mermaid
flowchart TD
    A[アカウント更新オペコード実行] --> B{"アカウントのフィールドは\nトランザクション開始時と同じ?"}
    B -- はい（初回更新） --> C["GAS_COLD_ACCOUNT_WRITE\nを課金（高コスト）"]
    B -- いいえ（既に更新済み） --> D["GAS_WARM_WRITE\nを課金（低コスト）"]
```

判定基準は「**アカウントのフィールドがトランザクション開始時の値と等しいかどうか**」です。
等しい場合はまだ更新されていないことを意味するためコールドライトとなり、等しくない場合は既に更新済みのためウォームライトとなります。

以下の図は、同一アドレスに複数回ETHを送金する場合の現行方式と**EIP7973**のコスト比較です。

![fig1-cold-warm-comparison.png](/images/qiita-03c8df8c-0ec0-4132-bb8f-a4a22ffaae95.webp)

例えば、あるアドレスにETHを2回送金する場合、1回目の送金では`balance`フィールドが初めて変更されるため`GAS_COLD_ACCOUNT_WRITE`が課金されます。
2回目の送金では`balance`は既にトランザクション開始時と異なっているため、`GAS_WARM_WRITE`のみが課金されます。

#### `SSTORE`のガスコスト更新

`SSTORE`（ストレージ書き込み）のコストも更新されます。
従来は1つのコストとして扱われていましたが、**EIP7973**では以下の2つのコンポーネントに分離されます。

- アカウントタプルの更新コスト（コールド or ウォーム）
- ストレージトライの更新コスト

ストレージが更新されると、アカウントの`storage_root`フィールドも変更されます。
そのため、既に`SSTORE`でストレージを更新していれば、アカウントタプルは「**ダーティ**」（変更済み）状態になっています。
この場合、アカウントタプルの更新コストはウォームライト扱いになり、コスト削減の恩恵を受けます。

以下はガスコスト計算の疑似コード（Python記法）です。

```python
# 現在値とトランザクション開始時の値を取得
state = evm.message.block_env.state
original_value = get_storage_original(state, evm.message.current_target, key)
current_value = get_storage(state, evm.message.current_target, key)

# アカウント情報を取得（現在の状態とトランザクション開始時の状態）
original_account = get_account_original(state, evm.message.current_target)
account = get_account(state, evm.message.current_target)

gas_cost = Uint(0)

# アカウント書き込みコスト（コールド/ウォーム判定）
if account != original_account:
    gas_cost += GAS_WARM_WRITE          # 既に更新済み → ウォームライト
else:
    gas_cost += GAS_COLD_ACCOUNT_WRITE  # 初回更新 → コールドライト

# スロット固有のコスト（コールドアクセス判定）
if (target, key) not in accessed_storage_keys:
    accessed_storage_keys.add((target, key))
    gas_cost += GAS_COLD_SLOAD

# スロットの値変更に基づくコスト
if original_value == current_value and current_value != new_value:
    if original_value == 0:
        gas_cost += GAS_STORAGE_SET
    else:
        gas_cost += GAS_COLD_STORAGE_WRITE - GAS_COLD_SLOAD
else:
    gas_cost += GAS_WARM_ACCESS

# リファンドカウンター計算
if current_value != new_value:
    if original_value != 0 and current_value != 0 and new_value == 0:
        evm.refund_counter += int(GAS_STORAGE_CLEAR_REFUND)
    if original_value != 0 and current_value == 0:
        evm.refund_counter -= int(GAS_STORAGE_CLEAR_REFUND)
    if original_value == new_value:
        if original_value == 0:
            evm.refund_counter += int(GAS_STORAGE_SET - GAS_WARM_ACCESS)
        else:
            evm.refund_counter += int(GAS_COLD_STORAGE_WRITE - GAS_COLD_SLOAD - GAS_WARM_ACCESS)
        # アカウント書き込みがコールドだった場合のリファンド
        if account == original_account:
            evm.refund_counter += GAS_COLD_ACCOUNT_WRITE
```

このコードでは、まず`get_account_original`（トランザクション開始時のアカウント状態）と`get_account`（現在のアカウント状態）を比較してコールド/ウォームを判定しています。
`original_account`と`account`が異なれば、そのトランザクション内で既にアカウントが更新されているためウォームライトとなります。

リファンドカウンターの計算では、スロットがトランザクション開始時の値に戻された場合に差額がリファンドされます。
特に注目すべきは最後の条件で、アカウント書き込みがコールドだった場合（`account == original_account`）に`GAS_COLD_ACCOUNT_WRITE`分がリファンドされる点です。

#### **EIP7928**との互換性

並列実行との互換性のため、現在のトランザクションより前に実行されたトランザクションでブロックレベルアクセスリスト（BAL）にアカウントの更新が記録されている場合、比較対象の値はアカウントトライからではなくBALのエントリから取得されます。
これにより、並列実行環境でも正しくコールド/ウォーム判定が機能します。

## 補足

#### なぜアカウント更新は高いのか

Ethereumのステートはメルクルパトリシアツリー（MPT）で管理されています。
アカウントはこのツリーのリーフ（末端ノード）であり、アカウントの`storage_root`自体もアカウント固有のMPTの根です。

ストレージを更新する時は、アカウントのMPT（`storage_root`の更新）とグローバルのMPT（ステートルートの更新）の2つを更新する必要があります。
一方、`nonce`や`balance`の更新はグローバルのMPTのみの更新で済みます。

しかし、いずれの場合もMPTの再計算はコストが高い処理です。
同じアカウントへの複数回の更新をバッチ処理すれば、MPTの再計算は1回で済むため、2回目以降の実際のコストは低くなります。
**EIP7973**はこの実態をガスコストに反映させます。

#### ネット計量を採用しなかった理由

**EIP2200**のストレージ書き込みでは、トランザクション終了時の値がトランザクション開始時と同じ場合にリファンドが行われる「**ネット計量**」が採用されています。
アカウント書き込みにも同様のネット計量を導入することが検討されましたが、シンプルさを優先して見送られました。

#### ベンチマーク未確定

この提案のガスコスト値はまだ最終確定していません。
ステートフルベンチマーク（実際のステート操作のパフォーマンスを測定するベンチマーク）が現在開発中であり、測定データに基づいて最終的な値が決定される予定です。

## 互換性

この提案はガスの再価格設定を伴うため、ハードフォーク（計画的なネットワークアップグレード）が必要です。

#### ウォレットの更新

`eth_estimateGas`を使用しているウォレットは、新しいガスパラメータに対応した更新が必要です。
更新しないとガスの見積もりが過大になり、潜在的な攻撃ベクトルを生む可能性があります。

#### ノードソフトウェアの更新

`eth_estimateGas`などのRPCメソッドが新しいコスト計算式を反映する必要があります。

ウォレットとノードが適切に更新されれば、ユーザーは従来通りのワークフローを維持できます。

## セキュリティ

アカウントアクセス操作のコストを削減することで、新たな攻撃ベクトルが生まれる可能性があります。
dAppsやユーザーの振る舞いへの潜在的な影響を理解するために、さらなる分析が必要です。

ガスコストの引き下げにより、1トランザクションあたりに実行可能なアカウント更新の回数が増えるため、ステート膨張やDoS攻撃の観点からの検討も重要です。
ベンチマーク結果に基づいて、コールドライトとウォームライトのコスト差が適切に設定されることで、セキュリティと効率のバランスが確保される予定です。

## 引用

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

https://ethereum-magicians.org/t/eip-7973-warm-account-write-metering/25907

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

## 最後に

今回は「**EIP7973**による同じアドレスへの2回目以降のETH送金コスト削減」についてまとめてきました。
いかがだったでしょうか？

ストレージ書き込みには既に「**2回目以降は安くなる**」仕組みが適用されているのに、アカウントフィールドの更新にはなかったというのは気づきにくい不公平です。
**EIP7973**はこの不公平を解消し、ETH送金がWETH送金と同等のコスト効率になることを目指しています。

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

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

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

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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