|
| 1 | +""" |
| 2 | +Tests for EIP-7928 Block Access Lists - OOG Gas Check Ordering. |
| 3 | +
|
| 4 | +Verifies that account access is NOT recorded in BAL when EXTCODECOPY fails |
| 5 | +due to OOG at memory expansion. Gas for all components (cold access + copy + |
| 6 | +memory expansion) must be checked BEFORE recording account access. |
| 7 | +""" |
| 8 | + |
| 9 | +import pytest |
| 10 | +from execution_testing import ( |
| 11 | + Account, |
| 12 | + Alloc, |
| 13 | + BalAccountExpectation, |
| 14 | + Block, |
| 15 | + BlockAccessListExpectation, |
| 16 | + BlockchainTestFiller, |
| 17 | + Bytecode, |
| 18 | + Fork, |
| 19 | + Op, |
| 20 | + Transaction, |
| 21 | +) |
| 22 | + |
| 23 | +from .spec import ref_spec_7928 |
| 24 | + |
| 25 | +REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path |
| 26 | +REFERENCE_SPEC_VERSION = ref_spec_7928.version |
| 27 | + |
| 28 | + |
| 29 | +pytestmark = pytest.mark.valid_from("Amsterdam") |
| 30 | + |
| 31 | + |
| 32 | +@pytest.mark.parametrize( |
| 33 | + "memory_offset,copy_size,gas_shortfall", |
| 34 | + [ |
| 35 | + pytest.param(0x10000, 32, "large", id="large_offset"), |
| 36 | + pytest.param(256, 32, "boundary", id="boundary"), |
| 37 | + ], |
| 38 | +) |
| 39 | +def test_extcodecopy_oog_at_memory_expansion( |
| 40 | + pre: Alloc, |
| 41 | + blockchain_test: BlockchainTestFiller, |
| 42 | + fork: Fork, |
| 43 | + memory_offset: int, |
| 44 | + copy_size: int, |
| 45 | + gas_shortfall: str, |
| 46 | +) -> None: |
| 47 | + """ |
| 48 | + Test EXTCODECOPY OOG at memory expansion - target should NOT appear in BAL. |
| 49 | +
|
| 50 | + Gas for all components (cold access + copy + memory expansion) must be |
| 51 | + checked BEFORE recording account access. |
| 52 | + """ |
| 53 | + alice = pre.fund_eoa() |
| 54 | + gas_costs = fork.gas_costs() |
| 55 | + |
| 56 | + target_contract = pre.deploy_contract(code=Bytecode(Op.STOP)) |
| 57 | + |
| 58 | + # Build EXTCODECOPY contract with appropriate PUSH sizes |
| 59 | + if memory_offset <= 0xFF: |
| 60 | + dest_push = Op.PUSH1(memory_offset) |
| 61 | + elif memory_offset <= 0xFFFF: |
| 62 | + dest_push = Op.PUSH2(memory_offset) |
| 63 | + else: |
| 64 | + dest_push = Op.PUSH3(memory_offset) |
| 65 | + |
| 66 | + extcodecopy_contract_code = Bytecode( |
| 67 | + Op.PUSH1(copy_size) |
| 68 | + + Op.PUSH1(0) |
| 69 | + + dest_push |
| 70 | + + Op.PUSH20(target_contract) |
| 71 | + + Op.EXTCODECOPY |
| 72 | + + Op.STOP |
| 73 | + ) |
| 74 | + |
| 75 | + extcodecopy_contract = pre.deploy_contract(code=extcodecopy_contract_code) |
| 76 | + |
| 77 | + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() |
| 78 | + intrinsic_gas_cost = intrinsic_gas_calculator() |
| 79 | + |
| 80 | + push_cost = gas_costs.G_VERY_LOW * 4 |
| 81 | + cold_access_cost = gas_costs.G_COLD_ACCOUNT_ACCESS |
| 82 | + copy_cost = gas_costs.G_COPY * ((copy_size + 31) // 32) |
| 83 | + |
| 84 | + if gas_shortfall == "large": |
| 85 | + # Provide gas for push + cold access + copy, but NOT memory expansion |
| 86 | + tx_gas_limit = intrinsic_gas_cost + push_cost + cold_access_cost + copy_cost |
| 87 | + else: |
| 88 | + # Calculate memory cost and provide exactly 1 less than needed |
| 89 | + words = (memory_offset + copy_size + 31) // 32 |
| 90 | + memory_cost = (words * gas_costs.G_MEMORY) + (words * words // 512) |
| 91 | + total_gas_needed = push_cost + cold_access_cost + copy_cost + memory_cost |
| 92 | + tx_gas_limit = intrinsic_gas_cost + total_gas_needed - 1 |
| 93 | + |
| 94 | + tx = Transaction( |
| 95 | + sender=alice, |
| 96 | + to=extcodecopy_contract, |
| 97 | + gas_limit=tx_gas_limit, |
| 98 | + ) |
| 99 | + |
| 100 | + block = Block( |
| 101 | + txs=[tx], |
| 102 | + expected_block_access_list=BlockAccessListExpectation( |
| 103 | + account_expectations={ |
| 104 | + extcodecopy_contract: BalAccountExpectation.empty(), |
| 105 | + target_contract: None, |
| 106 | + } |
| 107 | + ), |
| 108 | + ) |
| 109 | + |
| 110 | + blockchain_test( |
| 111 | + pre=pre, |
| 112 | + blocks=[block], |
| 113 | + post={ |
| 114 | + alice: Account(nonce=1), |
| 115 | + extcodecopy_contract: Account(), |
| 116 | + target_contract: Account(), |
| 117 | + }, |
| 118 | + ) |
0 commit comments