Skip to content

Commit 28e339e

Browse files
raxhvlfselmo
andauthored
✨ feat(tests): EIP-7928 account access test cases (#2182)
* ✨ feat(tests): test_bal_account_access_target * ✨ feat(tests): test_bal_call_with_value_transfer * ✨ feat(tests): test_bal_callcode_with_value_transfer * 🧹 chore: Global fork marker * ✨ feat(tests): test_bal_delegated_storage_writes * ✨ feat(tests): test_bal_delegated_storage_reads * ✨ feat(test): test_bal_block_rewards * refactor: rebase and fix conflicts --------- Co-authored-by: raxhvl <[email protected]> Co-authored-by: fselmo <[email protected]>
1 parent 20c1b63 commit 28e339e

File tree

2 files changed

+320
-6
lines changed

2 files changed

+320
-6
lines changed

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py

Lines changed: 313 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131
REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path
3232
REFERENCE_SPEC_VERSION = ref_spec_7928.version
3333

34+
pytestmark = pytest.mark.valid_from("Amsterdam")
35+
3436

35-
@pytest.mark.valid_from("Amsterdam")
3637
def test_bal_nonce_changes(
3738
pre: Alloc,
3839
blockchain_test: BlockchainTestFiller,
@@ -68,7 +69,6 @@ def test_bal_nonce_changes(
6869
)
6970

7071

71-
@pytest.mark.valid_from("Amsterdam")
7272
def test_bal_balance_changes(
7373
pre: Alloc,
7474
blockchain_test: BlockchainTestFiller,
@@ -128,7 +128,6 @@ def test_bal_balance_changes(
128128
)
129129

130130

131-
@pytest.mark.valid_from("Amsterdam")
132131
def test_bal_storage_writes(
133132
pre: Alloc,
134133
blockchain_test: BlockchainTestFiller,
@@ -174,7 +173,6 @@ def test_bal_storage_writes(
174173
)
175174

176175

177-
@pytest.mark.valid_from("Amsterdam")
178176
def test_bal_storage_reads(
179177
pre: Alloc,
180178
blockchain_test: BlockchainTestFiller,
@@ -213,7 +211,6 @@ def test_bal_storage_reads(
213211
)
214212

215213

216-
@pytest.mark.valid_from("Amsterdam")
217214
def test_bal_code_changes(
218215
pre: Alloc,
219216
blockchain_test: BlockchainTestFiller,
@@ -290,7 +287,6 @@ def test_bal_code_changes(
290287
)
291288

292289

293-
@pytest.mark.valid_from("Amsterdam")
294290
@pytest.mark.parametrize("self_destruct_in_same_tx", [True, False], ids=["same_tx", "new_tx"])
295291
@pytest.mark.parametrize("pre_funded", [True, False], ids=["pre_funded", "not_pre_funded"])
296292
def test_bal_self_destruct(
@@ -424,3 +420,314 @@ def test_bal_self_destruct(
424420
blocks=[block],
425421
post=post,
426422
)
423+
424+
425+
@pytest.mark.parametrize(
426+
"account_access_opcode",
427+
[
428+
pytest.param(lambda target_addr: Op.BALANCE(target_addr), id="balance"),
429+
pytest.param(lambda target_addr: Op.EXTCODESIZE(target_addr), id="extcodesize"),
430+
pytest.param(lambda target_addr: Op.EXTCODECOPY(target_addr, 0, 0, 32), id="extcodecopy"),
431+
pytest.param(lambda target_addr: Op.EXTCODEHASH(target_addr), id="extcodehash"),
432+
pytest.param(lambda target_addr: Op.CALL(0, target_addr, 50, 0, 0, 0, 0), id="call"),
433+
pytest.param(
434+
lambda target_addr: Op.CALLCODE(0, target_addr, 0, 0, 0, 0, 0), id="callcode"
435+
),
436+
pytest.param(
437+
lambda target_addr: Op.DELEGATECALL(0, target_addr, 0, 0, 0, 0), id="delegatecall"
438+
),
439+
pytest.param(
440+
lambda target_addr: Op.STATICCALL(0, target_addr, 0, 0, 0, 0), id="staticcall"
441+
),
442+
],
443+
)
444+
def test_bal_account_access_target(
445+
pre: Alloc,
446+
blockchain_test: BlockchainTestFiller,
447+
account_access_opcode,
448+
):
449+
"""Ensure BAL captures target address of account access opcodes."""
450+
alice = pre.fund_eoa()
451+
target_contract = pre.deploy_contract(code=Op.STOP)
452+
453+
oracle_contract = pre.deploy_contract(
454+
balance=100,
455+
code=account_access_opcode(target_contract),
456+
)
457+
458+
tx = Transaction(sender=alice, to=oracle_contract, gas_limit=5_000_000, gas_price=0xA)
459+
460+
block = Block(
461+
txs=[tx],
462+
expected_block_access_list=BlockAccessListExpectation(
463+
account_expectations={
464+
alice: BalAccountExpectation(
465+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)]
466+
),
467+
target_contract: BalAccountExpectation(),
468+
oracle_contract: BalAccountExpectation(),
469+
}
470+
),
471+
)
472+
473+
blockchain_test(pre=pre, blocks=[block], post={})
474+
475+
476+
def test_bal_call_with_value_transfer(
477+
pre: Alloc,
478+
blockchain_test: BlockchainTestFiller,
479+
):
480+
"""
481+
Ensure BAL captures balance changes from CALL opcode with
482+
value transfer.
483+
"""
484+
alice = pre.fund_eoa()
485+
bob = pre.fund_eoa(amount=0)
486+
487+
# Oracle contract that uses CALL to transfer 100 wei to Bob
488+
oracle_code = Op.CALL(0, bob, 100, 0, 0, 0, 0)
489+
oracle_contract = pre.deploy_contract(code=oracle_code, balance=200)
490+
491+
tx = Transaction(sender=alice, to=oracle_contract, gas_limit=1_000_000, gas_price=0xA)
492+
493+
block = Block(
494+
txs=[tx],
495+
expected_block_access_list=BlockAccessListExpectation(
496+
account_expectations={
497+
alice: BalAccountExpectation(
498+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
499+
),
500+
oracle_contract: BalAccountExpectation(
501+
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
502+
),
503+
bob: BalAccountExpectation(
504+
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
505+
),
506+
}
507+
),
508+
)
509+
510+
blockchain_test(pre=pre, blocks=[block], post={})
511+
512+
513+
def test_bal_callcode_with_value_transfer(
514+
pre: Alloc,
515+
blockchain_test: BlockchainTestFiller,
516+
):
517+
"""
518+
Ensure BAL captures balance changes from CALLCODE opcode with
519+
value transfer.
520+
"""
521+
alice = pre.fund_eoa()
522+
bob = pre.fund_eoa(amount=0)
523+
524+
# TargetContract sends 100 wei to bob
525+
target_code = Op.CALL(0, bob, 100, 0, 0, 0, 0)
526+
target_contract = pre.deploy_contract(code=target_code)
527+
528+
# Oracle contract that uses CALLCODE to execute TargetContract's code
529+
oracle_code = Op.CALLCODE(50_000, target_contract, 100, 0, 0, 0, 0)
530+
oracle_contract = pre.deploy_contract(code=oracle_code, balance=200)
531+
532+
tx = Transaction(sender=alice, to=oracle_contract, gas_limit=1_000_000, gas_price=0xA)
533+
534+
block = Block(
535+
txs=[tx],
536+
expected_block_access_list=BlockAccessListExpectation(
537+
account_expectations={
538+
alice: BalAccountExpectation(
539+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
540+
),
541+
oracle_contract: BalAccountExpectation(
542+
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
543+
),
544+
bob: BalAccountExpectation(
545+
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
546+
),
547+
target_contract: BalAccountExpectation(),
548+
}
549+
),
550+
)
551+
552+
blockchain_test(pre=pre, blocks=[block], post={})
553+
554+
555+
@pytest.mark.parametrize(
556+
"delegated_opcode",
557+
[
558+
pytest.param(
559+
lambda target_addr: Op.DELEGATECALL(50000, target_addr, 0, 0, 0, 0), id="delegatecall"
560+
),
561+
pytest.param(
562+
lambda target_addr: Op.CALLCODE(50000, target_addr, 0, 0, 0, 0, 0), id="callcode"
563+
),
564+
],
565+
)
566+
def test_bal_delegated_storage_writes(
567+
pre: Alloc,
568+
blockchain_test: BlockchainTestFiller,
569+
delegated_opcode,
570+
):
571+
"""
572+
Ensure BAL captures delegated storage writes via
573+
DELEGATECALL and CALLCODE.
574+
"""
575+
alice = pre.fund_eoa()
576+
577+
# TargetContract that writes 0x42 to slot 0x01
578+
target_code = Op.SSTORE(0x01, 0x42)
579+
target_contract = pre.deploy_contract(code=target_code)
580+
581+
# Oracle contract that uses delegated opcode to execute
582+
# TargetContract's code
583+
oracle_code = delegated_opcode(target_contract)
584+
oracle_contract = pre.deploy_contract(code=oracle_code)
585+
586+
tx = Transaction(
587+
sender=alice,
588+
to=oracle_contract,
589+
gas_limit=1_000_000,
590+
)
591+
592+
block = Block(
593+
txs=[tx],
594+
expected_block_access_list=BlockAccessListExpectation(
595+
account_expectations={
596+
alice: BalAccountExpectation(
597+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
598+
),
599+
oracle_contract: BalAccountExpectation(
600+
storage_changes=[
601+
BalStorageSlot(
602+
slot=0x01,
603+
slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)],
604+
)
605+
],
606+
),
607+
target_contract: BalAccountExpectation(),
608+
}
609+
),
610+
)
611+
612+
blockchain_test(pre=pre, blocks=[block], post={})
613+
614+
615+
@pytest.mark.parametrize(
616+
"delegated_opcode",
617+
[
618+
pytest.param(
619+
lambda target_addr: Op.DELEGATECALL(50000, target_addr, 0, 0, 0, 0), id="delegatecall"
620+
),
621+
pytest.param(
622+
lambda target_addr: Op.CALLCODE(50000, target_addr, 0, 0, 0, 0, 0), id="callcode"
623+
),
624+
],
625+
)
626+
def test_bal_delegated_storage_reads(
627+
pre: Alloc,
628+
blockchain_test: BlockchainTestFiller,
629+
delegated_opcode,
630+
):
631+
"""
632+
Ensure BAL captures delegated storage reads via
633+
DELEGATECALL and CALLCODE.
634+
"""
635+
alice = pre.fund_eoa()
636+
637+
# TargetContract that reads from slot 0x01
638+
target_code = Op.SLOAD(0x01) + Op.STOP
639+
target_contract = pre.deploy_contract(code=target_code)
640+
641+
# Oracle contract with storage slot 0x01 = 0x42,
642+
# uses delegated opcode to execute TargetContract's code
643+
oracle_code = delegated_opcode(target_contract)
644+
oracle_contract = pre.deploy_contract(code=oracle_code, storage={0x01: 0x42})
645+
646+
tx = Transaction(
647+
sender=alice,
648+
to=oracle_contract,
649+
gas_limit=1_000_000,
650+
)
651+
652+
block = Block(
653+
txs=[tx],
654+
expected_block_access_list=BlockAccessListExpectation(
655+
account_expectations={
656+
alice: BalAccountExpectation(
657+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
658+
),
659+
oracle_contract: BalAccountExpectation(
660+
storage_reads=[0x01],
661+
),
662+
target_contract: BalAccountExpectation(),
663+
}
664+
),
665+
)
666+
667+
blockchain_test(pre=pre, blocks=[block], post={})
668+
669+
670+
def test_bal_block_rewards(
671+
pre: Alloc,
672+
blockchain_test: BlockchainTestFiller,
673+
fork,
674+
):
675+
"""Ensure BAL captures fee recipient balance changes from block rewards."""
676+
alice_initial_balance = 1_000_000
677+
alice = pre.fund_eoa(amount=alice_initial_balance)
678+
bob = pre.fund_eoa(amount=0)
679+
charlie = pre.fund_eoa(amount=0) # fee recipient
680+
681+
intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
682+
intrinsic_gas_cost = intrinsic_gas_calculator(
683+
calldata=b"",
684+
contract_creation=False,
685+
access_list=[],
686+
)
687+
tx_gas_limit = intrinsic_gas_cost + 1000 # add a small buffer
688+
gas_price = 0xA
689+
base_fee_per_gas = 0x2 # Set base fee for EIP-1559
690+
691+
tx = Transaction(
692+
sender=alice,
693+
to=bob,
694+
value=100,
695+
gas_limit=tx_gas_limit,
696+
gas_price=gas_price,
697+
)
698+
699+
# EIP-1559 fee calculation:
700+
# - Total gas cost
701+
total_gas_cost = intrinsic_gas_cost * gas_price
702+
# - Tip portion
703+
tip_to_charlie = intrinsic_gas_cost * (gas_price - base_fee_per_gas)
704+
705+
alice_final_balance = alice_initial_balance - 100 - total_gas_cost
706+
707+
block = Block(
708+
txs=[tx],
709+
fee_recipient=charlie, # Set Charlie as the fee recipient
710+
base_fee_per_gas=base_fee_per_gas, # Set base fee for EIP-1559
711+
expected_block_access_list=BlockAccessListExpectation(
712+
account_expectations={
713+
alice: BalAccountExpectation(
714+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
715+
balance_changes=[
716+
BalBalanceChange(tx_index=1, post_balance=alice_final_balance)
717+
],
718+
),
719+
bob: BalAccountExpectation(
720+
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
721+
),
722+
charlie: BalAccountExpectation(
723+
balance_changes=[BalBalanceChange(tx_index=1, post_balance=tip_to_charlie)],
724+
),
725+
}
726+
),
727+
)
728+
729+
blockchain_test(
730+
pre=pre,
731+
blocks=[block],
732+
post={},
733+
)

tests/amsterdam/eip7928_block_level_access_lists/test_cases.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
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 |
1010
| `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 |
1111
| `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 |
12+
| `test_bal_account_access_target` | Ensure BAL captures target addresses of account access opcodes | Alice calls `Oracle` contract which uses account access opcodes (`BALANCE`, `EXTCODESIZE`, `EXTCODECOPY`, `EXTCODEHASH`, `CALL`, `CALLCODE`, `DELEGATECALL`, `STATICCALL`) on `TargetContract`. | BAL MUST include Alice, `Oracle`, and `TargetContract` with empty changes for `TargetContract` and nonce changes for Alice. | ✅ Completed |
13+
| `test_bal_call_with_value_transfer` | Ensure BAL captures balance changes from `CALL` opcode with value transfer | Alice calls `Oracle` contract (200 wei balance) which uses `CALL` opcode to transfer 100 wei to Bob (0 wei balance). | BAL MUST include Alice (nonce changes), Oracle (balance change to 100 wei), and Bob (balance change to 100 wei). | ✅ Completed |
14+
| `test_bal_callcode_with_value_transfer` | Ensure BAL captures balance changes from `CALLCODE` opcode with value transfer | Alice calls `Oracle` contract (200 wei balance) which uses `CALLCODE` opcode to execute `TargetContract`'s code with 100 wei value transfer to Bob (0 wei balance). | BAL MUST include Alice (nonce changes), `Oracle` (balance change to 100 wei), Bob (balance change to 100 wei), and `TargetContract` (empty changes). | ✅ Completed |
15+
| `test_bal_delegated_storage_writes` | Ensure BAL captures delegated storage writes via `DELEGATECALL` and `CALLCODE` | Alice calls `Oracle` contract which uses `DELEGATECALL`/`CALLCODE` to `TargetContract` that writes `0x42` to slot `0x01`. | BAL MUST include Alice (nonce changes), `Oracle` (storage changes for slot `0x01` = `0x42`), and `TargetContract` (empty changes). | ✅ Completed |
16+
| `test_bal_delegated_storage_reads` | Ensure BAL captures delegated storage reads via `DELEGATECALL` and `CALLCODE` | Alice calls `Oracle` contract (with slot `0x01` = `0x42`) which uses `DELEGATECALL`/`CALLCODE` to `TargetContract` that reads from slot `0x01`. | BAL MUST include Alice (nonce changes), `Oracle` (storage reads for slot `0x01`), and `TargetContract` (empty changes). | ✅ Completed |
17+
| `test_bal_block_rewards` | BAL tracks fee recipient balance changes from block rewards | Alice sends 100 wei to Bob with Charlie as fee recipient | BAL MUST include fee recipient Charlie with `balance_changes` reflecting transaction fees collected from the block. | ✅ Completed |
18+
| `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 |
1219
| `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 |
1320
| `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 |
1421
| `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 |

0 commit comments

Comments
 (0)