Skip to content

Commit 291fe00

Browse files
authored
feat(tests): Add flexibility to expected absent scenarios for BALs (#2124)
* fix(tests): Validate order for BAL coming from t8n The specs are currently correctly written, but we were not validating all of the ordering according to EIP-7928: - Addresses: lexicographic (bytewise). - Storage keys: lexicographic within each account. - Block access indices: ascending within each change list. This change validates the order of the BAL before we even begin to compare against our expectation. We also now validate that the expectations we define are subsequences within the BAL (expected order). - refactor: Explicit check for the fields we care about up front for `model_fields_set` - refactor: awkward comparison method should just be a validation method (_validate_change_lists) * chore: Unit test BAL ordering validation checks * chore: Add note to CHANGELOG * chore: move release note up to unreleased * feat(tests): Add flexibility to expected absent scenarios for BALs - We should have flexibility in defining the absence cases we expect for block-level access lists. This allows us to define absence validators for any case we might want to validate against. I don't expect these to grow very much but this does provide some flexibility. * feat: Validate all validators use @validate_call appropriately * chore: changelog note for #2124 * fix: no need to raise from any parsing errors; raise general case * feat: add sanity checks to modifiers; abstract common logic
1 parent fdad459 commit 291fe00

File tree

6 files changed

+627
-147
lines changed

6 files changed

+627
-147
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Test fixtures for use by clients are available for each release on the [Github r
2020
### 📋 Misc
2121

2222
- ✨ Add tighter validation for EIP-7928 model coming from t8n when filling ([#2138](https://github.com/ethereum/execution-spec-tests/pull/2138)).
23+
- ✨ Add flexible API for absence checks for EIP-7928 (BAL) tests ([#2124](https://github.com/ethereum/execution-spec-tests/pull/2124)).
2324

2425
### 🧪 Test Cases
2526

src/ethereum_test_types/block_access_list/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ class BalAccountExpectation(CamelModel):
196196
storage_reads: List[StorageKey] = Field(
197197
default_factory=list, description="List of expected read storage slots"
198198
)
199+
should_not_exist: List["AbsenceValidator"] = Field(
200+
default_factory=list, description="List of validators checking for forbidden conditions"
201+
)
199202

200203

201204
class BlockAccessListExpectation(CamelModel):
@@ -400,6 +403,11 @@ def _compare_account_expectations(
400403
Only validates fields that were explicitly set in the expected model,
401404
using model_fields_set to determine what was intentionally specified.
402405
"""
406+
# Run absence validators first
407+
if "should_not_exist" in expected.model_fields_set:
408+
for validator in expected.should_not_exist:
409+
validator(actual)
410+
403411
change_fields = {
404412
"nonce_changes",
405413
"balance_changes",
@@ -574,8 +582,12 @@ def _validate_change_lists(field_name: str, expected: List, actual: List) -> Non
574582
)
575583

576584

585+
AbsenceValidator = Callable[[BalAccountChange], None]
586+
587+
577588
__all__ = [
578589
# Core models
590+
"AbsenceValidator",
579591
"BlockAccessList",
580592
"BlockAccessListExpectation",
581593
"BalAccountExpectation",
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""
2+
Absence validator functions for BAL testing.
3+
4+
This module provides validator functions that check for the absence of specific
5+
changes in Block Access Lists. These validators are used with the
6+
``should_not_exist`` field in BalAccountExpectation to ensure certain changes
7+
do *not* occur.
8+
9+
All validator functions must be decorated with
10+
``@validate_call(validate_return=True)`` to ensure proper type validation.
11+
This is enforced via tests.
12+
"""
13+
14+
from typing import Set
15+
16+
from pydantic import validate_call
17+
18+
from ethereum_test_base_types import Number, StorageKey
19+
20+
from . import AbsenceValidator, BalAccountChange
21+
22+
23+
@validate_call(validate_return=True)
24+
def no_nonce_changes(tx_indices: Set[Number] | None = None) -> AbsenceValidator:
25+
"""
26+
Forbid nonce changes at specified transaction indices or all indices if None.
27+
28+
Args:
29+
tx_indices: Set of transaction indices to check. If None,
30+
checks all transactions.
31+
32+
"""
33+
34+
def check(account: BalAccountChange) -> None:
35+
for nonce_change in account.nonce_changes:
36+
if tx_indices is None or nonce_change.tx_index in tx_indices:
37+
raise AssertionError(
38+
f"Unexpected nonce change found at tx {nonce_change.tx_index}"
39+
)
40+
41+
return check
42+
43+
44+
@validate_call(validate_return=True)
45+
def no_balance_changes(tx_indices: Set[Number] | None = None) -> AbsenceValidator:
46+
"""
47+
Forbid balance changes at specified transaction indices or all indices
48+
if None.
49+
50+
Args:
51+
tx_indices: Set of transaction indices to check. If None,
52+
checks all transactions.
53+
54+
"""
55+
56+
def check(account: BalAccountChange) -> None:
57+
for balance_change in account.balance_changes:
58+
if tx_indices is None or balance_change.tx_index in tx_indices:
59+
raise AssertionError(
60+
f"Unexpected balance change found at tx {balance_change.tx_index}"
61+
)
62+
63+
return check
64+
65+
66+
@validate_call(validate_return=True)
67+
def no_storage_changes(
68+
slots: Set[StorageKey] | None = None,
69+
tx_indices: Set[Number] | None = None,
70+
) -> AbsenceValidator:
71+
"""
72+
Forbid storage changes at specified slots and/or transaction indices.
73+
74+
Args:
75+
slots: Set of storage slots to check. If None, checks all slots.
76+
tx_indices: Set of transaction indices to check. If None,
77+
checks all transactions.
78+
79+
"""
80+
81+
def check(account: BalAccountChange) -> None:
82+
for storage_slot in account.storage_changes:
83+
if slots is None or storage_slot.slot in slots:
84+
for slot_change in storage_slot.slot_changes:
85+
if tx_indices is None or slot_change.tx_index in tx_indices:
86+
raise AssertionError(
87+
"Unexpected storage change found at slot "
88+
f"{storage_slot.slot} in tx "
89+
f"{slot_change.tx_index}"
90+
)
91+
92+
return check
93+
94+
95+
@validate_call(validate_return=True)
96+
def no_storage_reads(slots: Set[StorageKey] | None = None) -> AbsenceValidator:
97+
"""
98+
Forbid storage reads at specified slots or all slots if None.
99+
100+
Args:
101+
slots: Set of storage slots to check. If None, checks all slots.
102+
103+
"""
104+
105+
def check(account: BalAccountChange) -> None:
106+
for read_slot in account.storage_reads:
107+
if slots is None or read_slot in slots:
108+
raise AssertionError(f"Unexpected storage read found at slot {read_slot}")
109+
110+
return check
111+
112+
113+
@validate_call(validate_return=True)
114+
def no_code_changes(tx_indices: Set[Number] | None = None) -> AbsenceValidator:
115+
"""
116+
Forbid code changes at specified transaction indices or all indices
117+
if None.
118+
119+
Args:
120+
tx_indices: Set of transaction indices to check. If None,
121+
checks all transactions.
122+
123+
"""
124+
125+
def check(account: BalAccountChange) -> None:
126+
for code_change in account.code_changes:
127+
if tx_indices is None or code_change.tx_index in tx_indices:
128+
raise AssertionError(f"Unexpected code change found at tx {code_change.tx_index}")
129+
130+
return check
131+
132+
133+
__all__ = [
134+
"AbsenceValidator",
135+
"no_nonce_changes",
136+
"no_balance_changes",
137+
"no_storage_changes",
138+
"no_storage_reads",
139+
"no_code_changes",
140+
]

0 commit comments

Comments
 (0)