EIP-7702 delegationのライフサイクル:設定・変更・解除・storageを検証する
EIP-7702のdelegationを同じEOAへ設定し、AからBへ変更して解除するまでを、code・storage・nonce・balance・allowanceと失敗ケースに分けて検証します。

EIP-7702のdelegationを解除すると、EOAへ置かれた23-byteの案内は消えます。しかし、EOAのstorage、ETH balance、token contractが持つallowance、delegate独自のsessionやmoduleまで一括で消えるわけではありません。
先に結論を固定すると、delegationをclearしても、storage、allowance、session stateは同時には消えません。緊急時に「codeが0xへ戻ったから全権限を回収できた」と判断しないことが大切です。
この記事は、EIP-7702でEOAの実行文脈を確認する基礎記事の続編です。同じローカルEOAへdelegate Aを設定し、compatibleなBへ変更し、zero addressでclearするまでを、FoundryとAnvil Pragueで追います。
実在wallet、public RPC、mainnet account、実tokenは使いません。掲載addressとprivate keyはすべてAnvil既定のローカル検証値、またはテスト専用値です。
まず、clearで変わるものと残るものを分ける
| state | delegationをclearした直後 | 保存している場所 |
|---|---|---|
| delegation indicator | `0xef0100 | |
| EOA address | 同じ | Ethereum account state |
| EOA nonce | clear authorizationがvalidなら増える | authority EOA |
| ETH balance | clearだけでは移動しない。gas支払いは別 | authority EOA |
| delegateが使ったstorage | 自動では消えない | authority EOAのstorage |
| ERC-20 allowance | 自動では消えない | token contractのowner → spender state |
| session key / module | 保存先と無効化規則による | EOA storageまたは外部contract |
| delegate implementation code | clearの対象ではない | delegate address側 |
「delegationを解除する」と「token approvalをrevokeする」は別操作です。allowanceのowner、token、spenderを確認する手順は、Spending cap・approve・revokeの確認方法で分けています。
authorization tupleはchain ID・delegate・nonceへ署名する
EIP-7702のtype 0x04 transactionは、次のauthorization tupleを持ちます。
[chain_id, address, nonce, y_parity, r, s]
署名対象のhashは次の形です。
keccak256(0x05 || rlp([chain_id, address, nonce]))
chain_id: authorizationを有効にするchain。0ならcross-chain扱いaddress: EOAが処理を委任するcode address。zero addressならclearnonce: authority EOAの現在nonceと一致させる再利用防止値y_parity / r / s: authorityを復元するsecp256k1署名
これは外側のtype 0x04 transaction署名とは別です。transactionを送るaccountと、authorizationへ署名するauthorityは異なっていても構いません。
clientはauthorization listをtransaction executionより先に、ただしtransaction senderのnonceを増やした後に処理します。tupleがwrong chainやnonce mismatchなら、そのtupleをskipして次へ進みます。複数のvalid tupleが同じauthorityを対象にする場合は、最後のvalid occurrenceが最終codeを決めます。
self executorではnonceが2回増える
今回のlifecycleは、authority自身がtype 0x04 transactionを送っています。そのため1回のphaseで、次の2回が起きます。
- transaction senderとしてauthority nonceが1増える
- valid authorizationの処理でauthority nonceがもう1増える
viem 2.56.0ではexecutor: 'self'を指定すると、この順序を見越してauthorization nonceをtransaction nonceより1大きく準備します。
const authorization = await authorityClient.signAuthorization({
contractAddress: delegateA,
executor: 'self',
})
await authorityClient.sendTransaction({
to: authority.address,
authorizationList: [authorization],
data: configureAndApproveData,
})
relayerがtransactionを送る場合、authorityはtransaction senderではないため、valid authorizationによる増加は通常1回です。EIP-7702なら常にnonceが2増えるわけではありません。
同じEOAをbefore → A → B → clearで読む
実行環境を固定しました。
| 項目 | 固定値 |
|---|---|
| EIP-7702 | Final、source commit bbc3f95844c37612a2f1b9e7477990bb717ecfa0 |
| execution spec | Prague、commit c4deda5b3cfc5c1c8429dcd9159a6fb5636d8486 |
| Foundry / Anvil | 1.8.0、commit 61ae26af36320d4fa1020f7db53785885e29eeb5 |
| Solidity | 0.8.36、EVM prague |
| viem | 2.56.0、tag commit 063faa52a247233c32445c5eda735ade21ca5801 |
| chain ID | local 31337 |
| base fee / gas price | 0。balance境界だけを分離する再現設定 |
| 外部RPC / 実資産 | なし |
phaseごとに、eth_getCode相当、EOA nonce、balance、slot 0、slot 1、mock ERC-20 allowanceを同じ列で読みました。
| phase | code / target | nonce | balance | slot 0 | session slot 1 | allowance |
|---|---|---|---|---|---|---|
| before | 0x / none |
0 | 10,000 ETH | 0 | zero address | 0 |
| delegate A | 23 bytes / A | 2 | 10,000 ETH | 41 | 0x…bEEF |
250 |
| delegate B | 23 bytes / B | 4 | 10,000 ETH | 42 | 0x…bEEF |
250 |
| clear | 0x / none |
6 | 10,000 ETH | 42 | 0x…bEEF |
250 |
10,000 ETHという値はAnvilの初期balanceです。base feeとgas priceを0へ固定したため変わりません。実networkではtransaction senderがgasを支払えばbalanceは減ります。
再現値はpublic/fixtures/eip-7702-lifecycle-6017.jsonへ保存しています。code targetやtest-only addressを含みますが、mainnet deploymentや利用推奨addressではありません。
delegate Aを設定してstorageとallowanceを作る
delegate Aはslot 0へnumber、slot 1へsessionKeyを置きます。同じself transaction内でmock tokenへapproveも呼びました。
contract LifecycleDelegateA {
uint256 public number;
address public sessionKey;
modifier onlySelf() {
require(msg.sender == address(this), "only self");
_;
}
function configureAndApprove(
uint256 newNumber,
address newSessionKey,
address token,
address spender,
uint256 amount
) external onlySelf {
number = newNumber;
sessionKey = newSessionKey;
require(IApprovalToken(token).approve(spender, amount));
}
}
delegated codeはauthority EOAの文脈で動きます。したがってnumberとsessionKeyはimplementation A自身ではなくauthority EOAのstorageへ入り、token contractが見るmsg.senderもauthorityです。
allowanceはEOAのslotへ保存されません。mock token contract側のmappingへ、次の組み合わせで保存されます。
token.allowance(authority, spender) = 250
この保存先の違いが、clear後にもallowanceが残る理由です。
compatibleなdelegate Bは同じstorageを引き継ぐ
delegate BはAと同じ順序・同じ型でstate variableを宣言しています。
contract LifecycleDelegateB {
uint256 public number;
address public sessionKey;
function incrementNumber() external onlySelf {
number += 1;
}
}
AからBへdesignationを変えた直後、Bはslot 0の41とslot 1のsession keyを読みました。incrementNumber()後はslot 0が42になり、allowanceは250のままです。
これは、この小さなA/Bが偶然ではなく意図して同じlayoutを共有した結果です。任意の2 implementationを差し替えて安全という意味ではありません。
incompatibleなlayoutは残存値を別の意味で読む
失敗再現では、同じslotを別の型と意味へ割り当てました。
contract IncompatibleLifecycleDelegate {
address public admin; // slot 0: Aではnumber
uint256 public dailyLimit; // slot 1: AではsessionKey
}
Aがslot 0へ保存した41は、incompatible delegateからaddress(0x29)に見えます。slot 1の0x…bEEFは、dailyLimit = 48879として読めます。EVMは「名前が違うから危険」と自動判定せず、同じ256-bit slotをそのまま解釈します。
ローカル再現では期待したadminと一致しないためstorage collisionでrevertしました。しかし、実装が明示的に検査しなければ、誤った値のまま処理を続ける可能性があります。
ERC-7201は、namespace IDからstorage rootを導く規約を定義しています。EIP-7702自身もdelegate変更時のcollision対策としてnamespaced storageに触れています。ただし、annotationを書くだけでcompilerが移行安全性を保証するわけではありません。
変更前には少なくとも次を固定します。
- old / new implementationのstorage layoutとnamespace ID
- 追加・削除・型変更したfield
- mapping、array、packed fieldのrootと意味
- initializer / migrationを誰がいつ実行するか
- old session、module、nonce、permissionを新実装がどう読むか
- rollbackしたときにnew stateをold codeがどう読むか
zero addressでclearしてもstorageは消えない
clear authorizationでは、delegate addressへzero addressを指定します。validならclientはauthority code hashをempty codeへ戻し、authority nonceを増やします。
const clearAuthorization = await authorityClient.signAuthorization({
contractAddress: zeroAddress,
executor: 'self',
})
Anvilでclear後に確認した結果は次の通りです。
code.length = 0
nonce = 6
storage slot 0 = 42
storage slot 1 = 0x...bEEF
ETH balance = 10000 ETH // zero-fee local run
token allowance = 250
EIP-7702には、clearと同時に全storage keyを消すprotocol処理はありません。仕様は、必要ならstorageを消すための専用delegateを設計できると説明しています。
ただし、「専用delegateを一度呼べば、任意のaccount storageを完全に消せる」と一般化もできません。mappingや動的配列のすべてのkeyを列挙できるか、誰がclear関数を呼べるか、途中revertやgas上限をどう扱うかを実装ごとに検証します。
allowance・session key・moduleは保存先ごとに止める
delegationのclearは、権限回収の一項目です。少なくとも次を別々に確認します。
ERC-20 allowance
allowanceはtoken contractがownerとspenderの組み合わせで返します。clear後もallowance(authority, spender)を読み、必要なら同じtoken・spenderのrevokeを別transactionとして検討します。
EOA storage内のsession key
codeがemptyの間は、そのsession keyを読むdelegate functionもありません。しかし値はslotに残るため、同じlayoutのdelegateを再設定すると再び有効と解釈される可能性があります。
session keyを無効化したというためには、key record、permission範囲、expiry、delegate独自nonceを、実装が定義する方法で更新した証拠が必要です。
外部registryやmodule
module、guardian、permission registryが別contractにある場合、EOAのcode変更ではそのcontract stateは変わりません。外部contract側のdisable / remove結果をreadし直します。
application nonce
EIP-7702 authorization nonceはaccount nonceです。delegateが署名付き操作へ使うapplication nonce、session nonce、ERC-4337 nonce keyとは別です。UserOperation失敗をvalidation・paymaster・executionに分ける方法でも、EntryPointとaccount内部の状態を分けて確認しています。
failure 4ケースはreceiptだけで判定しない
| case | local result | 読み方 |
|---|---|---|
| wrong chain | outer receiptはsuccess、authority code 0 bytes、nonce 0 | invalid tupleがskipされた。receipt successはdesignation成功を意味しない |
| stale nonce replay | 1回目・replayともouter receiptはsuccess、nonceは1のまま | 2回目のtupleがskipされ、A designationは変わらない |
| incompatible storage | 意味検査がstorage collisionでrevert |
code変更自体とlayout安全性は別 |
| delegate call revert | receiptはreverted、reverting delegateへのdesignationとnonce 1は残る | valid authorizationはexecution revertでrollbackされない |
wrong chain
chain 31338へ署名したtupleをchain 31337のtype 0x04へ入れました。transaction自体はblockへ入りましたが、authorityのcodeとnonceは変わりませんでした。
nonce replay
nonce 0のauthorizationをrelayerが一度使うと、authority nonceは1になりました。同じsigned tupleをもう一度含めたouter transactionもsuccessでしたが、tupleはnonce mismatchでskipされ、nonceとdesignationは変わりませんでした。
「replay transactionがrevertするはず」と決め打ちせず、authority code、target、nonceを最終stateで確認します。
delegate call revert
reverting delegateへのvalid authorizationとfail() callを同じtype 0x04へ入れると、execution receiptはrevertedになりました。それでもauthority codeはreverting delegateを指し、nonceは1です。
後続callが失敗したからdelegationも設定されなかった、とは判断できません。transaction調査ではreceipt・revert・logs・final stateの確認順と合わせ、authority codeを独立して読みます。
Forge helperとAnvil transactionの役割を分ける
Foundry 1.8.0のsignDelegation、attachDelegation、signAndAttachDelegationは、Prague以降のtestでdelegated codeを扱えます。今回のForge testは6/6で、次を確認しました。
- EOA storageへ書き、implementation storageは変わらない
- A → B → clearでraw storageとallowanceが残る
- stale nonceのattachを拒否する
- wrong-chain signatureがauthorityを変えない
- incompatible layoutが別の意味でslotを読む
- delegate callがrevertしても設定済みdesignationが残る
ただしattachDelegationはtest VMへcodeを直接反映する便宜helperで、単独では実type 0x04のauthorization nonce transitionを再現しません。そこでAnvil 1.8.0へviemでraw transactionを送り、RPCから0 → 2 → 4 → 6を別に確認しました。
ローカル検証の実行は次の一つにまとめています。
CI_REQUIRE_FOUNDRY=1 npm run test:contracts
delegate addressが同じでもcodeの意味は固定とは限らない
delegation indicatorが保持するのはdelegate addressで、runtime code hashではありません。targetがproxyなら、EOA側の23 bytesが同じままimplementationやbehaviorが変わる場合があります。
proxy targetの実行先とauthorityを追うときは、ERC-1967のimplementation・admin・beacon slot、upgrade event、storage layoutを読む方法で、delegate addressの先をさらに分解できます。
確認時は次を分けます。
- authorityの23-byte codeとdelegate target
- target addressのcurrent runtime code / code hash
- proxyならimplementation、admin、upgrade event
- storage layoutとmigration version
- 確認日時、chain、client
EIP-6780以後、既存contractのSELFDESTRUCTは、同じtransactionで作成された例外を除きcodeやstorageを削除しません。SELFDESTRUCTを一般的なdelegate code消去・差し替え手段として扱わず、authority側のdesignation clearとtarget側のcode lifecycleを別々に読みます。
batch callをdelegateへ持たせる場合も、wallet requestとaccount executionは同じ層ではありません。wallet_sendCallsとERC-7821の実装・検証で、EIP-7702 self-call、atomic rollback、wallet statusを分けています。
緊急時に確認する順番
実在accountで不審なdelegationが疑われる場合、この記事の検証用private keyやaddressを使わず、利用walletとchainの公式手順を確認してください。少なくとも次を一つずつ記録します。
- 新しい署名・transaction・dapp接続を止める
- chain IDと対象accountを固定する
- account codeがemptyか23 bytesか、delegate targetはどこか読む
- account nonce、pending / replaced transactionを確認する
- target code、proxy implementation、upgrade権限、verificationを確認する
- storage namespace、session key、module、guardian、application nonceを確認する
- tokenごとのowner・spender・allowanceを確認する
- walletが対応するclear手順のcall内容とfeeを確認する
- receipt後にcode、nonce、allowance、session / module stateを再度読む
実在walletのdelegation表示やclear UIは、生成画像で代用していません。画面captureが必要な検証はブラウザ実機検証の集約Issueで、wallet version、chain、確認日、個人情報のredactionを含めて扱います。
delegation authorizationへ署名する前に、eth_sign、typed data、Permit、SIWEとの違いから確認する場合は、wallet署名要求の見分け方と停止フローでmethod、origin、chain、delegate、nonceを同じrequestへ対応させてください。
walletのsite permissionを外した後に、allowance、server session、session key、module、delegationのどれが残るかを横断確認する場合は、DApp切断と権限解除の違いで保存場所、read方法、transaction要否を一件ずつ対応させてください。
まとめ
- zero address authorizationはEOAのdelegation indicatorをempty codeへ戻す
- clearでもEOA storage、balance、token allowanceは自動で消えない
- compatibleなA / Bはstateを引き継げるが、incompatible layoutは同じslotを別の意味で読む
- self executorではouter transactionとauthorizationでnonceが2回増える。relayer経路と同一視しない
- wrong-chainやstale tupleはskipされるため、outer receipt successだけで設定成功を断定しない
- delegate callがrevertしても、先に処理されたvalid designationは残る
- target address、runtime code、proxy upgrade、storage layout、external permissionを別々に確認する
delegationのclearは重要ですが、権限回収の完了条件そのものではありません。code、storage、nonce、balance、allowance、session / moduleを、それぞれの保存先で読み直すことが最後の確認です。
この記事とローカル検証は特定wallet、delegate、revoke手順、安全性を推奨・保証するものではありません。将来wallet security、smart-account SDK、monitoring、audit serviceのsponsorを掲載する場合も、広告と仕様・検証結果・緊急時確認項目を分離します。
確認した一次情報
- EIP-7702 Set Code for EOAs確認日: 2026/08/30
- Ethereum execution-specs Prague EOA delegation確認日: 2026/08/30
- Foundry signDelegation確認日: 2026/08/30
- Foundry v1.8.0確認日: 2026/08/30
- viem signAuthorization確認日: 2026/08/30
- viem Sending Transactions with EIP-7702確認日: 2026/08/30
- ERC-7201 Namespaced Storage Layout確認日: 2026/08/30
- ERC-20 Token Standard確認日: 2026/08/30
- EIP-6780 SELFDESTRUCT only in same transaction確認日: 2026/08/30
- Solidity 0.8.36確認日: 2026/08/30



