Skip to content

Commit e45c0ed

Browse files
authored
feat(test): Increase BAL coverage with tests and BAL expectations for some existing (ethereum#2834)
1 parent 87c52b4 commit e45c0ed

13 files changed

Lines changed: 776 additions & 49 deletions

File tree

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,29 +1502,26 @@ def test_bal_precompile_funded(
15021502
)
15031503

15041504

1505-
@pytest.mark.parametrize_by_fork(
1506-
"precompile",
1507-
lambda fork: [
1508-
pytest.param(addr, id=f"0x{int.from_bytes(addr, 'big'):02x}")
1509-
for addr in fork.precompiles()
1510-
],
1511-
)
1512-
def test_bal_precompile_call(
1505+
@pytest.mark.with_all_precompiles
1506+
@pytest.mark.with_all_call_opcodes
1507+
def test_bal_precompile_call_opcode(
15131508
pre: Alloc,
15141509
blockchain_test: BlockchainTestFiller,
1515-
precompile: Address,
1510+
precompile: int,
1511+
call_opcode: Op,
15161512
) -> None:
15171513
"""
1518-
Ensure BAL records precompile when called via contract.
1514+
Ensure BAL records the precompile address regardless of call opcode.
15191515
1520-
Alice calls Oracle contract which calls precompile.
1521-
BAL must include precompile with no balance/storage/code changes.
1516+
Alice calls Oracle contract which invokes the precompile via the
1517+
parametrized call opcode. For DELEGATECALL/CALLCODE the precompile
1518+
provides the code but is not the call target, so its access has to
1519+
be recorded explicitly rather than incidentally.
15221520
"""
15231521
alice = pre.fund_eoa()
15241522

1525-
# Oracle contract that calls the precompile
15261523
oracle = pre.deploy_contract(
1527-
code=Op.CALL(100_000, precompile, 0, 0, 0, 0, 0) + Op.STOP
1524+
code=call_opcode(gas=100_000, address=precompile) + Op.STOP
15281525
)
15291526

15301527
tx = Transaction(

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
"""Tests for the effects of EIP-7702 transactions on EIP-7928."""
22

3+
from typing import Dict
4+
35
import pytest
46
from execution_testing import (
7+
EOA,
58
Account,
9+
Address,
610
Alloc,
711
AuthorizationTuple,
812
BalAccountExpectation,
@@ -515,6 +519,72 @@ def test_bal_7702_invalid_nonce_authorization(
515519
)
516520

517521

522+
@pytest.mark.pre_alloc_mutable()
523+
def test_bal_7702_invalid_authority_has_code_authorization(
524+
pre: Alloc,
525+
blockchain_test: BlockchainTestFiller,
526+
) -> None:
527+
"""
528+
Ensure BAL handles failed authorization where the authority already has
529+
non-empty, non-delegation code (EIP-7702 step-5 rejection, post-load).
530+
"""
531+
# Pre-existing non-delegation code on the authority blocks the 7702
532+
# delegation at step 5, after the authority is already loaded.
533+
alice = pre.fund_eoa(amount=0, code=Op.STOP, nonce=1)
534+
bob = pre.fund_eoa(amount=0)
535+
relayer = pre.fund_eoa()
536+
oracle = pre.deploy_contract(code=Op.STOP)
537+
538+
tx = Transaction(
539+
sender=relayer, # Sponsored transaction
540+
to=bob,
541+
value=10,
542+
gas_limit=1_000_000,
543+
gas_price=0xA,
544+
authorization_list=[
545+
AuthorizationTuple(
546+
address=oracle,
547+
nonce=alice.nonce,
548+
signer=alice,
549+
)
550+
],
551+
)
552+
553+
block = Block(
554+
txs=[tx],
555+
expected_block_access_list=BlockAccessListExpectation(
556+
account_expectations={
557+
bob: BalAccountExpectation(
558+
balance_changes=[
559+
BalBalanceChange(block_access_index=1, post_balance=10)
560+
]
561+
),
562+
relayer: BalAccountExpectation(
563+
nonce_changes=[
564+
BalNonceChange(block_access_index=1, post_nonce=1)
565+
],
566+
),
567+
# Alice loaded at step 4 then step 5 rejects.
568+
alice: BalAccountExpectation.empty(),
569+
# Oracle never loaded as delegation target.
570+
oracle: None,
571+
}
572+
),
573+
)
574+
575+
post = {
576+
relayer: Account(nonce=1),
577+
bob: Account(balance=10),
578+
alice: Account(code=Op.STOP, nonce=1),
579+
}
580+
581+
blockchain_test(
582+
pre=pre,
583+
blocks=[block],
584+
post=post,
585+
)
586+
587+
518588
def test_bal_7702_invalid_chain_id_authorization(
519589
pre: Alloc,
520590
blockchain_test: BlockchainTestFiller,
@@ -639,6 +709,124 @@ def test_bal_7702_delegated_via_call_opcode(
639709
)
640710

641711

712+
@pytest.mark.parametrize(
713+
"destination_is_loop",
714+
[False, True],
715+
ids=["chain", "loop"],
716+
)
717+
def test_bal_7702_multi_hop_delegation_chain(
718+
pre: Alloc,
719+
blockchain_test: BlockchainTestFiller,
720+
destination_is_loop: bool,
721+
) -> None:
722+
"""
723+
Multi-hop EIP-7702 delegation: `chain` resolves A->B->C; `loop`
724+
resolves A->B->A. In both cases the EVM follows the delegation once
725+
and runs B's `0xef0100<dest>` bytecode as legacy code, which aborts
726+
on the INVALID `0xef` opcode. For `chain`, C MUST NOT appear in the
727+
BAL (second-hop target is never loaded as an execution target). For
728+
`loop`, A is already in the BAL via the first delegation; the CALL
729+
still fails.
730+
"""
731+
alice = pre.fund_eoa()
732+
auth_a = pre.fund_eoa(amount=0)
733+
auth_b = pre.fund_eoa(amount=0)
734+
target_c = pre.deploy_contract(code=Op.STOP)
735+
second_destination = auth_a if destination_is_loop else target_c
736+
737+
entry_code = Op.SSTORE(0, Op.CALL(50_000, auth_a, 0, 0, 0, 0, 0)) + Op.STOP
738+
entry_address = pre.deploy_contract(
739+
code=entry_code,
740+
storage={0: 0xDEAD},
741+
)
742+
743+
tx = Transaction(
744+
sender=alice,
745+
to=entry_address,
746+
gas_limit=1_000_000,
747+
gas_price=0xA,
748+
authorization_list=[
749+
AuthorizationTuple(
750+
address=auth_b,
751+
nonce=0,
752+
signer=auth_a,
753+
),
754+
AuthorizationTuple(
755+
address=second_destination,
756+
nonce=0,
757+
signer=auth_b,
758+
),
759+
],
760+
)
761+
762+
account_expectations: Dict[EOA | Address, BalAccountExpectation | None] = {
763+
alice: BalAccountExpectation(
764+
nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)],
765+
),
766+
auth_a: BalAccountExpectation(
767+
nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)],
768+
code_changes=[
769+
BalCodeChange(
770+
block_access_index=1,
771+
new_code=Spec7702.delegation_designation(auth_b),
772+
)
773+
],
774+
),
775+
auth_b: BalAccountExpectation(
776+
nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)],
777+
code_changes=[
778+
BalCodeChange(
779+
block_access_index=1,
780+
new_code=Spec7702.delegation_designation(
781+
second_destination
782+
),
783+
)
784+
],
785+
),
786+
# CALL returned 0; SSTORE(0, 0) overwrites the 0xDEAD witness.
787+
entry_address: BalAccountExpectation(
788+
storage_changes=[
789+
BalStorageSlot(
790+
slot=0,
791+
slot_changes=[
792+
BalStorageChange(block_access_index=1, post_value=0)
793+
],
794+
)
795+
],
796+
storage_reads=[],
797+
),
798+
}
799+
if not destination_is_loop:
800+
account_expectations[target_c] = None
801+
802+
block = Block(
803+
txs=[tx],
804+
expected_block_access_list=BlockAccessListExpectation(
805+
account_expectations=account_expectations
806+
),
807+
)
808+
809+
post: Dict[EOA | Address, Account] = {
810+
auth_a: Account(
811+
nonce=1,
812+
code=Spec7702.delegation_designation(auth_b),
813+
),
814+
auth_b: Account(
815+
nonce=1,
816+
code=Spec7702.delegation_designation(second_destination),
817+
),
818+
entry_address: Account(storage={0: 0}),
819+
}
820+
if not destination_is_loop:
821+
post[target_c] = Account(code=bytes(Op.STOP), nonce=1)
822+
823+
blockchain_test(
824+
pre=pre,
825+
blocks=[block],
826+
post=post,
827+
)
828+
829+
642830
def test_bal_7702_null_address_delegation_no_code_change(
643831
pre: Alloc,
644832
blockchain_test: BlockchainTestFiller,

0 commit comments

Comments
 (0)