|
5 | 5 | import pytest
|
6 | 6 |
|
7 | 7 | from ethereum_test_base_types import AccessList, Address, Hash
|
| 8 | +from ethereum_test_forks import Fork |
8 | 9 | from ethereum_test_tools import (
|
9 | 10 | Account,
|
10 | 11 | Alloc,
|
@@ -862,3 +863,249 @@ def test_bal_2930_slot_listed_and_unlisted_reads(
|
862 | 863 | storage_reader: Account(storage={0x01: 0x42, 0x02: 0x43}),
|
863 | 864 | },
|
864 | 865 | )
|
| 866 | + |
| 867 | + |
| 868 | +def test_bal_self_transfer( |
| 869 | + pre: Alloc, |
| 870 | + blockchain_test: BlockchainTestFiller, |
| 871 | + fork, |
| 872 | +): |
| 873 | + """Test that BAL correctly handles self-transfers.""" |
| 874 | + start_balance = 1_000_000 |
| 875 | + alice = pre.fund_eoa(amount=start_balance) |
| 876 | + |
| 877 | + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() |
| 878 | + intrinsic_gas_cost = intrinsic_gas_calculator() |
| 879 | + |
| 880 | + tx = Transaction( |
| 881 | + sender=alice, to=alice, gas_limit=intrinsic_gas_cost, value=100, gas_price=0xA |
| 882 | + ) |
| 883 | + |
| 884 | + block = Block( |
| 885 | + txs=[tx], |
| 886 | + expected_block_access_list=BlockAccessListExpectation( |
| 887 | + account_expectations={ |
| 888 | + alice: BalAccountExpectation( |
| 889 | + nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)], |
| 890 | + balance_changes=[ |
| 891 | + BalBalanceChange( |
| 892 | + tx_index=1, |
| 893 | + post_balance=start_balance - intrinsic_gas_cost * tx.gas_price, |
| 894 | + ) |
| 895 | + ], |
| 896 | + ) |
| 897 | + } |
| 898 | + ), |
| 899 | + ) |
| 900 | + |
| 901 | + blockchain_test(pre=pre, blocks=[block], post={}) |
| 902 | + |
| 903 | + |
| 904 | +def test_bal_zero_value_transfer( |
| 905 | + pre: Alloc, |
| 906 | + blockchain_test: BlockchainTestFiller, |
| 907 | + fork, |
| 908 | +): |
| 909 | + """Test that BAL correctly handles zero-value transfers.""" |
| 910 | + start_balance = 1_000_000 |
| 911 | + alice = pre.fund_eoa(amount=start_balance) |
| 912 | + bob = pre.fund_eoa(amount=100) |
| 913 | + |
| 914 | + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() |
| 915 | + intrinsic_gas_cost = intrinsic_gas_calculator() |
| 916 | + |
| 917 | + tx = Transaction(sender=alice, to=bob, gas_limit=intrinsic_gas_cost, value=0, gas_price=0xA) |
| 918 | + |
| 919 | + block = Block( |
| 920 | + txs=[tx], |
| 921 | + expected_block_access_list=BlockAccessListExpectation( |
| 922 | + account_expectations={ |
| 923 | + alice: BalAccountExpectation( |
| 924 | + nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)], |
| 925 | + balance_changes=[ |
| 926 | + BalBalanceChange( |
| 927 | + tx_index=1, |
| 928 | + post_balance=start_balance - intrinsic_gas_cost * tx.gas_price, |
| 929 | + ) |
| 930 | + ], |
| 931 | + ), |
| 932 | + # Include the address; omit from balance_changes. |
| 933 | + bob: BalAccountExpectation(balance_changes=[]), |
| 934 | + } |
| 935 | + ), |
| 936 | + ) |
| 937 | + |
| 938 | + blockchain_test(pre=pre, blocks=[block], post={}) |
| 939 | + |
| 940 | + |
| 941 | +def test_bal_pure_contract_call( |
| 942 | + pre: Alloc, |
| 943 | + blockchain_test: BlockchainTestFiller, |
| 944 | + fork: Fork, |
| 945 | +): |
| 946 | + """Test that BAL captures contract access for pure computation calls.""" |
| 947 | + alice = pre.fund_eoa() |
| 948 | + pure_contract = pre.deploy_contract(code=Op.ADD(0x3, 0x2)) |
| 949 | + |
| 950 | + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() |
| 951 | + gas_limit = intrinsic_gas_calculator() + 5_000 # Buffer |
| 952 | + |
| 953 | + tx = Transaction(sender=alice, to=pure_contract, gas_limit=gas_limit, gas_price=0xA) |
| 954 | + |
| 955 | + block = Block( |
| 956 | + txs=[tx], |
| 957 | + expected_block_access_list=BlockAccessListExpectation( |
| 958 | + account_expectations={ |
| 959 | + alice: BalAccountExpectation( |
| 960 | + nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)], |
| 961 | + ), |
| 962 | + # Ensure called contract is tracked |
| 963 | + pure_contract: BalAccountExpectation.empty(), |
| 964 | + } |
| 965 | + ), |
| 966 | + ) |
| 967 | + |
| 968 | + blockchain_test(pre=pre, blocks=[block], post={}) |
| 969 | + |
| 970 | + |
| 971 | +def test_bal_noop_storage_write( |
| 972 | + pre: Alloc, |
| 973 | + blockchain_test: BlockchainTestFiller, |
| 974 | + fork: Fork, |
| 975 | +): |
| 976 | + """Test that BAL correctly handles no-op storage write.""" |
| 977 | + alice = pre.fund_eoa() |
| 978 | + storage_contract = pre.deploy_contract(code=Op.SSTORE(0x01, 0x42), storage={0x01: 0x42}) |
| 979 | + |
| 980 | + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() |
| 981 | + gas_limit = ( |
| 982 | + intrinsic_gas_calculator() |
| 983 | + # Sufficient gas for write |
| 984 | + + fork.gas_costs().G_COLD_SLOAD |
| 985 | + + fork.gas_costs().G_COLD_ACCOUNT_ACCESS |
| 986 | + + fork.gas_costs().G_STORAGE_SET |
| 987 | + + fork.gas_costs().G_BASE * 10 # Buffer for push |
| 988 | + ) |
| 989 | + |
| 990 | + tx = Transaction(sender=alice, to=storage_contract, gas_limit=gas_limit, gas_price=0xA) |
| 991 | + |
| 992 | + block = Block( |
| 993 | + txs=[tx], |
| 994 | + expected_block_access_list=BlockAccessListExpectation( |
| 995 | + account_expectations={ |
| 996 | + alice: BalAccountExpectation( |
| 997 | + nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)], |
| 998 | + ), |
| 999 | + storage_contract: BalAccountExpectation( |
| 1000 | + storage_reads=[0x01], |
| 1001 | + storage_changes=[], |
| 1002 | + ), |
| 1003 | + } |
| 1004 | + ), |
| 1005 | + ) |
| 1006 | + |
| 1007 | + blockchain_test(pre=pre, blocks=[block], post={}) |
| 1008 | + |
| 1009 | + |
| 1010 | +@pytest.mark.parametrize( |
| 1011 | + "abort_opcode", |
| 1012 | + [ |
| 1013 | + pytest.param(Op.REVERT(0, 0), id="revert"), |
| 1014 | + pytest.param(Op.INVALID, id="invalid"), |
| 1015 | + ], |
| 1016 | +) |
| 1017 | +def test_bal_aborted_storage_access( |
| 1018 | + pre: Alloc, blockchain_test: BlockchainTestFiller, abort_opcode: Op |
| 1019 | +): |
| 1020 | + """Ensure BAL captures storage access in aborted transactions correctly.""" |
| 1021 | + alice = pre.fund_eoa() |
| 1022 | + storage_contract = pre.deploy_contract( |
| 1023 | + code=Op.SLOAD(0x01) + Op.SSTORE(0x02, 0x42) + abort_opcode, |
| 1024 | + storage={0x01: 0x10}, # Pre-existing value in slot 0x01 |
| 1025 | + ) |
| 1026 | + |
| 1027 | + tx = Transaction(sender=alice, to=storage_contract, gas_limit=5_000_000, gas_price=0xA) |
| 1028 | + |
| 1029 | + block = Block( |
| 1030 | + txs=[tx], |
| 1031 | + expected_block_access_list=BlockAccessListExpectation( |
| 1032 | + account_expectations={ |
| 1033 | + alice: BalAccountExpectation( |
| 1034 | + nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)] |
| 1035 | + ), |
| 1036 | + storage_contract: BalAccountExpectation( |
| 1037 | + storage_changes=[], |
| 1038 | + storage_reads=[0x01, 0x02], |
| 1039 | + ), |
| 1040 | + } |
| 1041 | + ), |
| 1042 | + ) |
| 1043 | + |
| 1044 | + blockchain_test( |
| 1045 | + pre=pre, |
| 1046 | + blocks=[block], |
| 1047 | + post={}, |
| 1048 | + ) |
| 1049 | + |
| 1050 | + |
| 1051 | +@pytest.mark.parametrize( |
| 1052 | + "account_access_opcode", |
| 1053 | + [ |
| 1054 | + pytest.param(lambda target_addr: Op.BALANCE(target_addr), id="balance"), |
| 1055 | + pytest.param(lambda target_addr: Op.EXTCODESIZE(target_addr), id="extcodesize"), |
| 1056 | + pytest.param(lambda target_addr: Op.EXTCODECOPY(target_addr, 0, 0, 32), id="extcodecopy"), |
| 1057 | + pytest.param(lambda target_addr: Op.EXTCODEHASH(target_addr), id="extcodehash"), |
| 1058 | + pytest.param(lambda target_addr: Op.CALL(0, target_addr, 50, 0, 0, 0, 0), id="call"), |
| 1059 | + pytest.param( |
| 1060 | + lambda target_addr: Op.CALLCODE(0, target_addr, 0, 0, 0, 0, 0), id="callcode" |
| 1061 | + ), |
| 1062 | + pytest.param( |
| 1063 | + lambda target_addr: Op.DELEGATECALL(0, target_addr, 0, 0, 0, 0), id="delegatecall" |
| 1064 | + ), |
| 1065 | + pytest.param( |
| 1066 | + lambda target_addr: Op.STATICCALL(0, target_addr, 0, 0, 0, 0), id="staticcall" |
| 1067 | + ), |
| 1068 | + ], |
| 1069 | +) |
| 1070 | +@pytest.mark.parametrize( |
| 1071 | + "abort_opcode", |
| 1072 | + [ |
| 1073 | + pytest.param(Op.REVERT(0, 0), id="revert"), |
| 1074 | + pytest.param(Op.INVALID, id="invalid"), |
| 1075 | + ], |
| 1076 | +) |
| 1077 | +def test_bal_aborted_account_access( |
| 1078 | + pre: Alloc, |
| 1079 | + blockchain_test: BlockchainTestFiller, |
| 1080 | + account_access_opcode, |
| 1081 | + abort_opcode: Op, |
| 1082 | +): |
| 1083 | + """Ensure BAL captures account access in aborted transactions.""" |
| 1084 | + alice = pre.fund_eoa() |
| 1085 | + target_contract = pre.deploy_contract(code=Op.STOP) |
| 1086 | + |
| 1087 | + abort_contract = pre.deploy_contract( |
| 1088 | + balance=100, |
| 1089 | + code=account_access_opcode(target_contract) + abort_opcode, |
| 1090 | + ) |
| 1091 | + |
| 1092 | + tx = Transaction(sender=alice, to=abort_contract, gas_limit=5_000_000, gas_price=0xA) |
| 1093 | + |
| 1094 | + block = Block( |
| 1095 | + txs=[tx], |
| 1096 | + expected_block_access_list=BlockAccessListExpectation( |
| 1097 | + account_expectations={ |
| 1098 | + alice: BalAccountExpectation( |
| 1099 | + nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)] |
| 1100 | + ), |
| 1101 | + target_contract: BalAccountExpectation.empty(), |
| 1102 | + abort_contract: BalAccountExpectation.empty(), |
| 1103 | + } |
| 1104 | + ), |
| 1105 | + ) |
| 1106 | + |
| 1107 | + blockchain_test( |
| 1108 | + pre=pre, |
| 1109 | + blocks=[block], |
| 1110 | + post={}, |
| 1111 | + ) |
0 commit comments