Skip to content

Commit 20c1b63

Browse files
raxhvlfselmonerolation
authored
✨ feat(tests): EIP-7928 SELFDESTRUCT tests (#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 #2197 * Add tests for EIP-7928 around precompiles (doc) * fix(tests): Fix expectations for self-destruct tests --------- Co-authored-by: raxhvl <[email protected]> Co-authored-by: fselmo <[email protected]> Co-authored-by: Toni Wahrstätter <[email protected]>
1 parent a5f0b21 commit 20c1b63

File tree

9 files changed

+291
-28
lines changed

9 files changed

+291
-28
lines changed

.github/configs/eels_resolutions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@
5252
"Amsterdam": {
5353
"git_url": "https://github.com/fselmo/execution-specs.git",
5454
"branch": "feat/amsterdam-fork-and-block-access-lists",
55-
"commit": "39e0b59613be4100d2efc86702ff594c54e5bd81"
55+
"commit": "6abe0ecb792265211d93bc3fea2f932e5d2cfa90"
5656
}
5757
}

docs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Test fixtures for use by clients are available for each release on the [Github r
3232

3333
### 🧪 Test Cases
3434

35+
- ✨ Add an EIP-7928 test case targeting the `SELFDESTRUCT` opcode. ([#2159](https://github.com/ethereum/execution-spec-tests/pull/2159)).
36+
3537
## [v5.0.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.0.0) - 2025-09-05
3638

3739
## 🇯🇵 Summary

src/ethereum_test_types/block_access_list/account_absent_values.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class BalAccountAbsentValues(CamelModel):
7474
7575
"""
7676

77+
model_config = CamelModel.model_config | {"extra": "forbid"}
78+
7779
nonce_changes: List[BalNonceChange] = Field(
7880
default_factory=list,
7981
description="List of nonce changes that should NOT exist in the BAL. "

src/ethereum_test_types/block_access_list/account_changes.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
class BalNonceChange(CamelModel, RLPSerializable):
2323
"""Represents a nonce change in the block access list."""
2424

25+
model_config = CamelModel.model_config | {"extra": "forbid"}
26+
2527
tx_index: HexNumber = Field(
2628
HexNumber(1),
2729
description="Transaction index where the change occurred",
@@ -34,6 +36,8 @@ class BalNonceChange(CamelModel, RLPSerializable):
3436
class BalBalanceChange(CamelModel, RLPSerializable):
3537
"""Represents a balance change in the block access list."""
3638

39+
model_config = CamelModel.model_config | {"extra": "forbid"}
40+
3741
tx_index: HexNumber = Field(
3842
HexNumber(1),
3943
description="Transaction index where the change occurred",
@@ -46,6 +50,8 @@ class BalBalanceChange(CamelModel, RLPSerializable):
4650
class BalCodeChange(CamelModel, RLPSerializable):
4751
"""Represents a code change in the block access list."""
4852

53+
model_config = CamelModel.model_config | {"extra": "forbid"}
54+
4955
tx_index: HexNumber = Field(
5056
HexNumber(1),
5157
description="Transaction index where the change occurred",
@@ -58,6 +64,8 @@ class BalCodeChange(CamelModel, RLPSerializable):
5864
class BalStorageChange(CamelModel, RLPSerializable):
5965
"""Represents a change to a specific storage slot."""
6066

67+
model_config = CamelModel.model_config | {"extra": "forbid"}
68+
6169
tx_index: HexNumber = Field(
6270
HexNumber(1),
6371
description="Transaction index where the change occurred",
@@ -70,6 +78,8 @@ class BalStorageChange(CamelModel, RLPSerializable):
7078
class BalStorageSlot(CamelModel, RLPSerializable):
7179
"""Represents all changes to a specific storage slot."""
7280

81+
model_config = CamelModel.model_config | {"extra": "forbid"}
82+
7383
slot: StorageKey = Field(..., description="Storage slot key")
7484
slot_changes: List[BalStorageChange] = Field(
7585
default_factory=list, description="List of changes to this slot"
@@ -81,6 +91,8 @@ class BalStorageSlot(CamelModel, RLPSerializable):
8191
class BalAccountChange(CamelModel, RLPSerializable):
8292
"""Represents all changes to a specific account in a block."""
8393

94+
model_config = CamelModel.model_config | {"extra": "forbid"}
95+
8496
address: Address = Field(..., description="Account address")
8597
nonce_changes: List[BalNonceChange] = Field(
8698
default_factory=list, description="List of nonce changes"

src/ethereum_test_types/block_access_list/expectations.py

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class BalAccountExpectation(CamelModel):
3232
used for expectations.
3333
"""
3434

35+
model_config = CamelModel.model_config | {"extra": "forbid"}
36+
3537
nonce_changes: List[BalNonceChange] = Field(
3638
default_factory=list, description="List of expected nonce changes"
3739
)
@@ -199,21 +201,33 @@ def _validate_bal_ordering(bal: "BlockAccessList") -> None:
199201
f"{bal.root[i - 1].address} >= {bal.root[i].address}"
200202
)
201203

202-
# Check transaction index ordering within accounts
204+
# Check transaction index ordering and uniqueness within accounts
203205
for account in bal.root:
204-
change_lists: List[BlockAccessListChangeLists] = [
205-
account.nonce_changes,
206-
account.balance_changes,
207-
account.code_changes,
206+
changes_to_check: List[tuple[str, BlockAccessListChangeLists]] = [
207+
("nonce_changes", account.nonce_changes),
208+
("balance_changes", account.balance_changes),
209+
("code_changes", account.code_changes),
208210
]
209-
for change_list in change_lists:
210-
for i in range(1, len(change_list)):
211-
if change_list[i - 1].tx_index >= change_list[i].tx_index:
212-
raise BlockAccessListValidationError(
213-
f"Transaction indices not in ascending order in account "
214-
f"{account.address}: {change_list[i - 1].tx_index} >= "
215-
f"{change_list[i].tx_index}"
216-
)
211+
212+
for field_name, change_list in changes_to_check:
213+
if not change_list:
214+
continue
215+
216+
tx_indices = [c.tx_index for c in change_list]
217+
218+
# Check both ordering and duplicates
219+
if tx_indices != sorted(tx_indices):
220+
raise BlockAccessListValidationError(
221+
f"Transaction indices not in ascending order in {field_name} of account "
222+
f"{account.address}. Got: {tx_indices}, Expected: {sorted(tx_indices)}"
223+
)
224+
225+
if len(tx_indices) != len(set(tx_indices)):
226+
duplicates = sorted({idx for idx in tx_indices if tx_indices.count(idx) > 1})
227+
raise BlockAccessListValidationError(
228+
f"Duplicate transaction indices in {field_name} of account "
229+
f"{account.address}. Duplicates: {duplicates}"
230+
)
217231

218232
# Check storage slot ordering
219233
for i in range(1, len(account.storage_changes)):
@@ -224,19 +238,29 @@ def _validate_bal_ordering(bal: "BlockAccessList") -> None:
224238
f"{account.storage_changes[i].slot}"
225239
)
226240

227-
# Check transaction index ordering within storage slots
241+
# Check transaction index ordering and uniqueness within storage
242+
# slots
228243
for storage_slot in account.storage_changes:
229-
for i in range(1, len(storage_slot.slot_changes)):
230-
if (
231-
storage_slot.slot_changes[i - 1].tx_index
232-
>= storage_slot.slot_changes[i].tx_index
233-
):
234-
raise BlockAccessListValidationError(
235-
f"Transaction indices not in ascending order in storage slot "
236-
f"{storage_slot.slot} of account {account.address}: "
237-
f"{storage_slot.slot_changes[i - 1].tx_index} >= "
238-
f"{storage_slot.slot_changes[i].tx_index}"
239-
)
244+
if not storage_slot.slot_changes:
245+
continue
246+
247+
tx_indices = [c.tx_index for c in storage_slot.slot_changes]
248+
249+
# Check both ordering and duplicates
250+
if tx_indices != sorted(tx_indices):
251+
raise BlockAccessListValidationError(
252+
f"Transaction indices not in ascending order in storage slot "
253+
f"{storage_slot.slot} of account {account.address}. "
254+
f"Got: {tx_indices}, Expected: {sorted(tx_indices)}"
255+
)
256+
257+
if len(tx_indices) != len(set(tx_indices)):
258+
duplicates = sorted({idx for idx in tx_indices if tx_indices.count(idx) > 1})
259+
raise BlockAccessListValidationError(
260+
f"Duplicate transaction indices in storage slot "
261+
f"{storage_slot.slot} of account {account.address}. "
262+
f"Duplicates: {duplicates}"
263+
)
240264

241265
# Check storage reads ordering
242266
for i in range(1, len(account.storage_reads)):

src/ethereum_test_types/tests/test_block_access_lists.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,83 @@ def test_actual_bal_tx_indices_ordering(field_name):
335335
expectation.verify_against(actual_bal)
336336

337337

338+
@pytest.mark.parametrize(
339+
"field_name",
340+
["nonce_changes", "balance_changes", "code_changes"],
341+
)
342+
def test_actual_bal_duplicate_tx_indices(field_name):
343+
"""
344+
Test that actual BAL must not have duplicate tx indices in change lists.
345+
"""
346+
addr = Address(0xA)
347+
348+
# Duplicate tx_index=1
349+
changes = []
350+
if field_name == "nonce_changes":
351+
changes = [
352+
BalNonceChange(tx_index=1, post_nonce=1),
353+
BalNonceChange(tx_index=1, post_nonce=2), # duplicate tx_index
354+
BalNonceChange(tx_index=2, post_nonce=3),
355+
]
356+
elif field_name == "balance_changes":
357+
changes = [
358+
BalBalanceChange(tx_index=1, post_balance=100),
359+
BalBalanceChange(tx_index=1, post_balance=200), # duplicate tx_index
360+
BalBalanceChange(tx_index=2, post_balance=300),
361+
]
362+
elif field_name == "code_changes":
363+
changes = [
364+
BalCodeChange(tx_index=1, new_code=b"code1"),
365+
BalCodeChange(tx_index=1, new_code=b""), # duplicate tx_index
366+
BalCodeChange(tx_index=2, new_code=b"code2"),
367+
]
368+
369+
actual_bal = BlockAccessList([BalAccountChange(address=addr, **{field_name: changes})])
370+
371+
expectation = BlockAccessListExpectation(account_expectations={})
372+
373+
with pytest.raises(
374+
BlockAccessListValidationError,
375+
match=f"Duplicate transaction indices in {field_name}.*Duplicates: \\[1\\]",
376+
):
377+
expectation.verify_against(actual_bal)
378+
379+
380+
def test_actual_bal_storage_duplicate_tx_indices():
381+
"""
382+
Test that storage changes must not have duplicate tx indices within same
383+
slot.
384+
"""
385+
addr = Address(0xA)
386+
387+
# Create storage changes with duplicate tx_index within the same slot
388+
actual_bal = BlockAccessList(
389+
[
390+
BalAccountChange(
391+
address=addr,
392+
storage_changes=[
393+
BalStorageSlot(
394+
slot=0x01,
395+
slot_changes=[
396+
BalStorageChange(tx_index=1, post_value=0x100),
397+
BalStorageChange(tx_index=1, post_value=0x200), # duplicate tx_index
398+
BalStorageChange(tx_index=2, post_value=0x300),
399+
],
400+
)
401+
],
402+
)
403+
]
404+
)
405+
406+
expectation = BlockAccessListExpectation(account_expectations={})
407+
408+
with pytest.raises(
409+
BlockAccessListValidationError,
410+
match="Duplicate transaction indices in storage slot.*Duplicates: \\[1\\]",
411+
):
412+
expectation.verify_against(actual_bal)
413+
414+
338415
def test_expected_addresses_auto_sorted():
339416
"""
340417
Test that expected addresses are automatically sorted before comparison.

src/pytest_plugins/eels_resolutions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@
5555
"Amsterdam": {
5656
"git_url": "https://github.com/fselmo/execution-specs.git",
5757
"branch": "feat/amsterdam-fork-and-block-access-lists",
58-
"commit": "39e0b59613be4100d2efc86702ff594c54e5bd81"
58+
"commit": "6abe0ecb792265211d93bc3fea2f932e5d2cfa90"
5959
}
6060
}

0 commit comments

Comments
 (0)