|
1 | 1 | """Tests for the effects of EIP-7702 transactions on EIP-7928.""" |
2 | 2 |
|
| 3 | +from typing import Dict |
| 4 | + |
3 | 5 | import pytest |
4 | 6 | from execution_testing import ( |
| 7 | + EOA, |
5 | 8 | Account, |
| 9 | + Address, |
6 | 10 | Alloc, |
7 | 11 | AuthorizationTuple, |
8 | 12 | BalAccountExpectation, |
@@ -515,6 +519,72 @@ def test_bal_7702_invalid_nonce_authorization( |
515 | 519 | ) |
516 | 520 |
|
517 | 521 |
|
| 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 | + |
518 | 588 | def test_bal_7702_invalid_chain_id_authorization( |
519 | 589 | pre: Alloc, |
520 | 590 | blockchain_test: BlockchainTestFiller, |
@@ -639,6 +709,124 @@ def test_bal_7702_delegated_via_call_opcode( |
639 | 709 | ) |
640 | 710 |
|
641 | 711 |
|
| 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 | + |
642 | 830 | def test_bal_7702_null_address_delegation_no_code_change( |
643 | 831 | pre: Alloc, |
644 | 832 | blockchain_test: BlockchainTestFiller, |
|
0 commit comments