Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ Test fixtures for use by clients are available for each release on the [Github r
- ✨ Add flexible API for absence checks for EIP-7928 (BAL) tests ([#2124](https://github.com/ethereum/execution-spec-tests/pull/2124)).
- 🐞 Use ``engine_newPayloadV5`` for `>=Amsterdam` forks in `consume engine` ([#2170](https://github.com/ethereum/execution-spec-tests/pull/2170)).
- πŸ”€ Refactor EIP-7928 (BAL) absence checks into a friendlier class-based DevEx ([#2175](https://github.com/ethereum/execution-spec-tests/pull/2175)).
- 🐞 Tighten up validation for empty lists on Block-Level Access List tests ([#2118](https://github.com/ethereum/execution-spec-tests/pull/2118)).

### πŸ§ͺ Test Cases

- ✨ Add safe EIP-6110 workaround to allow Geth/Reth to pass invalid deposit request tests even thought they are out of spec ([#2177](https://github.com/ethereum/execution-spec-tests/pull/2177), [#2233](https://github.com/ethereum/execution-spec-tests/pull/2233)).
- ✨ Add an EIP-7928 test case targeting the `SELFDESTRUCT` opcode. ([#2159](https://github.com/ethereum/execution-spec-tests/pull/2159)).
- ✨ Add essential tests for coverage gaps in EIP-7951 (`p256verify` precompile) ([#2179](https://github.com/ethereum/execution-spec-tests/pull/2159), [#2203](https://github.com/ethereum/execution-spec-tests/pull/2203), [#2215](https://github.com/ethereum/execution-spec-tests/pull/2215), [#2216](https://github.com/ethereum/execution-spec-tests/pull/2216), [#2217](https://github.com/ethereum/execution-spec-tests/pull/2217), [#2218](https://github.com/ethereum/execution-spec-tests/pull/2218), [#2221](https://github.com/ethereum/execution-spec-tests/pull/2221), [#2229](https://github.com/ethereum/execution-spec-tests/pull/2229), [#2230](https://github.com/ethereum/execution-spec-tests/pull/2230), [#2237](https://github.com/ethereum/execution-spec-tests/pull/2237), [#2238](https://github.com/ethereum/execution-spec-tests/pull/2238)).
- ✨ Add EIP-7928 successful and OOG single-opcode tests ([#2118](https://github.com/ethereum/execution-spec-tests/pull/2118)).

## [v5.0.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.0.0) - 2025-09-05

Expand Down
74 changes: 34 additions & 40 deletions src/ethereum_test_types/block_access_list/expectations.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ def _compare_account_expectations(
if field_name not in expected.model_fields_set:
continue

# Check if explicitly set to empty but actual has values
if not expected_list and actual_list:
raise AssertionError(f"Expected {field_name} to be empty but found {actual_list}")

if field_name == "storage_reads":
# storage_reads is a simple list of StorageKey
actual_idx = 0
Expand Down Expand Up @@ -377,49 +381,39 @@ def _compare_account_expectations(

else:
# Handle nonce_changes, balance_changes, code_changes
if not expected_list and actual_list:
# Empty expected but non-empty actual - error
item_type = field_name.replace("_changes", "")
raise AssertionError(
f"Expected {field_name} to be empty but found {actual_list}"
)

# Create tuples for comparison (ordering already validated)
if field_name == "nonce_changes":
expected_tuples = [(c.tx_index, c.post_nonce) for c in expected_list]
actual_tuples = [(c.tx_index, c.post_nonce) for c in actual_list]
item_type = "nonce"
elif field_name == "balance_changes":
expected_tuples = [(c.tx_index, int(c.post_balance)) for c in expected_list]
actual_tuples = [(c.tx_index, int(c.post_balance)) for c in actual_list]
item_type = "balance"
elif field_name == "code_changes":
expected_tuples = [(c.tx_index, bytes(c.new_code)) for c in expected_list]
actual_tuples = [(c.tx_index, bytes(c.new_code)) for c in actual_list]
item_type = "code"
else:
# Create tuples for comparison (ordering already validated)
if field_name == "nonce_changes":
expected_tuples = [(c.tx_index, c.post_nonce) for c in expected_list]
actual_tuples = [(c.tx_index, c.post_nonce) for c in actual_list]
item_type = "nonce"
elif field_name == "balance_changes":
expected_tuples = [
(c.tx_index, int(c.post_balance)) for c in expected_list
]
actual_tuples = [(c.tx_index, int(c.post_balance)) for c in actual_list]
item_type = "balance"
elif field_name == "code_changes":
expected_tuples = [(c.tx_index, bytes(c.new_code)) for c in expected_list]
actual_tuples = [(c.tx_index, bytes(c.new_code)) for c in actual_list]
item_type = "code"
else:
# sanity check
raise ValueError(f"Unexpected field type: {field_name}")

# Check that expected forms a subsequence of actual
actual_idx = 0
for exp_tuple in expected_tuples:
found = False
while actual_idx < len(actual_tuples):
if actual_tuples[actual_idx] == exp_tuple:
found = True
actual_idx += 1
break
# sanity check
raise ValueError(f"Unexpected field type: {field_name}")

# Check that expected forms a subsequence of actual
actual_idx = 0
for exp_tuple in expected_tuples:
found = False
while actual_idx < len(actual_tuples):
if actual_tuples[actual_idx] == exp_tuple:
found = True
actual_idx += 1
break
actual_idx += 1

if not found:
raise AssertionError(
f"{item_type.capitalize()} change {exp_tuple} not found "
f"or not in correct order. Actual changes: {actual_tuples}"
)
if not found:
raise AssertionError(
f"{item_type.capitalize()} change {exp_tuple} not found "
f"or not in correct order. Actual changes: {actual_tuples}"
)


__all__ = [
Expand Down
55 changes: 35 additions & 20 deletions src/ethereum_test_types/tests/test_block_access_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,47 +77,62 @@ def test_empty_list_validation():
[
BalAccountChange(
address=alice,
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
balance_changes=[], # no balance changes
nonce_changes=[],
balance_changes=[],
code_changes=[],
storage_changes=[],
storage_reads=[],
),
]
)

expectation = BlockAccessListExpectation(
account_expectations={
alice: BalAccountExpectation(
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
balance_changes=[], # explicitly expect no balance changes
nonce_changes=[],
balance_changes=[],
code_changes=[],
storage_changes=[],
storage_reads=[],
),
}
)

expectation.verify_against(actual_bal)


def test_empty_list_validation_fails():
@pytest.mark.parametrize(
"field,value",
[
["nonce_changes", BalNonceChange(tx_index=1, post_nonce=1)],
["balance_changes", BalBalanceChange(tx_index=1, post_balance=100)],
["code_changes", BalCodeChange(tx_index=1, new_code=b"code")],
[
"storage_changes",
BalStorageSlot(
slot=0x01,
slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)],
),
],
["storage_reads", 0x01],
],
)
def test_empty_list_validation_fails(field: str, value) -> None:
"""Test that validation fails when expecting empty but field has values."""
alice = Address(0xA)

actual_bal = BlockAccessList(
[
BalAccountChange(
address=alice,
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
),
]
)
bal_acct_change = BalAccountChange(address=alice)
setattr(bal_acct_change, field, [value])
actual_bal = BlockAccessList([bal_acct_change])

expectation = BlockAccessListExpectation(
account_expectations={
# expect no balance changes (wrongly)
alice: BalAccountExpectation(balance_changes=[]),
}
)
alice_acct_expectation = BalAccountExpectation()
setattr(alice_acct_expectation, field, [])

expectation = BlockAccessListExpectation(account_expectations={alice: alice_acct_expectation})

with pytest.raises(
BlockAccessListValidationError,
match="Expected balance_changes to be empty",
match=f"Expected {field} to be empty",
):
expectation.verify_against(actual_bal)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
Block,
BlockchainTestFiller,
Initcode,
Storage,
Transaction,
compute_create_address,
)
Expand Down Expand Up @@ -128,89 +127,6 @@ def test_bal_balance_changes(
)


def test_bal_storage_writes(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
):
"""Ensure BAL captures storage writes."""
storage = Storage({0x01: 0}) # type: ignore
storage_contract = pre.deploy_contract(
code=Op.SSTORE(0x01, 0x42) + Op.STOP,
# pre-fill with canary value to detect writes in post-state
storage=storage.canary(),
)
alice = pre.fund_eoa()

tx = Transaction(
sender=alice,
to=storage_contract,
gas_limit=100000,
)

block = Block(
txs=[tx],
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
storage_contract: BalAccountExpectation(
storage_changes=[
BalStorageSlot(
slot=0x01,
slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)],
)
],
),
}
),
)

blockchain_test(
pre=pre,
blocks=[block],
post={
alice: Account(nonce=1),
storage_contract: Account(storage={0x01: 0x42}),
},
)


def test_bal_storage_reads(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
):
"""Ensure BAL captures storage reads."""
storage_contract = pre.deploy_contract(
code=Op.SLOAD(0x01) + Op.STOP,
storage={0x01: 0x42},
)
alice = pre.fund_eoa()

tx = Transaction(
sender=alice,
to=storage_contract,
gas_limit=100000,
)

block = Block(
txs=[tx],
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
storage_contract: BalAccountExpectation(
storage_reads=[0x01],
),
}
),
)

blockchain_test(
pre=pre,
blocks=[block],
post={
alice: Account(nonce=1),
storage_contract: Account(storage={0x01: 0x42}),
},
)


def test_bal_code_changes(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
Expand Down
Loading
Loading