Skip to content

Commit 5defdbb

Browse files
raxhvlfselmonerolation
authored
✨ feat(tests): EIP-7928 SELFDESTRUCT tests (ethereum#2159)
* ✨ feat(tests): EIP-7928 SELFDESTRUCT tests * feat: point to latest commit in BALs specs (resolver) * feat: Validate t8n BAL does not have duplicate entries for the same tx_index * 📄 docs: Changelog entry * chore: avoid extra fields in BAL classes, related to ethereum#2197 * Add tests for EIP-7928 around precompiles (doc) * fix(tests): Fix expectations for self-destruct tests --------- Co-authored-by: raxhvl <raxhvl@users.noreply.github.com> Co-authored-by: fselmo <fselmo2@gmail.com> Co-authored-by: Toni Wahrstätter <51536394+nerolation@users.noreply.github.com>
1 parent 20409f1 commit 5defdbb

2 files changed

Lines changed: 147 additions & 1 deletion

File tree

amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
"""Tests for EIP-7928 using the consistent data class pattern."""
22

3+
from typing import Dict
4+
35
import pytest
46

7+
from ethereum_test_base_types import Address
58
from ethereum_test_tools import (
69
Account,
710
Alloc,
811
Block,
912
BlockchainTestFiller,
13+
Initcode,
1014
Storage,
1115
Transaction,
1216
compute_create_address,
@@ -284,3 +288,139 @@ def test_bal_code_changes(
284288
),
285289
},
286290
)
291+
292+
293+
@pytest.mark.valid_from("Amsterdam")
294+
@pytest.mark.parametrize("self_destruct_in_same_tx", [True, False], ids=["same_tx", "new_tx"])
295+
@pytest.mark.parametrize("pre_funded", [True, False], ids=["pre_funded", "not_pre_funded"])
296+
def test_bal_self_destruct(
297+
pre: Alloc,
298+
blockchain_test: BlockchainTestFiller,
299+
self_destruct_in_same_tx: bool,
300+
pre_funded: bool,
301+
):
302+
"""Ensure BAL captures balance changes caused by `SELFDESTRUCT`."""
303+
alice = pre.fund_eoa()
304+
bob = pre.fund_eoa(amount=0)
305+
306+
selfdestruct_code = (
307+
Op.SLOAD(0x01) # Read from storage slot 0x01
308+
+ Op.SSTORE(0x02, 0x42) # Write to storage slot 0x02
309+
+ Op.SELFDESTRUCT(bob)
310+
)
311+
# A pre existing self-destruct contract with initial storage
312+
kaboom = pre.deploy_contract(code=selfdestruct_code, storage={0x01: 0x123})
313+
314+
# A template for self-destruct contract
315+
self_destruct_init_code = Initcode(deploy_code=selfdestruct_code)
316+
template = pre.deploy_contract(code=self_destruct_init_code)
317+
318+
transfer_amount = expected_recipient_balance = 100
319+
pre_fund_amount = 10
320+
321+
if self_destruct_in_same_tx:
322+
# The goal is to create a self-destructing contract in the same
323+
# transaction to trigger deletion of code as per EIP-6780.
324+
# The factory contract below creates a new self-destructing
325+
# contract and calls it in this transaction.
326+
327+
bytecode_size = len(self_destruct_init_code)
328+
factory_bytecode = (
329+
# Clone template memory
330+
Op.EXTCODECOPY(template, 0, 0, bytecode_size)
331+
# Fund 100 wei and deploy the clone
332+
+ Op.CREATE(transfer_amount, 0, bytecode_size)
333+
# Call the clone, which self-destructs
334+
+ Op.CALL(100_000, Op.DUP6, 0, 0, 0, 0, 0)
335+
+ Op.STOP
336+
)
337+
338+
factory = pre.deploy_contract(code=factory_bytecode)
339+
kaboom_same_tx = compute_create_address(address=factory, nonce=1)
340+
341+
# Determine which account will be self-destructed
342+
self_destructed_account = kaboom_same_tx if self_destruct_in_same_tx else kaboom
343+
344+
if pre_funded:
345+
expected_recipient_balance += pre_fund_amount
346+
pre.fund_address(address=self_destructed_account, amount=pre_fund_amount)
347+
348+
tx = Transaction(
349+
sender=alice,
350+
to=factory if self_destruct_in_same_tx else kaboom,
351+
value=transfer_amount,
352+
gas_limit=1_000_000,
353+
gas_price=0xA,
354+
)
355+
356+
block = Block(
357+
txs=[tx],
358+
expected_block_access_list=BlockAccessListExpectation(
359+
account_expectations={
360+
alice: BalAccountExpectation(
361+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
362+
),
363+
bob: BalAccountExpectation(
364+
balance_changes=[
365+
BalBalanceChange(tx_index=1, post_balance=expected_recipient_balance)
366+
]
367+
),
368+
self_destructed_account: BalAccountExpectation(
369+
balance_changes=[BalBalanceChange(tx_index=1, post_balance=0)]
370+
if pre_funded
371+
else [],
372+
# Accessed slots for same-tx are recorded as reads (0x02)
373+
storage_reads=[0x01, 0x02] if self_destruct_in_same_tx else [0x01],
374+
# Storage changes are recorded for non-same-tx
375+
# self-destructs
376+
storage_changes=[
377+
BalStorageSlot(
378+
slot=0x02, slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)]
379+
)
380+
]
381+
if not self_destruct_in_same_tx
382+
else [],
383+
code_changes=[], # should not be present
384+
nonce_changes=[], # should not be present
385+
),
386+
}
387+
),
388+
)
389+
390+
post: Dict[Address, Account] = {
391+
alice: Account(nonce=1),
392+
bob: Account(balance=expected_recipient_balance),
393+
}
394+
395+
# If the account was self-destructed in the same transaction,
396+
# we expect the account to non-existent and its balance to be 0.
397+
if self_destruct_in_same_tx:
398+
post.update(
399+
{
400+
factory: Account(
401+
nonce=2, # incremented after CREATE
402+
balance=0, # spent on CREATE
403+
code=factory_bytecode,
404+
),
405+
kaboom_same_tx: Account.NONEXISTENT, # type: ignore
406+
# The pre-existing contract remains unaffected
407+
kaboom: Account(balance=0, code=selfdestruct_code, storage={0x01: 0x123}),
408+
}
409+
)
410+
else:
411+
post.update(
412+
{
413+
# This contract was self-destructed in a separate tx.
414+
# From EIP 6780: `SELFDESTRUCT` does not delete any data
415+
# (including storage keys, code, or the account itself).
416+
kaboom: Account(
417+
balance=0, code=selfdestruct_code, storage={0x01: 0x123, 0x2: 0x42}
418+
),
419+
}
420+
)
421+
422+
blockchain_test(
423+
pre=pre,
424+
blocks=[block],
425+
post=post,
426+
)

amsterdam/eip7928_block_level_access_lists/test_cases.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@
77
| `test_bal_storage_writes` | Ensure BAL captures storage writes | Alice calls contract that writes to storage slot `0x01` | BAL MUST include storage changes with correct slot and value | ✅ Completed |
88
| `test_bal_storage_reads` | Ensure BAL captures storage reads | Alice calls contract that reads from storage slot `0x01` | BAL MUST include storage access for the read operation | ✅ Completed |
99
| `test_bal_code_changes` | Ensure BAL captures changes to account code | Alice deploys factory contract that creates new contract | BAL MUST include code changes for newly deployed contract | ✅ Completed |
10-
| `test_bal_2930_slot_listed_but_untouched` | Ensure 2930 access list alone doesn’t appear in BAL | Include `(KV, S=0x01)` in tx’s EIP-2930 access list; tx executes code that does **no** `SLOAD`/`SSTORE` to `S` (e.g., pure arithmetic/log). | BAL **MUST NOT** contain any entry for `(KV, S)` — neither reads nor writes — because the slot wasn’t touched. | 🟡 Planned |
10+
| `test_bal_self_destruct` | Ensure BAL captures storage access and balance changes caused by `SELFDESTRUCT` | Parameterized test: Alice interacts with a contract (either existing or created same-tx) that reads from storage slot 0x01, writes to storage slot 0x02, then executes `SELFDESTRUCT` with Bob as recipient. Contract may be pre-funded with 10 wei | BAL MUST include Alice's nonce change (increment) and Bob's balance change (100 or 110 depending on pre-funding). For the self-destructing contract: storage_reads=[0x01], empty storage_changes=[], and if pre-funded, balance_changes with post_balance=0; if not pre-funded, no balance change recorded. MUST NOT have code_changes or nonce_changes entries | ✅ Completed |
11+
| `test_bal_2930_slot_listed_but_untouched` | Ensure 2930 access list alone doesn't appear in BAL | Include `(KV, S=0x01)` in tx's EIP-2930 access list; tx executes code that does **no** `SLOAD`/`SSTORE` to `S` (e.g., pure arithmetic/log). | BAL **MUST NOT** contain any entry for `(KV, S)` — neither reads nor writes — because the slot wasn't touched. | 🟡 Planned |
1112
| `test_bal_2930_slot_listed_and_modified` | Ensure BAL records writes only because the slot is touched | Same access list as above, but tx executes `SSTORE` to `S`. | BAL **MUST** include `storage_changes` for `(KV, S)` (and no separate read record for that slot if implementation deduplicates). Presence in the access list is irrelevant; inclusion is due to the actual write. | 🟡 Planned |
1213
| `test_bal_7702_delegated_create` | BAL tracks EIP-7702 delegation indicator write and contract creation | Alice sends a type-4 (7702) tx authorizing herself to delegate to `Deployer` code which executes `CREATE` | BAL MUST include for **Alice**: `code_changes` (delegation indicator), `nonce_changes` (increment from 7702 processing), and `balance_changes` (post-gas). For **Child**: `code_changes` (runtime bytecode) and `nonce_changes = 1`. | 🟡 Planned |
1314
| `test_bal_self_transfer` | BAL handles self-transfers correctly | Alice sends `1 ETH` to **Alice** | BAL MUST include **one** entry for Alice with `balance_changes` reflecting **gas only** (value cancels out) and a nonce change; Coinbase balance updated for fees; no separate recipient row. | 🟡 Planned |
1415
| `test_bal_system_contracts_2935_4788` | BAL includes pre-exec system writes for parent hash & beacon root | Build a block with `N` normal txs; 2935 & 4788 active | BAL MUST include `HISTORY_STORAGE_ADDRESS` (EIP-2935) and `BEACON_ROOTS_ADDRESS` (EIP-4788) with `storage_changes` to ring-buffer slots; each write uses `tx_index = N` (system op). | 🟡 Planned |
1516
| `test_bal_system_dequeue_withdrawals_eip7002` | BAL tracks post-exec system dequeues for withdrawals | Pre-populate EIP-7002 withdrawal requests; produce a block where dequeues occur | BAL MUST include the 7002 system contract with `storage_changes` (queue head/tail slots 0–3) using `tx_index = len(txs)` and balance changes for withdrawal recipients. | 🟡 Planned |
1617
| `test_bal_system_dequeue_consolidations_eip7251` | BAL tracks post-exec system dequeues for consolidations | Pre-populate EIP-7251 consolidation requests; produce a block where dequeues occur | BAL MUST include the 7251 system contract with `storage_changes` (queue slots 0–3) using `tx_index = len(txs)`. | 🟡 Planned |
18+
| `test_bal_create2_to_A_read_then_selfdestruct` | BAL records balance change for A and storage access (no persistent change) | Tx0: Alice sends ETH to address **A**. Tx1: Deployer `CREATE2` a contract **at A**; contract does `SLOAD(B)` and immediately `SELFDESTRUCT(beneficiary=X)` in the same tx. | BAL **MUST** include **A** with `balance_changes` (funding in Tx0 and transfer on selfdestruct in Tx1). BAL **MUST** include storage key **B** as an accessed `StorageKey`, and **MUST NOT** include **B** under `storage_changes` (no persistence due to same-tx create+destruct). | 🟡 Planned |
19+
| `test_bal_create2_to_A_write_then_selfdestruct` | BAL records balance change for A and storage access even if a write occurred (no persistent change) | Tx0: Alice sends ETH to **A**. Tx1: Deployer `CREATE2` contract **at A**; contract does `SSTORE(B, v)` (optionally `SLOAD(B)`), then `SELFDESTRUCT(beneficiary=Y)` in the same tx. | BAL **MUST** include **A** with `balance_changes` (Tx0 fund; Tx1 outflow to `Y`). BAL **MUST** include **B** as `StorageKey` accessed, and **MUST NOT** include **B** under `storage_changes` (ephemeral write discarded because the contract was created and destroyed in the same tx). | 🟡 Planned |
20+
| `test_bal_precompile_funded_then_called` | BAL records precompile with balance change (fund) and access (call) | **Tx0**: Alice sends `1 ETH` to `ecrecover` (0x01). **Tx1**: Alice (or Bob) calls `ecrecover` with valid input and `0 ETH`. | BAL **MUST** include address `0x01` with `balance_changes` (from Tx0). No `storage_changes` or `code_changes`. | 🟡 Planned |
21+
| `test_bal_precompile_call_only` | BAL records precompile when called with no balance change | Alice calls `ecrecover` (0x01) with a valid input, sending **0 ETH**. | BAL **MUST** include address `0x01` in access list, with **no** `balance_changes`, `storage_changes`, or `code_changes`. | 🟡 Planned |
22+
1723

1824
> ℹ️ Scope describes whether a test spans a single transaction (`tx`) or entire block (`blk`).

0 commit comments

Comments
 (0)