diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index 9f7400b433..da9287f65d 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -1073,19 +1073,13 @@ def test_bal_noop_storage_write( ) -> None: """Test that BAL correctly handles no-op storage write.""" alice = pre.fund_eoa() - storage_contract = pre.deploy_contract( - code=Op.SSTORE(0x01, 0x42), storage={0x01: 0x42} + code = Op.SSTORE( + 0x01, 0x42, key_warm=False, original_value=0, new_value=0x42 ) + storage_contract = pre.deploy_contract(code=code, storage={0x01: 0x42}) intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - gas_limit = ( - intrinsic_gas_calculator() - # Sufficient gas for write - + fork.gas_costs().G_COLD_SLOAD - + fork.gas_costs().G_COLD_ACCOUNT_ACCESS - + fork.gas_costs().G_STORAGE_SET - + fork.gas_costs().G_BASE * 10 # Buffer for push - ) + gas_limit = intrinsic_gas_calculator() + code.gas_cost(fork) tx = Transaction( sender=alice, to=storage_contract, gas_limit=gas_limit, gas_price=0xA diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index c0e8886d35..0df15e32cb 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -113,24 +113,25 @@ def test_bal_sstore_and_oog( 4. exact gas (success) -> storage write in BAL """ alice = pre.fund_eoa() - gas_costs = fork.gas_costs() # Create contract that attempts SSTORE to cold storage slot 0x01 - storage_contract_code = Bytecode(Op.SSTORE(0x01, 0x42)) + storage_contract_code = Op.SSTORE( + 0x01, 0x42, key_warm=False, original_value=0, new_value=0x42 + ) storage_contract = pre.deploy_contract(code=storage_contract_code) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas_cost = intrinsic_gas_calculator() + intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()() + + # Full cost: PUSHes + SSTORE (G_COLD_SLOAD + G_STORAGE_SET) + full_cost = storage_contract_code.gas_cost(fork) + + # Push cost for stipend boundary calculations + push_code = Op.PUSH1(0x42) + Op.PUSH1(0x01) + push_cost = push_code.gas_cost(fork) - # Costs: - # - PUSH1 (value and slot) = G_VERY_LOW * 2 - # - SSTORE cold (to zero slot) = G_STORAGE_SET + G_COLD_SLOAD - sload_cost = gas_costs.G_COLD_SLOAD - sstore_cost = gas_costs.G_STORAGE_SET - sstore_cold_cost = sstore_cost + sload_cost - push_cost = gas_costs.G_VERY_LOW * 2 - stipend = gas_costs.G_CALL_STIPEND + # G_CALL_STIPEND is a threshold check, not a gas cost — keep from gas_costs + stipend = fork.gas_costs().G_CALL_STIPEND if out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND: # 2300 after PUSHes (fails stipend check: 2300 <= 2300) @@ -140,10 +141,10 @@ def test_bal_sstore_and_oog( tx_gas_limit = intrinsic_gas_cost + push_cost + stipend + 1 elif out_of_gas_at == OutOfGasAt.EXACT_GAS_MINUS_1: # fail at charge_gas() at exact gas - 1 (boundary condition) - tx_gas_limit = intrinsic_gas_cost + push_cost + sstore_cold_cost - 1 + tx_gas_limit = intrinsic_gas_cost + full_cost - 1 else: # exact gas for successful SSTORE - tx_gas_limit = intrinsic_gas_cost + push_cost + sstore_cold_cost + tx_gas_limit = intrinsic_gas_cost + full_cost tx = Transaction( sender=alice, @@ -209,26 +210,19 @@ def test_bal_sload_and_oog( Ensure BAL handles SLOAD and OOG during SLOAD appropriately. """ alice = pre.fund_eoa() - gas_costs = fork.gas_costs() # Create contract that attempts SLOAD from cold storage slot 0x01 - storage_contract_code = Bytecode( + storage_contract_code = ( Op.PUSH1(0x01) # Storage slot (cold) - + Op.SLOAD # Load value from slot - this will OOG + + Op.SLOAD(key_warm=False) # Load value from slot - this will OOG + Op.STOP ) storage_contract = pre.deploy_contract(code=storage_contract_code) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas_cost = intrinsic_gas_calculator() + intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()() - # Costs: - # - PUSH1 (slot) = G_VERY_LOW - # - SLOAD cold = G_COLD_SLOAD - push_cost = gas_costs.G_VERY_LOW - sload_cold_cost = gas_costs.G_COLD_SLOAD - tx_gas_limit = intrinsic_gas_cost + push_cost + sload_cold_cost + tx_gas_limit = intrinsic_gas_cost + storage_contract_code.gas_cost(fork) if fails_at_sload: # subtract 1 gas to ensure OOG at SLOAD @@ -275,26 +269,19 @@ def test_bal_balance_and_oog( """Ensure BAL handles BALANCE and OOG during BALANCE appropriately.""" alice = pre.fund_eoa() bob = pre.fund_eoa() - gas_costs = fork.gas_costs() # Create contract that attempts to check Bob's balance - balance_checker_code = Bytecode( + balance_checker_code = ( Op.PUSH20(bob) # Bob's address - + Op.BALANCE # Check balance (cold access) + + Op.BALANCE(address_warm=False) # Check balance (cold access) + Op.STOP ) balance_checker = pre.deploy_contract(code=balance_checker_code) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas_cost = intrinsic_gas_calculator() + intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()() - # Costs: - # - PUSH20 = G_VERY_LOW - # - BALANCE cold = G_COLD_ACCOUNT_ACCESS - push_cost = gas_costs.G_VERY_LOW - balance_cold_cost = gas_costs.G_COLD_ACCOUNT_ACCESS - tx_gas_limit = intrinsic_gas_cost + push_cost + balance_cold_cost + tx_gas_limit = intrinsic_gas_cost + balance_checker_code.gas_cost(fork) if fails_at_balance: # subtract 1 gas to ensure OOG at BALANCE @@ -347,29 +334,22 @@ def test_bal_extcodesize_and_oog( Ensure BAL handles EXTCODESIZE and OOG during EXTCODESIZE appropriately. """ alice = pre.fund_eoa() - gas_costs = fork.gas_costs() # Create target contract with some code - target_contract = pre.deploy_contract(code=Bytecode(Op.STOP)) + target_contract = pre.deploy_contract(code=Op.STOP) # Create contract that checks target's code size - codesize_checker_code = Bytecode( + codesize_checker_code = ( Op.PUSH20(target_contract) # Target contract address - + Op.EXTCODESIZE # Check code size (cold access) + + Op.EXTCODESIZE(address_warm=False) # Check code size (cold access) + Op.STOP ) codesize_checker = pre.deploy_contract(code=codesize_checker_code) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas_cost = intrinsic_gas_calculator() + intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()() - # Costs: - # - PUSH20 = G_VERY_LOW - # - EXTCODESIZE cold = G_COLD_ACCOUNT_ACCESS - push_cost = gas_costs.G_VERY_LOW - extcodesize_cold_cost = gas_costs.G_COLD_ACCOUNT_ACCESS - tx_gas_limit = intrinsic_gas_cost + push_cost + extcodesize_cold_cost + tx_gas_limit = intrinsic_gas_cost + codesize_checker_code.gas_cost(fork) if fails_at_extcodesize: # subtract 1 gas to ensure OOG at EXTCODESIZE @@ -438,7 +418,6 @@ def test_bal_call_no_delegation_and_oog_before_target_access( When target_is_warm=True, we use EIP-2930 tx access list to warm the target. Access list warming does NOT add to BAL - only EVM access does. """ - gas_costs = fork.gas_costs() alice = pre.fund_eoa() target = ( @@ -449,8 +428,17 @@ def test_bal_call_no_delegation_and_oog_before_target_access( ret_size = 32 if memory_expansion else 0 + # Full gas metadata: includes create_cost when applicable call_code = Op.CALL( - gas=0, address=target, value=value, ret_size=ret_size, ret_offset=0 + gas=0, + address=target, + value=value, + ret_size=ret_size, + ret_offset=0, + address_warm=target_is_warm, + value_transfer=value > 0, + account_new=value > 0 and target_is_empty, + new_memory_size=ret_size, ) caller = pre.deploy_contract(code=call_code, balance=value) @@ -464,30 +452,22 @@ def test_bal_call_no_delegation_and_oog_before_target_access( access_list=access_list ) - bytecode_cost = gas_costs.G_VERY_LOW * 7 - - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - transfer_cost = gas_costs.G_CALL_VALUE if value > 0 else 0 - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - - # Create cost: only if value > 0 AND target is empty - create_cost = ( - gas_costs.G_NEW_ACCOUNT if (value > 0 and target_is_empty) else 0 - ) - - # static gas (before state access): access + transfer + memory - static_gas_cost = access_cost + transfer_cost + memory_cost - # second check includes create_cost - second_check_cost = static_gas_cost + create_cost - if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS: - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost - 1 + # Static gas (before state access): no create_cost + call_static = Op.CALL( + gas=0, + address=target, + value=value, + ret_size=ret_size, + ret_offset=0, + address_warm=target_is_warm, + value_transfer=value > 0, + account_new=False, + new_memory_size=ret_size, + ) + gas_limit = intrinsic_cost + call_static.gas_cost(fork) - 1 else: # SUCCESS - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost + gas_limit = intrinsic_cost + call_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -583,7 +563,6 @@ def test_bal_call_no_delegation_oog_after_target_access( to empty accounts, creating the gap tested here. """ - gas_costs = fork.gas_costs() alice = pre.fund_eoa() # empty target required for create_cost gap @@ -594,9 +573,18 @@ def test_bal_call_no_delegation_oog_after_target_access( # memory expansion / no expansion ret_size = 32 if memory_expansion else 0 - # caller contract - no warmup code, we use tx access list instead + # Static gas (before state access): no create_cost + # Pass static check, fail at second check due to create cost call_code = Op.CALL( - gas=0, address=target, value=value, ret_size=ret_size, ret_offset=0 + gas=0, + address=target, + value=value, + ret_size=ret_size, + ret_offset=0, + address_warm=target_is_warm, + value_transfer=True, + account_new=False, + new_memory_size=ret_size, ) caller = pre.deploy_contract(code=call_code, balance=value) @@ -611,24 +599,7 @@ def test_bal_call_no_delegation_oog_after_target_access( access_list=access_list ) - # Bytecode cost: 7 pushes for Op.CALL (no warmup code) - bytecode_cost = gas_costs.G_VERY_LOW * 7 - - # Access cost for CALL - warm if in tx access list - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - transfer_cost = gas_costs.G_CALL_VALUE # value > 0, so always charged - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - - # static gas cost (before state access): access + transfer + memory - static_gas_cost = access_cost + transfer_cost + memory_cost - - # Pass static check, fail at second check due to create cost - # (create_cost = G_NEW_ACCOUNT = 25000 for empty target + value > 0) - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost + gas_limit = intrinsic_cost + call_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -697,7 +668,6 @@ def test_bal_call_7702_delegation_and_oog( When target_is_warm or delegation_is_warm, we use EIP-2930 tx access list. Access list warming does NOT add targets to BAL - only EVM access does. """ - gas_costs = fork.gas_costs() alice = pre.fund_eoa() delegation_target = pre.deploy_contract(code=Op.STOP) @@ -706,12 +676,19 @@ def test_bal_call_7702_delegation_and_oog( # memory expansion / no expansion ret_size = 32 if memory_expansion else 0 + # Full gas metadata: includes delegation cost call_code = Op.CALL( gas=0, address=target, value=value, ret_size=ret_size, ret_offset=0, + address_warm=target_is_warm, + value_transfer=value > 0, + account_new=False, + new_memory_size=ret_size, + delegated_address=True, + delegated_address_warm=delegation_is_warm, ) caller = pre.deploy_contract(code=call_code, balance=value) @@ -728,36 +705,29 @@ def test_bal_call_7702_delegation_and_oog( access_list=access_list ) - bytecode_cost = gas_costs.G_VERY_LOW * 7 - - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - transfer_cost = gas_costs.G_CALL_VALUE if value > 0 else 0 - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - delegation_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if delegation_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS + # Static gas (before state access): no delegation + call_static = Op.CALL( + gas=0, + address=target, + value=value, + ret_size=ret_size, + ret_offset=0, + address_warm=target_is_warm, + value_transfer=value > 0, + account_new=False, + new_memory_size=ret_size, ) - static_gas_cost = access_cost + transfer_cost + memory_cost - - # The EVM's second check cost is static_gas + delegation_cost. - second_check_cost = static_gas_cost + delegation_cost - if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS: - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost - 1 + gas_limit = intrinsic_cost + call_static.gas_cost(fork) - 1 elif oog_boundary == OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS: # Enough for static_gas only - not enough for delegation_cost - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost + gas_limit = intrinsic_cost + call_static.gas_cost(fork) elif oog_boundary == OutOfGasBoundary.OOG_SUCCESS_MINUS_1: - # One less than second_check_cost - not enough for full call - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost - 1 + # One less than full cost - not enough for full call + gas_limit = intrinsic_cost + call_code.gas_cost(fork) - 1 else: - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost + gas_limit = intrinsic_cost + call_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -857,7 +827,6 @@ def test_bal_delegatecall_no_delegation_and_oog_before_target_access( target. Access list warming does NOT add to BAL - only EVM access does. """ alice = pre.fund_eoa() - gas_costs = fork.gas_costs() target = pre.deploy_contract(code=Op.STOP) @@ -869,6 +838,8 @@ def test_bal_delegatecall_no_delegation_and_oog_before_target_access( gas=0, ret_size=ret_size, ret_offset=ret_offset, + address_warm=target_is_warm, + new_memory_size=ret_size, ) caller = pre.deploy_contract(code=delegatecall_code) @@ -883,24 +854,10 @@ def test_bal_delegatecall_no_delegation_and_oog_before_target_access( access_list=access_list ) - # 6 pushes: retSize, retOffset, argsSize, argsOffset, address, gas - bytecode_cost = gas_costs.G_VERY_LOW * 6 - - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - - # static gas (before state access) == second check (no delegation cost) - static_gas_cost = access_cost + memory_cost - if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS: - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost - 1 + gas_limit = intrinsic_cost + delegatecall_code.gas_cost(fork) - 1 else: # SUCCESS - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost + gas_limit = intrinsic_cost + delegatecall_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -973,7 +930,6 @@ def test_bal_delegatecall_7702_delegation_and_oog( behaviors. """ alice = pre.fund_eoa() - gas_costs = fork.gas_costs() delegation_target = pre.deploy_contract(code=Op.STOP) target = pre.fund_eoa(amount=0, delegation=delegation_target) @@ -982,11 +938,16 @@ def test_bal_delegatecall_7702_delegation_and_oog( ret_size = 32 if memory_expansion else 0 ret_offset = 0 + # Full gas metadata: includes delegation cost delegatecall_code = Op.DELEGATECALL( gas=0, address=target, ret_size=ret_size, ret_offset=ret_offset, + address_warm=target_is_warm, + new_memory_size=ret_size, + delegated_address=True, + delegated_address_warm=delegation_is_warm, ) caller = pre.deploy_contract(code=delegatecall_code) @@ -1004,35 +965,26 @@ def test_bal_delegatecall_7702_delegation_and_oog( access_list=access_list ) - bytecode_cost = gas_costs.G_VERY_LOW * 6 - - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - delegation_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if delegation_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS + # Static gas (before state access): no delegation + delegatecall_static = Op.DELEGATECALL( + gas=0, + address=target, + ret_size=ret_size, + ret_offset=ret_offset, + address_warm=target_is_warm, + new_memory_size=ret_size, ) - static_gas_cost = access_cost + memory_cost - - # The EVM's second check cost is static_gas + delegation_cost. - second_check_cost = static_gas_cost + delegation_cost - if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS: - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost - 1 + gas_limit = intrinsic_cost + delegatecall_static.gas_cost(fork) - 1 elif oog_boundary == OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS: # Enough for static_gas only - not enough for delegation_cost - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost + gas_limit = intrinsic_cost + delegatecall_static.gas_cost(fork) elif oog_boundary == OutOfGasBoundary.OOG_SUCCESS_MINUS_1: - # One less than second_check_cost - not enough for full call - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost - 1 + # One less than full cost - not enough for full call + gas_limit = intrinsic_cost + delegatecall_code.gas_cost(fork) - 1 else: - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost + gas_limit = intrinsic_cost + delegatecall_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -1111,7 +1063,6 @@ def test_bal_callcode_no_delegation_and_oog_before_target_access( target. Access list warming does NOT add to BAL - only EVM access does. CALLCODE has no balance transfer to target (runs in caller's context). """ - gas_costs = fork.gas_costs() alice = pre.fund_eoa() target = pre.deploy_contract(code=Op.STOP) @@ -1119,7 +1070,15 @@ def test_bal_callcode_no_delegation_and_oog_before_target_access( ret_size = 32 if memory_expansion else 0 callcode_code = Op.CALLCODE( - gas=0, address=target, value=value, ret_size=ret_size, ret_offset=0 + gas=0, + address=target, + value=value, + ret_size=ret_size, + ret_offset=0, + address_warm=target_is_warm, + value_transfer=value > 0, + account_new=False, + new_memory_size=ret_size, ) caller = pre.deploy_contract(code=callcode_code, balance=value) @@ -1133,23 +1092,10 @@ def test_bal_callcode_no_delegation_and_oog_before_target_access( access_list=access_list ) - bytecode_cost = gas_costs.G_VERY_LOW * 7 - - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - transfer_cost = gas_costs.G_CALL_VALUE if value > 0 else 0 - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - - # static gas: access + transfer + memory (== second check, no delegation) - static_gas_cost = access_cost + transfer_cost + memory_cost - if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS: - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost - 1 + gas_limit = intrinsic_cost + callcode_code.gas_cost(fork) - 1 else: # SUCCESS - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost + gas_limit = intrinsic_cost + callcode_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -1230,7 +1176,6 @@ def test_bal_callcode_7702_delegation_and_oog( second check (delegation_cost) - all 3 scenarios produce distinct behaviors. """ - gas_costs = fork.gas_costs() alice = pre.fund_eoa() delegation_target = pre.deploy_contract(code=Op.STOP) @@ -1239,12 +1184,19 @@ def test_bal_callcode_7702_delegation_and_oog( # memory expansion / no expansion ret_size = 32 if memory_expansion else 0 + # Full gas metadata: includes delegation cost callcode_code = Op.CALLCODE( gas=0, address=target, value=value, ret_size=ret_size, ret_offset=0, + address_warm=target_is_warm, + value_transfer=value > 0, + account_new=False, + new_memory_size=ret_size, + delegated_address=True, + delegated_address_warm=delegation_is_warm, ) caller = pre.deploy_contract(code=callcode_code, balance=value) @@ -1261,36 +1213,29 @@ def test_bal_callcode_7702_delegation_and_oog( access_list=access_list ) - bytecode_cost = gas_costs.G_VERY_LOW * 7 - - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - transfer_cost = gas_costs.G_CALL_VALUE if value > 0 else 0 - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - delegation_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if delegation_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS + # Static gas (before state access): no delegation + callcode_static = Op.CALLCODE( + gas=0, + address=target, + value=value, + ret_size=ret_size, + ret_offset=0, + address_warm=target_is_warm, + value_transfer=value > 0, + account_new=False, + new_memory_size=ret_size, ) - static_gas_cost = access_cost + transfer_cost + memory_cost - - # The EVM's second check cost is static_gas + delegation_cost. - second_check_cost = static_gas_cost + delegation_cost - if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS: - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost - 1 + gas_limit = intrinsic_cost + callcode_static.gas_cost(fork) - 1 elif oog_boundary == OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS: # Enough for static_gas only - not enough for delegation_cost - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost + gas_limit = intrinsic_cost + callcode_static.gas_cost(fork) elif oog_boundary == OutOfGasBoundary.OOG_SUCCESS_MINUS_1: - # One less than second_check_cost - not enough for full call - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost - 1 + # One less than full cost - not enough for full call + gas_limit = intrinsic_cost + callcode_code.gas_cost(fork) - 1 else: - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost + gas_limit = intrinsic_cost + callcode_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -1367,7 +1312,6 @@ def test_bal_staticcall_no_delegation_and_oog_before_target_access( target. Access list warming does NOT add to BAL - only EVM access does. """ alice = pre.fund_eoa() - gas_costs = fork.gas_costs() target = pre.deploy_contract(code=Op.STOP) @@ -1379,6 +1323,8 @@ def test_bal_staticcall_no_delegation_and_oog_before_target_access( gas=0, ret_size=ret_size, ret_offset=ret_offset, + address_warm=target_is_warm, + new_memory_size=ret_size, ) caller = pre.deploy_contract(code=staticcall_code) @@ -1393,24 +1339,10 @@ def test_bal_staticcall_no_delegation_and_oog_before_target_access( access_list=access_list ) - # 6 pushes: retSize, retOffset, argsSize, argsOffset, address, gas - bytecode_cost = gas_costs.G_VERY_LOW * 6 - - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - - # static gas (before state access) == second check (no delegation cost) - static_gas_cost = access_cost + memory_cost - if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS: - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost - 1 + gas_limit = intrinsic_cost + staticcall_code.gas_cost(fork) - 1 else: # SUCCESS - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost + gas_limit = intrinsic_cost + staticcall_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -1483,7 +1415,6 @@ def test_bal_staticcall_7702_delegation_and_oog( behaviors. """ alice = pre.fund_eoa() - gas_costs = fork.gas_costs() delegation_target = pre.deploy_contract(code=Op.STOP) target = pre.fund_eoa(amount=0, delegation=delegation_target) @@ -1492,11 +1423,16 @@ def test_bal_staticcall_7702_delegation_and_oog( ret_size = 32 if memory_expansion else 0 ret_offset = 0 + # Full gas metadata: includes delegation cost staticcall_code = Op.STATICCALL( gas=0, address=target, ret_size=ret_size, ret_offset=ret_offset, + address_warm=target_is_warm, + new_memory_size=ret_size, + delegated_address=True, + delegated_address_warm=delegation_is_warm, ) caller = pre.deploy_contract(code=staticcall_code) @@ -1514,35 +1450,26 @@ def test_bal_staticcall_7702_delegation_and_oog( access_list=access_list ) - bytecode_cost = gas_costs.G_VERY_LOW * 6 - - access_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if target_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS - ) - memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - delegation_cost = ( - gas_costs.G_WARM_ACCOUNT_ACCESS - if delegation_is_warm - else gas_costs.G_COLD_ACCOUNT_ACCESS + # Static gas (before state access): no delegation + staticcall_static = Op.STATICCALL( + gas=0, + address=target, + ret_size=ret_size, + ret_offset=ret_offset, + address_warm=target_is_warm, + new_memory_size=ret_size, ) - static_gas_cost = access_cost + memory_cost - - # The EVM's second check cost is static_gas + delegation_cost - second_check_cost = static_gas_cost + delegation_cost - if oog_boundary == OutOfGasBoundary.OOG_BEFORE_TARGET_ACCESS: - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost - 1 + gas_limit = intrinsic_cost + staticcall_static.gas_cost(fork) - 1 elif oog_boundary == OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS: # Enough for static_gas only - not enough for delegation_cost - gas_limit = intrinsic_cost + bytecode_cost + static_gas_cost + gas_limit = intrinsic_cost + staticcall_static.gas_cost(fork) elif oog_boundary == OutOfGasBoundary.OOG_SUCCESS_MINUS_1: - # One less than second_check_cost - not enough for full call - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost - 1 + # One less than full cost - not enough for full call + gas_limit = intrinsic_cost + staticcall_code.gas_cost(fork) - 1 else: - gas_limit = intrinsic_cost + bytecode_cost + second_check_cost + gas_limit = intrinsic_cost + staticcall_code.gas_cost(fork) tx = Transaction( sender=alice, @@ -1635,65 +1562,60 @@ def test_bal_extcodecopy_and_oog( checked BEFORE recording account access. """ alice = pre.fund_eoa() - gas_costs = fork.gas_costs() # Create target contract with some code - target_contract = pre.deploy_contract( - code=Bytecode(Op.PUSH1(0x42) + Op.STOP) - ) + target_contract = pre.deploy_contract(code=Op.PUSH1(0x42) + Op.STOP) - # Build EXTCODECOPY contract with appropriate PUSH sizes - if memory_offset <= 0xFF: - dest_push = Op.PUSH1(memory_offset) - elif memory_offset <= 0xFFFF: - dest_push = Op.PUSH2(memory_offset) - else: - dest_push = Op.PUSH3(memory_offset) - - extcodecopy_contract_code = Bytecode( - Op.PUSH1(copy_size) - + Op.PUSH1(0) # codeOffset - + dest_push # destOffset - + Op.PUSH20(target_contract) - + Op.EXTCODECOPY - + Op.STOP + # Full EXTCODECOPY: access + copy + memory expansion + extcodecopy_code = Op.EXTCODECOPY( + address=target_contract, + dest_offset=memory_offset, + offset=0, + size=copy_size, + address_warm=False, + data_size=copy_size, + new_memory_size=memory_offset + copy_size, ) - extcodecopy_contract = pre.deploy_contract(code=extcodecopy_contract_code) - - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas_cost = intrinsic_gas_calculator() + extcodecopy_contract = pre.deploy_contract(code=extcodecopy_code + Op.STOP) - # Calculate costs - push_cost = gas_costs.G_VERY_LOW * 4 - cold_access_cost = gas_costs.G_COLD_ACCOUNT_ACCESS - copy_cost = gas_costs.G_COPY * ((copy_size + 31) // 32) + intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()() if oog_scenario == "success": # Provide enough gas for everything including memory expansion - memory_cost = fork.memory_expansion_gas_calculator()( - new_bytes=memory_offset + copy_size - ) - execution_cost = push_cost + cold_access_cost + copy_cost + memory_cost - tx_gas_limit = intrinsic_gas_cost + execution_cost + tx_gas_limit = intrinsic_gas_cost + extcodecopy_code.gas_cost(fork) target_in_bal = True elif oog_scenario == "oog_at_cold_access": - # Provide gas for pushes but 1 less than cold access cost - execution_cost = push_cost + cold_access_cost - tx_gas_limit = intrinsic_gas_cost + execution_cost - 1 + # Provide gas for pushes but 1 less than cold access + extcodecopy_access_only = Op.EXTCODECOPY( + address=target_contract, + dest_offset=memory_offset, + offset=0, + size=copy_size, + address_warm=False, + data_size=0, + new_memory_size=0, + ) + tx_gas_limit = ( + intrinsic_gas_cost + extcodecopy_access_only.gas_cost(fork) - 1 + ) target_in_bal = False elif oog_scenario == "oog_at_memory_large_offset": # Provide gas for push + cold access + copy, but NOT memory expansion - execution_cost = push_cost + cold_access_cost + copy_cost - tx_gas_limit = intrinsic_gas_cost + execution_cost + extcodecopy_no_mem = Op.EXTCODECOPY( + address=target_contract, + dest_offset=memory_offset, + offset=0, + size=copy_size, + address_warm=False, + data_size=copy_size, + new_memory_size=0, + ) + tx_gas_limit = intrinsic_gas_cost + extcodecopy_no_mem.gas_cost(fork) target_in_bal = False elif oog_scenario == "oog_at_memory_boundary": - # Calculate memory cost and provide exactly 1 less than needed - memory_cost = fork.memory_expansion_gas_calculator()( - new_bytes=memory_offset + copy_size - ) - execution_cost = push_cost + cold_access_cost + copy_cost + memory_cost - tx_gas_limit = intrinsic_gas_cost + execution_cost - 1 + # Calculate full cost and provide exactly 1 less than needed + tx_gas_limit = intrinsic_gas_cost + extcodecopy_code.gas_cost(fork) - 1 target_in_bal = False else: raise ValueError(f"Invariant: unknown oog_scenario {oog_scenario}")