From 358a26f1583efe6c98011b50996ebe473b052bdd Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:24:44 +0100 Subject: [PATCH] new(tests): Explicit test for EXTDELEGATECALL value cost (#911) --- .../eip7069_extcall/test_gas.py | 44 +++++++++++++++ tests/osaka/eip7692_eof_v1/gas_test.py | 53 +++++++++++-------- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/tests/osaka/eip7692_eof_v1/eip7069_extcall/test_gas.py b/tests/osaka/eip7692_eof_v1/eip7069_extcall/test_gas.py index 43196df560..b2417fb711 100644 --- a/tests/osaka/eip7692_eof_v1/eip7069_extcall/test_gas.py +++ b/tests/osaka/eip7692_eof_v1/eip7069_extcall/test_gas.py @@ -140,3 +140,47 @@ def test_ext_calls_gas( cold_gas=cold_gas + cost_memory_bytes(mem_expansion_bytes, 0), warm_gas=warm_gas + cost_memory_bytes(mem_expansion_bytes, 0), ) + + +@pytest.mark.parametrize("opcode", [Op.EXTCALL, Op.EXTDELEGATECALL, Op.EXTSTATICCALL]) +@pytest.mark.parametrize("value", [0, 1]) +def test_transfer_gas_is_cleared( + state_test: StateTestFiller, + pre: Alloc, + state_env: Environment, + opcode: Op, + value: int, +): + """ + Test that EXT*CALL call doesn't charge for value transfer, even if the outer call + transfered value. + + NOTE: This is particularly possible for EXTDELEGATECALL, which carries over the value sent + in the outer call, however, we extend the test to all 3 EXT*CALL opcodes for good measure. + """ + noop_callee_address = pre.deploy_contract(Container.Code(Op.STOP)) + + extdelegatecall_contract_address = pre.deploy_contract( + Container.Code(opcode(address=noop_callee_address) + Op.STOP) + ) + + push_gas = (4 if opcode == Op.EXTCALL else 3) * 3 + + gas_test( + state_test, + state_env, + pre, + setup_code=Op.PUSH1(value) + Op.PUSH0 * 2 + Op.PUSH20(extdelegatecall_contract_address), + subject_code=Op.EXTCALL, + subject_balance=5 * value, + tear_down_code=Op.STOP, + # NOTE: CALL_WITH_VALUE_GAS is charged only once on the outer EXTCALL, while the base + # call gas - twice. + cold_gas=2 * COLD_ACCOUNT_ACCESS_GAS + + (CALL_WITH_VALUE_GAS if value > 0 else 0) + + push_gas, + warm_gas=2 * WARM_ACCOUNT_ACCESS_GAS + + (CALL_WITH_VALUE_GAS if value > 0 else 0) + + push_gas, + out_of_gas_testing=False, + ) diff --git a/tests/osaka/eip7692_eof_v1/gas_test.py b/tests/osaka/eip7692_eof_v1/gas_test.py index 9e7d0bb602..c381db0a42 100644 --- a/tests/osaka/eip7692_eof_v1/gas_test.py +++ b/tests/osaka/eip7692_eof_v1/gas_test.py @@ -38,6 +38,7 @@ def gas_test( subject_address: Address | None = None, subject_balance: int = 0, oog_difference: int = 1, + out_of_gas_testing: bool = True, ): """ Creates a State Test to check the gas cost of a sequence of EOF code. @@ -104,27 +105,33 @@ def gas_test( + (Op.DUP3 + Op.SWAP1 + Op.SUB + Op.PUSH2(slot_warm_gas) + Op.SSTORE) # store cold gas: DUP2 is the gas of the baseline gas run + (Op.DUP2 + Op.SWAP1 + Op.SUB + Op.PUSH2(slot_cold_gas) + Op.SSTORE) - # oog gas run: - # - DUP7 is the gas of the baseline gas run, after other CALL args were pushed - # - subtract the gas charged by the harness - # - add warm gas charged by the subject - # - subtract `oog_difference` to cause OOG exception (1 by default) - + Op.SSTORE( - slot_oog_call_result, - Op.CALL( - gas=Op.ADD(warm_gas - gas_single_gas_run - oog_difference, Op.DUP7), - address=address_subject, - ), - ) - # sanity gas run: not subtracting 1 to see if enough gas makes the call succeed - + Op.SSTORE( - slot_sanity_call_result, - Op.CALL( - gas=Op.ADD(warm_gas - gas_single_gas_run, Op.DUP7), - address=address_subject, - ), + + ( + ( + # do an oog gas run, unless skipped with `out_of_gas_testing=False`: + # - DUP7 is the gas of the baseline gas run, after other CALL args were pushed + # - subtract the gas charged by the harness + # - add warm gas charged by the subject + # - subtract `oog_difference` to cause OOG exception (1 by default) + Op.SSTORE( + slot_oog_call_result, + Op.CALL( + gas=Op.ADD(warm_gas - gas_single_gas_run - oog_difference, Op.DUP7), + address=address_subject, + ), + ) + # sanity gas run: not subtracting 1 to see if enough gas makes the call succeed + + Op.SSTORE( + slot_sanity_call_result, + Op.CALL( + gas=Op.ADD(warm_gas - gas_single_gas_run, Op.DUP7), + address=address_subject, + ), + ) + + Op.STOP + ) + if out_of_gas_testing + else Op.STOP ) - + Op.STOP ), evm_code_type=EVMCodeType.LEGACY, # Needs to be legacy to use GAS opcode ) @@ -134,12 +141,14 @@ def gas_test( storage={ slot_warm_gas: warm_gas, slot_cold_gas: cold_gas, - slot_oog_call_result: LEGACY_CALL_FAILURE, - slot_sanity_call_result: LEGACY_CALL_SUCCESS, }, ), } + if out_of_gas_testing: + post[address_legacy_harness].storage[slot_oog_call_result] = LEGACY_CALL_FAILURE + post[address_legacy_harness].storage[slot_sanity_call_result] = LEGACY_CALL_SUCCESS + tx = Transaction(to=address_legacy_harness, gas_limit=env.gas_limit, sender=sender) state_test(env=env, pre=pre, tx=tx, post=post)