はじめに
初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
今回は「SELFDESTRUCTでETHが消えないようにし、残高だけを保持するEIP8246」についてまとめていきます。
EIP8246は、SELFDESTRUCT に残っているETHの burn 処理をなくす提案です。
ここでの burn は、ETHが総供給から消える処理を指します。
同じトランザクション内で作られたコントラクトが SELFDESTRUCT した時でも、残高は消さず、コード・ストレージ・nonce だけを終了処理で消します。
以下にまとめられている提案を解説しながらまとめていきます。
概要
EIP8246は、SELFDESTRUCT によってETHが総供給から消える処理を取り除く提案です。
SELFDESTRUCT の対象としてマークされたアカウントは、トランザクション終了時にコード・ストレージ・nonce を消されます。
一方で、残高は保持されます。
この変更は、SELFDESTRUCT を完全に無効化する提案ではありません。
同じトランザクション内で作られたコントラクトに対して、終了処理でコードやストレージを消す効果は残します。
変わるのは、残高を消してETHを burn する処理です。
全体像は以下の図です。
左側は、SELFDESTRUCT の残り処理によってETHが消えるパターンです。
右側は、EIP8246 後の扱いです。
残高は保持され、実行できるコードと永続ストレージは残りません。
EIP6780 によって、SELFDESTRUCT の多くの削除効果はすでに制限されています。
ただし、同じトランザクション内で作られたコントラクトが SELFDESTRUCT を実行した時だけ、ETHが burn される余地が残っていました。
EIP8246 は、この残り処理を消します。
動機
SELFDESTRUCT の残り burn は、実際の利用がほとんどありません。
それでも、EVM実装、仕様、テストでは特殊な処理として扱う必要があります。
使用頻度が低い機能のために、クライアント実装やテストケースへ複雑さが残ることが課題です。
EIP6780 の後でも、同じトランザクション内で作られたコントラクトではETHを burn できます。
具体的には、作成されたコントラクトが自分自身を受取先にして SELFDESTRUCT する時や、SELFDESTRUCT 後に CALL や別の SELFDESTRUCT で追加のETHを受け取る時です。
提案では、Ethereumメインネットのジェネシスからおよそ2500万ブロックまでを再実行した結果が示されています。
Cancun後にこの処理で burn された事例は2件で、トランザクション終了処理中に残高が burn された事例は0件でした。
一方、Cancun前の履歴では、自己 burn は合計54件でした。
この数字から、残っている burn 処理は、EIP6780 で取り除かれた挙動よりさらに利用頻度が低いと分かります。
そのため、この処理を消しても影響範囲はかなり限定されます。
もう1つの理由は、ETHが総供給から消えるEVM上の最後の仕組みをなくすことです。
SELFDESTRUCT の残り burn を消すと、EVM命令だけでETHが総供給から離れる処理がなくなります。
仕様
EIP8246 が変更するのは、SELFDESTRUCT のうち、同じトランザクション内で作られたコントラクトに関する残高処理と終了処理です。
その他の SELFDESTRUCT の挙動は変わりません。
仕様の要点は以下です。
| 対象 | 変更後の扱い |
|---|---|
同じトランザクション内で作られたコントラクトが、自分自身を受取先にして SELFDESTRUCT する時 |
そのアカウントの残高は変わりません。 |
トランザクション終了時に SELFDESTRUCT 対象としてマークされたアカウント |
削除ではなく、nonce を0へ戻し、残高を保持し、コードとストレージを消します。 |
上記以外の SELFDESTRUCT
|
EIP6780 後の挙動から変わりません。 |
| 終了処理後の残高が0のアカウント | EIP161 によってステートから削除されます。 |
実行時の残高
同じトランザクション内で作られたコントラクトが SELFDESTRUCT を実行し、受取先がそのコントラクト自身だった時、残高は変わりません。
これにより、SELFDESTRUCT(address(this)) のような処理でETHが消えなくなります。
この変更は、受取先への送金処理を新しく追加するものではありません。
自分自身を受取先にした時に、残高が消える特殊処理をやめるだけです。
トランザクション終了時の扱い
トランザクション終了時に、SELFDESTRUCT 対象としてマークされたアカウントは以下の状態へ変更されます。
| 要素 | 終了処理後 |
|---|---|
nonce |
0 に戻ります。 |
balance |
変わりません。 |
code |
消えます。 |
storage |
すべて消えます。 |
同じトランザクション内で作られて SELFDESTRUCT したコントラクトの終了処理は以下です。
図の中心は、終了処理で「残高」と「実行可能な状態」を分ける点です。
残高は保持されますが、コードとストレージは消され、nonce は0に戻ります。
残高が0なら、EIP161 のルールでステートから削除されます。
残高のみのアカウント
終了処理後に残高が残ると、そのアドレスは残高のみを持つアカウントとしてステートに残ります。
コードはないため、そのアドレスのコントラクト処理は実行できません。
ストレージも消えているため、以前の状態を読み出すこともできません。
nonce を0へ戻す点も重要です。
残高だけが残っても、将来の別トランザクションで CREATE2 による同一アドレスへの再作成を妨げないためです。
設計根拠
EIP8246 は、burn を別の場所で補正するのではなく、burn が起きる元の処理をなくします。
これにより、EVM実装や仕様で「このパターンだけETHを消す」という特殊処理を持ち続ける必要がなくなります。
一方で、SELFDESTRUCT の終了処理をすべて消すわけではありません。
同じトランザクション内で作られたコントラクトについては、コード・ストレージ・nonce を消す効果を残します。
そのため、SELFDESTRUCT 後のアドレスは実行できるコントラクトとして残りません。
残高だけを残す設計は、変更範囲を最小限にするためのものです。
代替案として、nonce、コード、ストレージ、残高をすべて保持する設計もあります。
しかし、それは SELFDESTRUCT をより広く無効化する変更になり、今回の目的である burn 削除より大きな意味変更になります。
nonce を0へ戻す理由は、将来の CREATE2 を妨げないためです。
残高のみのアカウントが残っても、コードがなく、nonce が0であれば、同じアドレスへ後続トランザクションでコントラクトを作り直せます。
互換性
EIP8246 はコンセンサスルールを変えるため、ハードフォークが必要です。
同じトランザクション内で作られたコントラクトが SELFDESTRUCT を実行する時の最終ステートが変わります。
影響範囲は以下の図です。
変更対象は、同じトランザクション内で作られたコントラクトの残り burn 処理です。
同じトランザクション内で作られていないコントラクトの SELFDESTRUCT は、EIP6780 後の扱いから変わりません。
また、残高0のアカウントが EIP161 によって削除される点も残ります。
以前は、同じトランザクション内で作られたコントラクトが自分自身を受取先にして SELFDESTRUCT すると、ETHを burn できました。
また、SELFDESTRUCT 後にそのコントラクトへETHが送られ、トランザクション終了時に残高が消えるパターンもありました。
EIP8246 後は、どちらのパターンでもETHは burn されません。
以前は、この種のコントラクトはトランザクション終了時に削除されました。
変更後は、最終残高が0のコントラクトは削除され、最終残高が0ではないコントラクトは残高のみのアカウントとして残ります。
コード、ストレージ、nonce は残りません。
CREATE2 で同じアドレスへ後続トランザクションから作り直す使い方は動作し続けます。
nonce が0に戻り、コードも消えるため、残高だけが残ったアカウントでも再作成を妨げません。
この提案は、SELFDESTRUCT 全体をさらに広く変更するのではなく、残っていた burn 処理だけを対象にします。
注意点として、Optimism系のL2では SELFDESTRUCT の burn 機能が通常運用で使われています。
メインネットと同じ変更を入れるには、別の移行計画が必要です。
テストケース
提案では、SELFDESTRUCT の組み合わせが多いため、既存の SELFDESTRUCT テストを更新しつつ、変更対象のテストを追加する必要があるとしています。
特に、以下の軸を組み合わせて確認します。
| 軸 | 確認内容 |
|---|---|
| 呼び出し階層 | トップレベルのトランザクションか、内部呼び出しか。 |
| 呼び出し種別 |
CALL、CREATE、CREATE2 のどれか。 |
| コード状態 | 初期化コード内で実行するか、デプロイ済みコードから実行するか。 |
| 実行結果 |
SELFDESTRUCT が成功するか、ガス不足で失敗するか。 |
revert |
SELFDESTRUCT の効果が確定するか、内部呼び出しの revert で戻るか。 |
| 残高 |
SELFDESTRUCT 実行時の残高が0か、0ではないか。 |
命令レベル
命令レベルでは、同じトランザクションで作られたコントラクトがどの受取先へ SELFDESTRUCT するかを確認します。
| No | ケース |
|---|---|
| 1 | 同じトランザクションで作られたコントラクトが、自分自身を受取先にして SELFDESTRUCT する。 |
| 2 | 自分自身を受取先にした SELFDESTRUCT の後に、もう一度自分自身へ SELFDESTRUCT する。 |
| 3 | 自分自身を受取先にした SELFDESTRUCT の後に、別アドレスへ SELFDESTRUCT する。 |
| 4 | 別アドレスを受取先にした SELFDESTRUCT の後に、自分自身へ SELFDESTRUCT する。 |
トランザクション終了処理
終了処理では、SELFDESTRUCT 後にETHを受け取るパターンや、コード・ストレージ・nonce が消えるかを確認します。
| No | ケース |
|---|---|
| 5 | 別アドレスへ SELFDESTRUCT した後、CALL で対象アカウントへETHを送る。 |
| 6 | 別アドレスへ SELFDESTRUCT した後、複数回の CALL で対象アカウントへETHを送る。 |
| 7 | 別アドレスへ SELFDESTRUCT した後、CALL でETHを送り、さらに別アドレスへ SELFDESTRUCT する。 |
| 8 | 別アドレスへ SELFDESTRUCT した後、CALL でETHを送り、さらに自分自身へ SELFDESTRUCT する。 |
| 9 | 自分自身へ SELFDESTRUCT した後、CALL で対象アカウントへETHを送る。 |
| 10 | 自分自身へ SELFDESTRUCT した後、複数回の CALL で対象アカウントへETHを送る。 |
| 11 | 自分自身へ SELFDESTRUCT した後、CALL でETHを送り、さらに別アドレスへ SELFDESTRUCT する。 |
| 12 | 自分自身へ SELFDESTRUCT した後、CALL でETHを送り、さらに自分自身へ SELFDESTRUCT する。 |
| 13 | 新しいアカウントを作り、そのアカウントが別アカウントを作って nonce を増やした後、自分自身へ SELFDESTRUCT する。 |
| 14 | 新しいアカウントを作り、そのアカウントが別アカウントを作って nonce を増やした後、別アドレスへ SELFDESTRUCT する。 |
| 15 | 新しいアカウントを作り、そのアカウントがストレージを書き込んだ後、自分自身へ SELFDESTRUCT する。 |
| 16 | 新しいアカウントを作り、そのアカウントがストレージを書き込んだ後、別アドレスへ SELFDESTRUCT する。 |
複数トランザクション
複数トランザクションでは、CREATE2 で残高のある新しいアカウントを作り、そのアカウントが自分自身へ SELFDESTRUCT した後、同じ処理をもう一度実行します。
ここで確認するのは、残高のみのアカウントが残っても、nonce とコードが消えているため、後続トランザクションで再作成できることです。
セキュリティ
SELFDESTRUCT の挙動変更は、複数の呼び出しやコントラクト作成を組み合わせたテストへ広く影響します。
そのため、変更範囲は小さく見えても、テスト量は多くなります。
提案では、EIP6780 向けの既存テストを活用して、新しい終了処理へ合わせる方針が示されています。
もう1つの注意点は、ETHの burn を前提にした使い方です。
その使い方では、変更後に小額残高だけを持つアカウントが残ります。
ただし、新しいアカウントを作るガスコストはトランザクション送信者が払うため、無料でステートを増やす DoS 手段にはなりません。
提案で示されたメインネット再実行では、Cancun後から約2500万ブロックまでに、この処理で記録された事例は2件でした。
そのため、実際の影響はかなり限定的です。
最後に
今回は「SELFDESTRUCTでETHが消えないようにし、残高だけを保持するEIP8246」についてまとめてきました。
EIP8246 は、SELFDESTRUCT によってETHが消える処理をなくす提案です。
残高は保持しつつ、コード・ストレージ・nonce は消すため、実行可能なコントラクトとしては残りません。
利用頻度の低い特殊処理を消し、EVM実装とテストを単純にする変更です。
他でも色々記事を書いているのでぜひよろしければ読んでいってください!


