Skip to content

Commit 476f3c3

Browse files
raxhvlfselmo
andauthored
✨ feat(tests): EIP-7928 test cases for EIP-2930 transactions (#2167)
* ✨ feat(tests): EIP-7928 tests for EIP-2930: 🧹 chore(tests): Move to new test file ✨ feat(tests): EIP-7928test_bal_2930_slot_listed_and_unlisted_reads ✨ feat(tests): EIP-7928 test_bal_2930_slot_listed_and_unlisted_writes ✨ feat(tests): EIP-7928 test_bal_2930_slot_listed_but_untouched * fix: issues after refactor; rename test file * ✨ feat(test): test_bal_2930_account_listed_but_untouched * chore: move 2930 tests into main test file * chore: add changelog entry * fix: fix rebase error for OOG 7928 tests * fix: validate expected empty account changes --------- Co-authored-by: raxhvl <[email protected]> Co-authored-by: fselmo <[email protected]>
1 parent 28ca6de commit 476f3c3

File tree

6 files changed

+280
-14
lines changed

6 files changed

+280
-14
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Test fixtures for use by clients are available for each release on the [Github r
3838
- ✨ Add an EIP-7928 test case targeting the `SELFDESTRUCT` opcode. ([#2159](https://github.com/ethereum/execution-spec-tests/pull/2159)).
3939
- ✨ 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)).
4040
- ✨ Add EIP-7928 successful and OOG single-opcode tests ([#2118](https://github.com/ethereum/execution-spec-tests/pull/2118)).
41+
- ✨ Add EIP-7928 tests for EIP-2930 interactions ([#2167](https://github.com/ethereum/execution-spec-tests/pull/2167)).
4142

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

src/ethereum_test_types/block_access_list/expectations.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ def verify_against(self, actual_bal: "BlockAccessList") -> None:
168168
raise BlockAccessListValidationError(
169169
f"Address {address} should not be in BAL but was found"
170170
)
171+
elif expectation == BalAccountExpectation():
172+
# explicit check for NO account changes for the address
173+
if actual_accounts_by_addr.get(address) != BalAccountChange(address=address):
174+
raise BlockAccessListValidationError(
175+
f"No account changes expected for {address} but found "
176+
f"changes: {actual_accounts_by_addr[address]}"
177+
)
171178
else:
172179
# check address is present and validate changes
173180
if address not in actual_accounts_by_addr:

src/ethereum_test_types/tests/test_block_access_lists.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,31 @@ def test_address_exclusion_validation_raises_when_address_is_present():
6969
expectation.verify_against(actual_bal)
7070

7171

72+
def test_empty_account_changes_raises_when_changes_are_present():
73+
"""
74+
Test that validation fails when expected empty changes but actual
75+
has changes.
76+
"""
77+
alice = Address(0xA)
78+
79+
actual_bal = BlockAccessList(
80+
[
81+
BalAccountChange(
82+
address=alice,
83+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
84+
),
85+
]
86+
)
87+
88+
expectation = BlockAccessListExpectation(account_expectations={alice: BalAccountExpectation()})
89+
90+
with pytest.raises(
91+
BlockAccessListValidationError,
92+
match=f"No account changes expected for {alice}",
93+
):
94+
expectation.verify_against(actual_bal)
95+
96+
7297
def test_empty_list_validation():
7398
"""Test that empty list validates correctly."""
7499
alice = Address(0xA)
@@ -121,12 +146,30 @@ def test_empty_list_validation_fails(field: str, value) -> None:
121146
"""Test that validation fails when expecting empty but field has values."""
122147
alice = Address(0xA)
123148

124-
bal_acct_change = BalAccountChange(address=alice)
125-
setattr(bal_acct_change, field, [value])
126-
actual_bal = BlockAccessList([bal_acct_change])
149+
alice_acct_change = BalAccountChange(
150+
address=alice,
151+
storage_reads=[0x02],
152+
)
153+
154+
if field == "storage_reads":
155+
alice_acct_change.storage_reads = [value]
156+
# set another field to non-empty to avoid all-empty account change
157+
alice_acct_change.nonce_changes = [BalNonceChange(tx_index=1, post_nonce=1)]
158+
159+
else:
160+
setattr(alice_acct_change, field, [value])
161+
actual_bal = BlockAccessList([alice_acct_change])
127162

128-
alice_acct_expectation = BalAccountExpectation()
129-
setattr(alice_acct_expectation, field, [])
163+
alice_acct_expectation = BalAccountExpectation(
164+
storage_reads=[0x02],
165+
)
166+
if field == "storage_reads":
167+
alice_acct_expectation.storage_reads = []
168+
# match the filled field in actual to avoid all-empty
169+
# account expectation
170+
alice_acct_expectation.nonce_changes = [BalNonceChange(tx_index=1, post_nonce=1)]
171+
else:
172+
setattr(alice_acct_expectation, field, [])
130173

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

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py

Lines changed: 217 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66

7-
from ethereum_test_base_types import Address
7+
from ethereum_test_base_types import AccessList, Address, Hash
88
from ethereum_test_tools import (
99
Account,
1010
Alloc,
@@ -345,7 +345,7 @@ def test_bal_self_destruct(
345345
pytest.param(lambda target_addr: Op.EXTCODESIZE(target_addr), id="extcodesize"),
346346
pytest.param(lambda target_addr: Op.EXTCODECOPY(target_addr, 0, 0, 32), id="extcodecopy"),
347347
pytest.param(lambda target_addr: Op.EXTCODEHASH(target_addr), id="extcodehash"),
348-
pytest.param(lambda target_addr: Op.CALL(0, target_addr, 50, 0, 0, 0, 0), id="call"),
348+
pytest.param(lambda target_addr: Op.CALL(0, target_addr, 0, 0, 0, 0, 0), id="call"),
349349
pytest.param(
350350
lambda target_addr: Op.CALLCODE(0, target_addr, 0, 0, 0, 0, 0), id="callcode"
351351
),
@@ -647,3 +647,218 @@ def test_bal_block_rewards(
647647
blocks=[block],
648648
post={},
649649
)
650+
651+
652+
def test_bal_2930_account_listed_but_untouched(
653+
pre: Alloc,
654+
blockchain_test: BlockchainTestFiller,
655+
):
656+
"""Ensure BAL excludes untouched access list accounts."""
657+
alice = pre.fund_eoa()
658+
bob = pre.fund_eoa()
659+
oracle = pre.deploy_contract(code=Op.STOP)
660+
661+
access_list = AccessList(
662+
address=oracle,
663+
storage_keys=[Hash(0x1)],
664+
)
665+
666+
gas_limit = 1_000_000
667+
668+
tx = Transaction(ty=1, sender=alice, to=bob, gas_limit=gas_limit, access_list=[access_list])
669+
670+
block = Block(
671+
txs=[tx],
672+
expected_block_access_list=BlockAccessListExpectation(
673+
account_expectations={
674+
alice: BalAccountExpectation(
675+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
676+
),
677+
# The address excluded from BAL since state is not accessed
678+
oracle: None,
679+
}
680+
),
681+
)
682+
683+
blockchain_test(
684+
pre=pre,
685+
blocks=[block],
686+
post={
687+
alice: Account(nonce=1),
688+
},
689+
)
690+
691+
692+
def test_bal_2930_slot_listed_but_untouched(
693+
pre: Alloc,
694+
blockchain_test: BlockchainTestFiller,
695+
fork,
696+
):
697+
"""Ensure BAL excludes untouched access list storage slots."""
698+
alice = pre.fund_eoa()
699+
pure_calculator = pre.deploy_contract(
700+
# Pure add operation
701+
Op.ADD(35, 7)
702+
)
703+
704+
access_list = AccessList(
705+
address=pure_calculator,
706+
storage_keys=[Hash(0x1)],
707+
)
708+
709+
intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
710+
gas_limit = (
711+
intrinsic_gas_calculator(
712+
calldata=b"",
713+
contract_creation=False,
714+
access_list=[access_list],
715+
)
716+
+ 1000
717+
) # intrinsic + buffer
718+
719+
tx = Transaction(
720+
ty=1, sender=alice, to=pure_calculator, gas_limit=gas_limit, access_list=[access_list]
721+
)
722+
723+
block = Block(
724+
txs=[tx],
725+
expected_block_access_list=BlockAccessListExpectation(
726+
account_expectations={
727+
alice: BalAccountExpectation(
728+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
729+
),
730+
# The account was loaded.
731+
pure_calculator: BalAccountExpectation(),
732+
}
733+
),
734+
)
735+
736+
blockchain_test(
737+
pre=pre,
738+
blocks=[block],
739+
post={
740+
alice: Account(nonce=1),
741+
},
742+
)
743+
744+
745+
def test_bal_2930_slot_listed_and_unlisted_writes(
746+
pre: Alloc,
747+
blockchain_test: BlockchainTestFiller,
748+
fork,
749+
):
750+
"""
751+
Ensure BAL includes storage writes regardless of access list presence.
752+
"""
753+
alice = pre.fund_eoa()
754+
storage_writer = pre.deploy_contract(code=Op.SSTORE(0x01, 0x42) + Op.SSTORE(0x02, 0x43))
755+
756+
# Access list only includes slot 0x01, but contract writes to both
757+
# 0x01 and 0x02
758+
access_list = AccessList(
759+
address=storage_writer,
760+
storage_keys=[Hash(0x01)],
761+
)
762+
763+
intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
764+
gas_limit = (
765+
intrinsic_gas_calculator(
766+
calldata=b"",
767+
contract_creation=False,
768+
access_list=[access_list],
769+
)
770+
+ 50000
771+
) # intrinsic + buffer for storage writes
772+
773+
tx = Transaction(
774+
ty=1, sender=alice, to=storage_writer, gas_limit=gas_limit, access_list=[access_list]
775+
)
776+
777+
block = Block(
778+
txs=[tx],
779+
expected_block_access_list=BlockAccessListExpectation(
780+
account_expectations={
781+
alice: BalAccountExpectation(
782+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
783+
),
784+
storage_writer: BalAccountExpectation(
785+
storage_changes=[
786+
BalStorageSlot(
787+
slot=0x01,
788+
slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)],
789+
),
790+
BalStorageSlot(
791+
slot=0x02,
792+
slot_changes=[BalStorageChange(tx_index=1, post_value=0x43)],
793+
),
794+
],
795+
),
796+
}
797+
),
798+
)
799+
800+
blockchain_test(
801+
pre=pre,
802+
blocks=[block],
803+
post={
804+
alice: Account(nonce=1),
805+
storage_writer: Account(storage={0x01: 0x42, 0x02: 0x43}),
806+
},
807+
)
808+
809+
810+
def test_bal_2930_slot_listed_and_unlisted_reads(
811+
pre: Alloc,
812+
blockchain_test: BlockchainTestFiller,
813+
fork,
814+
):
815+
"""Ensure BAL includes storage reads regardless of access list presence."""
816+
alice = pre.fund_eoa()
817+
storage_reader = pre.deploy_contract(
818+
code=Op.SLOAD(0x01) + Op.SLOAD(0x02),
819+
storage={0x01: 0x42, 0x02: 0x43}, # Pre-populate storage with values
820+
)
821+
822+
# Access list only includes slot 0x01, but contract reads from both
823+
# 0x01 and 0x02
824+
access_list = AccessList(
825+
address=storage_reader,
826+
storage_keys=[Hash(0x01)],
827+
)
828+
829+
intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
830+
gas_limit = (
831+
intrinsic_gas_calculator(
832+
calldata=b"",
833+
contract_creation=False,
834+
access_list=[access_list],
835+
)
836+
+ 50000
837+
) # intrinsic + buffer for storage reads
838+
839+
tx = Transaction(
840+
ty=1, sender=alice, to=storage_reader, gas_limit=gas_limit, access_list=[access_list]
841+
)
842+
843+
block = Block(
844+
txs=[tx],
845+
expected_block_access_list=BlockAccessListExpectation(
846+
account_expectations={
847+
alice: BalAccountExpectation(
848+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
849+
),
850+
storage_reader: BalAccountExpectation(
851+
storage_reads=[0x01, 0x02],
852+
),
853+
}
854+
),
855+
)
856+
857+
blockchain_test(
858+
pre=pre,
859+
blocks=[block],
860+
post={
861+
alice: Account(nonce=1),
862+
storage_reader: Account(storage={0x01: 0x42, 0x02: 0x43}),
863+
},
864+
)

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ def test_bal_sload_and_oog(
153153
sload_cold_cost = gas_costs.G_COLD_SLOAD
154154
tx_gas_limit = intrinsic_gas_cost + push_cost + sload_cold_cost
155155

156-
# if fails_at_sload:
157-
# # subtract 1 gas to ensure OOG at SLOAD
158-
# tx_gas_limit -= 1
156+
if fails_at_sload:
157+
# subtract 1 gas to ensure OOG at SLOAD
158+
tx_gas_limit -= 1
159159

160160
tx = Transaction(
161161
sender=alice,

0 commit comments

Comments
 (0)