diff --git a/tests/prague/eip7692_eof_v1/eip7069_extcall/helpers.py b/tests/prague/eip7692_eof_v1/eip7069_extcall/helpers.py index c06f7dd4d4..432294faca 100644 --- a/tests/prague/eip7692_eof_v1/eip7069_extcall/helpers.py +++ b/tests/prague/eip7692_eof_v1/eip7069_extcall/helpers.py @@ -18,6 +18,8 @@ slot_calldata_2 = next(_slot) slot_cold_gas = next(_slot) slot_warm_gas = next(_slot) +slot_oog_call_result = next(_slot) +slot_sanity_call_result = next(_slot) slot_last_slot = next(_slot) @@ -26,6 +28,8 @@ """Storage values for common testing fields""" value_code_worked = 0x2015 +value_call_legacy_abort = 0 +value_call_legacy_success = 1 """Memory and storage value for calldata""" value_calldata_1 = 0xC1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1 diff --git a/tests/prague/eip7692_eof_v1/eip7069_extcall/test_gas.py b/tests/prague/eip7692_eof_v1/eip7069_extcall/test_gas.py index 8619bb9993..3e418eaa22 100644 --- a/tests/prague/eip7692_eof_v1/eip7069_extcall/test_gas.py +++ b/tests/prague/eip7692_eof_v1/eip7069_extcall/test_gas.py @@ -6,13 +6,20 @@ import pytest from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction -from ethereum_test_tools.eof.v1 import Container, Section +from ethereum_test_tools.eof.v1 import Container from ethereum_test_tools.vm.opcode import Opcodes as Op from ethereum_test_vm import Bytecode, EVMCodeType from .. import EOF_FORK_NAME from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION -from .helpers import slot_cold_gas, slot_warm_gas +from .helpers import ( + slot_cold_gas, + slot_oog_call_result, + slot_sanity_call_result, + slot_warm_gas, + value_call_legacy_abort, + value_call_legacy_success, +) REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION @@ -20,6 +27,12 @@ pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) +COLD_ACCOUNT_ACCESS_GAS = 2600 +WARM_ACCOUNT_ACCESS_GAS = 100 +CALL_WITH_VALUE_GAS = 9000 +ACCOUNT_CREATION_GAS = 25000 + + @pytest.fixture def state_env() -> Environment: """ @@ -42,37 +55,43 @@ def gas_test( cold_gas: int, warm_gas: int | None = None, ): - """Creates a State Test to check the gas cost of a sequence of EOF code.""" + """ + Creates a State Test to check the gas cost of a sequence of EOF code. + + `setup_code` and `tear_down_code` are called multiple times during the test, and MUST NOT have + any side-effects which persist across message calls, and in particular, any effects on the gas + usage of `subject_code`. + """ if cold_gas <= 0: - raise ValueError(f"Target gas allocations (warm_gas) must be > 0, got {cold_gas}") + raise ValueError(f"Target gas allocations (cold_gas) must be > 0, got {cold_gas}") if warm_gas is None: warm_gas = cold_gas - sender = pre.fund_eoa(10**18) + sender = pre.fund_eoa() - address_baseline = pre.deploy_contract( - Container(sections=[Section.Code(setup_code + tear_down_code)]) - ) + address_baseline = pre.deploy_contract(Container.Code(setup_code + tear_down_code)) address_subject = pre.deploy_contract( - Container(sections=[Section.Code(setup_code + subject_code + tear_down_code)]) + Container.Code(setup_code + subject_code + tear_down_code) ) + # 2 times GAS, POP, CALL, 6 times PUSH1 - instructions charged for at every gas run + gas_single_gas_run = 2 * 2 + 2 + WARM_ACCOUNT_ACCESS_GAS + 6 * 3 address_legacy_harness = pre.deploy_contract( code=( # warm subject and baseline without executing (Op.BALANCE(address_subject) + Op.POP + Op.BALANCE(address_baseline) + Op.POP) - # cold gas run + # Baseline gas run + ( Op.GAS - + Op.CALL(address=address_subject, gas=500_000) + + Op.CALL(address=address_baseline, gas=Op.GAS) + Op.POP + Op.GAS + Op.SWAP1 + Op.SUB ) - # Baseline gas run + # cold gas run + ( Op.GAS - + Op.CALL(address=address_baseline, gas=500_000) + + Op.CALL(address=address_subject, gas=Op.GAS) + Op.POP + Op.GAS + Op.SWAP1 @@ -81,16 +100,36 @@ def gas_test( # warm gas run + ( Op.GAS - + Op.CALL(address=address_subject, gas=500_000) + + Op.CALL(address=address_subject, gas=Op.GAS) + Op.POP + Op.GAS + Op.SWAP1 + Op.SUB ) - # Store warm gas - + (Op.DUP2 + Op.SWAP1 + Op.SUB + Op.PUSH2(slot_warm_gas) + Op.SSTORE) - # store cold gas - + (Op.SWAP1 + Op.SUB + Op.PUSH2(slot_cold_gas) + Op.SSTORE) + # Store warm gas: DUP3 is the gas of the baseline gas run + + (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 1 to cause OOG exception + + Op.SSTORE( + slot_oog_call_result, + Op.CALL( + gas=Op.ADD(warm_gas - gas_single_gas_run - 1, 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 ), evm_code_type=EVMCodeType.LEGACY, # Needs to be legacy to use GAS opcode @@ -101,22 +140,93 @@ def gas_test( storage={ slot_warm_gas: warm_gas, slot_cold_gas: cold_gas, + slot_oog_call_result: value_call_legacy_abort, + slot_sanity_call_result: value_call_legacy_success, }, ), } - tx = Transaction(to=address_legacy_harness, gas_limit=2_000_000, sender=sender) + tx = Transaction(to=address_legacy_harness, gas_limit=env.gas_limit, sender=sender) state_test(env=env, pre=pre, tx=tx, post=post) @pytest.mark.parametrize( - ["opcode", "pre_setup", "cold_gas", "warm_gas"], + ["opcode", "pre_setup", "cold_gas", "warm_gas", "new_account"], [ - pytest.param(Op.EXTCALL, Op.PUSH0, 2600, 100, id="EXTCALL"), - pytest.param(Op.EXTCALL, Op.PUSH1(1), 2600 + 9000, 100 + 9000, id="EXTCALL_with_value"), - pytest.param(Op.EXTDELEGATECALL, Op.NOOP, 2600, 100, id="EXTSTATICCALL"), - pytest.param(Op.EXTSTATICCALL, Op.NOOP, 2600, 100, id="EXTDELEGATECALL"), + pytest.param( + Op.EXTCALL, + Op.PUSH0, + COLD_ACCOUNT_ACCESS_GAS, + WARM_ACCOUNT_ACCESS_GAS, + False, + id="EXTCALL", + ), + pytest.param( + Op.EXTCALL, + Op.PUSH1(1), + COLD_ACCOUNT_ACCESS_GAS + CALL_WITH_VALUE_GAS, + WARM_ACCOUNT_ACCESS_GAS + CALL_WITH_VALUE_GAS, + False, + id="EXTCALL_with_value", + ), + pytest.param( + Op.EXTDELEGATECALL, + Op.NOOP, + COLD_ACCOUNT_ACCESS_GAS, + WARM_ACCOUNT_ACCESS_GAS, + False, + id="EXTDELEGATECALL", + ), + pytest.param( + Op.EXTSTATICCALL, + Op.NOOP, + COLD_ACCOUNT_ACCESS_GAS, + WARM_ACCOUNT_ACCESS_GAS, + False, + id="EXTSTATICCALL", + ), + pytest.param( + Op.EXTCALL, + Op.PUSH0, + COLD_ACCOUNT_ACCESS_GAS, + WARM_ACCOUNT_ACCESS_GAS, + True, + id="EXTCALL_new_acc", + ), + pytest.param( + Op.EXTCALL, + Op.PUSH1(1), + COLD_ACCOUNT_ACCESS_GAS + ACCOUNT_CREATION_GAS + CALL_WITH_VALUE_GAS, + WARM_ACCOUNT_ACCESS_GAS + ACCOUNT_CREATION_GAS + CALL_WITH_VALUE_GAS, + True, + id="EXTCALL_with_value_new_acc", + ), + pytest.param( + Op.EXTDELEGATECALL, + Op.NOOP, + COLD_ACCOUNT_ACCESS_GAS, + WARM_ACCOUNT_ACCESS_GAS, + True, + id="EXTDELEGATECALL_new_acc", + ), + pytest.param( + Op.EXTSTATICCALL, + Op.NOOP, + COLD_ACCOUNT_ACCESS_GAS, + WARM_ACCOUNT_ACCESS_GAS, + True, + id="EXTSTATICCALL_new_acc", + ), + ], +) +@pytest.mark.parametrize( + ["mem_expansion_size", "mem_expansion_extra_gas"], + [ + pytest.param(0, 0, id="no_mem_expansion"), + pytest.param(1, 3, id="1byte_mem_expansion"), + pytest.param(32, 3, id="1word_mem_expansion"), + pytest.param(33, 6, id="33bytes_mem_expansion"), ], ) def test_ext_calls_gas( @@ -126,18 +236,23 @@ def test_ext_calls_gas( opcode: Op, pre_setup: Op, cold_gas: int, - warm_gas: int | None, + warm_gas: int, + new_account: bool, + mem_expansion_size: int, + mem_expansion_extra_gas: int, ): - """Tests 4 variations of EXT*CALL gas, both warm and cold""" - address_target = pre.deploy_contract(Container(sections=[Section.Code(code=Op.STOP)])) + """Tests variations of EXT*CALL gas, both warm and cold, without and with mem expansions""" + address_target = ( + pre.fund_eoa(0) if new_account else pre.deploy_contract(Container.Code(Op.STOP)) + ) gas_test( state_test, state_env, pre, - setup_code=pre_setup + Op.PUSH0 + Op.PUSH0 + Op.PUSH20(address_target), + setup_code=pre_setup + Op.PUSH1(mem_expansion_size) + Op.PUSH0 + Op.PUSH20(address_target), subject_code=opcode, tear_down_code=Op.STOP, - cold_gas=cold_gas, - warm_gas=warm_gas, + cold_gas=cold_gas + mem_expansion_extra_gas, + warm_gas=warm_gas + mem_expansion_extra_gas, ) diff --git a/tests/prague/eip7692_eof_v1/tracker.md b/tests/prague/eip7692_eof_v1/tracker.md index 453a988560..7cdbe3728b 100644 --- a/tests/prague/eip7692_eof_v1/tracker.md +++ b/tests/prague/eip7692_eof_v1/tracker.md @@ -376,48 +376,48 @@ ### Execution -- [x] EXTDELEGATECALL from EOF to EOF (evmone-tests: state_tests/state_transition/eof_calls/eof1_extdelegatecall_eof1.json) -- [x] EXTDELEGATECALL from EOF to legacy fails (evmone-tests: state_tests/state_transition/eof_calls/eof1_extdelegatecall_legacy.json) -- [ ] EXTSTATICCALL forwards static mode (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_static.json) -- [x] EXTCALL with value success (evmone-tests: state_tests/state_transition/eof_calls/extcall_with_value.json) -- [ ] EXTCALL with value from EXTSTATICCALL (evmone-tests: state_tests/state_transition/eof_calls/extcall_static_with_value.json) -- [x] EXTCALL with value, not enough balance (evmone-tests: state_tests/state_transition/eof_calls/extcall_failing_with_value_balance_check.json) -- [ ] EXTCALL with value, check additional charge for value (evmone-tests: state_tests/state_transition/eof_calls/extcall_failing_with_value_additional_cost.json) -- [x] EXTCALL with gas not enough for callee to get 5000 gas (evmone-tests: state_tests/state_transition/eof_calls/extcall_min_callee_gas_failure_mode.json) -- [x] RETURNDATA* after EXTCALL (evmone-tests: state_tests/state_transition/eof_calls/extcall_output.json) -- [x] RETURNDATA* after EXTDELEGATECALL (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_output.json state_tests/state_transition/eof_calls/extdelegatecall_returndatasize.json state_tests/state_transition/eof_calls/returndatacopy.json state_tests/state_transition/eof_calls/returndataload.json) -- [x] RETURNDATA* after EXTSTATICCALL (evmone-tests: state_tests/state_transition/eof_calls/extstaticcall_output.json) -- [x] RETURNDATA* after aborted EXT*CALL (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_returndatasize_abort.json) -- [x] Failed EXTCALL clears returndata from previous EXTCALL (evmone-tests: state_tests/state_transition/eof_calls/extcall_clears_returndata.json) -- [ ] EXTCALL not enough gas for input memory charge (evmone-tests: state_tests/state_transition/eof_calls/extcall_memory.json) -- [ ] EXTDELEGATECALL not enough gas for input memory charge (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_memory.json) -- [ ] EXTSTATICCALL not enough gas for input memory charge (evmone-tests: state_tests/state_transition/eof_calls/extstaticcall_memory.json) -- [x] EXTCALL exception due to target address overflow (bits set in high 12 bytes) (evmone-tests: state_tests/state_transition/eof_calls/extcall_ase_ready_violation.json) -- [x] EXTDELEGATECALL exception due to target address overflow (bits set in high 12 bytes) (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_ase_ready_violation.json) -- [x] EXTSTATICCALL exception due to target address overflow (bits set in high 12 bytes) (evmone-tests: state_tests/state_transition/eof_calls/extstaticcall_ase_ready_violation.json) -- [ ] EXTCALL not enough gas for warming up target address (evmone-tests: state_tests/state_transition/eof_calls/extcall_cold_oog.json) -- [ ] EXTDELEGATECALL not enough gas for warming up target address (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_cold_oog.json) -- [ ] EXTSTATICCALL not enough gas for warming up target address (evmone-tests: state_tests/state_transition/eof_calls/extstaticcall_cold_oog.json) -- [ ] EXTCALL not enough gas for account creation cost (transfer value to non-existing account) (evmone-tests: state_tests/state_transition/eof_calls/extcall_value_zero_to_nonexistent_account.json) -- [x] OOG after EXTCALL (evmone-tests: state_tests/state_transition/eof_calls/extcall_then_oog.json) -- [x] OOG after EXTDELEGATECALL (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_then_oog.json) -- [x] OOG after EXTSTATICCALL (evmone-tests: state_tests/state_transition/eof_calls/extstaticcall_then_oog.json) -- [x] REVERT inside EXTCALL (evmone-tests: state_tests/state_transition/eof_calls/extcall_callee_revert.json) -- [x] REVERT inside EXTDELEGATECALL (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_callee_revert.json) -- [x] REVERT inside EXTSTATICCALL (evmone-tests: state_tests/state_transition/eof_calls/extstaticcall_callee_revert.json) -- [x] EXTCALL with input (evmone-tests: state_tests/state_transition/eof_calls/extcall_input.json) -- [x] EXTDELEGATECALL with input (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_input.json) -- [x] EXTSTATICCALL with input (evmone-tests: state_tests/state_transition/eof_calls/extstaticcall_input.json) -- [x] EXTCALL with just enough gas for MIN_RETAINED_GAS and MIN_CALLEE_GAS (evmone-tests: state_tests/state_transition/eof_calls/extcall_with_value_enough_gas.json) -- [x] EXTCALL with not enough gas for MIN_CALLEE_GAS (evmone-tests: state_tests/state_transition/eof_calls/extcall_with_value_low_gas.json) +- [x] EXTDELEGATECALL from EOF to EOF (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_eof_sstore) +- [x] EXTDELEGATECALL from EOF to legacy fails (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_legacy_sstore) +- [ ] EXTDELEGATECALL forwards static mode (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_static.json) +- [x] EXTCALL with value success (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_with_value) +- [x] EXTCALL with value from EXTSTATICCALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_static_flag_with_value) +- [x] EXTCALL with value, not enough balance (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_with_value) +- [x] EXTCALL with value, check additional charge for value (./eip7692_eof_v1/eip7069_extcall/test_gas.py::test_ext_calls_gas) +- [x] EXTCALL with gas not enough for callee to get 5000 gas (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_min_callee_gas) +- [x] RETURNDATA* after EXTCALL (./eip7069_extcall/test_returndataload.py) +- [x] RETURNDATA* after EXTDELEGATECALL (./eip7069_extcall/test_returndataload.py) +- [x] RETURNDATA* after EXTSTATICCALL (./eip7069_extcall/test_returndataload.py) +- [x] RETURNDATA* after aborted EXT*CALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_clear_return_buffer) +- [x] Failed EXTCALL clears returndata from previous EXTCALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_clear_return_buffer) +- [x] EXTCALL not enough gas for input memory charge (./eip7692_eof_v1/eip7069_extcall/test_gas.py::test_ext_calls_gas) +- [x] EXTDELEGATECALL not enough gas for input memory charge (./eip7692_eof_v1/eip7069_extcall/test_gas.py::test_ext_calls_gas) +- [x] EXTSTATICCALL not enough gas for input memory charge (./eip7692_eof_v1/eip7069_extcall/test_gas.py::test_ext_calls_gas) +- [x] EXTCALL exception due to target address overflow (bits set in high 12 bytes) (./eip7069_extcall/test_address_space_extension.py) +- [x] EXTDELEGATECALL exception due to target address overflow (bits set in high 12 bytes) (./eip7069_extcall/test_address_space_extension.py) +- [x] EXTSTATICCALL exception due to target address overflow (bits set in high 12 bytes) (./eip7069_extcall/test_address_space_extension.py) +- [x] EXTCALL not enough gas for warming up target address (./eip7692_eof_v1/eip7069_extcall/test_gas.py::test_ext_calls_gas) +- [x] EXTDELEGATECALL not enough gas for warming up target address (./eip7692_eof_v1/eip7069_extcall/test_gas.py::test_ext_calls_gas) +- [x] EXTSTATICCALL not enough gas for warming up target address (./eip7692_eof_v1/eip7069_extcall/test_gas.py::test_ext_calls_gas) +- [x] EXTCALL not enough gas for account creation cost (transfer value to non-existing account) (./eip7692_eof_v1/eip7069_extcall/test_gas.py::test_ext_calls_gas) +- [x] OOG after EXTCALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_eof_then_fails) +- [x] OOG after EXTDELEGATECALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_eof_then_fails) +- [x] OOG after EXTSTATICCALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_eof_then_fails) +- [x] REVERT inside EXTCALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_revert_abort) +- [x] REVERT inside EXTDELEGATECALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_revert_abort) +- [x] REVERT inside EXTSTATICCALL (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_revert_abort) +- [x] EXTCALL with input (./eip7692_eof_v1/eip7069_extcall/test_calldata.py) +- [x] EXTDELEGATECALL with input (./eip7692_eof_v1/eip7069_extcall/test_calldata.py) +- [x] EXTSTATICCALL with input (./eip7692_eof_v1/eip7069_extcall/test_calldata.py) +- [x] EXTCALL with just enough gas for MIN_RETAINED_GAS and MIN_CALLEE_GAS (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_min_callee_gas) +- [x] EXTCALL with not enough gas for MIN_CALLEE_GAS (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_min_callee_gas) - [ ] ADDRESS and CALLER inside EXTCALL (evmone-tests: state_tests/state_transition/eof_calls/extcall_recipient_and_code_address.json) - [ ] ADDRESS and CALLER inside EXTDELEGATECALL (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_recipient_and_code_address.json) - [ ] ADDRESS and CALLER inside EXTSTATICCALL (evmone-tests: state_tests/state_transition/eof_calls/extstaticcall_recipient_and_code_address.json) - [ ] Refund inside EXTCALL is applied after the transaction (evmone-tests: state_tests/state_transition/eof_calls/extcall_gas_refund_propagation.json) - [ ] Refund inside EXTDELEGATECALL is applied after the transaction (evmone-tests: state_tests/state_transition/eof_calls/extdelegatecall_gas_refund_propagation.json) -- [x] EXTSTATICCALL from EOF to non-pure legacy contract failing (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_CallsFiller.yml) -- [x] EXTSTATICCALL from EOF to pure EOF contract (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_CallsFiller.yml) -- [x] EXTSTATICCALL from EOF to non-pure EOF contract failing (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_CallsFiller.yml) +- [x] EXTSTATICCALL from EOF to non-pure legacy contract failing (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_legacy_sstore) +- [x] EXTSTATICCALL from EOF to pure EOF contract (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_legacy_mstore) +- [x] EXTSTATICCALL from EOF to non-pure EOF contract failing (./eip7692_eof_v1/eip7069_extcall/test_calls.py::test_eof_calls_eof_sstore) ## EIP-7620: EOF Contract Creation diff --git a/whitelist.txt b/whitelist.txt index 849b514fc5..9b88980359 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -249,6 +249,7 @@ makeconftest marioevz markdownlint md +mem metaclass mixhash mkdocs