From aecb7de8b163b01b87203662aea2a3449352d966 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Fri, 31 Jul 2026 12:27:48 +0200 Subject: [PATCH] fix(tests): rewrite ported static gas-snapshot tests with fork-derived measurements --- .claude/commands/enhance-ported-test.md | 33 + .../berlin/stEIP2930/test_coinbase_t01.py | 193 +- .../berlin/stEIP2930/test_coinbase_t2.py | 183 +- .../stRevertTest/test_cost_revert.py | 631 +----- .../stRevertTest/test_revert_depth2.py | 229 +- .../stRevertTest/test_state_revert.py | 341 +-- ...more_gas_on_depth2_then_transaction_has.py | 220 +- ...st_static_call_goes_oog_on_second_level.py | 101 +- ...t_static_call_goes_oog_on_second_level2.py | 162 +- ...t_static_call_oog_additional_gas_costs1.py | 79 +- ...ic_call_oog_additional_gas_costs2_paris.py | 79 +- .../test_static_call_recursive_bomb_log2.py | 90 +- .../stStaticCall/test_static_callcall_00.py | 262 +-- .../test_static_callcallcall_000.py | 296 +-- .../test_static_callcallcallcode_001.py | 277 +-- ...test_static_callcodecall_10_suicide_end.py | 159 +- ...est_static_callcodecall_10_suicide_end2.py | 154 +- ...c_callcodecallcallcode_101_oogm_after_3.py | 277 +-- ...ic_callcodecallcodecall_110_suicide_end.py | 204 +- ...c_callcodecallcodecall_110_suicide_end2.py | 250 +-- ...allcodecallcodecall_110_suicide_middle2.py | 126 +- .../test_static_zero_value_call_oog_revert.py | 92 +- .../stCreateTest/test_create_large_result.py | 599 +++--- .../test_create_oo_gafter_max_codesize.py | 284 +-- .../test_call1_mb1024_calldepth.py | 170 +- .../test_refund_multimple_suicide.py | 215 +- .../test_refund_single_suicide.py | 179 +- .../test_test_random_test.py | 61 +- .../vmIOandFlowOperations/test_gas.py | 162 +- .../vmIOandFlowOperations/test_jump.py | 621 ++---- .../test_jump_to_push.py | 1866 +---------------- .../vmIOandFlowOperations/test_jumpi.py | 913 +++----- .../frontier/vmTests/test_block_info.py | 213 +- .../frontier/vmTests/test_env_info.py | 422 +--- .../test_self_balance_call_types.py | 322 +-- .../test_call_one_v_call_suicide.py | 110 - .../test_call_one_v_call_suicide2.py | 118 -- .../test_call_v_call_suicide.py | 127 ++ .../test_call_zero_v_call_suicide.py | 101 - .../test_call_goes_oog_on_second_level.py | 176 +- .../test_call_goes_oog_on_second_level2.py | 142 +- .../test_gas_cost_exp.py | 187 +- .../test_gas_cost_jump.py | 258 +-- .../test_gas_cost_mem_seg.py | 827 +------- .../test_gas_cost_return.py | 158 +- .../test_raw_balance_gas.py | 57 +- 46 files changed, 3376 insertions(+), 9350 deletions(-) delete mode 100644 tests/ported_static/spurious_dragon/stEIP158Specific/test_call_one_v_call_suicide.py delete mode 100644 tests/ported_static/spurious_dragon/stEIP158Specific/test_call_one_v_call_suicide2.py create mode 100644 tests/ported_static/spurious_dragon/stEIP158Specific/test_call_v_call_suicide.py delete mode 100644 tests/ported_static/spurious_dragon/stEIP158Specific/test_call_zero_v_call_suicide.py diff --git a/.claude/commands/enhance-ported-test.md b/.claude/commands/enhance-ported-test.md index 0d0404b2a04..53a2beff30a 100644 --- a/.claude/commands/enhance-ported-test.md +++ b/.claude/commands/enhance-ported-test.md @@ -446,6 +446,13 @@ floor: pass `return_cost_deducted_prior_execution=True` to the intrinsic calculator whenever the tx has calldata, or the derived `executed` (and the cap) overstate. Validated on `test_refund_suicide50procent_cap`. +**Derive EIP-170 size boundaries from `fork.max_code_size()`.** +EIP-7954 raises the deployed-code ceiling (0x6000 → 0x10000 on +Amsterdam), so a pinned "one byte too big" size silently deploys on the +future fork. Compute max/toobig/huge sizes from the fork accessor and +let the created-account expectation follow. Validated on +`test_create_large_result`. + **A CREATE address collision burns the child's gas allowance** (the EIP-684 path): the withheld child grant is consumed, nothing is created, and under EIP-8037 the new-account state charge is refunded. Useful to @@ -458,12 +465,38 @@ legal budget gets there. For call-loop depth tests the honest shape is a fixed named budget with per-gas-schedule-era pinned depth counts, each shift explained (±1 frame ≈ 64·ln(cost ratio)). Validated on `test_loop_calls_depth_then_revert`. +**EIP-8037 corollary: pinned depths can shift by more than one frame.** +With reservoir 0, the unwind's single zero→non-zero store of a call +result (~111k with its state spill) can exceed the 63/64 retention of +the ancestor paying it, so that frame reverts too and the cascade drops +extra increments — trace the unwind before pinning. Validated on +`test_call1_mb1024_calldepth` (depth 4 → 2 on Amsterdam). **Framework wart: the SSTORE dirty-rewrite composite prices 100 on every fork**, but Constantinople/Petersburg charge 5,000 for a dirty re-store — a derived budget that must survive pre-Istanbul forks needs an explicit headroom constant for it (named, commented). Observed on `test_revert_depth_create_address_collision`'s ConstantinopleFix sweep. +The general form of the wart: **every composite prices every fork with +the Berlin schedule** (cold 2600/warm 100, EXP 50/byte, SSTORE 20k+2100, +CALL 2600+9000). Two consequences: (a) any measurement asserting a +warm/cold composite floors at Berlin — the pre-Berlin sweep fails on the +gas value itself; (b) a *budget* derived from composites overshoots by a +few thousand on TangerineWhistle..Istanbul, so a threshold that must +hold there (e.g. "retention stays ≤ the EIP-2200 stipend") needs a named +drift-headroom constant subtracted before deriving (validated on +`test_call_goes_oog_on_second_level2`, where the surplus pushed +Istanbul's retention past 2300). + +**Size a reservoir as `N * store.state_cost(fork)` to keep a frame-gas +scenario alive on EIP-8037 forks.** When a ported cascade needs several +cold first-set SSTOREs to fit inside small frame budgets (impossible +in-frame on 8037: each is ~111k), set +`state_gas_reservoir = N * entry_store.state_cost(fork)` for the N +stores: the frames then pay only `execution_cost` and the pre-8037 +arithmetic carries over unchanged. The expression needs no fork branch — +`state_cost` is 0 before 8037, and a zero reservoir is accepted there. +Validated on `test_call_goes_oog_on_second_level2`. **EIP-8037 repriced the code deposit's regular part — boundaries beware.** On 8037 forks the deposit charges only the keccak word cost diff --git a/tests/ported_static/berlin/stEIP2930/test_coinbase_t01.py b/tests/ported_static/berlin/stEIP2930/test_coinbase_t01.py index 754f52c4628..016ee823420 100644 --- a/tests/ported_static/berlin/stEIP2930/test_coinbase_t01.py +++ b/tests/ported_static/berlin/stEIP2930/test_coinbase_t01.py @@ -1,175 +1,100 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Measure the gas cost of a value-transferring CALL to the coinbase from +type-0 and type-1 (EIP-2930 access list) transactions +(by Ori Pomerantz qbzzt1@gmail.com). Ported from: state_tests/stEIP2930/coinbaseT01Filler.yml -@manually-enhanced: Do not overwrite. The target contract measures, via -`Op.GAS`, the regular gas of a `CALL` that transfers value to the warm, -already-existing coinbase. EIP-8038 reprices the value-transfer -component (`CALL_VALUE` 9 000 -> 10 300), so the measurement grows by -`gas_costs.CALL_VALUE - 9000`. That delta is derived from the fork's -own gas model, so it is exactly 0 before EIP-8038 and tracks future -parameter changes; do not hardcode the Amsterdam number. +@manually-enhanced: Do not overwrite. The legacy raw GAS-delta window is +reframed as a CodeGasMeasure over the CALL, asserting the fork-derived +composite (minus the returned stipend); the coinbase is warm from an +access-list entry or EIP-3651 (Shanghai), cold otherwise. """ import pytest from execution_testing import ( AccessList, Account, - Address, Alloc, - Bytes, + CodeGasMeasure, Environment, - Hash, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) +from execution_testing.forks import Shanghai from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x0 +TRANSFER_VALUE = 1_000_000 + @pytest.mark.ported_from( ["state_tests/stEIP2930/coinbaseT01Filler.yml"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Berlin") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="T0", - ), - pytest.param( - 1, - 0, - 0, - id="T1baseInList", - ), - pytest.param( - 2, - 0, - 0, - id="T1baseNotInList", - ), - ], + "access_list_case", + ["no_access_list", "coinbase_in_list", "coinbase_not_in_list"], + ids=["T0", "T1baseInList", "T1baseNotInList"], ) -@pytest.mark.pre_alloc_mutable def test_coinbase_t01( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + access_list_case: str, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x7704D8A022A1BA8F3539FC82C7D7FB065ABC0DF3) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000, nonce=1) + """Measure a value CALL to the coinbase per access-list variant.""" + # The coinbase must exist and be alive, so the value transfer never + # writes a new account. + coinbase = pre.fund_eoa(amount=1) + env = Environment(fee_recipient=coinbase) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=100, - ) + if access_list_case == "no_access_list": + # A plain type-0 transaction. + access_list = None + elif access_list_case == "coinbase_in_list": + access_list = [AccessList(address=coinbase, storage_keys=[])] + else: + # A type-1 transaction whose access list does not warm the + # coinbase. + access_list = [ + AccessList(address=pre.nonexistent_account(), storage_keys=[]) + ] - pre[coinbase] = Account(balance=0, nonce=1) - # Source: yul - # berlin - # { - # mstore(0, gas()) - # pop(call(gas(), , 1000000, 0, 0, 0, 0)) # noqa: E501 - # mstore(0x20, gas()) - # - # // The 24 is the cost of twi gas(), seven pushes(), a pop(), and an mstore() # noqa: E501 - # sstore(0, sub(sub(mload(0), mload(0x20)),33)) - # } - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=Op.GAS, - address=coinbase, - value=0xF4240, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x0), Op.MLOAD(offset=0x20)), 0x21 - ), - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=1, + # EIP-3651 (Shanghai) pre-warms the coinbase; before that it is only + # warm when the access list names it. + coinbase_warm = fork >= Shanghai or access_list_case == "coinbase_in_list" + call_code = Op.CALL( + address=coinbase, + value=TRANSFER_VALUE, + address_warm=coinbase_warm, + value_transfer=True, + ) + target = pre.deploy_contract( + code=CodeGasMeasure( + code=call_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), + balance=TRANSFER_VALUE, ) - - # EIP-8038 reprices the value-transfer component of `CALL`; with the - # coinbase warm and already in state, the measured gas grows by the - # `CALL_VALUE` reprice alone. Derived from the fork gas model so it - # is 0 before EIP-8038. - call_value_delta = fork.gas_costs().CALL_VALUE - 9000 - - expect_entries_: list[dict] = [ - { - "indexes": {"data": [1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 6800 + call_value_delta})}, - }, - { - "indexes": {"data": [0, 2], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 6800 + call_value_delta})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes("693c6139") + Hash(0x0), - Bytes("693c6139") + Hash(0x0), - Bytes("693c6139") + Hash(0x0), - ] - tx_gas = [16777216] - tx_access_lists: dict[int, list] = { - 1: [ - AccessList( - address=coinbase, - storage_keys=[], - ), - ], - 2: [ - AccessList( - address=Address(0x000000000000000000000000000000000000BA5A), - storage_keys=[], - ), - ], - } tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - nonce=1, - gas_price=1000, - access_list=tx_access_lists.get(d), - error=_exc, + access_list=access_list, + state_gas_reservoir=0, ) + # The coinbase consumes nothing, so the stipend handed over with the + # value comes back unused. + measured_gas = call_code.gas_cost(fork) - fork.gas_costs().CALL_STIPEND + + post = {target: Account(storage={GAS_SLOT: measured_gas}, balance=0)} + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/berlin/stEIP2930/test_coinbase_t2.py b/tests/ported_static/berlin/stEIP2930/test_coinbase_t2.py index 5e8924e0b17..8c1ce867aee 100644 --- a/tests/ported_static/berlin/stEIP2930/test_coinbase_t2.py +++ b/tests/ported_static/berlin/stEIP2930/test_coinbase_t2.py @@ -1,169 +1,98 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Measure the gas cost of a value-transferring CALL to the coinbase from +type-2 (EIP-1559) transactions with access lists +(by Ori Pomerantz qbzzt1@gmail.com). Ported from: state_tests/stEIP2930/coinbaseT2Filler.yml -@manually-enhanced: Do not overwrite. The target contract measures, via -`Op.GAS`, the regular gas of a `CALL` that transfers value to the warm, -already-existing coinbase. EIP-8038 reprices the value-transfer -component (`CALL_VALUE` 9 000 -> 10 300), so the measurement grows by -`gas_costs.CALL_VALUE - 9000`. That delta is derived from the fork's -own gas model, so it is exactly 0 before EIP-8038 and tracks future -parameter changes; do not hardcode the Amsterdam number. +@manually-enhanced: Do not overwrite. The legacy raw GAS-delta window is +reframed as a CodeGasMeasure over the CALL, asserting the fork-derived +composite (minus the returned stipend); the coinbase is warm from an +access-list entry or EIP-3651 (Shanghai), cold otherwise. """ import pytest from execution_testing import ( AccessList, Account, - Address, Alloc, - Bytes, + CodeGasMeasure, Environment, - Hash, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) +from execution_testing.forks import Shanghai from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x0 +TRANSFER_VALUE = 1_000_000 + @pytest.mark.ported_from( ["state_tests/stEIP2930/coinbaseT2Filler.yml"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("London") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="T2baseInList", - ), - pytest.param( - 1, - 0, - 0, - id="T2baseNotInList", - ), - ], + "coinbase_in_list", + [True, False], + ids=["T2baseInList", "T2baseNotInList"], ) -@pytest.mark.pre_alloc_mutable def test_coinbase_t2( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + coinbase_in_list: bool, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x7704D8A022A1BA8F3539FC82C7D7FB065ABC0DF3) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000, nonce=1) + """Measure a value CALL to the coinbase per access-list variant.""" + # The coinbase must exist and be alive, so the value transfer never + # writes a new account. + coinbase = pre.fund_eoa(amount=1) + env = Environment(fee_recipient=coinbase) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=100, - ) + if coinbase_in_list: + access_list = [AccessList(address=coinbase, storage_keys=[])] + else: + # The access list does not warm the coinbase. + access_list = [ + AccessList(address=pre.nonexistent_account(), storage_keys=[]) + ] - pre[coinbase] = Account(balance=0, nonce=1) - # Source: yul - # berlin - # { - # mstore(0, gas()) - # pop(call(gas(), , 1000000, 0, 0, 0, 0)) # noqa: E501 - # mstore(0x20, gas()) - # - # // The 24 is the cost of twi gas(), seven pushes(), a pop(), and an mstore() # noqa: E501 - # sstore(0, sub(sub(mload(0), mload(0x20)),33)) - # } - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=Op.GAS, - address=coinbase, - value=0xF4240, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x0), Op.MLOAD(offset=0x20)), 0x21 - ), - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=1, + # EIP-3651 (Shanghai) pre-warms the coinbase; before that it is only + # warm when the access list names it. + coinbase_warm = fork >= Shanghai or coinbase_in_list + call_code = Op.CALL( + address=coinbase, + value=TRANSFER_VALUE, + address_warm=coinbase_warm, + value_transfer=True, + ) + target = pre.deploy_contract( + code=CodeGasMeasure( + code=call_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), + balance=TRANSFER_VALUE, ) - - # EIP-8038 reprices the value-transfer component of `CALL`; with the - # coinbase warm and already in state, the measured gas grows by the - # `CALL_VALUE` reprice alone. Derived from the fork gas model so it - # is 0 before EIP-8038. - call_value_delta = fork.gas_costs().CALL_VALUE - 9000 - - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 6800 + call_value_delta})}, - }, - { - "indexes": {"data": [1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 6800 + call_value_delta})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes("693c6139") + Hash(0x0), - Bytes("693c6139") + Hash(0x0), - ] - tx_gas = [16777216] - tx_access_lists: dict[int, list] = { - 0: [ - AccessList( - address=coinbase, - storage_keys=[], - ), - ], - 1: [ - AccessList( - address=Address(0x000000000000000000000000000000000000BA5A), - storage_keys=[], - ), - ], - } tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - max_fee_per_gas=10000, + max_fee_per_gas=10_000, max_priority_fee_per_gas=100, - nonce=1, - access_list=tx_access_lists.get(d), - error=_exc, + access_list=access_list, + state_gas_reservoir=0, ) + # The coinbase consumes nothing, so the stipend handed over with the + # value comes back unused. + measured_gas = call_code.gas_cost(fork) - fork.gas_costs().CALL_STIPEND + + post = {target: Account(storage={GAS_SLOT: measured_gas}, balance=0)} + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stRevertTest/test_cost_revert.py b/tests/ported_static/byzantium/stRevertTest/test_cost_revert.py index 7c6fddec42c..d7309883dfc 100644 --- a/tests/ported_static/byzantium/stRevertTest/test_cost_revert.py +++ b/tests/ported_static/byzantium/stRevertTest/test_cost_revert.py @@ -1,560 +1,145 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Measure what a failing callee costs its caller for every call kind and +failure kind: a reverting callee charges only the gas it actually used +before reverting, while out-of-gas, undefined-opcode, bad-jump and +stack failures consume the callee's whole grant. Ported from: state_tests/stRevertTest/costRevertFiller.yml + +@manually-enhanced: Do not overwrite. The hand-rolled GAS-delta +dispatcher (pinned 2609 delta, 0x2A/0x27 overheads, 80M gas limit that +capped validity at Prague) became a CodeGasMeasure per call kind; the +grant-consumed cases collapse to a sentinel via an in-EVM threshold, +and the revert case asserts the fork-derived cost. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, - Hash, + Bytecode, + CodeGasMeasure, + Conditional, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x0 +# Stored instead of the raw delta when the callee consumed its whole +# grant (the grant depends on the transaction budget, the delta of a +# cheap failure does not). +SENTINEL = 0xFFFFFF +# Any measured delta above this means the callee consumed its grant: +# far above every cheap-failure delta, far below any grant here. +CONSUMED_ALL_THRESHOLD = 0x100000 +# Slack the measurer keeps beyond its result SSTORE for the threshold +# collapse logic after the measured call. +COLLAPSE_HEADROOM = 2_000 + +FAILURE_MODES = [ + "revert", + "out_of_gas", + "xtreme_oog", + "bad_opcode", + "jump_badly", + "stack_underflow", + "stack_overflow", +] +CALL_KINDS = [Op.CALL, Op.DELEGATECALL, Op.STATICCALL, Op.CALLCODE] + @pytest.mark.ported_from( ["state_tests/stRevertTest/costRevertFiller.yml"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.valid_until("Prague") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="revert", - ), - pytest.param( - 1, - 0, - 0, - id="outOfGas", - ), - pytest.param( - 2, - 0, - 0, - id="xtremeOOG", - ), - pytest.param( - 3, - 0, - 0, - id="badOpcode", - ), - pytest.param( - 4, - 0, - 0, - id="jumpBadly", - ), - pytest.param( - 5, - 0, - 0, - id="stackUnder", - ), - pytest.param( - 6, - 0, - 0, - id="stackOver", - ), - pytest.param( - 7, - 0, - 0, - id="revert", - ), - pytest.param( - 8, - 0, - 0, - id="outOfGas", - ), - pytest.param( - 9, - 0, - 0, - id="xtremeOOG", - ), - pytest.param( - 10, - 0, - 0, - id="badOpcode", - ), - pytest.param( - 11, - 0, - 0, - id="jumpBadly", - ), - pytest.param( - 12, - 0, - 0, - id="stackUnder", - ), - pytest.param( - 13, - 0, - 0, - id="stackOver", - ), - pytest.param( - 14, - 0, - 0, - id="revert", - ), - pytest.param( - 15, - 0, - 0, - id="outOfGas", - ), - pytest.param( - 16, - 0, - 0, - id="xtremeOOG", - ), - pytest.param( - 17, - 0, - 0, - id="badOpcode", - ), - pytest.param( - 18, - 0, - 0, - id="jumpBadly", - ), - pytest.param( - 19, - 0, - 0, - id="stackUnder", - ), - pytest.param( - 20, - 0, - 0, - id="stackOver", - ), - pytest.param( - 21, - 0, - 0, - id="revert", - ), - pytest.param( - 22, - 0, - 0, - id="outOfGas", - ), - pytest.param( - 23, - 0, - 0, - id="xtremeOOG", - ), - pytest.param( - 24, - 0, - 0, - id="badOpcode", - ), - pytest.param( - 25, - 0, - 0, - id="jumpBadly", - ), - pytest.param( - 26, - 0, - 0, - id="stackUnder", - ), - pytest.param( - 27, - 0, - 0, - id="stackOver", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize("call_kind", CALL_KINDS, ids=lambda op: op._name_) +@pytest.mark.parametrize("failure_mode", FAILURE_MODES) def test_cost_revert( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + call_kind: Op, + failure_mode: str, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000001000) - contract_1 = Address(0x0000000000000000000000000000000000001001) - contract_2 = Address(0x0000000000000000000000000000000000001002) - contract_3 = Address(0x0000000000000000000000000000000000001003) - contract_4 = Address(0x0000000000000000000000000000000000001004) - contract_5 = Address(0x0000000000000000000000000000000000001005) - contract_6 = Address(0x0000000000000000000000000000000000001006) - contract_7 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) + """Measure a failing callee's cost to its caller per call kind.""" + revert_code = Op.REVERT(offset=0x0, size=0x10, new_memory_size=0x10) + callee_code: Bytecode | bytes + if failure_mode == "revert": + callee_code = revert_code + Op.STOP + elif failure_mode == "out_of_gas": + # Hashing a 16 MiB window costs more than any legal budget. + callee_code = Op.SHA3(offset=0x0, size=0x1000000) + Op.STOP + elif failure_mode == "xtreme_oog": + # A 2**256-1 hash size overflows any gas accounting. + callee_code = Op.SHA3(offset=0x0, size=Op.SUB(0x0, 0x1)) + Op.STOP + elif failure_mode == "bad_opcode": + # 0xBA is undefined on every fork. + callee_code = b"\xba" + elif failure_mode == "jump_badly": + # Position zero is a PUSH, not a JUMPDEST. + callee_code = Op.JUMP(pc=0x0) + elif failure_mode == "stack_underflow": + # LT on an empty stack. + callee_code = Op.LT + Op.STOP + else: + # Each loop iteration leaves one PC value behind until the + # stack overflows. + callee_code = Op.JUMPDEST + Op.PC + Op.JUMP(pc=Op.SUB(Op.PC, 0x4)) + callee = pre.deploy_contract(code=callee_code) - pre[sender] = Account(balance=0x100000000000) - # Source: lll - # { - # (revert 0 0x10) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.REVERT(offset=0x0, size=0x10) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001000), # noqa: E501 - ) - # Source: lll - # { - # (while 1 (sha3 0 0x1000000)) - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPDEST - + Op.JUMPI(pc=0x13, condition=Op.ISZERO(0x1)) - + Op.POP(Op.SHA3(offset=0x0, size=0x1000000)) - + Op.JUMP(pc=0x0) - + Op.JUMPDEST - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001001), # noqa: E501 - ) - # Source: lll - # { - # (sha3 0 (- 0 1)) - # } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.SHA3(offset=0x0, size=Op.SUB(0x0, 0x1)) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001002), # noqa: E501 - ) - # Source: raw - # 0x610103600155600060006000600061dead6175305a03f450BA - contract_3 = pre.deploy_contract( # noqa: F841 - code=bytes.fromhex( - "610103600155600060006000600061dead6175305a03f450ba" - ), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001003), # noqa: E501 - ) - # Source: raw - # 0x610104600155600060006000600061dead6175305a03f450600056 - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x104) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=0xDEAD, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.JUMP(pc=0x0), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001004), # noqa: E501 - ) - # Source: raw - # 0x1000 - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.LT + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001005), # noqa: E501 - ) - # Source: raw - # 0x5b586004580356 - contract_6 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPDEST + Op.PC + Op.JUMP(pc=Op.SUB(Op.PC, 0x4)), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001006), # noqa: E501 + # Ask for everything except what the result SSTORE and the collapse + # need after the call. + reserve = ( + Op.SSTORE(key_warm=False, original_value=0, new_value=1).gas_cost(fork) + + COLLAPSE_HEADROOM ) - # Source: lll - # { - # (if (= $36 0) { ; CALL - # [0x00] (gas) - # - # ; Leave us some gas even if the call takes all of it - # (call (- (gas) 30000) $4 0 0 0 0 0) - # - # [0x20] (gas) - # - # ; Opcodes between the two gas measurements cost 42 gas - # - # ; 0-1 GAS 2 0 79978808 - # ; 1-1 PUSH1 3 2 79978806 - # ; 2-1 MSTORE 6 5 79978803 - # ; 3-1 PUSH1 3 11 79978797 - # ; 4-1 PUSH1 3 14 79978794 - # ; 5-1 PUSH1 3 17 79978791 - # ; 6-1 PUSH1 3 20 79978788 - # ; 7-1 PUSH1 3 23 79978785 - # ; 8-1 PUSH1 3 26 79978782 - # ; 9-1 CALLDATALOAD 3 29 79978779 - # ; 10-1 PUSH2 3 38 79978770 - # ; 11-1 GAS 2 41 79978767 - # ; 12-1 SUB 3 43 79978765 - # ; - # ; The call goes here, and the cost varies based - # ; on what the call does - # ; - # ; 17-1 POP 2 24761 79954047 - # - # ... (59 more lines) - contract_7 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI( - pc=Op.PUSH2[0x11], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x24), 0x0), + call_args = { + "gas": Op.SUB(Op.GAS, reserve), + "address": callee, + } + if call_kind in (Op.CALL, Op.CALLCODE): + call_args["value"] = 0x0 + call_op = call_kind(**call_args) # type: ignore[arg-type] + measurer = pre.deploy_contract( + code=CodeGasMeasure( + code=call_op, + extra_stack_items=1, + sstore_key=GAS_SLOT, ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x3B]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=Op.CALLDATALOAD(offset=0x4), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + + Conditional( + condition=Op.GT(Op.SLOAD(key=GAS_SLOT), CONSUMED_ALL_THRESHOLD), + if_true=Op.SSTORE(key=GAS_SLOT, value=SENTINEL), ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x0), Op.MLOAD(offset=0x20)), 0x2A - ), - ) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x4D], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x24), 0x1), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x75]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=Op.CALLDATALOAD(offset=0x4), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x0), Op.MLOAD(offset=0x20)), 0x27 - ), - ) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x87], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x24), 0x2), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0xAF]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.STATICCALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=Op.CALLDATALOAD(offset=0x4), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x0), Op.MLOAD(offset=0x20)), 0x27 - ), - ) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0xC1], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x24), 0x3), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0xEB]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=Op.SUB(Op.GAS, 0x7530), - address=Op.CALLDATALOAD(offset=0x4), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x0), Op.MLOAD(offset=0x20)), 0x2A - ), - ) - + Op.JUMPDEST - + Op.JUMPI(pc=0x100, condition=Op.GT(Op.SLOAD(key=0x0), 0x4000000)) - + Op.SLOAD(key=0x0) - + Op.JUMP(pc=0x105) - + Op.JUMPDEST - + Op.PUSH3[0xFFFFFF] - + Op.JUMPDEST - + Op.PUSH1[0x0] - + Op.SSTORE + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0, 21, 14, 7], "gas": -1, "value": -1}, - "network": [">=Cancun=Cancun Bytecode: + """ + Build the shared slot-0 increment used by every contract. + + The SLOAD pays the slot's cold access, so the SSTORE on the same + slot is warm. + """ + return Op.SSTORE( + key=0x0, + value=Op.ADD(0x1, Op.SLOAD(key=0x0)), + key_warm=True, + original_value=0, + new_value=1, + ) + @pytest.mark.ported_from( ["state_tests/stRevertTest/RevertDepth2Filler.json"], ) -@pytest.mark.valid_from("Frontier") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-g0", - ), - pytest.param( - 0, - 1, - 0, - id="-g1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize("cut_point", ["final_sstore", "second_call"]) def test_revert_depth2( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + cut_point: str, ) -> None: - """Test_revert_depth2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, + """Unwind depth-two storage increments on a top-frame OOG.""" + # Leaf: increments its slot 0. Called once per chain; the second + # call sees a warm address, a warm key, and a dirty slot. + leaf_code = _increment() + Op.STOP + leaf = pre.deploy_contract(code=leaf_code) + leaf_first_consumed = leaf_code.gas_cost(fork) + # The second call's consumption is undercounted (its dirty SSTORE + # is priced differently across forks): undercounting keeps the + # derived budget below the true total, which is the safe side. + leaf_second_read = Op.ADD(0x1, Op.SLOAD(key=0x0, key_warm=True)) + leaf_second_consumed = leaf_second_read.gas_cost(fork) + leaf_first_grant = leaf_first_consumed + GRANT_HEADROOM + leaf_second_grant = ( + leaf_second_consumed + DIRTY_SSTORE_HEADROOM + GRANT_HEADROOM ) - # Source: lll - # { [[0]] (ADD 1 (SLOAD 0)) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(0x1, Op.SLOAD(key=0x0))) - + Op.STOP, - nonce=0, - ) - # Source: lll - # { [[0]] (ADD 1 (SLOAD 0)) [[1]] (CALL 50000 0 0 0 0 0) [[2]] (GAS)} # noqa: E501 - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(0x1, Op.SLOAD(key=0x0))) + # First chain: increment, then call the (cold) leaf. + chain1_code = ( + _increment() + Op.SSTORE( key=0x1, - value=Op.CALL( - gas=0xC350, - address=addr_2, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + value=Op.CALL(gas=leaf_first_grant, address=leaf), + key_warm=False, + original_value=0, + new_value=1, ) - + Op.SSTORE(key=0x2, value=Op.GAS) - + Op.STOP, - nonce=0, + + Op.STOP ) - # Source: lll - # { [[0]] (ADD 1 (SLOAD 0)) [[1]] (CALL 50000 0 0 0 0 0)} # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(0x1, Op.SLOAD(key=0x0))) + chain1 = pre.deploy_contract(code=chain1_code) + chain1_consumed = chain1_code.gas_cost(fork) + leaf_first_consumed + chain1_grant = chain1_code.gas_cost(fork) + leaf_first_grant + + # Second chain: increment, call the (now warm) leaf, snapshot GAS. + chain2_code = ( + _increment() + Op.SSTORE( key=0x1, value=Op.CALL( - gas=0xC350, - address=addr_2, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + gas=leaf_second_grant, address=leaf, address_warm=True ), + key_warm=False, + original_value=0, + new_value=1, ) - + Op.STOP, - nonce=0, + + Op.SSTORE( + key=0x2, + value=Op.GAS, + key_warm=False, + original_value=0, + new_value=1, + ) + + Op.STOP ) - # Source: lll - # { [[0]] (ADD 1 (SLOAD 0)) [[1]] (CALL 150000 0 0 0 0 0) [[2]] (CALL 150000 0 0 0 0 0)} # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(0x1, Op.SLOAD(key=0x0))) + chain2 = pre.deploy_contract(code=chain2_code) + chain2_consumed = chain2_code.gas_cost(fork) + leaf_second_consumed + chain2_grant = ( + chain2_code.gas_cost(fork) + leaf_second_grant + GRANT_HEADROOM + ) + + target_code = ( + _increment() + Op.SSTORE( key=0x1, - value=Op.CALL( - gas=0x249F0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + value=Op.CALL(gas=chain1_grant, address=chain1), + key_warm=False, + original_value=0, + new_value=1, ) + Op.SSTORE( key=0x2, - value=Op.CALL( - gas=0x249F0, - address=addr_3, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + value=Op.CALL(gas=chain2_grant, address=chain2), + key_warm=False, + original_value=0, + new_value=1, ) - + Op.STOP, - nonce=0, + + Op.STOP ) + target = pre.deploy_contract(code=target_code) - tx_data = [ - Bytes(""), - ] - tx_gas = [170685, 136685] + # One gas short of full completion: the OOG lands at the final + # SSTORE, or — cutting the second chain's whole cost too — already + # at the second call's machinery. + total = ( + fork.transaction_intrinsic_cost_calculator()() + + target_code.gas_cost(fork) + + chain1_consumed + + chain2_consumed + ) + gas_limit = total - 1 + if cut_point == "second_call": + final_sstore_cost = Op.SSTORE( + key_warm=False, original_value=0, new_value=1 + ).gas_cost(fork) + gas_limit -= chain2_consumed + final_sstore_cost + sender = pre.fund_eoa() tx = Transaction( - protected=fork.supports_protected_txs(), sender=sender, to=target, - data=tx_data[d], - gas_limit=tx_gas[g], + gas_limit=gas_limit, ) post = { - target: Account(storage={0: 0, 1: 0, 2: 0}), - addr: Account(storage={0: 0, 1: 0}), - addr_2: Account(storage={0: 0}), - addr_3: Account(storage={0: 0, 1: 0, 2: 0}), + target: Account(storage={}), + chain1: Account(storage={}), + chain2: Account(storage={}), + leaf: Account(storage={}), sender: Account(nonce=1), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stRevertTest/test_state_revert.py b/tests/ported_static/byzantium/stRevertTest/test_state_revert.py index e4a47789013..654cb1a892b 100644 --- a/tests/ported_static/byzantium/stRevertTest/test_state_revert.py +++ b/tests/ported_static/byzantium/stRevertTest/test_state_revert.py @@ -1,298 +1,127 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Verify every kind of frame failure — revert, out-of-gas, huge-size +out-of-gas, undefined opcode, bad jump, stack underflow and stack +overflow — rolls back the failing delegate frame's storage write while +the calling frame keeps its own state and observes the failure. Ported from: state_tests/stRevertTest/stateRevertFiller.yml + +@manually-enhanced: Do not overwrite. The ported dispatch computed +0x1000+d addresses that no contract occupied, so the failure modes +never executed; the target now delegate-calls the failing contract +directly and stores the observed failure (the ported never-dispatched +guard slot is superseded by this flag). The pinned 2^24 gas limit is +dropped for the maxed default. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Bytecode, + Fork, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# Sentinel the target writes before the failing delegate call. +SENTINEL = 0x60A7 +# Marker the failing frame writes first; it must never persist. +MARKER = 0x1001 +# Gas kept back from the throwaway delegate call to the dead address, +# left for the failure op itself. +DEAD_CALL_RESERVE = 0x7530 +# Empty address the failing frame delegate-calls before failing. +DEAD_ADDRESS = 0xDEAD + +FAILURE_MODES = [ + "revert", + "out_of_gas", + "xtreme_oog", + "bad_opcode", + "jump_badly", + "stack_underflow", + "stack_overflow", +] + @pytest.mark.ported_from( ["state_tests/stRevertTest/stateRevertFiller.yml"], ) @pytest.mark.valid_from("TangerineWhistle") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="revert", - ), - pytest.param( - 1, - 0, - 0, - id="outOfGas", - ), - pytest.param( - 2, - 0, - 0, - id="xtremeOOG", - ), - pytest.param( - 3, - 0, - 0, - id="badOpcode", - ), - pytest.param( - 4, - 0, - 0, - id="jumpBadly", - ), - pytest.param( - 5, - 0, - 0, - id="stackUnder", - ), - pytest.param( - 6, - 0, - 0, - id="stackOver", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.parametrize("failure_mode", FAILURE_MODES) def test_state_revert( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + failure_mode: str, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x100000000000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - # Source: lll - # { - # [[2]] 0x60A7 - # } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=0x60A7) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x4EDC28FF01C9F8731EDE6D0FD953DA91F749A659), # noqa: E501 - ) - # Source: lll - # { - # [[1]] 0x1000 - # (delegatecall (- (gas) 30000) 0xDEAD 0 0 0 0) - # (revert 0 0x10) - # } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x1000) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=0xDEAD, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.REVERT(offset=0x0, size=0x10) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x71A06D553F1AC38B5E568CE5A1B5DF253AD08D73), # noqa: E501 - ) - # Source: lll - # { - # [[1]] 0x1001 - # (delegatecall (- (gas) 30000) 0xDEAD 0 0 0 0) - # (while 1 (sha3 0 0x1000000)) - # } - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x1001) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=0xDEAD, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + """Roll back a failing delegate frame and observe the failure.""" + # Common prologue: record a marker (must revert), then make a + # throwaway delegate call to an empty address, keeping enough gas + # for the failure op. + prologue = Op.SSTORE(key=0x1, value=MARKER) + Op.POP( + Op.DELEGATECALL( + gas=Op.SUB(Op.GAS, DEAD_CALL_RESERVE), address=DEAD_ADDRESS ) - + Op.JUMPDEST - + Op.JUMPI(pc=0x2B, condition=Op.ISZERO(0x1)) - + Op.POP(Op.SHA3(offset=0x0, size=0x1000000)) - + Op.JUMP(pc=0x18) - + Op.JUMPDEST - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x16D83DA4C22C26F92C5A8D4CEDF367E171F60977), # noqa: E501 ) - # Source: lll - # { - # [[1]] 0x1002 - # (delegatecall (- (gas) 30000) 0xDEAD 0 0 0 0) - # (sha3 0 (- 0 1)) - # } - addr_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x1002) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=0xDEAD, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + failing_code: Bytecode | bytes + if failure_mode == "revert": + failing_code = prologue + Op.REVERT(offset=0x0, size=0x10) + Op.STOP + elif failure_mode == "out_of_gas": + # Hashing a 16 MiB window costs more than any legal budget. + failing_code = prologue + Op.SHA3(offset=0x0, size=0x1000000) + elif failure_mode == "xtreme_oog": + # A 2**256-1 hash size overflows any gas accounting. + failing_code = prologue + Op.SHA3(offset=0x0, size=Op.SUB(0x0, 0x1)) + elif failure_mode == "bad_opcode": + # 0xBA is undefined on every fork. + failing_code = bytes(prologue) + b"\xba" + elif failure_mode == "jump_badly": + # Position zero is a PUSH, not a JUMPDEST. + failing_code = prologue + Op.JUMP(pc=0x0) + elif failure_mode == "stack_underflow": + # The stack is empty after the prologue's POP. + failing_code = prologue + Op.ADD + Op.ADD + Op.ADD + else: + # Each loop iteration leaves one PC value behind until the + # stack overflows. + failing_code = ( + prologue + Op.JUMPDEST + Op.PC + Op.JUMP(pc=Op.SUB(Op.PC, 0x4)) ) - + Op.SHA3(offset=0x0, size=Op.SUB(0x0, 0x1)) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xEBE3A4514FECA3EB2819BF83EBD926C5E4143739), # noqa: E501 - ) - # Source: raw - # 0x610103600155600060006000600061dead6175305a03f450BA - addr_5 = pre.deploy_contract( # noqa: F841 - code=bytes.fromhex( - "610103600155600060006000600061dead6175305a03f450ba" - ), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x1985064D96BAAF3305FEE248DE22965FBF7FBAB6), # noqa: E501 - ) - # Source: raw - # 0x610104600155600060006000600061dead6175305a03f450600056 - addr_6 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x104) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=0xDEAD, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.JUMP(pc=0x0), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xDD77382F06BFEEA4258E6F7BFFC6D9D31B885815), # noqa: E501 - ) - # Source: raw - # 0x610105600155600060006000600061dead6175305a03f450010101 - addr_7 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x105) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=0xDEAD, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.ADD(Op.ADD, Op.ADD), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xBF0FC73E06F3B2ECA8CB8094BDB81D4D2AA2F9B0), # noqa: E501 - ) - # Source: raw - # 0x610106600155600060006000600061dead6175305a03f4505b586004580356 - addr_8 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x106) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x7530), - address=0xDEAD, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.JUMPDEST - + Op.PC - + Op.JUMP(pc=Op.SUB(Op.PC, 0x4)), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xE08A8DE27B3798640D504F1431A360F276B9F2AE), # noqa: E501 - ) - # Source: lll - # { - # [[0]] 0x60A7 - # (delegatecall (gas) (+ 0x1000 $4) 0 0 0 0) - # } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x60A7) - + Op.DELEGATECALL( - gas=Op.GAS, - address=Op.ADD(0x1000, Op.CALLDATALOAD(offset=0x4)), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + failing = pre.deploy_contract(code=failing_code) + + # The target writes its sentinel, delegate-calls the failing + # contract, and stores 1 if (and only if) that frame failed. + target = pre.deploy_contract( + code=Op.SSTORE(key=0x0, value=SENTINEL) + + Op.SSTORE( + key=0x2, + value=Op.ISZERO( + Op.DELEGATECALL(address=Op.CALLDATALOAD(offset=0x0)) + ), ) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x3559AFE49654B532B7E67E6ACD87DEB8C569E7AD), # noqa: E501 ) - tx_data = [ - Bytes("693c6139") + Hash(0x0), - Bytes("693c6139") + Hash(0x1), - Bytes("693c6139") + Hash(0x2), - Bytes("693c6139") + Hash(0x3), - Bytes("693c6139") + Hash(0x4), - Bytes("693c6139") + Hash(0x5), - Bytes("693c6139") + Hash(0x6), - ] - tx_gas = [16777216] - tx_value = [1] - tx = Transaction( protected=fork.supports_protected_txs(), - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], + data=Hash(failing, left_padding=True), ) - post = {target: Account(storage={0: 24743, 1: 0, 2: 0})} + # The sentinel survives, the failing frame's marker does not, and + # the observed failure flag is set. + post = { + target: Account(storage={0: SENTINEL, 1: 0, 2: 1}), + failing: Account(storage={}), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py b/tests/ported_static/byzantium/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py index d47a87e4f8f..cb543a6a2d5 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py @@ -1,222 +1,104 @@ """ -Test_static_call_ask_more_gas_on_depth2_then_transaction_has. +Verify a depth-two STATICCALL asking for more gas than its frame holds +is capped to all-but-one-64th and the chain still completes: the static +frames stay read-only, and only the writable depth-one frame records +its writes. Ported from: state_tests/stStaticCall/static_CallAskMoreGasOnDepth2ThenTransactionHasFiller.json + +@manually-enhanced: Do not overwrite. The ported d0/d1 twin chains were +folded into one chain parametrized on the leaf behavior; addresses are +dynamic and the oversized ask is named and guarded. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# The depth-one frame grants exactly this much to the depth-two frame. +DEPTH1_GAS_ASK = 0x30D40 +# The depth-two frame asks for more than its whole grant, so the ask is +# capped to all-but-one-64th of what it has (guarded below). +DEPTH2_GAS_ASK = 0x927C0 + @pytest.mark.ported_from( [ "state_tests/stStaticCall/static_CallAskMoreGasOnDepth2ThenTransactionHasFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") +@pytest.mark.parametrize("leaf_behavior", ["static_violation", "gas_snapshot"]) def test_static_call_ask_more_gas_on_depth2_then_transaction_has( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + leaf_behavior: str, ) -> None: - """Test_static_call_ask_more_gas_on_depth2_then_transaction_has.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Cap an oversized depth-two gas ask and complete the call chain.""" + assert DEPTH2_GAS_ASK > DEPTH1_GAS_ASK, ( + "the depth-two ask must exceed the frame's whole grant" ) - - # Source: lll - # { [[ 0 ]] (CALL (GAS) (CALLDATALOAD 0) (CALLVALUE) 0 0 0 0) [[ 1 ]] 1 } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=Op.GAS, - address=Op.CALLDATALOAD(offset=0x0), - value=Op.CALLVALUE, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + if leaf_behavior == "static_violation": + # Storage write inside the static context: the leaf frame halts + # and forfeits its capped grant. + leaf = pre.deploy_contract( + code=Op.SSTORE(key=0x8, value=0x1) + Op.STOP ) - + Op.SSTORE(key=0x1, value=0x1) - + Op.STOP, - nonce=0, - address=Address(0xC0E4183389EB57F779A986D8C878F89B9401DC8E), # noqa: E501 - ) - # Source: lll - # { (SSTORE 8 1)} - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x5044BFB29664A79DE12215897C630DC8A11B0B97), # noqa: E501 - ) - # Source: lll - # { (MSTORE 8 (GAS))} - addr_6 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x8, value=Op.GAS) + Op.STOP, - nonce=0, - address=Address(0x91B291A3336BC1357388354DF18CA061B39E3745), # noqa: E501 - ) - # Source: lll - # { (MSTORE 8 (GAS)) (MSTORE 9 (STATICCALL 600000 0 0 0 0)) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x8, value=Op.GAS) - + Op.MSTORE( - offset=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=0x5044BFB29664A79DE12215897C630DC8A11B0B97, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + else: + # Memory-only gas snapshot: the leaf frame succeeds. + leaf = pre.deploy_contract( + code=Op.MSTORE(offset=0x8, value=Op.GAS) + Op.STOP ) - + Op.STOP, - nonce=0, - address=Address(0xD9539C5A3DC4713D47A547BFC9A075BD97287080), # noqa: E501 - ) - # Source: lll - # { (MSTORE 8 (GAS)) (MSTORE 9 (STATICCALL 600000 0 0 0 0)) } # noqa: E501 - addr_5 = pre.deploy_contract( # noqa: F841 + depth2 = pre.deploy_contract( code=Op.MSTORE(offset=0x8, value=Op.GAS) + Op.MSTORE( offset=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=0x91B291A3336BC1357388354DF18CA061B39E3745, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + value=Op.STATICCALL(gas=DEPTH2_GAS_ASK, address=leaf), ) + Op.STOP, - nonce=0, - address=Address(0xE5A4D8074950EC8067D602848B666CA151B09C9F), # noqa: E501 ) - # Source: lll - # { (SSTORE 8 1) (SSTORE 9 (STATICCALL 200000 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + depth1 = pre.deploy_contract( code=Op.SSTORE(key=0x8, value=0x1) + Op.SSTORE( key=0x9, - value=Op.STATICCALL( - gas=0x30D40, - address=0xD9539C5A3DC4713D47A547BFC9A075BD97287080, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + value=Op.STATICCALL(gas=DEPTH1_GAS_ASK, address=depth2), ) + Op.STOP, - nonce=0, - address=Address(0xEF69A9B2C20255FB7BD2B0AC7D45601A03D570B0), # noqa: E501 ) - # Source: lll - # { (SSTORE 8 1) (SSTORE 9 (STATICCALL 200000 0 0 0 0)) } # noqa: E501 - addr_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=0x1) - + Op.SSTORE( - key=0x9, - value=Op.STATICCALL( - gas=0x30D40, - address=0xE5A4D8074950EC8067D602848B666CA151B09C9F, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + target = pre.deploy_contract( + code=Op.SSTORE( + key=0x0, + value=Op.CALL( + address=Op.CALLDATALOAD(offset=0x0), value=Op.CALLVALUE ), ) + + Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x8169DC735802BB5C18A777052CF4CE326B5FD725), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr: Account(storage={8: 1, 9: 1}), - addr_2: Account(storage={8: 0, 9: 0}), - addr_3: Account(storage={8: 0}), - target: Account(storage={0: 1, 1: 1}), - }, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr_4: Account(storage={8: 1, 9: 1}), - addr_5: Account(storage={8: 0, 9: 0}), - addr_6: Account(storage={8: 0}), - target: Account(storage={0: 1, 1: 1}), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(addr, left_padding=True), - Hash(addr_4, left_padding=True), - ] - tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - error=_exc, + data=Hash(depth1, left_padding=True), ) - state_test(env=env, pre=pre, post=post, tx=tx) + # The depth-two frame completes either way (its leaf's failure is + # invisible to storage), so both writable frames record success. + post = { + leaf: Account(storage={}), + depth2: Account(storage={}), + depth1: Account(storage={8: 1, 9: 1}), + target: Account(storage={0: 1, 1: 1}), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_call_goes_oog_on_second_level.py b/tests/ported_static/byzantium/stStaticCall/test_static_call_goes_oog_on_second_level.py index 2ccd4f8ce57..4d995283bb6 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_call_goes_oog_on_second_level.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_call_goes_oog_on_second_level.py @@ -1,17 +1,20 @@ """ -Test_static_call_goes_oog_on_second_level. +Verify a huge KECCAK256 at call depth three exhausts nearly the whole +transaction budget: the intermediate static frame still completes, but +the top frame's 1/64 retentions cannot fund its SSTORE, so it runs out +of gas and every write reverts. Ported from: state_tests/stStaticCall/static_CallGoesOOGOnSecondLevelFiller.json + +@manually-enhanced: Do not overwrite. Gas budget guarded by fork-derived +asserts (was a pinned 220000); sub-calls forward all gas. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, Fork, StateTestFiller, Transaction, @@ -21,80 +24,58 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# Post-intrinsic execution budget. Small enough that the top frame's +# 1/64 retentions cannot fund an SSTORE (guarded below), large enough to +# reach the depth-three KECCAK256. +GAS_BUDGET = 199_000 +# Hashing this window costs far more than the whole budget. +KECCAK_SIZE = 0x2FFFFF + @pytest.mark.ported_from( ["state_tests/stStaticCall/static_CallGoesOOGOnSecondLevelFiller.json"], ) -@pytest.mark.valid_from("Frontier") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") def test_static_call_goes_oog_on_second_level( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_static_call_goes_oog_on_second_level.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Run the top frame out of gas after a nested static call OOGs.""" + keccak_code = Op.SHA3( + offset=0x0, + size=KECCAK_SIZE, + data_size=KECCAK_SIZE, + new_memory_size=KECCAK_SIZE, ) - - # Source: lll - # { (KECCAK256 0x00 0x2fffff) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SHA3(offset=0x0, size=0x2FFFFF) + Op.STOP, - nonce=0, - ) - # Source: lll - # { (MSTORE 8 (GAS)) (MSTORE 9 (STATICCALL 600000 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + addr_2 = pre.deploy_contract(code=keccak_code + Op.STOP) + addr = pre.deploy_contract( code=Op.MSTORE(offset=0x8, value=Op.GAS) - + Op.MSTORE( - offset=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=addr_2, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) + + Op.MSTORE(offset=0x9, value=Op.STATICCALL(address=addr_2)) + Op.STOP, - nonce=0, ) - # Source: lll - # { (SSTORE 9 (STATICCALL 600000 0 0 0 0)) [[ 10 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) + target = pre.deploy_contract( + code=Op.SSTORE(key=0x9, value=Op.STATICCALL(address=addr)) + Op.SSTORE(key=0xA, value=Op.GAS) + Op.STOP, - nonce=0, ) + # The depth-three frame burns its whole grant on the KECCAK256; the + # top frame keeps only its own 1/64 plus the intermediate frame's + # returned 1/64, which must not fund the first SSTORE. + sstore_cost = Op.SSTORE( + key_warm=False, original_value=0, new_value=1 + ).gas_cost(fork) + assert GAS_BUDGET < keccak_code.gas_cost(fork), "the KECCAK256 must OOG" + assert GAS_BUDGET // 32 + 100 < sstore_cost, ( + "retained gas must not fund the top frame's SSTORE" + ) + gas_limit = fork.transaction_intrinsic_cost_calculator()() + GAS_BUDGET + tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, + sender=pre.fund_eoa(), to=target, - data=Bytes(""), - gas_limit=220000, + gas_limit=gas_limit, ) post = { @@ -103,4 +84,4 @@ def test_static_call_goes_oog_on_second_level( target: Account(storage={}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_call_goes_oog_on_second_level2.py b/tests/ported_static/byzantium/stStaticCall/test_static_call_goes_oog_on_second_level2.py index 4e0fb271e8b..8fc254e31bb 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_call_goes_oog_on_second_level2.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_call_goes_oog_on_second_level2.py @@ -1,147 +1,105 @@ """ -Test_static_call_goes_oog_on_second_level2. +Verify a nested static frame that fails — by writing storage inside a +static context or by burning its whole grant — leaves the top frame's +1/64 retentions unable to fund its SSTORE, so the top frame runs out of +gas and every write reverts. Ported from: state_tests/stStaticCall/static_CallGoesOOGOnSecondLevel2Filler.json + +@manually-enhanced: Do not overwrite. Gas budget guarded by fork-derived +asserts (was a pinned 160000); dynamic addresses; d0/d1 renamed to the +leaf behavior they select. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, + Fork, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# Post-intrinsic execution budget. Small enough that the top frame's +# 1/64 retentions cannot fund an SSTORE (guarded below), large enough to +# reach the depth-three leaf. +GAS_BUDGET = 139_000 + @pytest.mark.ported_from( ["state_tests/stStaticCall/static_CallGoesOOGOnSecondLevel2Filler.json"], ) -@pytest.mark.valid_from("Frontier") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") +@pytest.mark.parametrize("leaf_behavior", ["static_violation", "gas_burner"]) def test_static_call_goes_oog_on_second_level2( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + leaf_behavior: str, ) -> None: - """Test_static_call_goes_oog_on_second_level2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { (MSTORE 8 (GAS)) (MSTORE 9 (STATICCALL 600000 (CALLDATALOAD 0) 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + """Run the top frame out of gas after a nested static frame fails.""" + if leaf_behavior == "static_violation": + # Storage write inside a static context: exceptional halt that + # consumes the leaf's whole grant. + leaf = pre.deploy_contract( + code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP + ) + else: + # EXTCODESIZE loop needing far more gas than any grant here. + leaf = pre.deploy_contract( + code=Op.JUMPDEST + + Op.JUMPI( + pc=0x1C, + condition=Op.ISZERO(Op.LT(Op.MLOAD(offset=0x80), 0xC350)), + ) + + Op.POP(Op.EXTCODESIZE(address=0x1)) + + Op.MSTORE(offset=0x80, value=Op.ADD(Op.MLOAD(offset=0x80), 0x1)) + + Op.JUMP(pc=0x0) + + Op.JUMPDEST + + Op.STOP, + ) + addr = pre.deploy_contract( code=Op.MSTORE(offset=0x8, value=Op.GAS) + Op.MSTORE( offset=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=Op.CALLDATALOAD(offset=0x0), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + value=Op.STATICCALL(address=Op.CALLDATALOAD(offset=0x0)), ) + Op.STOP, - nonce=0, - address=Address(0x666EBB8AFC7A9BA4BEDB7D78F85184B65639531D), # noqa: E501 - ) - # Source: lll - # { (SSTORE 1 1) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - nonce=0, - address=Address(0xF2774CEE95A518A51CD32426D3CE8DB19F095B37), # noqa: E501 ) - # Source: lll - # { (def 'i 0x80) (for {} (< @i 50000) [i](+ @i 1) (EXTCODESIZE 1)) } - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPDEST - + Op.JUMPI( - pc=0x1C, condition=Op.ISZERO(Op.LT(Op.MLOAD(offset=0x80), 0xC350)) - ) - + Op.POP(Op.EXTCODESIZE(address=0x1)) - + Op.MSTORE(offset=0x80, value=Op.ADD(Op.MLOAD(offset=0x80), 0x1)) - + Op.JUMP(pc=0x0) - + Op.JUMPDEST - + Op.STOP, - nonce=0, - address=Address(0x45E70D14D712A8898DCE133FE063F71179F04059), # noqa: E501 - ) - # Source: lll - # { (MSTORE 0 (CALLDATALOAD 0)) [[ 0 ]] (STATICCALL 600000 0 32 0 0) [[ 1 ]] 1 } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 + target = pre.deploy_contract( code=Op.MSTORE(offset=0x0, value=Op.CALLDATALOAD(offset=0x0)) - + Op.SSTORE( - key=0x0, - value=Op.STATICCALL( - gas=0x927C0, - address=0x666EBB8AFC7A9BA4BEDB7D78F85184B65639531D, - args_offset=0x0, - args_size=0x20, - ret_offset=0x0, - ret_size=0x0, - ), - ) + + Op.SSTORE(key=0x0, value=Op.STATICCALL(address=addr, args_size=0x20)) + Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - nonce=0, - address=Address(0xB9C1C6C39CB3E528B2EF06493C17D63B7827077B), # noqa: E501 ) - tx_data = [ - Hash(addr_2, left_padding=True), - Hash(addr_3, left_padding=True), - ] - tx_gas = [160000] + # The depth-three frame consumes its whole grant either way; the top + # frame keeps only its own 1/64 plus the intermediate frame's + # returned 1/64, which must not fund the first SSTORE. + sstore_cost = Op.SSTORE( + key_warm=False, original_value=0, new_value=1 + ).gas_cost(fork) + assert GAS_BUDGET // 32 + 100 < sstore_cost, ( + "retained gas must not fund the top frame's SSTORE" + ) + gas_limit = fork.transaction_intrinsic_cost_calculator()() + GAS_BUDGET tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], + data=Hash(leaf, left_padding=True), + gas_limit=gas_limit, ) - post = {target: Account(storage={0: 0, 1: 0})} + post = { + leaf: Account(storage={}), + target: Account(storage={}), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_call_oog_additional_gas_costs1.py b/tests/ported_static/byzantium/stStaticCall/test_static_call_oog_additional_gas_costs1.py index 721a2dfe4e8..3e0ba153d17 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_call_oog_additional_gas_costs1.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_call_oog_additional_gas_costs1.py @@ -1,17 +1,19 @@ """ -Test_static_call_oog_additional_gas_costs1. +Verify a STATICCALL's additional gas costs (memory expansion plus the +callee's gas ask) fit the budget while the following SSTORE does not, so +the frame runs out of gas and every pending write reverts. Ported from: state_tests/stStaticCall/static_call_OOG_additionalGasCosts1Filler.json + +@manually-enhanced: Do not overwrite. Gas budget derived from the fork +(was a pinned 30000); dynamic callee; post asserts the revert. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, Fork, StateTestFiller, Transaction, @@ -21,61 +23,56 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# The legacy ask: granted in full to the code-less callee and returned +# unused, so it is exactly the caller's gas left after the call. +CALLEE_GAS_ASK = 0x1770 + @pytest.mark.ported_from( [ "state_tests/stStaticCall/static_call_OOG_additionalGasCosts1Filler.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Frontier") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") def test_static_call_oog_additional_gas_costs1( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_static_call_oog_additional_gas_costs1.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=3000000000, + """Run out of gas on the SSTORE right after a paid-for STATICCALL.""" + callee = pre.nonexistent_account() + call_code = Op.STATICCALL( + gas=CALLEE_GAS_ASK, + address=callee, + args_size=0x40, + ret_size=0x40, + new_memory_size=0x40, ) - - # Source: lll - # { [[ 0 ]] (STATICCALL 6000 0x1000000000000000000000000000000000000001 0 64 0 64 ) [[ 1 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.STATICCALL( - gas=0x1770, - address=0x1000000000000000000000000000000000000001, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), - ) + target = pre.deploy_contract( + code=Op.SSTORE(key=0x0, value=call_code) + Op.SSTORE(key=0x1, value=Op.GAS) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, ) + # Budget covers the call machinery and the callee's full ask; the ask + # comes back unused, leaving ~CALLEE_GAS_ASK gas — less than any + # fork's first-write SSTORE charge, so the first SSTORE OOGs. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = intrinsic + call_code.gas_cost(fork) + CALLEE_GAS_ASK + sstore_cost = Op.SSTORE( + key_warm=False, original_value=0, new_value=1 + ).gas_cost(fork) + assert CALLEE_GAS_ASK < sstore_cost, "ask must not fund the SSTORE" + tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, + sender=pre.fund_eoa(), to=target, - data=Bytes(""), - gas_limit=30000, + gas_limit=gas_limit, ) - post = {sender: Account(nonce=1)} + post = { + target: Account(storage={}), + callee: Account.NONEXISTENT, + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_call_oog_additional_gas_costs2_paris.py b/tests/ported_static/byzantium/stStaticCall/test_static_call_oog_additional_gas_costs2_paris.py index 0603ad7df08..33e8adb033f 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_call_oog_additional_gas_costs2_paris.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_call_oog_additional_gas_costs2_paris.py @@ -1,17 +1,20 @@ """ -Test_static_call_oog_additional_gas_costs2_paris. +Verify a STATICCALL to an existing EOA pays its additional gas costs +(memory expansion plus the callee's gas ask) within the budget while the +following SSTORE does not, so the frame runs out of gas, every pending +write reverts, and the EOA is untouched. Ported from: state_tests/stStaticCall/static_call_OOG_additionalGasCosts2_ParisFiller.json + +@manually-enhanced: Do not overwrite. Gas budget derived from the fork +(was a pinned 30000); post asserts the revert and the EOA's balance. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, Fork, StateTestFiller, Transaction, @@ -21,65 +24,57 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# The legacy ask: granted in full to the code-less callee and returned +# unused, so it is exactly the caller's gas left after the call. +CALLEE_GAS_ASK = 0x1770 +CALLEE_BALANCE = 10 + @pytest.mark.ported_from( [ "state_tests/stStaticCall/static_call_OOG_additionalGasCosts2_ParisFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Frontier") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") def test_static_call_oog_additional_gas_costs2_paris( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_static_call_oog_additional_gas_costs2_paris.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=3000000000, + """Run out of gas on the SSTORE right after a paid-for STATICCALL.""" + callee = pre.fund_eoa(amount=CALLEE_BALANCE) + call_code = Op.STATICCALL( + gas=CALLEE_GAS_ASK, + address=callee, + args_size=0x40, + ret_size=0x40, + new_memory_size=0x40, ) - - addr = pre.fund_eoa(amount=10) # noqa: F841 - # Source: lll - # { [[ 0 ]] (STATICCALL 6000 0 64 0 64 ) [[ 1 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.STATICCALL( - gas=0x1770, - address=addr, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), - ) + target = pre.deploy_contract( + code=Op.SSTORE(key=0x0, value=call_code) + Op.SSTORE(key=0x1, value=Op.GAS) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, ) + # Budget covers the call machinery and the callee's full ask; the ask + # comes back unused, leaving ~CALLEE_GAS_ASK gas — less than any + # fork's first-write SSTORE charge, so the first SSTORE OOGs. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = intrinsic + call_code.gas_cost(fork) + CALLEE_GAS_ASK + sstore_cost = Op.SSTORE( + key_warm=False, original_value=0, new_value=1 + ).gas_cost(fork) + assert CALLEE_GAS_ASK < sstore_cost, "ask must not fund the SSTORE" + tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, + sender=pre.fund_eoa(), to=target, - data=Bytes(""), - gas_limit=30000, + gas_limit=gas_limit, ) post = { target: Account(storage={}), - addr: Account(balance=10), + callee: Account(balance=CALLEE_BALANCE), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_call_recursive_bomb_log2.py b/tests/ported_static/byzantium/stStaticCall/test_static_call_recursive_bomb_log2.py index 95641e9cc1b..d130f252888 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_call_recursive_bomb_log2.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_call_recursive_bomb_log2.py @@ -1,17 +1,20 @@ """ -Test_static_call_recursive_bomb_log2. +Verify a static context defuses a logging recursion bomb: the bomb's +LOG0 violates the static context and halts its frame at depth one, so +the caller observes failure and continues. Ported from: state_tests/stStaticCall/static_CallRecursiveBombLog2Filler.json + +@manually-enhanced: Do not overwrite. Dropped the pinned 10B gas limit +(which capped validity at Prague) in favor of the maxed default; the +call result is stored +1 so the observed failure is a non-zero value. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, Fork, StateTestFiller, Transaction, @@ -21,81 +24,64 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# Gas each bomb level would keep for its LOG0 and MSTORE (never reached +# past depth one: the LOG0 halts the first static frame). +BOMB_GAS_RESERVE = 0x61A8 +# Gas the top frame keeps for its two post-call SSTOREs (guarded below). +TARGET_GAS_RESERVE = 0x186A0 + @pytest.mark.ported_from( ["state_tests/stStaticCall/static_CallRecursiveBombLog2Filler.json"], ) @pytest.mark.valid_from("Byzantium") -@pytest.mark.valid_until("Prague") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable def test_static_call_recursive_bomb_log2( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_static_call_recursive_bomb_log2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=20000000000, - ) - - # Source: lll - # { (MSTORE 0 (GAS)) (LOG0 0 32) (STATICCALL (- (GAS) 25000) (ADDRESS) 0 0 0 0) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + """Observe a LOG0 recursion bomb halt inside a static context.""" + addr = pre.deploy_contract( code=Op.MSTORE(offset=0x0, value=Op.GAS) + Op.LOG0(offset=0x0, size=0x20) + Op.STATICCALL( - gas=Op.SUB(Op.GAS, 0x61A8), - address=Op.ADDRESS, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + gas=Op.SUB(Op.GAS, BOMB_GAS_RESERVE), address=Op.ADDRESS ) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, ) - # Source: lll - # { [[ 0 ]] (STATICCALL ( - (GAS) 100000) 0 0 0 0) [[ 1 ]] 1 } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 + # Store the bomb call's result plus one so the observed failure (a + # stored 1) is distinguishable from a never-written slot. + target = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.STATICCALL( - gas=Op.SUB(Op.GAS, 0x186A0), - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + value=Op.ADD( + 0x1, + Op.STATICCALL( + gas=Op.SUB(Op.GAS, TARGET_GAS_RESERVE), address=addr + ), ), ) + Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - balance=0x1312D00, - nonce=0, ) - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, - to=target, - data=Bytes(""), - gas_limit=10000000000, - value=0x186A0, + # The failed bomb frame forfeits its whole grant; the reserve alone + # must fund the two SSTOREs that record the outcome. Only their + # execution cost draws on the frame's gas: the maxed transaction + # (no explicit gas limit) covers any state gas from its reservoir. + sstore_cost = Op.SSTORE( + key_warm=False, original_value=0, new_value=1 + ).execution_cost(fork) + assert TARGET_GAS_RESERVE > 2 * sstore_cost, ( + "the reserve must fund the outcome SSTOREs" ) + sender = pre.fund_eoa() + tx = Transaction(sender=sender, to=target) + post = { - target: Account(storage={0: 0, 1: 1}), + target: Account(storage={0: 1, 1: 1}), sender: Account(nonce=1), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcall_00.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcall_00.py index 1dc2606afd2..8a438e09d4c 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcall_00.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcall_00.py @@ -1,24 +1,25 @@ """ -Test_static_callcall_00. +Verify environment-probing writes two static frames deep are rolled +back: the probe frame's SSTOREs violate the static context and halt +only that frame, while memory-only probes succeed; either way the +enclosing frames complete and record success. Ported from: state_tests/stStaticCall/static_callcall_00Filler.json + +@manually-enhanced: Do not overwrite. The ported d0/d1 twin chains were +folded into one chain parametrized on the probe type; addresses are +dynamic and sub-calls forward all gas. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,225 +29,76 @@ @pytest.mark.ported_from( ["state_tests/stStaticCall/static_callcall_00Filler.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") +@pytest.mark.parametrize("probe_type", ["sstore_probes", "mstore_probes"]) def test_static_callcall_00( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + probe_type: str, ) -> None: - """Test_static_callcall_00.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=30000000, - ) - - # Source: lll - # { [[ 0 ]] (CALL (GAS) (CALLDATALOAD 0) (CALLVALUE) 0 0 0 0) [[ 1 ]] 1 } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=Op.GAS, - address=Op.CALLDATALOAD(offset=0x0), - value=Op.CALLVALUE, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + """Roll back environment probes stored inside a static context.""" + if probe_type == "sstore_probes": + # Every SSTORE violates the static context: the probe frame + # halts and none of these values persist. + probe = pre.deploy_contract( + code=Op.SSTORE(key=0x2, value=0x1) + + Op.SSTORE(key=0x4, value=Op.CALLER) + + Op.SSTORE(key=0x7, value=Op.CALLVALUE) + + Op.SSTORE(key=0xE6, value=Op.ADDRESS) + + Op.SSTORE(key=0xE8, value=Op.ORIGIN) + + Op.SSTORE(key=0xEC, value=Op.CALLDATASIZE) + + Op.SSTORE(key=0xEE, value=Op.CODESIZE) + + Op.SSTORE(key=0xF0, value=Op.GASPRICE) + + Op.STOP, ) - + Op.SSTORE(key=0x1, value=0x1) - + Op.STOP, - nonce=0, - address=Address(0xC0E4183389EB57F779A986D8C878F89B9401DC8E), # noqa: E501 - ) - # Source: lll - # { (SSTORE 2 1) (SSTORE 4 (CALLER)) (SSTORE 7 (CALLVALUE)) (SSTORE 230 (ADDRESS)) (SSTORE 232 (ORIGIN)) (SSTORE 236 (CALLDATASIZE)) (SSTORE 238 (CODESIZE)) (SSTORE 240 (GASPRICE))} # noqa: E501 - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=0x1) - + Op.SSTORE(key=0x4, value=Op.CALLER) - + Op.SSTORE(key=0x7, value=Op.CALLVALUE) - + Op.SSTORE(key=0xE6, value=Op.ADDRESS) - + Op.SSTORE(key=0xE8, value=Op.ORIGIN) - + Op.SSTORE(key=0xEC, value=Op.CALLDATASIZE) - + Op.SSTORE(key=0xEE, value=Op.CODESIZE) - + Op.SSTORE(key=0xF0, value=Op.GASPRICE) - + Op.STOP, - nonce=0, - address=Address(0x33F368F0B54063613CF5944941E8E0E4EEB64697), # noqa: E501 - ) - # Source: lll - # { (MSTORE 0 1) (MSTORE 32 (CALLER)) (MSTORE 64 (CALLVALUE)) (MSTORE 96 (ADDRESS)) (MSTORE 128 (ORIGIN)) (MSTORE 160 (CALLDATASIZE)) (MSTORE 192 (CODESIZE)) (MSTORE 224 (GASPRICE))} # noqa: E501 - addr_6 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0x1) - + Op.MSTORE(offset=0x20, value=Op.CALLER) - + Op.MSTORE(offset=0x40, value=Op.CALLVALUE) - + Op.MSTORE(offset=0x60, value=Op.ADDRESS) - + Op.MSTORE(offset=0x80, value=Op.ORIGIN) - + Op.MSTORE(offset=0xA0, value=Op.CALLDATASIZE) - + Op.MSTORE(offset=0xC0, value=Op.CODESIZE) - + Op.MSTORE(offset=0xE0, value=Op.GASPRICE) - + Op.STOP, - nonce=0, - address=Address(0x29736372C0FAB51DB4556614EF27D74A89ACFE21), # noqa: E501 - ) - # Source: lll - # { (STATICCALL 250000 0 64 0 64 ) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.STATICCALL( - gas=0x3D090, - address=0x33F368F0B54063613CF5944941E8E0E4EEB64697, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, + else: + # Memory-only probes are legal in a static context. + probe = pre.deploy_contract( + code=Op.MSTORE(offset=0x0, value=0x1) + + Op.MSTORE(offset=0x20, value=Op.CALLER) + + Op.MSTORE(offset=0x40, value=Op.CALLVALUE) + + Op.MSTORE(offset=0x60, value=Op.ADDRESS) + + Op.MSTORE(offset=0x80, value=Op.ORIGIN) + + Op.MSTORE(offset=0xA0, value=Op.CALLDATASIZE) + + Op.MSTORE(offset=0xC0, value=Op.CODESIZE) + + Op.MSTORE(offset=0xE0, value=Op.GASPRICE) + + Op.STOP, ) + depth2 = pre.deploy_contract( + code=Op.STATICCALL(address=probe, args_size=0x40, ret_size=0x40) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x620B442C84D5068E6B57D390A1AC99130205406E), # noqa: E501 ) - # Source: lll - # { (STATICCALL 250000 0 64 0 64 ) } # noqa: E501 - addr_5 = pre.deploy_contract( # noqa: F841 - code=Op.STATICCALL( - gas=0x3D090, - address=0x29736372C0FAB51DB4556614EF27D74A89ACFE21, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xDCC76191E9F918ECFE9FBA5414884D5EE621AE00), # noqa: E501 - ) - # Source: lll - # { [[ 0 ]] (STATICCALL 350000 0 64 0 64 ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + depth1 = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.STATICCALL( - gas=0x55730, - address=0x620B442C84D5068E6B57D390A1AC99130205406E, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), + value=Op.STATICCALL(address=depth2, args_size=0x40, ret_size=0x40), ) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x2F9EC0AFCB4EDCD7D38C6A48F5E36038263CA3CD), # noqa: E501 ) - # Source: lll - # { [[ 0 ]] (STATICCALL 350000 0 64 0 64 ) } # noqa: E501 - addr_4 = pre.deploy_contract( # noqa: F841 + target = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.STATICCALL( - gas=0x55730, - address=0xDCC76191E9F918ECFE9FBA5414884D5EE621AE00, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, + value=Op.CALL( + address=Op.CALLDATALOAD(offset=0x0), value=Op.CALLVALUE ), ) + + Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xBF23F3306533431B2EE5E4CA95E0A0834C090105), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - target: Account(storage={0: 1, 1: 1}), - addr: Account(storage={0: 1}), - addr_3: Account( - storage={ - 2: 0, - 4: 0, - 7: 0, - 230: 0, - 232: 0, - 236: 0, - 238: 0, - 240: 0, - }, - ), - }, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - target: Account(storage={0: 1, 1: 1}), - addr_4: Account(storage={0: 1}), - addr_6: Account( - storage={ - 2: 0, - 4: 0, - 7: 0, - 230: 0, - 232: 0, - 236: 0, - 238: 0, - 240: 0, - }, - ), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(addr, left_padding=True), - Hash(addr_4, left_padding=True), - ] - tx_gas = [3000000] - tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + data=Hash(depth1, left_padding=True), ) - state_test(env=env, pre=pre, post=post, tx=tx) + # The probe frame's failure is invisible one level up: depth2 + # completes either way, so the writable frames record success. + post = { + probe: Account(storage={}), + depth2: Account(storage={}), + depth1: Account(storage={0: 1}), + target: Account(storage={0: 1, 1: 1}), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcallcall_000.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcallcall_000.py index 2388568f0db..14d800fc56e 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcallcall_000.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcallcall_000.py @@ -1,24 +1,25 @@ """ -Test_static_callcallcall_000. +Verify environment-probing writes three static frames deep are rolled +back: the probe frame's SSTOREs violate the static context and halt +only that frame, while a memory-only probe succeeds; either way the +enclosing frames complete and record success. Ported from: state_tests/stStaticCall/static_callcallcall_000Filler.json + +@manually-enhanced: Do not overwrite. The ported d0/d1 twin chains were +folded into one chain parametrized on the probe type; addresses are +dynamic and sub-calls forward all gas. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,269 +29,78 @@ @pytest.mark.ported_from( ["state_tests/stStaticCall/static_callcallcall_000Filler.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") +@pytest.mark.parametrize("probe_type", ["sstore_probes", "mstore_probe"]) def test_static_callcallcall_000( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + probe_type: str, ) -> None: - """Test_static_callcallcall_000.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=30000000, - ) - - # Source: lll - # { [[ 0 ]] (CALL (GAS) (CALLDATALOAD 0) (CALLVALUE) 0 0 0 0) [[ 1 ]] 1 } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=Op.GAS, - address=Op.CALLDATALOAD(offset=0x0), - value=Op.CALLVALUE, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x1, value=0x1) - + Op.STOP, - nonce=0, - address=Address(0xC0E4183389EB57F779A986D8C878F89B9401DC8E), # noqa: E501 - ) - # Source: lll - # { (SSTORE 3 1) (SSTORE 4 (CALLER)) (SSTORE 7 (CALLVALUE)) (SSTORE 330 (ADDRESS)) (SSTORE 332 (ORIGIN)) (SSTORE 336 (CALLDATASIZE)) (SSTORE 338 (CODESIZE)) (SSTORE 340 (GASPRICE))} # noqa: E501 - addr_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x3, value=0x1) - + Op.SSTORE(key=0x4, value=Op.CALLER) - + Op.SSTORE(key=0x7, value=Op.CALLVALUE) - + Op.SSTORE(key=0x14A, value=Op.ADDRESS) - + Op.SSTORE(key=0x14C, value=Op.ORIGIN) - + Op.SSTORE(key=0x150, value=Op.CALLDATASIZE) - + Op.SSTORE(key=0x152, value=Op.CODESIZE) - + Op.SSTORE(key=0x154, value=Op.GASPRICE) - + Op.STOP, - nonce=0, - address=Address(0x181B4ED322E192361633CC3C0A418F259AB0CF4B), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) } - addr_8 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x335C5531B84765A7626E6E76688F18B81BE5259C), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) (STATICCALL 250000 0 64 0 64 ) (MSTORE 32 1) } # noqa: E501 - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) - + Op.POP( - Op.STATICCALL( - gas=0x3D090, - address=0x181B4ED322E192361633CC3C0A418F259AB0CF4B, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) + """Roll back environment probes stored inside a static context.""" + if probe_type == "sstore_probes": + # Every SSTORE violates the static context: the probe frame + # halts and none of these values persist. + probe = pre.deploy_contract( + code=Op.SSTORE(key=0x3, value=0x1) + + Op.SSTORE(key=0x4, value=Op.CALLER) + + Op.SSTORE(key=0x7, value=Op.CALLVALUE) + + Op.SSTORE(key=0x14A, value=Op.ADDRESS) + + Op.SSTORE(key=0x14C, value=Op.ORIGIN) + + Op.SSTORE(key=0x150, value=Op.CALLDATASIZE) + + Op.SSTORE(key=0x152, value=Op.CODESIZE) + + Op.SSTORE(key=0x154, value=Op.GASPRICE) + + Op.STOP, ) - + Op.MSTORE(offset=0x20, value=0x1) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x3F6D147A714319EF90C47921715DC5F0CCFE3B09), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) (STATICCALL 250000 0 64 0 64 ) (MSTORE 32 1) } # noqa: E501 - addr_7 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) - + Op.POP( - Op.STATICCALL( - gas=0x3D090, - address=0x335C5531B84765A7626E6E76688F18B81BE5259C, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) + else: + # A memory-only probe is legal in a static context. + probe = pre.deploy_contract( + code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, ) - + Op.MSTORE(offset=0x20, value=0x1) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x85EE033B8FF327153F5C82D191B4942102DEBFFC), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) (STATICCALL 400000 0 64 0 64 ) (MSTORE 32 1) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 + depth3 = pre.deploy_contract( code=Op.MSTORE(offset=0x3, value=0x1) - + Op.POP( - Op.STATICCALL( - gas=0x61A80, - address=0x3F6D147A714319EF90C47921715DC5F0CCFE3B09, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - ) + + Op.POP(Op.STATICCALL(address=probe, args_size=0x40, ret_size=0x40)) + Op.MSTORE(offset=0x20, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x36ACE903A154317B8FA379AAD88A425B7EF025DC), # noqa: E501 ) - # Source: lll - # { (MSTORE 3 1) (STATICCALL 300000 0 64 0 64 ) (MSTORE 32 1) } # noqa: E501 - addr_6 = pre.deploy_contract( # noqa: F841 + depth2 = pre.deploy_contract( code=Op.MSTORE(offset=0x3, value=0x1) - + Op.POP( - Op.STATICCALL( - gas=0x493E0, - address=0x85EE033B8FF327153F5C82D191B4942102DEBFFC, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - ) + + Op.POP(Op.STATICCALL(address=depth3, args_size=0x40, ret_size=0x40)) + Op.MSTORE(offset=0x20, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xD518EBB39FB88BEB34AD1598FE3CCD3F8E4C4708), # noqa: E501 ) - # Source: lll - # { [[ 0 ]] (STATICCALL 650000 0 64 0 64 ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + depth1 = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.STATICCALL( - gas=0x9EB10, - address=0x36ACE903A154317B8FA379AAD88A425B7EF025DC, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), + value=Op.STATICCALL(address=depth2, args_size=0x40, ret_size=0x40), ) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xFB157BFD4470AB46DFFEC6F8390B747C67F62B38), # noqa: E501 ) - # Source: lll - # { [[ 0 ]] (STATICCALL 350000 0 64 0 64 ) } # noqa: E501 - addr_5 = pre.deploy_contract( # noqa: F841 + target = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.STATICCALL( - gas=0x55730, - address=0xD518EBB39FB88BEB34AD1598FE3CCD3F8E4C4708, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, + value=Op.CALL( + address=Op.CALLDATALOAD(offset=0x0), value=Op.CALLVALUE ), ) + + Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xBF23F3306533431B2EE5E4CA95E0A0834C090105), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr: Account(storage={0: 1}), - addr_2: Account(storage={}), - addr_3: Account(storage={}), - addr_4: Account( - storage={ - 3: 0, - 4: 0, - 7: 0, - 330: 0, - 332: 0, - 336: 0, - 338: 0, - 340: 0, - }, - ), - target: Account(storage={0: 1, 1: 1}), - }, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr_5: Account(storage={0: 1}), - addr_6: Account(storage={}), - addr_7: Account(storage={}), - addr_8: Account( - storage={ - 3: 0, - 4: 0, - 7: 0, - 330: 0, - 332: 0, - 336: 0, - 338: 0, - 340: 0, - }, - ), - target: Account(storage={0: 1, 1: 1}), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(addr, left_padding=True), - Hash(addr_5, left_padding=True), - ] - tx_gas = [3000000] - tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + data=Hash(depth1, left_padding=True), ) - state_test(env=env, pre=pre, post=post, tx=tx) + # The probe frame's failure is invisible one level up: the static + # middle frames complete either way, so the writable frames record + # success. + post = { + probe: Account(storage={}), + depth3: Account(storage={}), + depth2: Account(storage={}), + depth1: Account(storage={0: 1}), + target: Account(storage={0: 1, 1: 1}), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcallcallcode_001.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcallcallcode_001.py index 10695d92061..8305270055f 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcallcallcode_001.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcallcallcode_001.py @@ -1,24 +1,25 @@ """ -Test_static_callcallcallcode_001. +Verify the static flag propagates through a DELEGATECALL made inside a +static context: the delegated probe's SSTOREs are still violations and +halt only the delegated frame, while a memory-only probe succeeds; +either way the enclosing frames complete and record success. Ported from: state_tests/stStaticCall/static_callcallcallcode_001Filler.json + +@manually-enhanced: Do not overwrite. The ported d0/d1 twin chains were +folded into one chain parametrized on the probe type; addresses are +dynamic and sub-calls forward all gas. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,253 +29,81 @@ @pytest.mark.ported_from( ["state_tests/stStaticCall/static_callcallcallcode_001Filler.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") +@pytest.mark.parametrize("probe_type", ["sstore_probes", "mstore_probe"]) def test_static_callcallcallcode_001( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + probe_type: str, ) -> None: - """Test_static_callcallcallcode_001.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=30000000, - ) - - # Source: lll - # { [[ 0 ]] (CALL (GAS) (CALLDATALOAD 0) (CALLVALUE) 0 0 0 0) [[ 1 ]] 1 } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=Op.GAS, - address=Op.CALLDATALOAD(offset=0x0), - value=Op.CALLVALUE, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x1, value=0x1) - + Op.STOP, - nonce=0, - address=Address(0xC0E4183389EB57F779A986D8C878F89B9401DC8E), # noqa: E501 - ) - # Source: lll - # { (SSTORE 3 1) (SSTORE 4 (CALLER)) (SSTORE 7 (CALLVALUE)) (SSTORE 330 (ADDRESS)) (SSTORE 332 (ORIGIN)) (SSTORE 336 (CALLDATASIZE)) (SSTORE 338 (CODESIZE)) (SSTORE 340 (GASPRICE)) } # noqa: E501 - addr_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x3, value=0x1) - + Op.SSTORE(key=0x4, value=Op.CALLER) - + Op.SSTORE(key=0x7, value=Op.CALLVALUE) - + Op.SSTORE(key=0x14A, value=Op.ADDRESS) - + Op.SSTORE(key=0x14C, value=Op.ORIGIN) - + Op.SSTORE(key=0x150, value=Op.CALLDATASIZE) - + Op.SSTORE(key=0x152, value=Op.CODESIZE) - + Op.SSTORE(key=0x154, value=Op.GASPRICE) - + Op.STOP, - nonce=0, - address=Address(0x7E63847AAD8CA50FB7C04777DCE6871A6BF8DE0C), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) } - addr_8 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x335C5531B84765A7626E6E76688F18B81BE5259C), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) (DELEGATECALL 250000 0 64 0 64 ) (MSTORE 3 1) } # noqa: E501 - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) - + Op.POP( - Op.DELEGATECALL( - gas=0x3D090, - address=0x7E63847AAD8CA50FB7C04777DCE6871A6BF8DE0C, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) + """Enforce the static flag across a nested DELEGATECALL.""" + if probe_type == "sstore_probes": + # Delegated into a static context, every SSTORE is a violation: + # the delegated frame halts and none of these values persist. + probe = pre.deploy_contract( + code=Op.SSTORE(key=0x3, value=0x1) + + Op.SSTORE(key=0x4, value=Op.CALLER) + + Op.SSTORE(key=0x7, value=Op.CALLVALUE) + + Op.SSTORE(key=0x14A, value=Op.ADDRESS) + + Op.SSTORE(key=0x14C, value=Op.ORIGIN) + + Op.SSTORE(key=0x150, value=Op.CALLDATASIZE) + + Op.SSTORE(key=0x152, value=Op.CODESIZE) + + Op.SSTORE(key=0x154, value=Op.GASPRICE) + + Op.STOP, ) - + Op.MSTORE(offset=0x3, value=0x1) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x82D1FD8C6ED53A58BD8B065074A6B572A7CA89FA), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) (DELEGATECALL 250000 0 64 0 64 ) (MSTORE 3 1) } # noqa: E501 - addr_7 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) - + Op.POP( - Op.DELEGATECALL( - gas=0x3D090, - address=0x335C5531B84765A7626E6E76688F18B81BE5259C, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) + else: + # A memory-only probe is legal in a static context. + probe = pre.deploy_contract( + code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, ) - + Op.MSTORE(offset=0x3, value=0x1) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xF18DDE9381A558C4BE0B84B0F3A17E22B3F9FFCE), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) (STATICCALL 300000 0 64 0 64 ) (MSTORE 3 1) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 + delegator = pre.deploy_contract( code=Op.MSTORE(offset=0x3, value=0x1) - + Op.POP( - Op.STATICCALL( - gas=0x493E0, - address=0x82D1FD8C6ED53A58BD8B065074A6B572A7CA89FA, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - ) + + Op.POP(Op.DELEGATECALL(address=probe, args_size=0x40, ret_size=0x40)) + Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x5CCB387AB81F41F0B490664795E7004D5D14BF91), # noqa: E501 ) - # Source: lll - # { (MSTORE 3 1) (STATICCALL 300000 0 64 0 64 )(MSTORE 3 1) } # noqa: E501 - addr_6 = pre.deploy_contract( # noqa: F841 + depth2 = pre.deploy_contract( code=Op.MSTORE(offset=0x3, value=0x1) + Op.POP( - Op.STATICCALL( - gas=0x493E0, - address=0xF18DDE9381A558C4BE0B84B0F3A17E22B3F9FFCE, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) + Op.STATICCALL(address=delegator, args_size=0x40, ret_size=0x40) ) + Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x69CE59F2414271F3E079542EF3893A021D7D68EA), # noqa: E501 ) - # Source: lll - # { [[ 0 ]] (STATICCALL 350000 0 64 0 64 ) (MSTORE 3 1) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + depth1 = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.STATICCALL( - gas=0x55730, - address=0x5CCB387AB81F41F0B490664795E7004D5D14BF91, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), + value=Op.STATICCALL(address=depth2, args_size=0x40, ret_size=0x40), ) + Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x9121E482ADD3986513A14639DB36D5EC5AE41FB8), # noqa: E501 ) - # Source: lll - # { [[ 0 ]] (STATICCALL 350000 0 64 0 64 ) } # noqa: E501 - addr_5 = pre.deploy_contract( # noqa: F841 + target = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.STATICCALL( - gas=0x55730, - address=0x69CE59F2414271F3E079542EF3893A021D7D68EA, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, + value=Op.CALL( + address=Op.CALLDATALOAD(offset=0x0), value=Op.CALLVALUE ), ) + + Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xBF23F3306533431B2EE5E4CA95E0A0834C090105), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr: Account(storage={0: 1, 1: 0, 3: 0, 4: 0}), - addr_2: Account(storage={1: 0, 3: 0, 4: 0}), - addr_3: Account( - storage={ - 2: 0, - 3: 0, - 4: 0, - 7: 0, - 330: 0, - 332: 0, - 336: 0, - 338: 0, - 340: 0, - }, - ), - target: Account(storage={0: 1, 1: 1}), - }, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 1, 1: 1})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(addr, left_padding=True), - Hash(addr_5, left_padding=True), - ] - tx_gas = [3000000] - tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + data=Hash(depth1, left_padding=True), ) - state_test(env=env, pre=pre, post=post, tx=tx) + # The delegated frame's failure is invisible one level up: the + # static frames complete either way, so the writable frames record + # success. + post = { + probe: Account(storage={}), + delegator: Account(storage={}), + depth2: Account(storage={}), + depth1: Account(storage={0: 1}), + target: Account(storage={0: 1, 1: 1}), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecall_10_suicide_end.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecall_10_suicide_end.py index e9983e65a6b..8d1c6c659ec 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecall_10_suicide_end.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecall_10_suicide_end.py @@ -1,142 +1,103 @@ """ -Test_static_callcodecall_10_suicide_end. +Verify a SELFDESTRUCT to self at the end of a delegated frame under +EIP-6780: the pre-existing account is not deleted, keeps its balance +and storage, and a static call made just before it only fails when its +callee writes storage. Ported from: state_tests/stStaticCall/static_callcodecall_10_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. Self-beneficiary expressed as +ADDRESS; the static call's result and a completion canary are stored +and asserted (the ported post checked only the balance). """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +TARGET_BALANCE = 10**18 +CANARY = 0xC0DE + @pytest.mark.ported_from( ["state_tests/stStaticCall/static_callcodecall_10_SuicideEndFiller.json"], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.parametrize("leaf_behavior", ["mstore", "static_violation"]) def test_static_callcodecall_10_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + leaf_behavior: str, ) -> None: - """Test_static_callcodecall_10_suicide_end.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=30000000, - ) - - # Source: lll - # { (MSTORE 2 1) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x2, value=0x1) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0xCFB5784A5E49924BECC2D5C5D2EE0A9B141E6216), # noqa: E501 - ) - # Source: lll - # { (SSTORE 2 1) } - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=0x1) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0x703B936FD4D674F0FF5D6957F61097152F8781B8), # noqa: E501 + """Keep a pre-existing account after SELFDESTRUCT to itself.""" + if leaf_behavior == "mstore": + # Memory-only callee: the static call succeeds. + leaf = pre.deploy_contract( + code=Op.MSTORE(offset=0x2, value=0x1) + Op.STOP + ) + else: + # Storage write inside the static context: the callee halts and + # the static call reports failure. + leaf = pre.deploy_contract( + code=Op.SSTORE(key=0x2, value=0x1) + Op.STOP + ) + # Runs in the target's context via DELEGATECALL: records the static + # call's result (+1 so failure stores a non-zero 1), then + # self-destructs with itself as the beneficiary. + delegate = pre.deploy_contract( + code=Op.SSTORE( + key=0x2, + value=Op.ADD( + 0x1, + Op.STATICCALL( + address=Op.CALLDATALOAD(offset=0x0), + args_size=0x40, + ret_size=0x40, + ), + ), + ) + + Op.SELFDESTRUCT(address=Op.ADDRESS) + + Op.STOP, ) - # Source: lll - # { (MSTORE 0 (CALLDATALOAD 0)) [[ 0 ]] (DELEGATECALL 150000 0 64 0 64 ) [[ 1 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 + target = pre.deploy_contract( code=Op.MSTORE(offset=0x0, value=Op.CALLDATALOAD(offset=0x0)) + Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, - address=0xDC07FFF80D888EBA04EAB962D37897F6C923462B, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, + address=delegate, args_size=0x40, ret_size=0x40 ), ) - + Op.SSTORE(key=0x1, value=Op.GAS) + + Op.SSTORE(key=0x1, value=CANARY) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x99B0D2D9EEA3205F4DE64FDC26910432824AB1A7), # noqa: E501 - ) - # Source: lll - # { (STATICCALL 50000 (CALLDATALOAD 0) 0 64 0 64 ) (SELFDESTRUCT ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0xC350, - address=Op.CALLDATALOAD(offset=0x0), - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - ) - + Op.SELFDESTRUCT(address=0x99B0D2D9EEA3205F4DE64FDC26910432824AB1A7) - + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0xDC07FFF80D888EBA04EAB962D37897F6C923462B), # noqa: E501 + balance=TARGET_BALANCE, ) - tx_data = [ - Hash(addr_2, left_padding=True), - Hash(addr_3, left_padding=True), - ] - tx_gas = [3000000] - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], + data=Hash(leaf, left_padding=True), ) - post = {target: Account(balance=0xDE0B6B3A7640000, nonce=0)} + # EIP-6780: the target pre-existed, so its self-destruct only moves + # its balance — to itself. Everything survives, and the writes made + # after the SELFDESTRUCT was registered persist. + static_call_result = 2 if leaf_behavior == "mstore" else 1 + post = { + leaf: Account(storage={}), + target: Account( + balance=TARGET_BALANCE, + storage={0: 1, 1: CANARY, 2: static_call_result}, + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecall_10_suicide_end2.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecall_10_suicide_end2.py index 92fe9bf3b88..cfcab27003f 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecall_10_suicide_end2.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecall_10_suicide_end2.py @@ -1,151 +1,89 @@ """ -Test_static_callcodecall_10_suicide_end2. +Verify a SELFDESTRUCT to self at the end of a CALLCODE frame under +EIP-6780: the pre-existing account is not deleted and keeps its whole +balance, including any value the transaction sent it. Ported from: state_tests/stStaticCall/static_callcodecall_10_SuicideEnd2Filler.json + +@manually-enhanced: Do not overwrite. Self-beneficiary expressed as +ADDRESS; the static call's result and a completion canary are stored +and asserted (the ported post checked only the balance). """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +TARGET_BALANCE = 10**18 +CANARY = 0xC0DE + @pytest.mark.ported_from( ["state_tests/stStaticCall/static_callcodecall_10_SuicideEnd2Filler.json"], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-v0", - ), - pytest.param( - 0, - 0, - 1, - id="-v1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.parametrize("tx_value", [0, 1]) def test_static_callcodecall_10_suicide_end2( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + tx_value: int, ) -> None: - """Test_static_callcodecall_10_suicide_end2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=30000000, - ) - - # Source: lll - # { (MSTORE 2 1) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x2, value=0x1) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0xCFB5784A5E49924BECC2D5C5D2EE0A9B141E6216), # noqa: E501 + """Keep a pre-existing account after SELFDESTRUCT to itself.""" + # Memory-only callee: the static call succeeds. + leaf = pre.deploy_contract(code=Op.MSTORE(offset=0x2, value=0x1) + Op.STOP) + # Runs in the target's context via CALLCODE: records the static + # call's result (+1 to distinguish it from a never-written slot), + # then self-destructs with itself as the beneficiary. + delegate = pre.deploy_contract( + code=Op.SSTORE( + key=0x2, + value=Op.ADD( + 0x1, + Op.STATICCALL(address=leaf, args_size=0x40, ret_size=0x40), + ), + ) + + Op.SELFDESTRUCT(address=Op.ADDRESS) + + Op.STOP, ) - # Source: lll - # { [[ 0 ]] (CALLCODE 150000 (CALLVALUE) 0 64 0 64 ) [[ 1 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 + target = pre.deploy_contract( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, - address=0xB60789F240AC9F12FCDE1E4BBD5042A7F30932D4, + address=delegate, value=Op.CALLVALUE, - args_offset=0x0, args_size=0x40, - ret_offset=0x0, ret_size=0x40, ), ) - + Op.SSTORE(key=0x1, value=Op.GAS) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x44D09DDF088DD88C0E91FA7EF74973FF94AD7414), # noqa: E501 - ) - # Source: lll - # { (STATICCALL 50000 0 64 0 64 ) (SELFDESTRUCT ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0xC350, - address=0xCFB5784A5E49924BECC2D5C5D2EE0A9B141E6216, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - ) - + Op.SELFDESTRUCT(address=0x44D09DDF088DD88C0E91FA7EF74973FF94AD7414) + + Op.SSTORE(key=0x1, value=CANARY) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0xB60789F240AC9F12FCDE1E4BBD5042A7F30932D4), # noqa: E501 + balance=TARGET_BALANCE, ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": -1, "value": 0}, - "network": [">=Cancun"], - "result": {target: Account(balance=0xDE0B6B3A7640000)}, - }, - { - "indexes": {"data": -1, "gas": -1, "value": 1}, - "network": [">=Cancun"], - "result": {target: Account(balance=0xDE0B6B3A7640001)}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes(""), - ] - tx_gas = [3000000] - tx_value = [0, 1] - tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + value=tx_value, ) - state_test(env=env, pre=pre, post=post, tx=tx) + # EIP-6780: the target pre-existed, so its self-destruct only moves + # its balance — to itself. The transferred value stays, and the + # writes made after the SELFDESTRUCT was registered persist. + post = { + leaf: Account(storage={}), + target: Account( + balance=TARGET_BALANCE + tx_value, + storage={0: 1, 1: CANARY, 2: 2}, + ), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py index d1005686e33..264530c62de 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py @@ -1,257 +1,160 @@ """ -Test_static_callcodecallcallcode_101_oogm_after_3. +Verify a failure deep inside a CALLCODE-DELEGATECALL chain — a gas +burner exhausting its grant after a static call, or a storage write +inside a static context — is absorbed where it happens: the enclosing +frames complete and the outer frame's constant stores overwrite the +delegated frame's records. Ported from: state_tests/stStaticCall/static_callcodecallcallcode_101_OOGMAfter_3Filler.json + +@manually-enhanced: Do not overwrite. Dropped the pinned 172000 budget +and its EIP-8037 top-up for the maxed default (the failing frames are +bounded by their fixed asks, now named and guarded); the d0/d1 chains +share their common contracts; addresses are dynamic. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, + Fork, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# Grant to the delegated frame below the entry: bounds the gas burner. +DELEGATE_GAS_ASK = 0xEAF6 +# Grant to the static frame in the middle of the chain. +STATIC_GAS_ASK = 0x9C90 +# Grant to the innermost delegate call. +LEAF_GAS_ASK = 0x4E34 +# The gas burner loops this many EXTCODESIZE probes. +BURNER_ITERATIONS = 0xC350 + @pytest.mark.ported_from( [ "state_tests/stStaticCall/static_callcodecallcallcode_101_OOGMAfter_3Filler.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow +@pytest.mark.valid_from("Byzantium") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], + "failure_mode", ["oog_after_static", "static_violation"] ) -@pytest.mark.pre_alloc_mutable def test_static_callcodecallcallcode_101_oogm_after_3( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + failure_mode: str, ) -> None: - """Test_static_callcodecallcallcode_101_oogm_after_3.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[ 0 ]] (CALLCODE (GAS) (CALLDATALOAD 0) 0 0 0 0 0) [[ 1 ]] 1 } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALLCODE( - gas=Op.GAS, - address=Op.CALLDATALOAD(offset=0x0), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + """Absorb a deep frame failure and overwrite its context writes.""" + if failure_mode == "oog_after_static": + # The leaf is memory-only, so the static branch succeeds; the + # burner after it exhausts the delegated frame's whole grant. + leaf = pre.deploy_contract( + code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP ) - + Op.SSTORE(key=0x1, value=0x1) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xAAB59F13D96113334FAB5C68E4E62B61F6CBF647), # noqa: E501 - ) - # Source: lll - # { (MSTORE 3 1) } - addr_4 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x335C5531B84765A7626E6E76688F18B81BE5259C), # noqa: E501 - ) - # Source: lll - # { (SSTORE 3 1) } - addr_8 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x3, value=0x1) + Op.STOP, - nonce=0, - address=Address(0xB126C622075B1189FB6C45E851641CFADDF65B36), # noqa: E501 - ) - # Source: lll - # { (DELEGATECALL 20020 0 64 0 64 ) (MSTORE 3 1) } # noqa: E501 - addr_3 = pre.deploy_contract( # noqa: F841 + else: + # The leaf is delegate-called inside a static context, so its + # SSTORE is a violation that halts only the leaf frame. + leaf = pre.deploy_contract( + code=Op.SSTORE(key=0x3, value=0x1) + Op.STOP + ) + static_callee = pre.deploy_contract( code=Op.POP( Op.DELEGATECALL( - gas=0x4E34, - address=0x335C5531B84765A7626E6E76688F18B81BE5259C, - args_offset=0x0, + gas=LEAF_GAS_ASK, + address=leaf, args_size=0x40, - ret_offset=0x0, ret_size=0x40, ) ) + Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x96BBA71C203B7339624A350FE004F71C3D669AEE), # noqa: E501 ) - # Source: lll - # { (DELEGATECALL 20020 0 64 0 64 ) (MSTORE 3 1) } # noqa: E501 - addr_7 = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.DELEGATECALL( - gas=0x4E34, - address=0xB126C622075B1189FB6C45E851641CFADDF65B36, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, + if failure_mode == "oog_after_static": + # Static call, then an EXTCODESIZE loop far exceeding the + # frame's grant (guarded below): the delegated frame OOGs. + delegated = pre.deploy_contract( + code=Op.POP( + Op.STATICCALL( + gas=STATIC_GAS_ASK, + address=static_callee, + args_size=0x40, + ret_size=0x40, + ) + ) + + Op.JUMPDEST + + Op.JUMPI( + pc=0x3E, + condition=Op.ISZERO( + Op.LT(Op.MLOAD(offset=0x80), BURNER_ITERATIONS) + ), ) + + Op.POP(Op.EXTCODESIZE(address=0x1)) + + Op.MSTORE(offset=0x80, value=Op.ADD(Op.MLOAD(offset=0x80), 0x1)) + + Op.JUMP(pc=0x22) + + Op.JUMPDEST + + Op.STOP, ) - + Op.MSTORE(offset=0x3, value=0x1) - + Op.STOP, - nonce=0, - address=Address(0x65BE40505E6165809F16BFC5CDBA14169BC97614), # noqa: E501 - ) - # Source: lll - # { (STATICCALL 40080 0 64 0 64 ) (def 'i 0x80) (for {} (< @i 50000) [i](+ @i 1) (EXTCODESIZE 1)) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0x9C90, - address=0x96BBA71C203B7339624A350FE004F71C3D669AEE, - args_offset=0x0, + else: + # The static call absorbs the leaf's violation; this frame + # completes. + delegated = pre.deploy_contract( + code=Op.STATICCALL( + gas=STATIC_GAS_ASK, + address=static_callee, args_size=0x40, - ret_offset=0x0, ret_size=0x40, ) + + Op.STOP, ) - + Op.JUMPDEST - + Op.JUMPI( - pc=0x3E, condition=Op.ISZERO(Op.LT(Op.MLOAD(offset=0x80), 0xC350)) - ) - + Op.POP(Op.EXTCODESIZE(address=0x1)) - + Op.MSTORE(offset=0x80, value=Op.ADD(Op.MLOAD(offset=0x80), 0x1)) - + Op.JUMP(pc=0x22) - + Op.JUMPDEST - + Op.STOP, - nonce=0, - address=Address(0xB867C4BF480D6DCD06716BCDB0F9BCF3BB5710BF), # noqa: E501 + burner_cost = BURNER_ITERATIONS * Op.EXTCODESIZE(address=0x1).gas_cost( + fork ) - # Source: lll - # { (STATICCALL 40080 0 64 0 64 ) } # noqa: E501 - addr_6 = pre.deploy_contract( # noqa: F841 - code=Op.STATICCALL( - gas=0x9C90, - address=0x65BE40505E6165809F16BFC5CDBA14169BC97614, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - + Op.STOP, - nonce=0, - address=Address(0x2ABA60E14F876DAC315953942316A9A2F80C3AD5), # noqa: E501 - ) - # Source: lll - # { [[ 0 ]] (DELEGATECALL 60150 0 64 0 64 ) [[ 1 ]] (GAS) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + assert DELEGATE_GAS_ASK < burner_cost, "the burner must exhaust its grant" + # Entry code, run in the target's context: records the delegated + # call's result and a gas snapshot, both overwritten by the target. + entry = pre.deploy_contract( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0xEAF6, - address=0xB867C4BF480D6DCD06716BCDB0F9BCF3BB5710BF, - args_offset=0x0, + gas=DELEGATE_GAS_ASK, + address=delegated, args_size=0x40, - ret_offset=0x0, ret_size=0x40, ), ) + Op.SSTORE(key=0x1, value=Op.GAS) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x77D2ECB3F4D887934C7C8F304831EA89E08CB30D), # noqa: E501 ) - # Source: lll - # { [[ 0 ]] (DELEGATECALL 60150 0 64 0 64 ) [[ 1 ]] 1 } # noqa: E501 - addr_5 = pre.deploy_contract( # noqa: F841 + target = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.DELEGATECALL( - gas=0xEAF6, - address=0x2ABA60E14F876DAC315953942316A9A2F80C3AD5, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), + value=Op.CALLCODE(address=Op.CALLDATALOAD(offset=0x0), value=0x0), ) + Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xE2FA228586F5C62A6728D17728F4622D05D84E45), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 1, 1: 1})}, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 1, 1: 1})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(addr, left_padding=True), - Hash(addr_5, left_padding=True), - ] - tx_gas = [172000] - if fork.is_eip_enabled(8037): - tx_gas[0] += 7 * Op.SSTORE(new_value=1).state_cost(fork) - tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + data=Hash(entry, left_padding=True), ) - state_test(env=env, pre=pre, post=post, tx=tx) + # Whatever the delegated chain recorded in the target's slots, the + # outer frame's constant stores overwrite it. + post = { + leaf: Account(storage={}), + static_callee: Account(storage={}), + target: Account(storage={0: 1, 1: 1}), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_end.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_end.py index ede0e6f1ce4..1664df6a392 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_end.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_end.py @@ -1,27 +1,38 @@ """ -Test_static_callcodecallcodecall_110_suicide_end. +Measure the gas consumed by a DELEGATECALL-DELEGATECALL-STATICCALL +chain that ends in a SELFDESTRUCT of the shared context account: under +EIP-6780 the pre-existing account survives, its balance moves to the +beneficiary, and the chain's cost is exactly the sum of each frame's +charges — independent of the transaction value, which delegate calls +carry without transferring. Ported from: state_tests/stStaticCall/static_callcodecallcodecall_110_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The ported absolute GAS snapshot +(slot 1) became a CodeGasMeasure delta asserted against fork-derived +frame costs; the SELFDESTRUCT beneficiary is the (warm) static leaf so +every address is dynamic, and its balance is asserted. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, - Storage, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +FLAG_SLOT = 0x0 +GAS_SLOT = 0x1 +TARGET_BALANCE = 10**18 + @pytest.mark.ported_from( [ @@ -29,129 +40,96 @@ ], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-v0", - ), - pytest.param( - 0, - 0, - 1, - id="-v1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.parametrize("tx_value", [0, 1]) def test_static_callcodecallcodecall_110_suicide_end( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + tx_value: int, ) -> None: - """Test_static_callcodecallcodecall_110_suicide_end.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) + """Measure a nested DELEGATECALL chain ending in SELFDESTRUCT.""" + # Static leaf: memory-only, runs in its own frame. + leaf_code = Op.MSTORE(offset=0x3, value=0x1, new_memory_size=0x23) + leaf = pre.deploy_contract(code=leaf_code + Op.STOP) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=30000000, - ) + # Innermost delegate (target's context): static-call the leaf, then + # self-destruct the context account. The beneficiary is the leaf — + # warm from the call just made, and deployable without the address + # circularity of the ported layout (which paid the same warm-access + # cost for the mid-chain delegate). + inner_code = Op.POP( + Op.STATICCALL( + address=leaf, + args_size=0x40, + ret_size=0x40, + new_memory_size=0x40, + ) + ) + Op.SELFDESTRUCT(address=leaf, address_warm=True) + inner = pre.deploy_contract(code=inner_code) - # Source: lll - # { (MSTORE 3 1) } - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0x48E2D4C0B593BFEBE5DDB4F13AA355B8BD83DDD3), # noqa: E501 - ) - # Source: lll - # { [[ 0 ]] (DELEGATECALL 150000 0 64 0 64 ) [[ 1 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.DELEGATECALL( - gas=0x249F0, - address=0x92D7028788CAA240253B7B2A92386464690CDC72, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), + # Middle delegate (target's context): DELEGATECALL the innermost. + mid_code = ( + Op.DELEGATECALL( + address=inner, + args_size=0x40, + ret_size=0x40, + new_memory_size=0x40, ) - + Op.SSTORE(key=0x1, value=Op.GAS) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x5F7FDF1F0D4F4AD14C6996AE17BB6698D22343D8), # noqa: E501 + + Op.STOP ) - # Source: lll - # { (DELEGATECALL 100000 0 64 0 64 ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.DELEGATECALL( - gas=0x186A0, - address=0xB7770360E0B87603E3D9C87C866451760C95ABCA, - args_offset=0x0, + mid = pre.deploy_contract(code=mid_code) + + # The measured window: flag-storing DELEGATECALL into the chain. + # The SSTORE keeps the chain's success observable in the window. + store_code = Op.SSTORE( + FLAG_SLOT, + Op.DELEGATECALL( + address=mid, args_size=0x40, - ret_offset=0x0, ret_size=0x40, - ) - + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0x92D7028788CAA240253B7B2A92386464690CDC72), # noqa: E501 + new_memory_size=0x40, + ), + key_warm=False, + original_value=0, + new_value=1, ) - # Source: lll - # { (STATICCALL 50000 0 64 0 64 ) (SELFDESTRUCT ) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0xC350, - address=0x48E2D4C0B593BFEBE5DDB4F13AA355B8BD83DDD3, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - ) - + Op.SELFDESTRUCT(address=0x92D7028788CAA240253B7B2A92386464690CDC72) - + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0xB7770360E0B87603E3D9C87C866451760C95ABCA), # noqa: E501 + target = pre.deploy_contract( + code=CodeGasMeasure( + code=store_code, + extra_stack_items=0, + sstore_key=GAS_SLOT, + ), + balance=TARGET_BALANCE, ) - tx_data = [ - Bytes(""), - ] - tx_gas = [3000000] - tx_value = [0, 1] - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], + value=tx_value, + state_gas_reservoir=0, + ) + + # Every frame succeeds, so the window measures the sum of each + # frame's actual charges; delegate calls move no value, so nothing + # here depends on the transaction value. + expected_gas = ( + store_code.gas_cost(fork) + + mid_code.gas_cost(fork) + + inner_code.gas_cost(fork) + + leaf_code.gas_cost(fork) ) - target_storage = Storage.model_validate({0: 1, 1: 0x2CEC03}) - if fork.is_eip_enabled(8037): - target_storage = Storage.model_validate({0: 1}) - target_storage.set_expect_any(1) - post = {target: Account(storage=target_storage, balance=0, nonce=0)} + # EIP-6780: the pre-existing target survives its self-destruct; its + # whole balance (including the transaction value) moved to the leaf. + post = { + target: Account( + balance=0, + storage={FLAG_SLOT: 1, GAS_SLOT: expected_gas}, + ), + leaf: Account( + balance=TARGET_BALANCE + tx_value, + storage={}, + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py index a72cbc5d33e..7c31ec81f07 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py @@ -1,30 +1,37 @@ """ -Test_static_callcodecallcodecall_110_suicide_end2. +Measure the gas consumed by a CALLCODE-CALLCODE-STATICCALL chain that +ends in a SELFDESTRUCT of the shared context account: under EIP-6780 +the pre-existing account survives, its balance moves to the +beneficiary, and the chain's cost is exactly the sum of each frame's +charges (minus the returned stipends when value is sent). Ported from: state_tests/stStaticCall/static_callcodecallcodecall_110_SuicideEnd2Filler.json + +@manually-enhanced: Do not overwrite. The ported absolute GAS snapshot +(slot 1) became a CodeGasMeasure delta asserted against fork-derived +frame costs; the SELFDESTRUCT beneficiary is the (warm) static leaf so +every address is dynamic, and its balance is asserted. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, - Storage, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +FLAG_SLOT = 0x0 +GAS_SLOT = 0x1 +TARGET_BALANCE = 10**18 + @pytest.mark.ported_from( [ @@ -32,165 +39,104 @@ ], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-v0", - ), - pytest.param( - 0, - 0, - 1, - id="-v1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.parametrize("tx_value", [0, 1]) def test_static_callcodecallcodecall_110_suicide_end2( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + tx_value: int, ) -> None: - """Test_static_callcodecallcodecall_110_suicide_end2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) + """Measure a nested CALLCODE chain ending in SELFDESTRUCT.""" + sends_value = tx_value > 0 - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=30000000, - ) + # Static leaf: memory-only, runs in its own frame. + leaf_code = Op.MSTORE(offset=0x3, value=0x1, new_memory_size=0x23) + leaf = pre.deploy_contract(code=leaf_code + Op.STOP) - # Source: lll - # { (MSTORE 3 1) } - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0x48E2D4C0B593BFEBE5DDB4F13AA355B8BD83DDD3), # noqa: E501 - ) - # Source: lll - # { [[ 0 ]] (CALLCODE 150000 (CALLVALUE) 0 64 0 64 ) [[ 1 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALLCODE( - gas=0x249F0, - address=0x1D36753CD1D8D4795799D3F4D0925C63F72B2685, - value=Op.CALLVALUE, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), + # Innermost delegate (target's context): static-call the leaf, then + # self-destruct the context account. The beneficiary is the leaf — + # warm from the call just made, and deployable without the address + # circularity of the ported layout (which paid the same warm-access + # cost for the mid-chain delegate). + inner_code = Op.POP( + Op.STATICCALL( + address=leaf, + args_size=0x40, + ret_size=0x40, + new_memory_size=0x40, ) - + Op.SSTORE(key=0x1, value=Op.GAS) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x44D09DDF088DD88C0E91FA7EF74973FF94AD7414), # noqa: E501 - ) - # Source: lll - # { (CALLCODE 100000 (CALLVALUE) 0 64 0 64 ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.CALLCODE( - gas=0x186A0, - address=0xB7770360E0B87603E3D9C87C866451760C95ABCA, + ) + Op.SELFDESTRUCT(address=leaf, address_warm=True) + inner = pre.deploy_contract(code=inner_code) + + # Middle delegate (target's context): CALLCODE into the innermost. + mid_code = ( + Op.CALLCODE( + address=inner, value=Op.CALLVALUE, - args_offset=0x0, args_size=0x40, - ret_offset=0x0, ret_size=0x40, + new_memory_size=0x40, + value_transfer=sends_value, ) - + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0x1D36753CD1D8D4795799D3F4D0925C63F72B2685), # noqa: E501 - ) - # Source: lll - # { (STATICCALL 50000 0 64 0 64 ) (SELFDESTRUCT ) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0xC350, - address=0x48E2D4C0B593BFEBE5DDB4F13AA355B8BD83DDD3, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ) - ) - + Op.SELFDESTRUCT(address=0x1D36753CD1D8D4795799D3F4D0925C63F72B2685) - + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0xB7770360E0B87603E3D9C87C866451760C95ABCA), # noqa: E501 + + Op.STOP ) + mid = pre.deploy_contract(code=mid_code) - if fork.is_eip_enabled(8037): - target_storage = Storage.model_validate({0: 1}) - target_storage.set_expect_any(1) - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - target: Account(storage=target_storage, balance=0, nonce=0) - }, - }, - ] - else: - expect_entries_ = [ - { - "indexes": {"data": -1, "gas": -1, "value": 0}, - "network": [">=Cancun"], - "result": { - target: Account( - storage={0: 1, 1: 0x2CEBFF}, - balance=0, - nonce=0, - ) - }, - }, - { - "indexes": {"data": -1, "gas": -1, "value": 1}, - "network": [">=Cancun"], - "result": { - target: Account( - storage={0: 1, 1: 0x2CB7A7}, - balance=0, - nonce=0, - ) - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes(""), - ] - tx_gas = [3000000] - tx_value = [0, 1] + # The measured window: flag-storing CALLCODE into the chain. The + # SSTORE keeps the chain's success observable inside the window. + store_code = Op.SSTORE( + FLAG_SLOT, + Op.CALLCODE( + address=mid, + value=Op.CALLVALUE, + args_size=0x40, + ret_size=0x40, + new_memory_size=0x40, + value_transfer=sends_value, + ), + key_warm=False, + original_value=0, + new_value=1, + ) + target = pre.deploy_contract( + code=CodeGasMeasure( + code=store_code, + extra_stack_items=0, + sstore_key=GAS_SLOT, + ), + balance=TARGET_BALANCE, + ) tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + value=tx_value, + state_gas_reservoir=0, + ) + + # Every frame succeeds, so the window measures the sum of each + # frame's actual charges; each value-bearing hop's callee returns + # its unused 2300 stipend, shaving it off the measurement. + expected_gas = ( + store_code.gas_cost(fork) + + mid_code.gas_cost(fork) + + inner_code.gas_cost(fork) + + leaf_code.gas_cost(fork) ) + if sends_value: + expected_gas -= 2 * fork.gas_costs().CALL_STIPEND + + # EIP-6780: the pre-existing target survives its self-destruct; its + # whole balance (including the transaction value) moved to the leaf. + post = { + target: Account( + balance=0, + storage={FLAG_SLOT: 1, GAS_SLOT: expected_gas}, + ), + leaf: Account( + balance=TARGET_BALANCE + tx_value, + storage={}, + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_middle2.py b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_middle2.py index d246dd46098..c4d1172cb03 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_middle2.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_callcodecallcodecall_110_suicide_middle2.py @@ -1,18 +1,21 @@ """ -Test_static_callcodecallcodecall_110_suicide_middle2. +Verify a SELFDESTRUCT to self in the middle of a CALLCODE-CALLCODE +chain halts its frame immediately — the static call after it never +runs — while under EIP-6780 the pre-existing account is not deleted +and the enclosing frames complete normally. Ported from: state_tests/stStaticCall/static_callcodecallcodecall_110_SuicideMiddle2Filler.json + +@manually-enhanced: Do not overwrite. Self-beneficiary expressed as +ADDRESS; the chain's result and a completion canary are stored and +asserted (the ported post checked only the balance). """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, - Fork, StateTestFiller, Transaction, ) @@ -21,6 +24,9 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +TARGET_BALANCE = 10**18 +CANARY = 0xC0DE + @pytest.mark.ported_from( [ @@ -28,100 +34,60 @@ ], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable def test_static_callcodecallcodecall_110_suicide_middle2( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: - """Test_static_callcodecallcodecall_110_suicide_middle2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=30000000, - ) - - # Source: lll - # { (MSTORE 3 1) } - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0x48E2D4C0B593BFEBE5DDB4F13AA355B8BD83DDD3), # noqa: E501 - ) - # Source: lll - # { [[ 0 ]] (CALLCODE 150000 (CALLVALUE) 0 64 0 64 ) [[ 1 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALLCODE( - gas=0x249F0, - address=0x1D36753CD1D8D4795799D3F4D0925C63F72B2685, - value=Op.CALLVALUE, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), - ) - + Op.SSTORE(key=0x1, value=Op.GAS) + """Halt a frame at SELFDESTRUCT to self and keep the account.""" + # Never reached: the SELFDESTRUCT before the static call halts the + # frame first. + leaf = pre.deploy_contract(code=Op.MSTORE(offset=0x3, value=0x1) + Op.STOP) + # Runs in the target's context two CALLCODEs deep: self-destructs + # with itself as the beneficiary, which ends the frame before the + # static call. + inner = pre.deploy_contract( + code=Op.SELFDESTRUCT(address=Op.ADDRESS) + + Op.STATICCALL(address=leaf, args_size=0x40, ret_size=0x40) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x44D09DDF088DD88C0E91FA7EF74973FF94AD7414), # noqa: E501 ) - # Source: lll - # { (CALLCODE 100000 (CALLVALUE) 0 64 0 64 ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + outer = pre.deploy_contract( code=Op.CALLCODE( - gas=0x186A0, - address=0x408F1ACEFFEC7BDAA35D77006CDCAEAD563BB694, + address=inner, value=Op.CALLVALUE, - args_offset=0x0, args_size=0x40, - ret_offset=0x0, ret_size=0x40, ) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0x1D36753CD1D8D4795799D3F4D0925C63F72B2685), # noqa: E501 ) - # Source: lll - # { (SELFDESTRUCT ) (STATICCALL 50000 0 64 0 64 ) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SELFDESTRUCT( - address=0x44D09DDF088DD88C0E91FA7EF74973FF94AD7414 - ) - + Op.STATICCALL( - gas=0xC350, - address=0x48E2D4C0B593BFEBE5DDB4F13AA355B8BD83DDD3, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, + target = pre.deploy_contract( + code=Op.SSTORE( + key=0x0, + value=Op.CALLCODE( + address=outer, + value=Op.CALLVALUE, + args_size=0x40, + ret_size=0x40, + ), ) + + Op.SSTORE(key=0x1, value=CANARY) + Op.STOP, - balance=0x2540BE400, - nonce=0, - address=Address(0x408F1ACEFFEC7BDAA35D77006CDCAEAD563BB694), # noqa: E501 + balance=TARGET_BALANCE, ) tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, + sender=pre.fund_eoa(), to=target, - data=Bytes(""), - gas_limit=3000000, ) - post = {target: Account(balance=0xDE0B6B3A7640000)} + # SELFDESTRUCT halts its frame successfully, so the chain reports + # success; EIP-6780 keeps the pre-existing target, whose balance + # only moved to itself. + post = { + leaf: Account(storage={}), + target: Account( + balance=TARGET_BALANCE, + storage={0: 1, 1: CANARY}, + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/byzantium/stStaticCall/test_static_zero_value_call_oog_revert.py b/tests/ported_static/byzantium/stStaticCall/test_static_zero_value_call_oog_revert.py index d718ee51249..519fdf14dbc 100644 --- a/tests/ported_static/byzantium/stStaticCall/test_static_zero_value_call_oog_revert.py +++ b/tests/ported_static/byzantium/stStaticCall/test_static_zero_value_call_oog_revert.py @@ -1,18 +1,19 @@ """ -Test_static_zero_value_call_oog_revert. +Verify a zero-value STATICCALL to an absent account succeeds without +creating it, then a huge KECCAK256 memory expansion runs the frame out +of gas so every stored gas snapshot reverts. Ported from: state_tests/stStaticCall/static_ZeroValue_CALL_OOGRevertFiller.json + +@manually-enhanced: Do not overwrite. Gas budget and the OOG guard are +derived from the fork (was a pinned 1350000); dynamic addresses. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, Fork, StateTestFiller, Transaction, @@ -22,71 +23,58 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# The legacy ask: granted in full to the code-less callee and returned +# unused. +CALLEE_GAS_ASK = 0xEA60 +# Hashing this window costs far more than the whole budget below. +KECCAK_SIZE = 0x2FFFFF + @pytest.mark.ported_from( ["state_tests/stStaticCall/static_ZeroValue_CALL_OOGRevertFiller.json"], ) -@pytest.mark.valid_from("Frontier") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") def test_static_zero_value_call_oog_revert( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_static_zero_value_call_oog_revert.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Revert stored gas snapshots when a KECCAK256 exhausts the frame.""" + callee = pre.nonexistent_account() + call_code = Op.STATICCALL(gas=CALLEE_GAS_ASK, address=callee) + keccak_code = Op.SHA3( + offset=0x0, + size=KECCAK_SIZE, + data_size=KECCAK_SIZE, + new_memory_size=KECCAK_SIZE, ) - - pre[sender] = Account(balance=0xE8D4A51000) - # Source: lll - # { [[0]](GAS) [[1]] (STATICCALL 60000 0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b 0 0 0 0) (KECCAK256 0x00 0x2fffff) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 + contract = pre.deploy_contract( code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.STATICCALL( - gas=0xEA60, - address=0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.POP(Op.SHA3(offset=0x0, size=0x2FFFFF)) + + Op.SSTORE(key=0x1, value=call_code) + + Op.POP(keccak_code) + Op.SSTORE(key=0x64, value=Op.GAS) + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 ) + # Budget covers the first SSTORE and the call (whose unused ask comes + # back), but is far below the KECCAK256's cost, so the frame OOGs + # there and both stored gas snapshots revert. + sstore_cost = Op.SSTORE( + key_warm=False, original_value=0, new_value=1 + ).gas_cost(fork) + budget = 2 * sstore_cost + call_code.gas_cost(fork) + CALLEE_GAS_ASK + assert budget < keccak_code.gas_cost(fork), "the KECCAK256 must OOG" + gas_limit = fork.transaction_intrinsic_cost_calculator()() + budget + tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=1350000, + sender=pre.fund_eoa(), + to=contract, + gas_limit=gas_limit, ) post = { - sender: Account(nonce=1), - Address( - 0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B - ): Account.NONEXISTENT, - contract_0: Account(storage={0: 0, 1: 0, 100: 0}), + callee: Account.NONEXISTENT, + contract: Account(storage={}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/frontier/stCreateTest/test_create_large_result.py b/tests/ported_static/frontier/stCreateTest/test_create_large_result.py index 936dde89a2a..ff3b4910345 100644 --- a/tests/ported_static/frontier/stCreateTest/test_create_large_result.py +++ b/tests/ported_static/frontier/stCreateTest/test_create_large_result.py @@ -1,408 +1,281 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Measure CREATE/CREATE2 gas when the constructor returns or reverts results +of increasing size: a legal deposit is priced per byte, an oversized one +(EIP-170) forfeits the whole child grant, and a revert charges only the +work done. Ported from: state_tests/stCreateTest/createLargeResultFiller.yml + +@manually-enhanced: Do not overwrite. The runtime EXTCODECOPY factory and +its absolute GAS-delta pins (functions of the removed 80M gas limit) were +replaced by a calldata-delivered init code and a gas-capped creator frame, +so every expectation derives from fork composites and the EIP-150 63/64 +rule; this also lifts the old Prague ceiling. The size axis derives from +fork.max_code_size() (EIP-7954 raises it). Floors at Berlin: the SSTORE +composites price warm/cold access, mismatching pre-Berlin schedules. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, Bytes, - Environment, + Fork, Hash, StateTestFiller, Transaction, compute_create_address, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +ADDR_SLOT = 0x0 +GAS_SLOT = 0x1 +HASH_SLOT = 0x2 +CREATE2_SALT = 0x5A17 +# A comfortably legal deployed-code size; the other sizes derive from the +# fork's deployed-code ceiling (EIP-170, raised by EIP-7954). +NORMAL_SIZE = 0x100 +# The init code image: the constructor template padded to this offset, +# followed by one word holding the requested deployed-code size. +SIZE_WORD_OFFSET = 0x100 +INIT_CODE_SIZE = SIZE_WORD_OFFSET + 0x20 +# Creator memory: the init code image, then the gas snapshot word. +SNAPSHOT_OFFSET = INIT_CODE_SIZE +CREATOR_MEMORY = SNAPSHOT_OFFSET + 0x20 +# Fixed budget for the creator frame; the forfeited grant of an oversized +# deposit derives from it via the 63/64 rule. It must cover the priciest +# legal deposit (the fork's whole code-size ceiling) with headroom. +CREATOR_GAS = 6_000_000 +BUDGET_MARGIN = 10_000 + @pytest.mark.ported_from( ["state_tests/stCreateTest/createLargeResultFiller.yml"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.valid_until("Prague") +@pytest.mark.valid_from("Berlin") @pytest.mark.parametrize( - "d, g, v", + "create_op, reverts, size_kind", [ - pytest.param( - 0, - 0, - 0, - id="CREATE-RETURN", - ), - pytest.param( - 1, - 0, - 0, - id="CREATE2-RETURN", - ), - pytest.param( - 2, - 0, - 0, - id="CREATE-REVERT", - ), - pytest.param( - 3, - 0, - 0, - id="CREATE2-REVERT", - ), - pytest.param( - 4, - 0, - 0, - id="CREATE-RETURN-MAX", - ), - pytest.param( - 5, - 0, - 0, - id="CREATE2-RETURN-MAX", - ), - pytest.param( - 6, - 0, - 0, - id="CREATE-REVERT-MAX", - ), - pytest.param( - 7, - 0, - 0, - id="CREATE2-REVERT-MAX", - ), - pytest.param( - 8, - 0, - 0, - id="CREATE-RETURN-TOOBIG", - ), - pytest.param( - 9, - 0, - 0, - id="CREATE2-RETURN-TOOBIG", - ), - pytest.param( - 10, - 0, - 0, - id="CREATE-REVERT-TOOBIG", - ), - pytest.param( - 11, - 0, - 0, - id="CREATE2-REVERT-TOOBIG", - ), - pytest.param( - 12, - 0, - 0, - id="CREATE-RETURN-HUGE", - ), - pytest.param( - 13, - 0, - 0, - id="CREATE2-RETURN-HUGE", - ), - pytest.param( - 14, - 0, - 0, - id="CREATE-REVERT-HUGE", - ), - pytest.param( - 15, - 0, - 0, - id="CREATE2-REVERT-HUGE", - ), + pytest.param(Op.CREATE, False, "normal", id="CREATE-RETURN"), + pytest.param(Op.CREATE2, False, "normal", id="CREATE2-RETURN"), + pytest.param(Op.CREATE, True, "normal", id="CREATE-REVERT"), + pytest.param(Op.CREATE2, True, "normal", id="CREATE2-REVERT"), + pytest.param(Op.CREATE, False, "max", id="CREATE-RETURN-MAX"), + pytest.param(Op.CREATE2, False, "max", id="CREATE2-RETURN-MAX"), + pytest.param(Op.CREATE, True, "max", id="CREATE-REVERT-MAX"), + pytest.param(Op.CREATE2, True, "max", id="CREATE2-REVERT-MAX"), + pytest.param(Op.CREATE, False, "toobig", id="CREATE-RETURN-TOOBIG"), + pytest.param(Op.CREATE2, False, "toobig", id="CREATE2-RETURN-TOOBIG"), + pytest.param(Op.CREATE, True, "toobig", id="CREATE-REVERT-TOOBIG"), + pytest.param(Op.CREATE2, True, "toobig", id="CREATE2-REVERT-TOOBIG"), + pytest.param(Op.CREATE, False, "huge", id="CREATE-RETURN-HUGE"), + pytest.param(Op.CREATE2, False, "huge", id="CREATE2-RETURN-HUGE"), + pytest.param(Op.CREATE, True, "huge", id="CREATE-REVERT-HUGE"), + pytest.param(Op.CREATE2, True, "huge", id="CREATE2-REVERT-HUGE"), ], ) -@pytest.mark.pre_alloc_mutable def test_create_large_result( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + create_op: Op, + reverts: bool, + size_kind: str, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x000000000000000000000000000000000000C0DE) - contract_1 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) + """Create a contract of the requested size and measure the gas.""" + max_code_size = fork.max_code_size() + deploy_size = { + "normal": NORMAL_SIZE, + "max": max_code_size, + "toobig": max_code_size + 1, + "huge": 2 * max_code_size, + }[size_kind] + deployed = deploy_size <= max_code_size and not reverts - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, + # The constructor writes a marker word, copies its trailing size word + # into scratch memory, and returns (or reverts) that many bytes. + child_memory = max(INIT_CODE_SIZE, deploy_size) + end_op = Op.REVERT if reverts else Op.RETURN + end_kwargs: dict = { + "offset": 0x0, + "size": Op.MLOAD( + offset=SIZE_WORD_OFFSET, + old_memory_size=INIT_CODE_SIZE, + new_memory_size=INIT_CODE_SIZE, + ), + "old_memory_size": INIT_CODE_SIZE, + "new_memory_size": child_memory, + } + if not reverts: + end_kwargs["code_deposit_size"] = deploy_size + child_code = ( + Op.MSTORE(offset=0x0, value=Op.NOT(0x0), new_memory_size=0x20) + + Op.CODECOPY( + dest_offset=SIZE_WORD_OFFSET, + offset=SIZE_WORD_OFFSET, + size=0x20, + data_size=0x20, + old_memory_size=0x20, + new_memory_size=INIT_CODE_SIZE, + ) + + end_op(**end_kwargs) + ) + assert len(bytes(child_code)) <= SIZE_WORD_OFFSET + init_code = Bytes( + bytes(child_code).ljust(SIZE_WORD_OFFSET, b"\x00") + Hash(deploy_size) ) - pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE, nonce=1) - # Source: yul - # london - # { - # // Store some data - # mstore(0, not(0)) - # - # // Copy the requested length from the constructor code - # codecopy(0x100, 0x100, 0x20) - # - # // Return it as the new contract - # return(0, mload(0x100)) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.NOT(0x0)) - + Op.CODECOPY(dest_offset=Op.DUP1, offset=0x100, size=0x20) - + Op.RETURN(offset=0x0, size=Op.MLOAD(offset=0x100)), - nonce=1, - address=Address(0x000000000000000000000000000000000000C0DE), # noqa: E501 + # The creator loads the init code from calldata, then measures the + # create inside a GAS-snapshot window and records the code hash. + prelude = Op.CALLDATACOPY( + dest_offset=0x0, + offset=0x0, + size=Op.CALLDATASIZE, + data_size=INIT_CODE_SIZE, + new_memory_size=INIT_CODE_SIZE, + ) + head = Op.MSTORE( + offset=SNAPSHOT_OFFSET, + value=Op.GAS, + old_memory_size=INIT_CODE_SIZE, + new_memory_size=CREATOR_MEMORY, + ) + create_kwargs: dict = { + "value": 0x0, + "offset": 0x0, + "size": INIT_CODE_SIZE, + "init_code_size": INIT_CODE_SIZE, + "old_memory_size": CREATOR_MEMORY, + "new_memory_size": CREATOR_MEMORY, + } + if create_op == Op.CREATE2: + create_kwargs["salt"] = CREATE2_SALT + create_code = create_op(**create_kwargs) + body = Op.SSTORE( + key=ADDR_SLOT, + value=create_code, + key_warm=False, + original_value=0, + new_value=1 if deployed else 0, ) - # Source: yul - # london - # { - # sstore(1, gas()) - # - # // The operation to run - # // F0 - CREATE - # // F5 - CREATE2 - # let operation := calldataload(0x04) - # - # // The constructor ends with - # // F3 - RETURN - # // FD - REVERT - # let constructorEnd := calldataload(0x24) - # - # // The size of the contract getting created - # let contractSize := calldataload(0x44) - # - # // Create the constructor. - # let codeSize := extcodesize(0xC0DE) - # extcodecopy(0xC0DE, 0, 0, codeSize) - # - # // Modify the last opcode - # mstore8(sub(codeSize, 1), constructorEnd) - # - # // Include the requested contract size - # mstore(0x100, contractSize) - # - # // Create the contract - # let newAddr - # switch operation - # ... (10 more lines) - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=Op.GAS) - + Op.CALLDATALOAD(offset=0x4) - + Op.CALLDATALOAD(offset=0x24) - + Op.CALLDATALOAD(offset=0x44) - + Op.SWAP1 - + Op.PUSH1[0x1] - + Op.EXTCODESIZE(address=contract_0) - + Op.EXTCODECOPY( - address=contract_0, dest_offset=Op.DUP1, offset=0x0, size=Op.DUP1 + tail = Op.SSTORE( + key=GAS_SLOT, + value=Op.SUB( + Op.MLOAD( + offset=SNAPSHOT_OFFSET, + old_memory_size=CREATOR_MEMORY, + new_memory_size=CREATOR_MEMORY, + ), + Op.GAS, + ), + key_warm=False, + original_value=0, + new_value=1, + ) + hash_store = Op.SSTORE( + key=HASH_SLOT, + value=Op.EXTCODEHASH( + address=Op.SLOAD(key=ADDR_SLOT, key_warm=True), + address_warm=deployed, + ), + key_warm=False, + original_value=0, + new_value=1 if deployed else 0, + ) + creator = pre.deploy_contract( + code=prelude + head + body + tail + hash_store + Op.STOP, + ) + + # A fixed outer grant makes the creator's window independent of the + # transaction gas limit (the entry's 63/64 withhold never binds). + entry = pre.deploy_contract( + code=Op.CALLDATACOPY(dest_offset=0x0, offset=0x0, size=Op.CALLDATASIZE) + + Op.CALL( + gas=CREATOR_GAS, + address=creator, + args_offset=0x0, + args_size=Op.CALLDATASIZE, ) - + Op.SUB - + Op.MSTORE8 - + Op.PUSH2[0x100] - + Op.MSTORE - + Op.PUSH1[0x0] - + Op.SWAP1 - + Op.JUMPI(pc=0x53, condition=Op.EQ(0xF0, Op.DUP1)) - + Op.PUSH1[0xF5] - + Op.JUMPI(pc=0x44, condition=Op.EQ) - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=Op.DUP1) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.SLOAD(key=0x1), Op.GAS)) - + Op.SSTORE(key=0x2, value=Op.EXTCODEHASH) - + Op.STOP - + Op.JUMPDEST - + Op.POP - + Op.CREATE2(value=Op.DUP1, offset=0x0, size=0x120, salt=0x5A17) - + Op.JUMP(pc=0x32) - + Op.JUMPDEST - + Op.POP * 2 - + Op.CREATE(value=Op.DUP1, offset=0x0, size=0x120) - + Op.JUMP(pc=0x32), - storage={0: 24743, 1: 24743, 2: 24743}, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 + + Op.STOP, ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun prelude.execution_cost(fork) + + gas_delta + + unwind_cost + + BUDGET_MARGIN + ), "creator budget too small" + else: + assert available // 64 > unwind_cost + BUDGET_MARGIN, ( + "grant retention too small for the unwind" + ) - tx_data = [ - Bytes("048071d3") + Hash(0xF0) + Hash(0xF3) + Hash(0x100), - Bytes("048071d3") + Hash(0xF5) + Hash(0xF3) + Hash(0x100), - Bytes("048071d3") + Hash(0xF0) + Hash(0xFD) + Hash(0x100), - Bytes("048071d3") + Hash(0xF5) + Hash(0xFD) + Hash(0x100), - Bytes("048071d3") + Hash(0xF0) + Hash(0xF3) + Hash(0x6000), - Bytes("048071d3") + Hash(0xF5) + Hash(0xF3) + Hash(0x6000), - Bytes("048071d3") + Hash(0xF0) + Hash(0xFD) + Hash(0x6000), - Bytes("048071d3") + Hash(0xF5) + Hash(0xFD) + Hash(0x6000), - Bytes("048071d3") + Hash(0xF0) + Hash(0xF3) + Hash(0x6001), - Bytes("048071d3") + Hash(0xF5) + Hash(0xF3) + Hash(0x6001), - Bytes("048071d3") + Hash(0xF0) + Hash(0xFD) + Hash(0x6001), - Bytes("048071d3") + Hash(0xF5) + Hash(0xFD) + Hash(0x6001), - Bytes("048071d3") + Hash(0xF0) + Hash(0xF3) + Hash(0xC000), - Bytes("048071d3") + Hash(0xF5) + Hash(0xF3) + Hash(0xC000), - Bytes("048071d3") + Hash(0xF0) + Hash(0xFD) + Hash(0xC000), - Bytes("048071d3") + Hash(0xF5) + Hash(0xFD) + Hash(0xC000), - ] - tx_gas = [80000000] + if create_op == Op.CREATE: + created = compute_create_address(address=creator, nonce=1) + else: + created = compute_create_address( + address=creator, + salt=CREATE2_SALT, + initcode=init_code, + opcode=Op.CREATE2, + ) + # The deployed image: the constructor's marker word, then zeroed + # memory, with the copied size word visible when the result is large + # enough to reach it. + image = bytearray(deploy_size) + image[0:32] = b"\xff" * 32 + if deploy_size > SIZE_WORD_OFFSET: + word = bytes(Hash(deploy_size)) + image[SIZE_WORD_OFFSET : SIZE_WORD_OFFSET + 0x20] = word[ + : deploy_size - SIZE_WORD_OFFSET + ] + deployed_code = Bytes(bytes(image)) - tx = Transaction( - sender=sender, - to=contract_1, - data=tx_data[d], - gas_limit=tx_gas[g], - nonce=1, - error=_exc, - ) + post = { + creator: Account( + storage={ + ADDR_SLOT: created if deployed else 0, + GAS_SLOT: gas_delta, + HASH_SLOT: deployed_code.keccak256() if deployed else 0, + }, + ), + created: ( + Account(nonce=1, balance=0) if deployed else Account.NONEXISTENT + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/frontier/stCreateTest/test_create_oo_gafter_max_codesize.py b/tests/ported_static/frontier/stCreateTest/test_create_oo_gafter_max_codesize.py index 4cd22b226bc..70a5102ac58 100644 --- a/tests/ported_static/frontier/stCreateTest/test_create_oo_gafter_max_codesize.py +++ b/tests/ported_static/frontier/stCreateTest/test_create_oo_gafter_max_codesize.py @@ -1,8 +1,17 @@ """ -Test_create_oo_gafter_max_codesize. +Verify mass creation of max-codesize contracts: a delegatecalled creator +commits its creations to the caller, a called creator either reverts them +all on a final INVALID or keeps them, and a self-destruct sweep removes a +prefix of the created contracts. Ported from: state_tests/stCreateTest/CreateOOGafterMaxCodesizeFiller.yml + +@manually-enhanced: Do not overwrite. The expect table was folded into a +post derived from the per-case creation counts. The compiled-Yul bytecode +(pc-sensitive, with embedded 0xC0DE* addresses) is kept verbatim, and the +4-gigagas budget keeps the test capped at Prague: EIP-7825 cannot fit +hundreds of max-codesize deposits in one transaction. """ import pytest @@ -17,15 +26,16 @@ Transaction, compute_create_address, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# Deployed size of every created contract, fixed by the init-code template +# contract's RETURN(0, 0x6000); the clones store their CODESIZE when run. +DEPLOYED_SIZE = 0x6000 +TX_GAS_LIMIT = 2**32 + @pytest.mark.ported_from( ["state_tests/stCreateTest/CreateOOGafterMaxCodesizeFiller.yml"], @@ -34,42 +44,48 @@ @pytest.mark.valid_until("Prague") @pytest.mark.slow @pytest.mark.parametrize( - "d, g, v", + "delegate_count, subcall_count, subcall_oog, selfdestruct_count", [ pytest.param( - 0, - 0, - 0, + 0x0, + 0xA, + True, + 0x0, id="LowContractCount_NoDelegateCreate_CallCreateOOG", ), pytest.param( - 1, - 0, - 0, + 0xA, + 0xA, + True, + 0x0, id="LowContractCount_DelegateCreate_CallCreateOOG", ), pytest.param( - 2, - 0, - 0, + 0xA, + 0xA, + False, + 0xE, id="LowContractCount_DelegateCreate_CallCreate_SelfDestruct", ), pytest.param( - 3, - 0, - 0, + 0x0, + 0xFA, + True, + 0x0, id="HighContractCount_NoDelegateCreate_CallCreateOOG", ), pytest.param( - 4, - 0, - 0, + 0xFA, + 0xFA, + True, + 0x0, id="HighContractCount_DelegateCreate_CallCreateOOG", ), pytest.param( - 5, - 0, - 0, + 0xFA, + 0xFA, + False, + 0x1EE, id="HighContractCount_DelegateCreate_CallCreate_SelfDestruct", ), ], @@ -78,12 +94,12 @@ def test_create_oo_gafter_max_codesize( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + delegate_count: int, + subcall_count: int, + subcall_oog: bool, + selfdestruct_count: int, ) -> None: - """Test_create_oo_gafter_max_codesize.""" + """Create max-codesize contracts by delegate, call, and sweep paths.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x00000000000000000000000000000000000C0DE0) contract_1 = Address(0x00000000000000000000000000000000000C0DE1) @@ -97,7 +113,7 @@ def test_create_oo_gafter_max_codesize( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4294967296, + gas_limit=TX_GAS_LIMIT, ) # Source: yul @@ -314,171 +330,57 @@ def test_create_oo_gafter_max_codesize( nonce=1, ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun=Cancun=Cancun=Cancun=Cancun=Cancun None: - """Test_call1_mb1024_calldepth.""" - coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=882500000000, - ) - - addr = pre.fund_eoa(amount=0xFFFFFFFFFFFFF) # noqa: F841 + """Recurse with 1MB call arguments until the gas budget starves it.""" # Source: lll - # { (def 'i 0x80) [[ 0 ]] (+ @@0 1) (if (LT @@0 1024) [[ 1 ]] (CALL (- (GAS) 1005000) 0 0 1000000 0 0) [[ 2 ]] 1 ) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1)) - + Op.JUMPI(pc=0x1B, condition=Op.LT(Op.SLOAD(key=0x0), 0x400)) - + Op.SSTORE(key=0x2, value=0x1) - + Op.JUMP(pc=0x47) - + Op.JUMPDEST - + Op.SSTORE( - key=0x1, - value=Op.CALL( - gas=Op.SUB(Op.GAS, 0xF55C8), - address=0x9D15232F6851F9F3A88F88A3B358ED1579977A5A, - value=0x0, - args_offset=0x0, - args_size=0xF4240, - ret_offset=0x0, - ret_size=0x0, + # { (def 'i 0x80) [[ 0 ]] (+ @@0 1) + # (if (LT @@0 1024) + # [[ 1 ]] (CALL (- (GAS) 1005000) 0 0 1000000 0 0) + # [[ 2 ]] 1 ) } + target = pre.deploy_contract( + code=Op.SSTORE( + key=COUNTER_SLOT, value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 0x1) + ) + + Conditional( + condition=Op.LT(Op.SLOAD(key=COUNTER_SLOT), DEPTH_LIMIT), + if_true=Op.SSTORE( + key=RESULT_SLOT, + value=Op.CALL( + gas=Op.SUB(Op.GAS, GAS_RESERVE), + address=Op.ADDRESS, + args_size=ARGS_SIZE, + ), ), + if_false=Op.SSTORE(key=DEPTH_LIMIT_SLOT, value=0x1), ) - + Op.JUMPDEST + Op.STOP, - balance=0xFFFFFFFFFFFFF, - nonce=0, - address=Address(0x9D15232F6851F9F3A88F88A3B358ED1579977A5A), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": 1, "value": -1}, - "network": [">=Cancun=Cancun None: - """Test_refund_multimple_suicide.""" - coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) - sender = pre.fund_eoa(amount=0x623A7C0) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, + """Self-destruct to self twice; derive the gas accounting.""" + # The dispatch runs in all three frames: taken with calldata (outer), + # not taken without (both inner). Both directions cost the same. + kill_code = Op.SELFDESTRUCT( + address=Op.CALLER, address_warm=True, account_new=False ) - - pre[coinbase] = Account(balance=0, nonce=1) - # Source: raw - # 0x606060405260e060020a600035046309e587a58114610031578063c04062261461004d578063dd4f1f2a1461005a575b005b61002f3373ffffffffffffffffffffffffffffffffffffffff16ff5b6100f5600061010961005e565b61002f5b60003090508073ffffffffffffffffffffffffffffffffffffffff166309e587a56040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750604080517f09e587a500000000000000000000000000000000000000000000000000000000815290516004828101926000929190829003018183876161da5a03f1156100025750505050565b604080519115158252519081900360200190f35b5060019056 # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x40, value=0x60) - + Op.DIV(Op.CALLDATALOAD(offset=0x0), Op.EXP(0x2, 0xE0)) - + Op.JUMPI(pc=Op.PUSH2[0x31], condition=Op.EQ(Op.DUP2, 0x9E587A5)) - + Op.JUMPI(pc=Op.PUSH2[0x4D], condition=Op.EQ(0xC0406226, Op.DUP1)) - + Op.JUMPI(pc=Op.PUSH2[0x5A], condition=Op.EQ(0xDD4F1F2A, Op.DUP1)) - + Op.JUMPDEST - + Op.STOP - + Op.JUMPDEST - + Op.PUSH2[0x2F] - + Op.SELFDESTRUCT( - address=Op.AND( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, Op.CALLER - ) - ) - + Op.JUMPDEST - + Op.PUSH2[0xF5] - + Op.PUSH1[0x0] - + Op.PUSH2[0x109] - + Op.JUMP(pc=Op.PUSH2[0x5E]) - + Op.JUMPDEST - + Op.PUSH2[0x2F] - + Op.JUMPDEST - + Op.PUSH1[0x0] - + Op.ADDRESS - + Op.SWAP1 - + Op.POP - + Op.AND(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, Op.DUP1) - + Op.PUSH4[0x9E587A5] - + Op.MLOAD(offset=0x40) - + Op.MSTORE(offset=Op.DUP2, value=Op.MUL(Op.EXP(0x2, 0xE0), Op.DUP2)) - + Op.PUSH1[0x4] - + Op.ADD - + Op.DUP1 - + Op.SWAP1 - + Op.POP - + Op.JUMPI( - pc=Op.PUSH2[0x2], - condition=Op.ISZERO( - Op.CALL( - gas=Op.SUB(Op.GAS, 0x61DA), - address=Op.DUP8, - value=0x0, - args_offset=Op.DUP2, - args_size=Op.SUB(Op.DUP4, Op.DUP1), - ret_offset=Op.MLOAD(offset=0x40), - ret_size=0x0, - ) - ), + run_pc = len(bytes(Op.JUMPI(pc=0, condition=Op.CALLDATASIZE))) + len( + bytes(kill_code) + ) + dispatch_code = Op.JUMPI(pc=run_pc, condition=Op.CALLDATASIZE) + run_code = ( + Op.JUMPDEST + + Op.SSTORE( + key=FIRST_FLAG_SLOT, + value=Op.CALL(address=Op.ADDRESS, address_warm=True), + key_warm=False, + original_value=0, + new_value=1, ) - + Op.POP - + Op.PUSH1[0x40] - + Op.MLOAD(offset=Op.DUP1) - + Op.MSTORE( - offset=Op.DUP2, - value=0x9E587A500000000000000000000000000000000000000000000000000000000, # noqa: E501 + + Op.SSTORE( + key=SECOND_FLAG_SLOT, + value=Op.CALL(address=Op.ADDRESS, address_warm=True), + key_warm=False, + original_value=0, + new_value=1, ) - + Op.SWAP1 - + Op.MLOAD - + Op.PUSH1[0x4] - + Op.ADD(Op.DUP2, Op.DUP3) - + Op.SWAP3 - + Op.PUSH1[0x0] - + Op.SWAP3 - + Op.SWAP2 - + Op.SWAP1 - + Op.DUP3 - + Op.SWAP1 - + Op.SUB - + Op.ADD - + Op.DUP2 - + Op.DUP4 - + Op.DUP8 - + Op.SUB(Op.GAS, 0x61DA) - + Op.JUMPI(pc=Op.PUSH2[0x2], condition=Op.ISZERO(Op.CALL)) - + Op.POP * 4 - + Op.JUMP - + Op.JUMPDEST - + Op.PUSH1[0x40] - + Op.MLOAD(offset=Op.DUP1) - + Op.SWAP2 - + Op.MSTORE(offset=Op.DUP3, value=Op.ISZERO(Op.ISZERO)) - + Op.MLOAD - + Op.SWAP1 - + Op.DUP2 - + Op.SWAP1 - + Op.ADD(0x20, Op.SUB) - + Op.SWAP1 - + Op.RETURN - + Op.JUMPDEST - + Op.POP - + Op.PUSH1[0x1] - + Op.SWAP1 - + Op.JUMP, - balance=0xDE0B6B3A7640000, - nonce=0, + + Op.STOP + ) + target = pre.deploy_contract( + code=dispatch_code + kill_code + run_code, + balance=TARGET_BALANCE, ) + # The refund cap is a fifth of the gas actually deducted before + # execution, which excludes the EIP-7623 calldata floor. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=RUN_SELECTOR, return_cost_deducted_prior_execution=True + ) + body_cost = ( + 3 * dispatch_code.gas_cost(fork) + + 2 * kill_code.gas_cost(fork) + + run_code.gas_cost(fork) + ) + executed = intrinsic + body_cost + gas_limit = executed + 5_000 + + sender = pre.fund_eoa(amount=INITIAL_BALANCE) tx = Transaction( protected=fork.supports_protected_txs(), sender=sender, to=target, - data=Bytes("c0406226"), - gas_limit=300000, + data=RUN_SELECTOR, + gas_limit=gas_limit, + gas_price=GAS_PRICE, ) - # EIP-2780 lowers the intrinsic for non-self non-value txs; the - # delta is negative on Amsterdam and raises the sender balance by - # ``gas_price * |delta|``. - intrinsic_delta = fork.transaction_intrinsic_cost_calculator()() - 21_000 + # EIP-3529 caps the refund at a fifth of the executed gas. The + # self-destruct refund (zero from London on) is counted once per + # account, not once per SELFDESTRUCT. + refund = min(kill_code.refund(fork), executed // 5) + gas_used = executed - refund + post = { - target: Account(balance=0xDE0B6B3A7640000), - coinbase: Account(balance=0), - sender: Account(balance=0x61EC43A - 10 * intrinsic_delta, nonce=1), + # EIP-6780: the pre-existing contract survives its self-destructs + # and, as its own beneficiary, keeps its balance. Before it, the + # account is deleted and the self-sent balance burned with it. + target: ( + Account( + balance=TARGET_BALANCE, + storage={FIRST_FLAG_SLOT: 1, SECOND_FLAG_SLOT: 1}, + ) + if fork.is_eip_enabled(6780) + else Account.NONEXISTENT + ), + sender: Account(balance=INITIAL_BALANCE - gas_used * GAS_PRICE), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/frontier/stRefundTest/test_refund_single_suicide.py b/tests/ported_static/frontier/stRefundTest/test_refund_single_suicide.py index 564f309361f..e0bb895a47e 100644 --- a/tests/ported_static/frontier/stRefundTest/test_refund_single_suicide.py +++ b/tests/ported_static/frontier/stRefundTest/test_refund_single_suicide.py @@ -1,151 +1,112 @@ """ -Test_refund_single_suicide. +Verify gas accounting when a contract calls itself and the inner frame +self-destructs with the contract as its own beneficiary: the balance +survives, and the sender pays the executed gas minus the capped refund. Ported from: state_tests/stRefundTest/refund_singleSuicideFiller.json -@manually-enhanced: Do not overwrite. The post-state asserts the sender -balance, which the original fixture hardcoded. EIP-2780 decomposes the -intrinsic and lowers it for this non-self, non-value tx, so the balance -is derived from the fork: ``intrinsic_delta`` subtracts the pre-EIP-2780 -baseline intrinsic 21_000 from the fork's intrinsic calculator (the -literal 21_000 is the old TX_BASE), making the delta 0 pre-EIP-2780 and -negative on Amsterdam. The sender balance is then adjusted by -``gas_price * intrinsic_delta`` (base fee 10). Do not hardcode it. +@manually-enhanced: Do not overwrite. The compiled Solidity dispatcher +(with its SUB(GAS, ...) forwarding and pinned sender balance) was rewritten +as a minimal calldata-dispatched self-call; the sender balance, refund cap +and budget derive from fork composites and the post branches on EIP-6780 +for the contract's survival. """ import pytest from execution_testing import ( Account, - Address, Alloc, Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +TARGET_BALANCE = 0xDE0B6B3A7640000 +INITIAL_BALANCE = 10**18 +GAS_PRICE = 10 +FLAG_SLOT = 0xA +# Any non-empty calldata selects the outer (self-call) path. +RUN_SELECTOR = Bytes("c0406226") + @pytest.mark.ported_from( ["state_tests/stRefundTest/refund_singleSuicideFiller.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("London") def test_refund_single_suicide( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_refund_single_suicide.""" - coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) - sender = pre.fund_eoa(amount=0x1C9C380) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, + """Self-destruct to self in an inner frame; derive the gas accounting.""" + # The dispatch runs in both frames: taken with calldata (outer), not + # taken without (inner). Both directions cost the same. + kill_code = Op.SELFDESTRUCT( + address=Op.CALLER, address_warm=True, account_new=False ) - - pre[coinbase] = Account(balance=0, nonce=1) - # Source: raw - # 0x606060405260e060020a600035046309e587a58114602e5780632e4699ed146049578063c040622614609b575b005b602c3373ffffffffffffffffffffffffffffffffffffffff16ff5b602c5b60003090508073ffffffffffffffffffffffffffffffffffffffff166309e587a56040518160e060020a0281526004018090506000604051808303816000876161da5a03f11560025750505050565b60a5600060b9604c565b604080519115158252519081900360200190f35b5060019056 # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x40, value=0x60) - + Op.DIV(Op.CALLDATALOAD(offset=0x0), Op.EXP(0x2, 0xE0)) - + Op.JUMPI(pc=0x2E, condition=Op.EQ(Op.DUP2, 0x9E587A5)) - + Op.JUMPI(pc=0x49, condition=Op.EQ(0x2E4699ED, Op.DUP1)) - + Op.JUMPI(pc=0x9B, condition=Op.EQ(0xC0406226, Op.DUP1)) - + Op.JUMPDEST - + Op.STOP - + Op.JUMPDEST - + Op.PUSH1[0x2C] - + Op.SELFDESTRUCT( - address=Op.AND( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, Op.CALLER - ) - ) - + Op.JUMPDEST - + Op.PUSH1[0x2C] - + Op.JUMPDEST - + Op.PUSH1[0x0] - + Op.ADDRESS - + Op.SWAP1 - + Op.POP - + Op.AND(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, Op.DUP1) - + Op.PUSH4[0x9E587A5] - + Op.MLOAD(offset=0x40) - + Op.MSTORE(offset=Op.DUP2, value=Op.MUL(Op.EXP(0x2, 0xE0), Op.DUP2)) - + Op.PUSH1[0x4] - + Op.ADD - + Op.DUP1 - + Op.SWAP1 - + Op.POP - + Op.JUMPI( - pc=0x2, - condition=Op.ISZERO( - Op.CALL( - gas=Op.SUB(Op.GAS, 0x61DA), - address=Op.DUP8, - value=0x0, - args_offset=Op.DUP2, - args_size=Op.SUB(Op.DUP4, Op.DUP1), - ret_offset=Op.MLOAD(offset=0x40), - ret_size=0x0, - ) - ), + run_pc = len(bytes(Op.JUMPI(pc=0, condition=Op.CALLDATASIZE))) + len( + bytes(kill_code) + ) + dispatch_code = Op.JUMPI(pc=run_pc, condition=Op.CALLDATASIZE) + run_code = ( + Op.JUMPDEST + + Op.SSTORE( + key=FLAG_SLOT, + value=Op.CALL(address=Op.ADDRESS, address_warm=True), + key_warm=False, + original_value=0, + new_value=1, ) - + Op.POP * 4 - + Op.JUMP - + Op.JUMPDEST - + Op.PUSH1[0xA5] - + Op.PUSH1[0x0] - + Op.PUSH1[0xB9] - + Op.JUMP(pc=0x4C) - + Op.JUMPDEST - + Op.PUSH1[0x40] - + Op.MLOAD(offset=Op.DUP1) - + Op.SWAP2 - + Op.MSTORE(offset=Op.DUP3, value=Op.ISZERO(Op.ISZERO)) - + Op.MLOAD - + Op.SWAP1 - + Op.DUP2 - + Op.SWAP1 - + Op.ADD(0x20, Op.SUB) - + Op.SWAP1 - + Op.RETURN - + Op.JUMPDEST - + Op.POP - + Op.PUSH1[0x1] - + Op.SWAP1 - + Op.JUMP, - balance=0xDE0B6B3A7640000, - nonce=0, + + Op.STOP + ) + target = pre.deploy_contract( + code=dispatch_code + kill_code + run_code, + balance=TARGET_BALANCE, + ) + + # The refund cap is a fifth of the gas actually deducted before + # execution, which excludes the EIP-7623 calldata floor. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=RUN_SELECTOR, return_cost_deducted_prior_execution=True + ) + body_cost = ( + 2 * dispatch_code.gas_cost(fork) + + kill_code.gas_cost(fork) + + run_code.gas_cost(fork) ) + executed = intrinsic + body_cost + gas_limit = executed + 5_000 + sender = pre.fund_eoa(amount=INITIAL_BALANCE) tx = Transaction( protected=fork.supports_protected_txs(), sender=sender, to=target, - data=Bytes("c0406226"), - gas_limit=300000, + data=RUN_SELECTOR, + gas_limit=gas_limit, + gas_price=GAS_PRICE, ) - # EIP-2780 lowers the intrinsic for non-self non-value txs; the - # delta is negative on Amsterdam and raises the sender balance by - # ``gas_price * |delta|``. - intrinsic_delta = fork.transaction_intrinsic_cost_calculator()() - 21_000 + # EIP-3529 caps the refund at a fifth of the executed gas. + refund = min(kill_code.refund(fork), executed // 5) + gas_used = executed - refund + post = { - target: Account(balance=0xDE0B6B3A7640000), - coinbase: Account(balance=0), - sender: Account(balance=0x1C5AF34 - 10 * intrinsic_delta, nonce=1), + # EIP-6780: the pre-existing contract survives its self-destruct + # and, as its own beneficiary, keeps its balance. Before it, the + # account is deleted and the self-sent balance burned with it. + target: ( + Account(balance=TARGET_BALANCE, storage={FLAG_SLOT: 1}) + if fork.is_eip_enabled(6780) + else Account.NONEXISTENT + ), + sender: Account(balance=INITIAL_BALANCE - gas_used * GAS_PRICE), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/frontier/stSystemOperationsTest/test_test_random_test.py b/tests/ported_static/frontier/stSystemOperationsTest/test_test_random_test.py index 225b269f7c6..4b1145c3839 100644 --- a/tests/ported_static/frontier/stSystemOperationsTest/test_test_random_test.py +++ b/tests/ported_static/frontier/stSystemOperationsTest/test_test_random_test.py @@ -1,59 +1,55 @@ """ -Test_test_random_test. +Run a fuzzed nested-CREATE sequence (a create inside another create's +operand chain, funded with a raw GAS reading) and verify the survivors. Ported from: state_tests/stSystemOperationsTest/testRandomTestFiller.json + +@manually-enhanced: Do not overwrite. Hardcoded addresses, the fixed +sender key, and the per-fork gas-limit band-aids were dropped; the stored +slot key derives from the creator's nonce and the stored value from the +block timestamp. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, Environment, Fork, StateTestFiller, Transaction, + compute_create_address, ) -from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +BLOCK_NUMBER = 1 +TIMESTAMP = 1000 +CONTRACT_BALANCE = 10**18 + @pytest.mark.ported_from( ["state_tests/stSystemOperationsTest/testRandomTestFiller.json"], ) @pytest.mark.valid_from("Frontier") -@pytest.mark.pre_alloc_mutable def test_test_random_test( state_test: StateTestFiller, fork: Fork, pre: Alloc, ) -> None: - """Test_test_random_test.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0F572E5295C57F15886F9B263E2F6D2D6C7B5EC6) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=3000000 if fork >= Amsterdam else 1000000, - ) + """Store the block timestamp under the second created address.""" + env = Environment(number=BLOCK_NUMBER, timestamp=TIMESTAMP) - pre[sender] = Account(balance=0xDE0B6B3A7640000) # Source: raw # 0x424443444243434383f0155af055 - contract_0 = pre.deploy_contract( # noqa: F841 + # The leading block-info reads are fuzzer junk left on the stack; the + # first CREATE deploys an empty one-byte-initcode contract, and the + # second (funded with a raw GAS reading as wei) supplies the storage + # key for the final SSTORE. + contract = pre.deploy_contract( code=Op.TIMESTAMP + Op.PREVRANDAO + Op.NUMBER @@ -68,24 +64,21 @@ def test_test_random_test( ), value=Op.TIMESTAMP, ), - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x0F572E5295C57F15886F9B263E2F6D2D6C7B5EC6), # noqa: E501 + balance=CONTRACT_BALANCE, ) tx = Transaction( protected=fork.supports_protected_txs(), - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=2300000 if fork >= Amsterdam else 300000, - value=0x186A0, + sender=pre.fund_eoa(), + to=contract, ) + # The deployed creator starts at nonce 1; the second CREATE uses 2. + second_created = compute_create_address(address=contract, nonce=2) post = { - contract_0: Account( - storage={0xEBCCE5F60530275EE9318CE1EFF9E4BFEE810172: 1000}, - nonce=2, + contract: Account( + storage={second_created: TIMESTAMP}, + nonce=3, ), } diff --git a/tests/ported_static/frontier/vmIOandFlowOperations/test_gas.py b/tests/ported_static/frontier/vmIOandFlowOperations/test_gas.py index fdfe01710a7..9ed1760138d 100644 --- a/tests/ported_static/frontier/vmIOandFlowOperations/test_gas.py +++ b/tests/ported_static/frontier/vmIOandFlowOperations/test_gas.py @@ -1,152 +1,76 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Verify the GAS opcode tracks consumption of the operations preceding it. Ported from: state_tests/VMTests/vmIOandFlowOperations/gasFiller.yml + +@manually-enhanced: Do not overwrite. The ported filler stored absolute GAS +readings behind a DELEGATECALL dispatcher (fragile against intrinsic-cost +changes); reframed as CodeGasMeasure deltas asserting fork-derived costs. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, - Hash, + Bytecode, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x0 +SECOND_MSTORE_OFFSET = 0x5A + @pytest.mark.ported_from( ["state_tests/VMTests/vmIOandFlowOperations/gasFiller.yml"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.valid_until("Prague") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="gas1", - ), - pytest.param( - 1, - 0, - 0, - id="gas2", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Frontier") +@pytest.mark.parametrize("measured", ["memory_writes", "gas_read"]) def test_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + measured: str, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000001000) - contract_1 = Address(0x0000000000000000000000000000000000001001) - contract_2 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = pre.fund_eoa(amount=0x100000000000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - # Source: lll - # { - # [0] 0xffffffffff - # [90] 0xeeee - # [[0]] (gas) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0xFFFFFFFFFF) - + Op.MSTORE(offset=0x5A, value=0xEEEE) - + Op.SSTORE(key=0x0, value=Op.GAS) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001000), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (gas) - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001001), # noqa: E501 - ) - # Source: lll - # { - # (delegatecall (gas) (+ 0x1000 $4) 0 0 0 0) - # } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.DELEGATECALL( - gas=Op.GAS, - address=Op.ADD(0x1000, Op.CALLDATALOAD(offset=0x4)), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + """Measure a known window of operations with two GAS readings.""" + window: Bytecode + extra_stack_items: int + if measured == "memory_writes": + # Two memory writes, the second expanding memory past the first. + window = Op.MSTORE( + offset=0x0, value=0xFFFFFFFFFF, new_memory_size=0x20 + ) + Op.MSTORE( + offset=SECOND_MSTORE_OFFSET, + value=0xEEEE, + old_memory_size=0x20, + new_memory_size=SECOND_MSTORE_OFFSET + 0x20, ) - + Op.STOP, - storage={0: 2989}, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 + extra_stack_items = 0 + else: + # The GAS opcode itself, whose reading the legacy filler stored. + window = Op.GAS + extra_stack_items = 1 + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=window, + extra_stack_items=extra_stack_items, + sstore_key=GAS_SLOT, + ), ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun=Cancun None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000001000) - contract_1 = Address(0x0000000000000000000000000000000000001001) - contract_2 = Address(0x0000000000000000000000000000000000001002) - contract_3 = Address(0x0000000000000000000000000000000000001003) - contract_4 = Address(0x0000000000000000000000000000000000001004) - contract_5 = Address(0x0000000000000000000000000000000000001005) - contract_6 = Address(0x0000000000000000000000000000000000001006) - contract_7 = Address(0x0000000000000000000000000000000000001007) - contract_8 = Address(0x0000000000000000000000000000000000001008) - contract_9 = Address(0x0000000000000000000000000000000000001009) - contract_10 = Address(0x000000000000000000000000000000000000100A) - contract_11 = Address(0x000000000000000000000000000000000000100B) - contract_12 = Address(0x000000000000000000000000000000000000100C) - contract_13 = Address(0x000000000000000000000000000000000000100D) - contract_14 = Address(0x000000000000000000000000000000000000100E) - contract_15 = Address(0x000000000000000000000000000000000000100F) - contract_16 = Address(0x0000000000000000000000000000000000001010) - contract_17 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = pre.fund_eoa(amount=0x100000000000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - # Source: lll - # { - # [[0]] 0x600D - # (asm 0x10 0x20 mul jump jumpdest) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMP(pc=Op.MUL(0x20, 0x10)) - + Op.JUMPDEST - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001000), # noqa: E501 - ) - # Source: lll - # { - # [[0]] 0x600D - # (asm 0x01 0x10 0x20 mul jumpi jumpdest) - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=Op.MUL(0x20, 0x10), condition=0x1) - + Op.JUMPDEST - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001001), # noqa: E501 - ) - # Source: raw - # 0x600456005B61600D60005500 - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0x4) - + Op.STOP - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=0x600D) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001002), # noqa: E501 - ) - # Source: lll - # { - # [[0]] 0x600D - # (asm 0x0fffffff jump) - # } - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMP(pc=0xFFFFFFF) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001003), # noqa: E501 - ) - # Source: raw - # 0x602360085660015b600255 - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x23] - + Op.JUMP(pc=0x8) - + Op.PUSH1[0x1] - + Op.JUMPDEST - + Op.PUSH1[0x2] - + Op.SSTORE, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001004), # noqa: E501 - ) - # Source: raw - # 0x61600D6000555B600656 - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) + Op.JUMPDEST + Op.JUMP(pc=0x6), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001005), # noqa: E501 - ) - # Source: raw - # 0x61600D60085660FF5B600055 - contract_6 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH2[0x600D] - + Op.JUMP(pc=0x8) - + Op.PUSH1[0xFF] - + Op.JUMPDEST - + Op.PUSH1[0x0] - + Op.SSTORE, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001006), # noqa: E501 - ) - # Source: raw - # 0x600B565B61600D600055005B600356 - contract_7 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0xB) - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=0x600D) - + Op.STOP - + Op.JUMPDEST - + Op.JUMP(pc=0x3), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001007), # noqa: E501 - ) - # Source: raw - # 0x600260050156005B61600D600055 - contract_8 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=Op.ADD(0x5, 0x2)) - + Op.STOP - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001008), # noqa: E501 - ) - # Source: raw - # 0x60055600605B61600D600055 - contract_9 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0x5) - + Op.STOP - + Op.PUSH1[0x5B] - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001009), # noqa: E501 - ) - # Source: raw - # 0x60055600600161600D600055 - contract_10 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0x5) - + Op.STOP - + Op.PUSH1[0x1] - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100A), # noqa: E501 - ) - # Source: raw - # 0x61600D600055600B565A5B5A600155 - contract_11 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMP(pc=0xB) - + Op.GAS - + Op.JUMPDEST - + Op.SSTORE(key=0x1, value=Op.GAS), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100B), # noqa: E501 - ) - # Source: raw - # 0x61600D6000556009565A5B5A600155 - contract_12 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMP(pc=0x9) - + Op.GAS - + Op.JUMPDEST - + Op.SSTORE(key=0x1, value=Op.GAS), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100C), # noqa: E501 - ) - # Source: raw - # 0x6801000000000000000b565b5b6001600155 - contract_13 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0x1000000000000000B) - + Op.JUMPDEST * 2 - + Op.SSTORE(key=0x1, value=0x1), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100D), # noqa: E501 - ) - # Source: raw - # 0x640100000007565b5b6001600155 - contract_14 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0x100000007) - + Op.JUMPDEST * 2 - + Op.SSTORE(key=0x1, value=0x1), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100E), # noqa: E501 - ) - # Source: lll - # { - # @0 (- 0 1) - # (asm 0 mload jump 0x600D 0x00 sstore) - # } - contract_15 = pre.deploy_contract( # noqa: F841 - code=Op.POP(Op.MLOAD(offset=0x0)) - + Op.POP(Op.SUB(0x0, 0x1)) - + Op.JUMP(pc=Op.MLOAD(offset=0x0)) - + Op.SSTORE(key=0x0, value=0x600D) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100F), # noqa: E501 - ) - # Source: raw - # 0x600E565B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B61600D600055 - contract_16 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0xE) - + Op.JUMPDEST * 16 - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001010), # noqa: E501 - ) - # Source: lll - # { - # ; limited gas because of the endless loop - # (delegatecall 0x10000 (+ 0x1000 $4) 0 0 0 0) - # } - contract_17 = pre.deploy_contract( # noqa: F841 - code=Op.DELEGATECALL( - gas=0x10000, - address=Op.ADD(0x1000, Op.CALLDATALOAD(offset=0x4)), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + """Run one JUMP scenario and check whether its store landed.""" + code: Bytecode + stores: bool + if case == "jump_hyperspace_computed_mul": + # Source: lll + # { [[0]] 0x600D (asm 0x10 0x20 mul jump jumpdest) } + code = ( + store_marker + + Op.JUMP(pc=Op.MUL(0x20, 0x10)) + + Op.JUMPDEST + + Op.STOP ) - + Op.STOP, - storage={0: 2989}, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": { - "data": [0, 1, 3, 4, 5, 9, 10, 11, 12, 13, 14, 15], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_17: Account(storage={0: 2989})}, - }, - { - "indexes": {"data": [2, 6, 7, 8, 16], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_17: Account(storage={0: 24589})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + stores = False + elif case == "jumpi_hyperspace_computed_mul": + # Source: lll + # { [[0]] 0x600D (asm 0x01 0x10 0x20 mul jumpi jumpdest) } + code = ( + store_marker + + Op.JUMPI(pc=Op.MUL(0x20, 0x10), condition=0x1) + + Op.JUMPDEST + + Op.STOP + ) + stores = False + elif case == "jump_over_stop": + # Source: raw + # 0x600456005B61600D60005500 + code = Op.JUMP(pc=0x4) + Op.STOP + Op.JUMPDEST + store_marker + Op.STOP + stores = True + elif case == "jump_hyperspace_0xfffffff": + # Source: lll + # { [[0]] 0x600D (asm 0x0fffffff jump) } + code = store_marker + Op.JUMP(pc=0xFFFFFFF) + Op.STOP + stores = False + elif case == "jump_to_push_opcode": + # Source: raw (jump lands on the PUSH1 opcode byte at pc 8) + # 0x602360085660015b600255 + code = ( + Op.PUSH1[0x23] + + Op.JUMP(pc=0x8) + + Op.PUSH1[0x1] + + Op.JUMPDEST + + Op.PUSH1[0x2] + + Op.SSTORE + ) + stores = False + elif case == "endless_loop": + # Source: raw + # 0x61600D6000555B600656 + code = store_marker + Op.JUMPDEST + Op.JUMP(pc=0x6) + stores = False + elif case == "jump_over_push_data": + # Source: raw + # 0x61600D60085660FF5B600055 + code = ( + Op.PUSH2[STORED] + + Op.JUMP(pc=0x8) + + Op.PUSH1[0xFF] + + Op.JUMPDEST + + Op.PUSH1[STORED_SLOT] + + Op.SSTORE + ) + stores = True + elif case == "jump_forward_and_back": + # Source: raw + # 0x600B565B61600D600055005B600356 + code = ( + Op.JUMP(pc=0xB) + + Op.JUMPDEST + + store_marker + + Op.STOP + + Op.JUMPDEST + + Op.JUMP(pc=0x3) + ) + stores = True + elif case == "jump_computed_add": + # Source: raw + # 0x600260050156005B61600D600055 + code = Op.JUMP(pc=Op.ADD(0x5, 0x2)) + Op.STOP + Op.JUMPDEST + code += store_marker + stores = True + elif case == "jump_to_push_data_jumpdest_byte": + # Source: raw (pc 5 is a 0x5B byte, but inside PUSH1's data) + # 0x60055600605B61600D600055 + code = Op.JUMP(pc=0x5) + Op.STOP + Op.PUSH1[0x5B] + store_marker + stores = False + elif case == "jump_to_push_data": + # Source: raw + # 0x60055600600161600D600055 + code = Op.JUMP(pc=0x5) + Op.STOP + Op.PUSH1[0x1] + store_marker + stores = False + elif case == "jump_to_gas_after_jumpdest": + # Source: raw (pc 0xB is a GAS opcode, one byte past the JUMPDEST) + # 0x61600D600055600B565A5B5A600155 + code = ( + store_marker + + Op.JUMP(pc=0xB) + + Op.GAS + + Op.JUMPDEST + + Op.SSTORE(key=0x1, value=Op.GAS) + ) + stores = False + elif case == "jump_to_gas_before_jumpdest": + # Source: raw (pc 0x9 is the GAS opcode before the JUMPDEST) + # 0x61600D6000556009565A5B5A600155 + code = ( + store_marker + + Op.JUMP(pc=0x9) + + Op.GAS + + Op.JUMPDEST + + Op.SSTORE(key=0x1, value=Op.GAS) + ) + stores = False + elif case == "jump_hyperspace_2pow64": + # Source: raw + # 0x6801000000000000000b565b5b6001600155 + code = ( + Op.JUMP(pc=0x1000000000000000B) + + Op.JUMPDEST * 2 + + Op.SSTORE(key=0x1, value=0x1) + ) + stores = False + elif case == "jump_hyperspace_2pow32": + # Source: raw + # 0x640100000007565b5b6001600155 + code = ( + Op.JUMP(pc=0x100000007) + + Op.JUMPDEST * 2 + + Op.SSTORE(key=0x1, value=0x1) + ) + stores = False + elif case == "jump_hyperspace_from_memory": + # Source: lll + # { @0 (- 0 1) (asm 0 mload jump 0x600D 0x00 sstore) } + code = ( + Op.MSTORE(offset=0x0, value=Op.SUB(0x0, 0x1)) + + Op.JUMP(pc=Op.MLOAD(offset=0x0)) + + store_marker + + Op.STOP + ) + stores = False + else: # jump_into_jumpdest_list + # Source: raw + # 0x600E565B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B61600D600055 + code = Op.JUMP(pc=0xE) + Op.JUMPDEST * 16 + store_marker + stores = True - tx_data = [ - Bytes("693c6139") + Hash(0x0), - Bytes("693c6139") + Hash(0x1), - Bytes("693c6139") + Hash(0x2), - Bytes("693c6139") + Hash(0x3), - Bytes("693c6139") + Hash(0x4), - Bytes("693c6139") + Hash(0x5), - Bytes("693c6139") + Hash(0x6), - Bytes("693c6139") + Hash(0x7), - Bytes("693c6139") + Hash(0x8), - Bytes("693c6139") + Hash(0x9), - Bytes("693c6139") + Hash(0xA), - Bytes("693c6139") + Hash(0xB), - Bytes("693c6139") + Hash(0xC), - Bytes("693c6139") + Hash(0xD), - Bytes("693c6139") + Hash(0xE), - Bytes("693c6139") + Hash(0xF), - Bytes("693c6139") + Hash(0x10), - ] - tx_gas = [16777216] - tx_value = [1] + contract = pre.deploy_contract( + code=code, + storage={STORED_SLOT: SENTINEL}, + ) tx = Transaction( - sender=sender, - to=contract_17, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + protected=fork.supports_protected_txs(), + sender=pre.fund_eoa(), + to=contract, + # The endless loop must run out of gas: give it a small bounded + # budget instead of a maxed-out one. + gas_limit=( + fork.transaction_intrinsic_cost_calculator()() + LOOP_GAS_BUDGET + if case == "endless_loop" + else None + ), ) - state_test(env=env, pre=pre, post=post, tx=tx) + post = { + contract: Account( + storage={STORED_SLOT: STORED if stores else SENTINEL}, + ), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/frontier/vmIOandFlowOperations/test_jump_to_push.py b/tests/ported_static/frontier/vmIOandFlowOperations/test_jump_to_push.py index f3168e3b044..f5ac5ddf2ea 100644 --- a/tests/ported_static/frontier/vmIOandFlowOperations/test_jump_to_push.py +++ b/tests/ported_static/frontier/vmIOandFlowOperations/test_jump_to_push.py @@ -1,1817 +1,129 @@ """ -Test_jump_to_push. +Verify jumpdest analysis around push data: a 0x5B byte inside any PUSH1.. +PUSH32 immediate is not a valid JUMP target, while the JUMPDEST right after +the push data is. Ported from: state_tests/VMTests/vmIOandFlowOperations/jumpToPushFiller.yml + +@manually-enhanced: Do not overwrite. The 95 per-size contracts and the +DELEGATECALL dispatcher (with its SUB(GAS, ...) forwarding pin and near-cap +gas limit) were folded into one generator over push size and jump target; +duplicated filler cases were dropped, the first-data-byte variants the +filler deployed but never called are now exercised, and the codeless-target +dispatch case keeps its own test with the call's success flag stored. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, - Hash, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +STORED_SLOT = 0x0 +STORED = 0x1 +SENTINEL = 0xBAD +FLAG_SLOT = 0x1 +# Explicit sub-call budget: asking for more gas than is available would +# abort the call on pre-EIP-150 forks, so the caller must not forward all. +DISPATCH_GAS = 100_000 + +store_marker = Op.SSTORE(key=STORED_SLOT, value=STORED) +# Layout: +PUSH_OPCODE_PC = len(bytes(store_marker)) + 3 + @pytest.mark.ported_from( ["state_tests/VMTests/vmIOandFlowOperations/jumpToPushFiller.yml"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Frontier") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 1, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 2, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 3, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 4, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 5, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 6, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 7, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 8, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 9, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 10, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 11, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 12, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 13, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 14, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 15, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 16, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 17, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 18, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 19, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 20, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 21, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 22, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 23, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 24, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 25, - 0, - 0, - id="jump-ok", - ), - pytest.param( - 26, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 27, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 28, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 29, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 30, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 31, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 32, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 33, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 34, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 35, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 36, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 37, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 38, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 39, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 40, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 41, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 42, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 43, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 44, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 45, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 46, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 47, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 48, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 49, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 50, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 51, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 52, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 53, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 54, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 55, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 56, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 57, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 58, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 59, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 60, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 61, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 62, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 63, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 64, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 65, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 66, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 67, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 68, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 69, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 70, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 71, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 72, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 73, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 74, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 75, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 76, - 0, - 0, - id="jump-fail", - ), - pytest.param( - 77, - 0, - 0, - id="jump-fail", - ), - ], + "target", ["jumpdest", "push_data_first", "push_data_last"] ) -@pytest.mark.pre_alloc_mutable +@pytest.mark.parametrize("push_size", range(1, 33), ids=lambda n: f"push{n}") def test_jump_to_push( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + push_size: int, + target: str, ) -> None: - """Test_jump_to_push.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x000000000000000000000000000000000000001A) - contract_1 = Address(0x000000000000000000000000000000000000001B) - contract_2 = Address(0x000000000000000000000000000000000000002A) - contract_3 = Address(0x000000000000000000000000000000000000002B) - contract_4 = Address(0x000000000000000000000000000000000000002C) - contract_5 = Address(0x000000000000000000000000000000000000003A) - contract_6 = Address(0x000000000000000000000000000000000000003B) - contract_7 = Address(0x000000000000000000000000000000000000003C) - contract_8 = Address(0x000000000000000000000000000000000000004A) - contract_9 = Address(0x000000000000000000000000000000000000004B) - contract_10 = Address(0x000000000000000000000000000000000000004C) - contract_11 = Address(0x000000000000000000000000000000000000005A) - contract_12 = Address(0x000000000000000000000000000000000000005B) - contract_13 = Address(0x000000000000000000000000000000000000005C) - contract_14 = Address(0x000000000000000000000000000000000000006A) - contract_15 = Address(0x000000000000000000000000000000000000006B) - contract_16 = Address(0x000000000000000000000000000000000000006C) - contract_17 = Address(0x000000000000000000000000000000000000007A) - contract_18 = Address(0x000000000000000000000000000000000000007B) - contract_19 = Address(0x000000000000000000000000000000000000007C) - contract_20 = Address(0x000000000000000000000000000000000000008A) - contract_21 = Address(0x000000000000000000000000000000000000008B) - contract_22 = Address(0x000000000000000000000000000000000000008C) - contract_23 = Address(0x000000000000000000000000000000000000009A) - contract_24 = Address(0x000000000000000000000000000000000000009B) - contract_25 = Address(0x000000000000000000000000000000000000009C) - contract_26 = Address(0x00000000000000000000000000000000000000AA) - contract_27 = Address(0x00000000000000000000000000000000000000AB) - contract_28 = Address(0x00000000000000000000000000000000000000AC) - contract_29 = Address(0x00000000000000000000000000000000000000BA) - contract_30 = Address(0x00000000000000000000000000000000000000BB) - contract_31 = Address(0x00000000000000000000000000000000000000BC) - contract_32 = Address(0x00000000000000000000000000000000000000CA) - contract_33 = Address(0x00000000000000000000000000000000000000CB) - contract_34 = Address(0x00000000000000000000000000000000000000CC) - contract_35 = Address(0x00000000000000000000000000000000000000DA) - contract_36 = Address(0x00000000000000000000000000000000000000DB) - contract_37 = Address(0x00000000000000000000000000000000000000DC) - contract_38 = Address(0x00000000000000000000000000000000000000EA) - contract_39 = Address(0x00000000000000000000000000000000000000EB) - contract_40 = Address(0x00000000000000000000000000000000000000EC) - contract_41 = Address(0x00000000000000000000000000000000000000FA) - contract_42 = Address(0x00000000000000000000000000000000000000FB) - contract_43 = Address(0x00000000000000000000000000000000000000FC) - contract_44 = Address(0x000000000000000000000000000000000000010A) - contract_45 = Address(0x000000000000000000000000000000000000010B) - contract_46 = Address(0x000000000000000000000000000000000000010C) - contract_47 = Address(0x000000000000000000000000000000000000011A) - contract_48 = Address(0x000000000000000000000000000000000000011B) - contract_49 = Address(0x000000000000000000000000000000000000011C) - contract_50 = Address(0x000000000000000000000000000000000000012A) - contract_51 = Address(0x000000000000000000000000000000000000012B) - contract_52 = Address(0x000000000000000000000000000000000000012C) - contract_53 = Address(0x000000000000000000000000000000000000013A) - contract_54 = Address(0x000000000000000000000000000000000000013B) - contract_55 = Address(0x000000000000000000000000000000000000013C) - contract_56 = Address(0x000000000000000000000000000000000000014A) - contract_57 = Address(0x000000000000000000000000000000000000014B) - contract_58 = Address(0x000000000000000000000000000000000000014C) - contract_59 = Address(0x000000000000000000000000000000000000015A) - contract_60 = Address(0x000000000000000000000000000000000000015B) - contract_61 = Address(0x000000000000000000000000000000000000015C) - contract_62 = Address(0x000000000000000000000000000000000000016A) - contract_63 = Address(0x000000000000000000000000000000000000016B) - contract_64 = Address(0x000000000000000000000000000000000000016C) - contract_65 = Address(0x000000000000000000000000000000000000017A) - contract_66 = Address(0x000000000000000000000000000000000000017B) - contract_67 = Address(0x000000000000000000000000000000000000017C) - contract_68 = Address(0x000000000000000000000000000000000000018A) - contract_69 = Address(0x000000000000000000000000000000000000018B) - contract_70 = Address(0x000000000000000000000000000000000000018C) - contract_71 = Address(0x000000000000000000000000000000000000019A) - contract_72 = Address(0x000000000000000000000000000000000000019B) - contract_73 = Address(0x000000000000000000000000000000000000019C) - contract_74 = Address(0x00000000000000000000000000000000000001AA) - contract_75 = Address(0x00000000000000000000000000000000000001AB) - contract_76 = Address(0x00000000000000000000000000000000000001AC) - contract_77 = Address(0x00000000000000000000000000000000000001BA) - contract_78 = Address(0x00000000000000000000000000000000000001BB) - contract_79 = Address(0x00000000000000000000000000000000000001BC) - contract_80 = Address(0x00000000000000000000000000000000000001CA) - contract_81 = Address(0x00000000000000000000000000000000000001CB) - contract_82 = Address(0x00000000000000000000000000000000000001CC) - contract_83 = Address(0x00000000000000000000000000000000000001DA) - contract_84 = Address(0x00000000000000000000000000000000000001DB) - contract_85 = Address(0x00000000000000000000000000000000000001DC) - contract_86 = Address(0x00000000000000000000000000000000000001EA) - contract_87 = Address(0x00000000000000000000000000000000000001EB) - contract_88 = Address(0x00000000000000000000000000000000000001EC) - contract_89 = Address(0x00000000000000000000000000000000000001FA) - contract_90 = Address(0x00000000000000000000000000000000000001FB) - contract_91 = Address(0x00000000000000000000000000000000000001FC) - contract_92 = Address(0x000000000000000000000000000000000000020A) - contract_93 = Address(0x000000000000000000000000000000000000020B) - contract_94 = Address(0x000000000000000000000000000000000000020C) - contract_95 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) + """Jump at, into, or past a push immediate made of 0x5B bytes.""" + if target == "jumpdest": + target_pc = PUSH_OPCODE_PC + push_size + 1 + stores = True + elif target == "push_data_first": + target_pc = PUSH_OPCODE_PC + 1 + stores = False + else: # push_data_last + target_pc = PUSH_OPCODE_PC + push_size + stores = False + assert target_pc <= 0xFF, "jump target must fit a PUSH1" - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - pre[sender] = Account(balance=0x100000000000) - # Source: raw - # 0x6001600055600B56615B5B5B - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xB) - + Op.PUSH2[0x5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000002A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956615B5B5B - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH2[0x5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000002B), # noqa: E501 - ) - # Source: raw - # 0x6001600055600A56615B5B5B - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xA) - + Op.PUSH2[0x5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000002C), # noqa: E501 - ) - # Source: raw - # 0x6001600055600C56625B5B5B5B - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xC) - + Op.PUSH3[0x5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000003A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956625B5B5B5B - contract_6 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH3[0x5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000003B), # noqa: E501 - ) - # Source: raw - # 0x6001600055600B56625B5B5B5B - contract_7 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xB) - + Op.PUSH3[0x5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000003C), # noqa: E501 - ) - # Source: raw - # 0x6001600055600D56635B5B5B5B5B - contract_8 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xD) - + Op.PUSH4[0x5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000004A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956635B5B5B5B5B - contract_9 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH4[0x5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000004B), # noqa: E501 - ) - # Source: raw - # 0x6001600055600C56635B5B5B5B5B - contract_10 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xC) - + Op.PUSH4[0x5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000004C), # noqa: E501 - ) - # Source: raw - # 0x6001600055600E56645B5B5B5B5B5B - contract_11 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xE) - + Op.PUSH5[0x5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000005A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956645B5B5B5B5B5B - contract_12 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH5[0x5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000005B), # noqa: E501 - ) - # Source: raw - # 0x6001600055600D56645B5B5B5B5B5B - contract_13 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xD) - + Op.PUSH5[0x5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000005C), # noqa: E501 - ) - # Source: raw - # 0x6001600055600F56655B5B5B5B5B5B5B - contract_14 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xF) - + Op.PUSH6[0x5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000006A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956655B5B5B5B5B5B5B - contract_15 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH6[0x5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000006B), # noqa: E501 + push_op = getattr(Op, f"PUSH{push_size}") + code = ( + store_marker + + Op.JUMP(pc=target_pc) + + push_op[int.from_bytes(b"\x5b" * push_size, byteorder="big")] + + Op.JUMPDEST ) - # Source: raw - # 0x6001600055600E56655B5B5B5B5B5B5B - contract_16 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xE) - + Op.PUSH6[0x5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000006C), # noqa: E501 + contract = pre.deploy_contract( + code=code, + storage={STORED_SLOT: SENTINEL}, ) - # Source: raw - # 0x6001600055601056665B5B5B5B5B5B5B5B - contract_17 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x10) - + Op.PUSH7[0x5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000007A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956665B5B5B5B5B5B5B5B - contract_18 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH7[0x5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000007B), # noqa: E501 - ) - # Source: raw - # 0x6001600055600F56665B5B5B5B5B5B5B5B - contract_19 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xF) - + Op.PUSH7[0x5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000007C), # noqa: E501 - ) - # Source: raw - # 0x6001600055601156675B5B5B5B5B5B5B5B5B - contract_20 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x11) - + Op.PUSH8[0x5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000008A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956675B5B5B5B5B5B5B5B5B - contract_21 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH8[0x5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000008B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601056675B5B5B5B5B5B5B5B5B - contract_22 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x10) - + Op.PUSH8[0x5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000008C), # noqa: E501 - ) - # Source: raw - # 0x6001600055601256685B5B5B5B5B5B5B5B5B5B - contract_23 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x12) - + Op.PUSH9[0x5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000009A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956685B5B5B5B5B5B5B5B5B5B - contract_24 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH9[0x5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000009B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601156685B5B5B5B5B5B5B5B5B5B - contract_25 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x11) - + Op.PUSH9[0x5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000009C), # noqa: E501 - ) - # Source: raw - # 0x6001600055601356695B5B5B5B5B5B5B5B5B5B5B - contract_26 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x13) - + Op.PUSH10[0x5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000AA), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956695B5B5B5B5B5B5B5B5B5B5B - contract_27 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH10[0x5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000AB), # noqa: E501 - ) - # Source: raw - # 0x6001600055601256695B5B5B5B5B5B5B5B5B5B5B - contract_28 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x12) - + Op.PUSH10[0x5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000AC), # noqa: E501 - ) - # Source: raw - # 0x60016000556014566A5B5B5B5B5B5B5B5B5B5B5B5B - contract_29 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x14) - + Op.PUSH11[0x5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000BA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009566A5B5B5B5B5B5B5B5B5B5B5B5B - contract_30 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH11[0x5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000BB), # noqa: E501 - ) - # Source: raw - # 0x60016000556013566A5B5B5B5B5B5B5B5B5B5B5B5B - contract_31 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x13) - + Op.PUSH11[0x5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000BC), # noqa: E501 - ) - # Source: raw - # 0x60016000556015566B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_32 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x15) - + Op.PUSH12[0x5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000CA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009566B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_33 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH12[0x5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000CB), # noqa: E501 - ) - # Source: raw - # 0x60016000556014566B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_34 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x14) - + Op.PUSH12[0x5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000CC), # noqa: E501 - ) - # Source: raw - # 0x60016000556016566C5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_35 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x16) - + Op.PUSH13[0x5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000DA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009566C5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_36 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH13[0x5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000DB), # noqa: E501 - ) - # Source: raw - # 0x60016000556015566C5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_37 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x15) - + Op.PUSH13[0x5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000DC), # noqa: E501 - ) - # Source: raw - # 0x60016000556017566D5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_38 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x17) - + Op.PUSH14[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000EA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009566D5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_39 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH14[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000EB), # noqa: E501 - ) - # Source: raw - # 0x60016000556016566D5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_40 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x16) - + Op.PUSH14[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000EC), # noqa: E501 - ) - # Source: raw - # 0x60016000556018566E5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_41 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x18) - + Op.PUSH15[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000FA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009566E5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_42 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH15[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000FB), # noqa: E501 - ) - # Source: raw - # 0x60016000556017566E5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_43 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x17) - + Op.PUSH15[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000000FC), # noqa: E501 - ) - # Source: raw - # 0x60016000556019566F5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_44 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x19) - + Op.PUSH16[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000010A), # noqa: E501 - ) - # Source: raw - # 0x60016000556009566F5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_45 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH16[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000010B), # noqa: E501 - ) - # Source: raw - # 0x60016000556018566F5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_46 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x18) - + Op.PUSH16[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000010C), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956705B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_48 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH17[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000011B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601956705B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_49 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x19) - + Op.PUSH17[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000011C), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956715B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_51 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH18[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000012B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601C56725B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_53 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1C) - + Op.PUSH19[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000013A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956725B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_54 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH19[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000013B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601D56735B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_56 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1D) - + Op.PUSH20[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000014A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956735B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_57 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH20[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000014B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601C56735B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_58 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1C) - + Op.PUSH20[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000014C), # noqa: E501 - ) - # Source: raw - # 0x6001600055601E56745B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_59 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1E) - + Op.PUSH21[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000015A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956745B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_60 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH21[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000015B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601D56745B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_61 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1D) - + Op.PUSH21[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000015C), # noqa: E501 - ) - # Source: raw - # 0x6001600055601F56755B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_62 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1F) - + Op.PUSH22[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000016A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956755B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_63 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH22[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000016B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601E56755B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_64 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1E) - + Op.PUSH22[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000016C), # noqa: E501 - ) - # Source: raw - # 0x6001600055602056765B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_65 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x20) - + Op.PUSH23[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000017A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956765B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_66 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH23[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000017B), # noqa: E501 - ) - # Source: raw - # 0x6001600055601F56765B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_67 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1F) - + Op.PUSH23[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000017C), # noqa: E501 - ) - # Source: raw - # 0x6001600055602156775B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_68 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x21) - + Op.PUSH24[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000018A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956775B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_69 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH24[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000018B), # noqa: E501 - ) - # Source: raw - # 0x6001600055602056775B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_70 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x20) - + Op.PUSH24[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000018C), # noqa: E501 - ) - # Source: raw - # 0x6001600055602256785B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_71 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x22) - + Op.PUSH25[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000019A), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956785B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_72 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH25[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000019B), # noqa: E501 - ) - # Source: raw - # 0x6001600055602156785B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_73 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x21) - + Op.PUSH25[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000019C), # noqa: E501 - ) - # Source: raw - # 0x6001600055602356795B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_74 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x23) - + Op.PUSH26[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001AA), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956795B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_75 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH26[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001AB), # noqa: E501 - ) - # Source: raw - # 0x6001600055602256795B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_76 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x22) - + Op.PUSH26[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001AC), # noqa: E501 - ) - # Source: raw - # 0x60016000556024567A5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_77 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x24) - + Op.PUSH27[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001BA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009567A5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_78 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH27[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001BB), # noqa: E501 - ) - # Source: raw - # 0x60016000556023567A5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_79 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x23) - + Op.PUSH27[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001BC), # noqa: E501 - ) - # Source: raw - # 0x60016000556025567B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_80 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x25) - + Op.PUSH28[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001CA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009567B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_81 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH28[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001CB), # noqa: E501 - ) - # Source: raw - # 0x60016000556024567B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_82 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x24) - + Op.PUSH28[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001CC), # noqa: E501 - ) - # Source: raw - # 0x60016000556026567C5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_83 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x26) - + Op.PUSH29[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001DA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009567C5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_84 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH29[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001DB), # noqa: E501 - ) - # Source: raw - # 0x60016000556025567C5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_85 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x25) - + Op.PUSH29[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001DC), # noqa: E501 - ) - # Source: raw - # 0x60016000556027567D5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_86 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x27) - + Op.PUSH30[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001EA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009567D5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_87 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH30[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001EB), # noqa: E501 - ) - # Source: raw - # 0x60016000556026567D5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_88 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x26) - + Op.PUSH30[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001EC), # noqa: E501 - ) - # Source: raw - # 0x60016000556028567E5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_89 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x28) - + Op.PUSH31[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001FA), # noqa: E501 - ) - # Source: raw - # 0x60016000556009567E5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_90 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH31[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001FB), # noqa: E501 - ) - # Source: raw - # 0x60016000556027567E5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_91 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x27) - + Op.PUSH31[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x00000000000000000000000000000000000001FC), # noqa: E501 - ) - # Source: raw - # 0x60016000556029567F5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_92 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x29) - + Op.PUSH32[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000020A), # noqa: E501 - ) - # Source: raw - # 0x60016000556009567F5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_93 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH32[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000020B), # noqa: E501 - ) - # Source: raw - # 0x60016000556028567F5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B # noqa: E501 - contract_94 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x28) - + Op.PUSH32[ - 0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - ] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000020C), # noqa: E501 - ) - # Source: yul - # berlin { - # let addr := calldataload(4) - # pop(delegatecall(sub(gas(), 5000), addr, 0, 0, 0, 0)) - # } - contract_95 = pre.deploy_contract( # noqa: F841 - code=Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x1388), - address=Op.CALLDATALOAD(offset=0x4), - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - + Op.STOP, - storage={0: 0}, - nonce=0, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 - ) - # Source: raw - # 0x6001600055600956605B5B - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x9) - + Op.PUSH1[0x5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000001B), # noqa: E501 - ) - # Source: raw - # 0x6001600055600A56605B5B - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0xA) - + Op.PUSH1[0x5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000001A), # noqa: E501 - ) - # Source: raw - # 0x6001600055601B56725B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_55 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1B) - + Op.PUSH19[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000013C), # noqa: E501 - ) - # Source: raw - # 0x6001600055601B56715B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_50 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1B) - + Op.PUSH18[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000012A), # noqa: E501 - ) - # Source: raw - # 0x6001600055601A56705B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_47 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1A) - + Op.PUSH17[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000011A), # noqa: E501 - ) - # Source: raw - # 0x6001600055601A56715B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B - contract_52 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.JUMP(pc=0x1A) - + Op.PUSH18[0x5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B] - + Op.JUMPDEST, - nonce=0, - address=Address(0x000000000000000000000000000000000000012C), # noqa: E501 + + tx = Transaction( + protected=fork.supports_protected_txs(), + sender=pre.fund_eoa(), + to=contract, ) - expect_entries_: list[dict] = [ - { - "indexes": { - "data": [ - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - ], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_95: Account(storage={0: 0})}, - }, - { - "indexes": { - "data": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - ], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_95: Account(storage={0: 1})}, - }, - ] + post = { + contract: Account( + storage={STORED_SLOT: STORED if stores else SENTINEL}, + ), + } + + state_test(pre=pre, post=post, tx=tx) - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - tx_data = [ - Bytes("693c6139") + Hash(contract_0, left_padding=True), - Bytes("693c6139") + Hash(contract_2, left_padding=True), - Bytes("693c6139") + Hash(contract_5, left_padding=True), - Bytes("693c6139") + Hash(contract_8, left_padding=True), - Bytes("693c6139") + Hash(contract_11, left_padding=True), - Bytes("693c6139") + Hash(contract_14, left_padding=True), - Bytes("693c6139") + Hash(contract_17, left_padding=True), - Bytes("693c6139") + Hash(contract_20, left_padding=True), - Bytes("693c6139") + Hash(contract_23, left_padding=True), - Bytes("693c6139") + Hash(contract_26, left_padding=True), - Bytes("693c6139") + Hash(contract_29, left_padding=True), - Bytes("693c6139") + Hash(contract_32, left_padding=True), - Bytes("693c6139") + Hash(contract_35, left_padding=True), - Bytes("693c6139") + Hash(contract_38, left_padding=True), - Bytes("693c6139") + Hash(contract_41, left_padding=True), - Bytes("693c6139") + Hash(contract_44, left_padding=True), - Bytes("693c6139") + Hash(contract_47, left_padding=True), - Bytes("693c6139") + Hash(contract_50, left_padding=True), - Bytes("693c6139") + Hash(contract_53, left_padding=True), - Bytes("693c6139") + Hash(contract_56, left_padding=True), - Bytes("693c6139") + Hash(contract_59, left_padding=True), - Bytes("693c6139") + Hash(contract_62, left_padding=True), - Bytes("693c6139") + Hash(contract_65, left_padding=True), - Bytes("693c6139") + Hash(contract_68, left_padding=True), - Bytes("693c6139") + Hash(contract_71, left_padding=True), - Bytes("693c6139") + Hash(contract_92, left_padding=True), - Bytes("693c6139") + Hash(0x1C), - Bytes("693c6139") + Hash(contract_4, left_padding=True), - Bytes("693c6139") + Hash(contract_7, left_padding=True), - Bytes("693c6139") + Hash(contract_10, left_padding=True), - Bytes("693c6139") + Hash(contract_13, left_padding=True), - Bytes("693c6139") + Hash(contract_16, left_padding=True), - Bytes("693c6139") + Hash(contract_19, left_padding=True), - Bytes("693c6139") + Hash(contract_22, left_padding=True), - Bytes("693c6139") + Hash(contract_25, left_padding=True), - Bytes("693c6139") + Hash(contract_28, left_padding=True), - Bytes("693c6139") + Hash(contract_31, left_padding=True), - Bytes("693c6139") + Hash(contract_34, left_padding=True), - Bytes("693c6139") + Hash(contract_37, left_padding=True), - Bytes("693c6139") + Hash(contract_40, left_padding=True), - Bytes("693c6139") + Hash(contract_43, left_padding=True), - Bytes("693c6139") + Hash(contract_46, left_padding=True), - Bytes("693c6139") + Hash(contract_49, left_padding=True), - Bytes("693c6139") + Hash(contract_52, left_padding=True), - Bytes("693c6139") + Hash(contract_55, left_padding=True), - Bytes("693c6139") + Hash(contract_58, left_padding=True), - Bytes("693c6139") + Hash(contract_61, left_padding=True), - Bytes("693c6139") + Hash(contract_64, left_padding=True), - Bytes("693c6139") + Hash(contract_67, left_padding=True), - Bytes("693c6139") + Hash(contract_70, left_padding=True), - Bytes("693c6139") + Hash(contract_73, left_padding=True), - Bytes("693c6139") + Hash(contract_94, left_padding=True), - Bytes("693c6139") + Hash(0x1C), - Bytes("693c6139") + Hash(contract_4, left_padding=True), - Bytes("693c6139") + Hash(contract_7, left_padding=True), - Bytes("693c6139") + Hash(contract_10, left_padding=True), - Bytes("693c6139") + Hash(contract_13, left_padding=True), - Bytes("693c6139") + Hash(contract_16, left_padding=True), - Bytes("693c6139") + Hash(contract_19, left_padding=True), - Bytes("693c6139") + Hash(contract_22, left_padding=True), - Bytes("693c6139") + Hash(contract_25, left_padding=True), - Bytes("693c6139") + Hash(contract_28, left_padding=True), - Bytes("693c6139") + Hash(contract_31, left_padding=True), - Bytes("693c6139") + Hash(contract_34, left_padding=True), - Bytes("693c6139") + Hash(contract_37, left_padding=True), - Bytes("693c6139") + Hash(contract_40, left_padding=True), - Bytes("693c6139") + Hash(contract_43, left_padding=True), - Bytes("693c6139") + Hash(contract_46, left_padding=True), - Bytes("693c6139") + Hash(contract_49, left_padding=True), - Bytes("693c6139") + Hash(contract_52, left_padding=True), - Bytes("693c6139") + Hash(contract_55, left_padding=True), - Bytes("693c6139") + Hash(contract_58, left_padding=True), - Bytes("693c6139") + Hash(contract_61, left_padding=True), - Bytes("693c6139") + Hash(contract_64, left_padding=True), - Bytes("693c6139") + Hash(contract_67, left_padding=True), - Bytes("693c6139") + Hash(contract_70, left_padding=True), - Bytes("693c6139") + Hash(contract_73, left_padding=True), - Bytes("693c6139") + Hash(contract_94, left_padding=True), - ] - tx_gas = [16777216] - tx_value = [1] +@pytest.mark.ported_from( + ["state_tests/VMTests/vmIOandFlowOperations/jumpToPushFiller.yml"], +) +@pytest.mark.valid_from("Homestead") +def test_jump_to_push_empty_target( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """The legacy dispatch to a codeless target succeeds, storing nothing.""" + target = pre.nonexistent_account() + caller = pre.deploy_contract( + code=Op.SSTORE( + key=FLAG_SLOT, + value=Op.DELEGATECALL(gas=DISPATCH_GAS, address=target), + ) + + Op.STOP, + storage={STORED_SLOT: SENTINEL}, + ) tx = Transaction( - sender=sender, - to=contract_95, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + protected=fork.supports_protected_txs(), + sender=pre.fund_eoa(), + to=caller, ) - state_test(env=env, pre=pre, post=post, tx=tx) + post = { + caller: Account( + storage={STORED_SLOT: SENTINEL, FLAG_SLOT: 1}, + ), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/frontier/vmIOandFlowOperations/test_jumpi.py b/tests/ported_static/frontier/vmIOandFlowOperations/test_jumpi.py index c2da7dde412..c4a4f62ae61 100644 --- a/tests/ported_static/frontier/vmIOandFlowOperations/test_jumpi.py +++ b/tests/ported_static/frontier/vmIOandFlowOperations/test_jumpi.py @@ -1,648 +1,339 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Verify JUMPI destination validation and conditional behavior: a taken jump +needs a JUMPDEST outside push data, and an untaken one falls through. Ported from: state_tests/VMTests/vmIOandFlowOperations/jumpiFiller.yml + +@manually-enhanced: Do not overwrite. The DELEGATECALL dispatcher (calldata +addressed, hardcoded addresses, near-cap gas limit) was dropped; each case +is called directly with a sentinel in the stored slot so a reverted frame +is distinguishable from a successful store. Two byte-identical duplicated +filler cases were folded, and the memory-sourced jump cases were restored +to their lll source's intent (jump to MLOAD of -1). """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, - Hash, + Bytecode, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +STORED_SLOT = 0x0 +# The value a successful path stores, and the sentinel it replaces. +STORED = 0x600D +SENTINEL = 0xBAD +# Budget above the intrinsic cost so the endless-loop case runs out of gas +# quickly instead of consuming a maxed-out transaction budget. +LOOP_GAS_BUDGET = 0x10000 + +store_marker = Op.SSTORE(key=STORED_SLOT, value=STORED) + @pytest.mark.ported_from( ["state_tests/VMTests/vmIOandFlowOperations/jumpiFiller.yml"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Frontier") @pytest.mark.parametrize( - "d, g, v", + "case", [ - pytest.param( - 0, - 0, - 0, - id="jump-hyperspace", - ), - pytest.param( - 1, - 0, - 0, - id="jump-hyperspace", - ), - pytest.param( - 2, - 0, - 0, - id="not-jump-hyperspace", - ), - pytest.param( - 3, - 0, - 0, - id="not-jump-hyperspace", - ), - pytest.param( - 4, - 0, - 0, - id="jump-stop-dest", - ), - pytest.param( - 5, - 0, - 0, - id="not-jump-stop-dest", - ), - pytest.param( - 6, - 0, - 0, - id="jump-hyperspace", - ), - pytest.param( - 7, - 0, - 0, - id="not-jump-hyperspace", - ), - pytest.param( - 8, - 0, - 0, - id="jump-not-jumpdest", - ), - pytest.param( - 9, - 0, - 0, - id="endless-loop", - ), - pytest.param( - 10, - 0, - 0, - id="jump-dest", - ), - pytest.param( - 11, - 0, - 0, - id="jump-dest", - ), - pytest.param( - 12, - 0, - 0, - id="jump-dynamic", - ), - pytest.param( - 13, - 0, - 0, - id="not-jump-dynamic", - ), - pytest.param( - 14, - 0, - 0, - id="jump-2-push", - ), - pytest.param( - 15, - 0, - 0, - id="jump-2-push", - ), - pytest.param( - 16, - 0, - 0, - id="jump-not-jumpdest", - ), - pytest.param( - 17, - 0, - 0, - id="jump-not-jumpdest", - ), - pytest.param( - 18, - 0, - 0, - id="jump-hyperspace", - ), - pytest.param( - 19, - 0, - 0, - id="not-jump-hyperspace", - ), - pytest.param( - 20, - 0, - 0, - id="jump-hyperspace", - ), - pytest.param( - 21, - 0, - 0, - id="not-jump-hyperspace", - ), - pytest.param( - 22, - 0, - 0, - id="jump-hyperspace", - ), - pytest.param( - 23, - 0, - 0, - id="not-jump-hyperspace", - ), - pytest.param( - 24, - 0, - 0, - id="jump-to-list", - ), - pytest.param( - 25, - 0, - 0, - id="loop", - ), + "jumpi_hyperspace_computed_mul", + "jumpi_not_taken_hyperspace_computed_mul", + "jumpi_over_stop", + "jumpi_not_taken_to_stop", + "jumpi_hyperspace_0xfffffff", + "jumpi_not_taken_hyperspace", + "jumpi_to_push_opcode", + "endless_loop", + "jumpi_over_push_data", + "jump_then_jumpi_back", + "jumpi_computed_add", + "jumpi_not_taken_computed_add", + "jumpi_to_push_data_jumpdest_byte", + "jumpi_to_push_data", + "jumpi_to_gas_after_jumpdest", + "jumpi_to_gas_before_jumpdest", + "jumpi_hyperspace_2pow64", + "jumpi_not_taken_2pow64", + "jumpi_hyperspace_2pow32", + "jumpi_not_taken_2pow32", + "jumpi_hyperspace_from_memory", + "jumpi_not_taken_from_memory", + "jumpi_into_jumpdest_list", + "countdown_loop", ], ) -@pytest.mark.pre_alloc_mutable def test_jumpi( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + case: str, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000001000) - contract_1 = Address(0x0000000000000000000000000000000000001001) - contract_2 = Address(0x0000000000000000000000000000000000000200) - contract_3 = Address(0x0000000000000000000000000000000000000201) - contract_4 = Address(0x0000000000000000000000000000000000001002) - contract_5 = Address(0x0000000000000000000000000000000000000202) - contract_6 = Address(0x0000000000000000000000000000000000001003) - contract_7 = Address(0x0000000000000000000000000000000000000203) - contract_8 = Address(0x0000000000000000000000000000000000001004) - contract_9 = Address(0x0000000000000000000000000000000000001005) - contract_10 = Address(0x0000000000000000000000000000000000001006) - contract_11 = Address(0x0000000000000000000000000000000000001007) - contract_12 = Address(0x0000000000000000000000000000000000001008) - contract_13 = Address(0x0000000000000000000000000000000000000208) - contract_14 = Address(0x0000000000000000000000000000000000001009) - contract_15 = Address(0x000000000000000000000000000000000000100A) - contract_16 = Address(0x000000000000000000000000000000000000100B) - contract_17 = Address(0x000000000000000000000000000000000000100C) - contract_18 = Address(0x000000000000000000000000000000000000100D) - contract_19 = Address(0x000000000000000000000000000000000000020D) - contract_20 = Address(0x000000000000000000000000000000000000100E) - contract_21 = Address(0x000000000000000000000000000000000000020E) - contract_22 = Address(0x000000000000000000000000000000000000100F) - contract_23 = Address(0x000000000000000000000000000000000000020F) - contract_24 = Address(0x0000000000000000000000000000000000000110) - contract_25 = Address(0x0000000000000000000000000000000000000111) - contract_26 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - pre[sender] = Account(balance=0x100000000000) - # Source: lll - # { - # [[0]] 0x600D - # (asm 0x01 0x10 0x20 mul jumpi jumpdest) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=Op.MUL(0x20, 0x10), condition=0x1) - + Op.JUMPDEST - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001000), # noqa: E501 - ) - # Source: lll - # { - # [[0]] 0x600D - # (asm 0x01 0x10 0x20 mul jumpi jumpdest) - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=Op.MUL(0x20, 0x10), condition=0x1) - + Op.JUMPDEST - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001001), # noqa: E501 - ) - # Source: lll - # { - # [[0]] 0x600D - # (asm 0x00 0x10 0x20 mul jumpi jumpdest) - # } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=Op.MUL(0x20, 0x10), condition=0x0) - + Op.JUMPDEST - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000000200), # noqa: E501 - ) - # Source: lll - # { - # [[0]] 0x600D - # (asm 0x00 0x10 0x20 mul jumpi jumpdest) - # } - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=Op.MUL(0x20, 0x10), condition=0x0) - + Op.JUMPDEST - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000000201), # noqa: E501 - ) - # Source: raw - # 0x6001600657005B61600D60005500 - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x6, condition=0x1) - + Op.STOP - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=0x600D) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001002), # noqa: E501 - ) - # Source: raw - # 0x6000600657005B61600D60005500 - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x6, condition=0x0) - + Op.STOP - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=0x600D) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000000202), # noqa: E501 - ) - # Source: lll - # { - # [[0]] 0x600D - # (asm 0xff 0x0fffffff jumpi) - # } - contract_6 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=0xFFFFFFF, condition=0xFF) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001003), # noqa: E501 - ) - # Source: lll - # { - # [[0]] 0x600D - # (asm 0x00 0x0fffffff jumpi) - # } - contract_7 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=0xFFFFFFF, condition=0x0) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000000203), # noqa: E501 - ) - # Source: raw - # 0x6023600160085760015b600255 - contract_8 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x23] - + Op.JUMPI(pc=0x8, condition=0x1) - + Op.PUSH1[0x1] - + Op.JUMPDEST - + Op.PUSH1[0x2] - + Op.SSTORE, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001004), # noqa: E501 - ) - # Source: raw - # 0x61600D6000555B6006600657 - contract_9 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPDEST - + Op.JUMPI(pc=0x6, condition=0x6), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001005), # noqa: E501 - ) - # Source: raw - # 0x61600D6001600A5760FF5B600055 - contract_10 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH2[0x600D] - + Op.JUMPI(pc=0xA, condition=0x1) - + Op.PUSH1[0xFF] - + Op.JUMPDEST - + Op.PUSH1[0x0] - + Op.SSTORE, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001006), # noqa: E501 - ) - # Source: raw - # 0x600B565B61600D600055005B6001600357 - contract_11 = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0xB) - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=0x600D) - + Op.STOP - + Op.JUMPDEST - + Op.JUMPI(pc=0x3, condition=0x1), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001007), # noqa: E501 - ) - # Source: raw - # 0x6001600460050157005B61600D600055 - contract_12 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=Op.ADD(0x5, 0x4), condition=0x1) - + Op.STOP - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001008), # noqa: E501 - ) - # Source: raw - # 0x6000600460050157005B61600D600055 - contract_13 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=Op.ADD(0x5, 0x4), condition=0x0) - + Op.STOP - + Op.JUMPDEST - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000000208), # noqa: E501 - ) - # Source: raw - # 0x600160075700605B61600D600055 - contract_14 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x7, condition=0x1) - + Op.STOP - + Op.PUSH1[0x5B] - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001009), # noqa: E501 - ) - # Source: raw - # 0x600160075700600161600D600055 - contract_15 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x7, condition=0x1) - + Op.STOP - + Op.PUSH1[0x1] - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100A), # noqa: E501 - ) - # Source: raw - # 0x61600D6000556001600D575A5B5A600155 - contract_16 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=0xD, condition=0x1) - + Op.GAS - + Op.JUMPDEST - + Op.SSTORE(key=0x1, value=Op.GAS), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100B), # noqa: E501 - ) - # Source: raw - # 0x61600D6000556001600B575A5B5A600155 - contract_17 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.JUMPI(pc=0xB, condition=0x1) - + Op.GAS - + Op.JUMPDEST - + Op.SSTORE(key=0x1, value=Op.GAS), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100C), # noqa: E501 - ) - # Source: raw - # 0x60116801000000000000000D575b5b61600D600055 - contract_18 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x1000000000000000D, condition=0x11) - + Op.JUMPDEST * 2 - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100D), # noqa: E501 - ) - # Source: raw - # 0x60006801000000000000000D575b5b61600D600055 - contract_19 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x1000000000000000D, condition=0x0) - + Op.JUMPDEST * 2 - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000020D), # noqa: E501 - ) - # Source: raw - # 0x6011640100000009575b5b61600D600055 - contract_20 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x100000009, condition=0x11) - + Op.JUMPDEST * 2 - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100E), # noqa: E501 - ) - # Source: raw - # 0x6000640100000009575b5b61600D600055 - contract_21 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x100000009, condition=0x0) - + Op.JUMPDEST * 2 - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000020E), # noqa: E501 - ) - # Source: lll - # { - # @0 (- 0 1) - # (asm 1 0 mload jumpi 0x600D 0x00 sstore) - # } - contract_22 = pre.deploy_contract( # noqa: F841 - code=Op.POP(Op.MLOAD(offset=0x0)) - + Op.POP(Op.SUB(0x0, 0x1)) - + Op.JUMPI(pc=Op.MLOAD(offset=0x0), condition=0x1) - + Op.SSTORE(key=0x0, value=0x600D) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000100F), # noqa: E501 - ) - # Source: lll - # { - # @0 (- 0 1) - # (asm 0 0 mload jumpi 0x600D 0x00 sstore) - # } - contract_23 = pre.deploy_contract( # noqa: F841 - code=Op.POP(Op.MLOAD(offset=0x0)) - + Op.POP(Op.SUB(0x0, 0x1)) - + Op.JUMPI(pc=Op.MLOAD(offset=0x0), condition=0x0) - + Op.SSTORE(key=0x0, value=0x600D) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x000000000000000000000000000000000000020F), # noqa: E501 - ) - # Source: raw - # 0x6001600E575B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B61600D600055 - contract_24 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0xE, condition=0x1) - + Op.JUMPDEST * 16 - + Op.SSTORE(key=0x0, value=0x600D), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000000110), # noqa: E501 - ) - # Source: raw - # 0x61600D60005560106000525B60016000510380600052600B57 - contract_25 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x600D) - + Op.MSTORE(offset=0x0, value=0x10) - + Op.JUMPDEST - + Op.SUB(Op.MLOAD(offset=0x0), 0x1) - + Op.MSTORE(offset=0x0, value=Op.DUP1) - + Op.PUSH1[0xB] - + Op.JUMPI, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000000111), # noqa: E501 - ) - # Source: lll - # { - # ; limited gas because of the endless loop - # (delegatecall 0x10000 $4 0 0 0 0) - # } - contract_26 = pre.deploy_contract( # noqa: F841 - code=Op.DELEGATECALL( - gas=0x10000, - address=Op.CALLDATALOAD(offset=0x4), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + """Run one JUMPI scenario and check whether its store landed.""" + code: Bytecode + stores: bool + if case == "jumpi_hyperspace_computed_mul": + # Source: lll + # { [[0]] 0x600D (asm 0x01 0x10 0x20 mul jumpi jumpdest) } + code = ( + store_marker + + Op.JUMPI(pc=Op.MUL(0x20, 0x10), condition=0x1) + + Op.JUMPDEST + + Op.STOP ) - + Op.STOP, - storage={0: 2989}, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": { - "data": [0, 1, 5, 6, 8, 9, 13, 14, 15, 16, 17, 18, 20, 22], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_26: Account(storage={0: 2989})}, - }, - { - "indexes": { - "data": [2, 3, 4, 7, 10, 11, 12, 19, 21, 23, 24, 25], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_26: Account(storage={0: 24589})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + stores = False + elif case == "jumpi_not_taken_hyperspace_computed_mul": + # Source: lll + # { [[0]] 0x600D (asm 0x00 0x10 0x20 mul jumpi jumpdest) } + code = ( + store_marker + + Op.JUMPI(pc=Op.MUL(0x20, 0x10), condition=0x0) + + Op.JUMPDEST + + Op.STOP + ) + stores = True + elif case == "jumpi_over_stop": + # Source: raw + # 0x6001600657005B61600D60005500 + code = ( + Op.JUMPI(pc=0x6, condition=0x1) + + Op.STOP + + Op.JUMPDEST + + store_marker + + Op.STOP + ) + stores = True + elif case == "jumpi_not_taken_to_stop": + # Source: raw (the untaken jump falls through to STOP; no store) + # 0x6000600657005B61600D60005500 + code = ( + Op.JUMPI(pc=0x6, condition=0x0) + + Op.STOP + + Op.JUMPDEST + + store_marker + + Op.STOP + ) + stores = False + elif case == "jumpi_hyperspace_0xfffffff": + # Source: lll + # { [[0]] 0x600D (asm 0xff 0x0fffffff jumpi) } + code = store_marker + Op.JUMPI(pc=0xFFFFFFF, condition=0xFF) + Op.STOP + stores = False + elif case == "jumpi_not_taken_hyperspace": + # Source: lll + # { [[0]] 0x600D (asm 0x00 0x0fffffff jumpi) } + code = store_marker + Op.JUMPI(pc=0xFFFFFFF, condition=0x0) + Op.STOP + stores = True + elif case == "jumpi_to_push_opcode": + # Source: raw (jump lands on a PUSH1 opcode byte) + # 0x6023600160085760015b600255 + code = ( + Op.PUSH1[0x23] + + Op.JUMPI(pc=0x8, condition=0x1) + + Op.PUSH1[0x1] + + Op.JUMPDEST + + Op.PUSH1[0x2] + + Op.SSTORE + ) + stores = False + elif case == "endless_loop": + # Source: raw + # 0x61600D6000555B6006600657 + code = store_marker + Op.JUMPDEST + Op.JUMPI(pc=0x6, condition=0x6) + stores = False + elif case == "jumpi_over_push_data": + # Source: raw + # 0x61600D6001600A5760FF5B600055 + code = ( + Op.PUSH2[STORED] + + Op.JUMPI(pc=0xA, condition=0x1) + + Op.PUSH1[0xFF] + + Op.JUMPDEST + + Op.PUSH1[STORED_SLOT] + + Op.SSTORE + ) + stores = True + elif case == "jump_then_jumpi_back": + # Source: raw + # 0x600B565B61600D600055005B6001600357 + code = ( + Op.JUMP(pc=0xB) + + Op.JUMPDEST + + store_marker + + Op.STOP + + Op.JUMPDEST + + Op.JUMPI(pc=0x3, condition=0x1) + ) + stores = True + elif case == "jumpi_computed_add": + # Source: raw + # 0x6001600460050157005B61600D600055 + code = ( + Op.JUMPI(pc=Op.ADD(0x5, 0x4), condition=0x1) + + Op.STOP + + Op.JUMPDEST + + store_marker + ) + stores = True + elif case == "jumpi_not_taken_computed_add": + # Source: raw (the untaken jump falls through to STOP; no store) + # 0x6000600460050157005B61600D600055 + code = ( + Op.JUMPI(pc=Op.ADD(0x5, 0x4), condition=0x0) + + Op.STOP + + Op.JUMPDEST + + store_marker + ) + stores = False + elif case == "jumpi_to_push_data_jumpdest_byte": + # Source: raw (pc 7 is a 0x5B byte, but inside PUSH1's data) + # 0x600160075700605B61600D600055 + code = ( + Op.JUMPI(pc=0x7, condition=0x1) + + Op.STOP + + Op.PUSH1[0x5B] + + store_marker + ) + stores = False + elif case == "jumpi_to_push_data": + # Source: raw + # 0x600160075700600161600D600055 + code = ( + Op.JUMPI(pc=0x7, condition=0x1) + + Op.STOP + + Op.PUSH1[0x1] + + store_marker + ) + stores = False + elif case == "jumpi_to_gas_after_jumpdest": + # Source: raw (pc 0xD is a GAS opcode, one byte past the JUMPDEST) + # 0x61600D6000556001600D575A5B5A600155 + code = ( + store_marker + + Op.JUMPI(pc=0xD, condition=0x1) + + Op.GAS + + Op.JUMPDEST + + Op.SSTORE(key=0x1, value=Op.GAS) + ) + stores = False + elif case == "jumpi_to_gas_before_jumpdest": + # Source: raw (pc 0xB is the GAS opcode before the JUMPDEST) + # 0x61600D6000556001600B575A5B5A600155 + code = ( + store_marker + + Op.JUMPI(pc=0xB, condition=0x1) + + Op.GAS + + Op.JUMPDEST + + Op.SSTORE(key=0x1, value=Op.GAS) + ) + stores = False + elif case == "jumpi_hyperspace_2pow64": + # Source: raw + # 0x60116801000000000000000D575b5b61600D600055 + code = ( + Op.JUMPI(pc=0x1000000000000000D, condition=0x11) + + Op.JUMPDEST * 2 + + store_marker + ) + stores = False + elif case == "jumpi_not_taken_2pow64": + # Source: raw + # 0x60006801000000000000000D575b5b61600D600055 + code = ( + Op.JUMPI(pc=0x1000000000000000D, condition=0x0) + + Op.JUMPDEST * 2 + + store_marker + ) + stores = True + elif case == "jumpi_hyperspace_2pow32": + # Source: raw + # 0x6011640100000009575b5b61600D600055 + code = ( + Op.JUMPI(pc=0x100000009, condition=0x11) + + Op.JUMPDEST * 2 + + store_marker + ) + stores = False + elif case == "jumpi_not_taken_2pow32": + # Source: raw + # 0x6000640100000009575b5b61600D600055 + code = ( + Op.JUMPI(pc=0x100000009, condition=0x0) + + Op.JUMPDEST * 2 + + store_marker + ) + stores = True + elif case == "jumpi_hyperspace_from_memory": + # Source: lll + # { @0 (- 0 1) (asm 1 0 mload jumpi 0x600D 0x00 sstore) } + code = ( + Op.MSTORE(offset=0x0, value=Op.SUB(0x0, 0x1)) + + Op.JUMPI(pc=Op.MLOAD(offset=0x0), condition=0x1) + + store_marker + + Op.STOP + ) + stores = False + elif case == "jumpi_not_taken_from_memory": + # Source: lll + # { @0 (- 0 1) (asm 0 0 mload jumpi 0x600D 0x00 sstore) } + code = ( + Op.MSTORE(offset=0x0, value=Op.SUB(0x0, 0x1)) + + Op.JUMPI(pc=Op.MLOAD(offset=0x0), condition=0x0) + + store_marker + + Op.STOP + ) + stores = True + elif case == "jumpi_into_jumpdest_list": + # Source: raw + # 0x6001600E575B5B5B5B5B5B5B5B5B5B5B5B5B5B5B5B61600D600055 + code = ( + Op.JUMPI(pc=0xE, condition=0x1) + Op.JUMPDEST * 16 + store_marker + ) + stores = True + else: # countdown_loop + # Source: raw (counts memory word 0 down from 0x10 to zero) + # 0x61600D60005560106000525B60016000510380600052600B57 + code = ( + store_marker + + Op.MSTORE(offset=0x0, value=0x10) + + Op.JUMPDEST + + Op.SUB(Op.MLOAD(offset=0x0), 0x1) + + Op.MSTORE(offset=0x0, value=Op.DUP1) + + Op.PUSH1[0xB] + + Op.JUMPI + ) + stores = True - tx_data = [ - Bytes("693c6139") + Hash(contract_0, left_padding=True), - Bytes("693c6139") + Hash(contract_1, left_padding=True), - Bytes("693c6139") + Hash(contract_2, left_padding=True), - Bytes("693c6139") + Hash(contract_3, left_padding=True), - Bytes("693c6139") + Hash(contract_4, left_padding=True), - Bytes("693c6139") + Hash(contract_5, left_padding=True), - Bytes("693c6139") + Hash(contract_6, left_padding=True), - Bytes("693c6139") + Hash(contract_7, left_padding=True), - Bytes("693c6139") + Hash(contract_8, left_padding=True), - Bytes("693c6139") + Hash(contract_9, left_padding=True), - Bytes("693c6139") + Hash(contract_10, left_padding=True), - Bytes("693c6139") + Hash(contract_11, left_padding=True), - Bytes("693c6139") + Hash(contract_12, left_padding=True), - Bytes("693c6139") + Hash(contract_13, left_padding=True), - Bytes("693c6139") + Hash(contract_14, left_padding=True), - Bytes("693c6139") + Hash(contract_15, left_padding=True), - Bytes("693c6139") + Hash(contract_16, left_padding=True), - Bytes("693c6139") + Hash(contract_17, left_padding=True), - Bytes("693c6139") + Hash(contract_18, left_padding=True), - Bytes("693c6139") + Hash(contract_19, left_padding=True), - Bytes("693c6139") + Hash(contract_20, left_padding=True), - Bytes("693c6139") + Hash(contract_21, left_padding=True), - Bytes("693c6139") + Hash(contract_22, left_padding=True), - Bytes("693c6139") + Hash(contract_23, left_padding=True), - Bytes("693c6139") + Hash(contract_24, left_padding=True), - Bytes("693c6139") + Hash(contract_25, left_padding=True), - ] - tx_gas = [16777216] - tx_value = [1] + contract = pre.deploy_contract( + code=code, + storage={STORED_SLOT: SENTINEL}, + ) tx = Transaction( - sender=sender, - to=contract_26, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + protected=fork.supports_protected_txs(), + sender=pre.fund_eoa(), + to=contract, + # The endless loop must run out of gas: give it a small bounded + # budget instead of a maxed-out one. + gas_limit=( + fork.transaction_intrinsic_cost_calculator()() + LOOP_GAS_BUDGET + if case == "endless_loop" + else None + ), ) - state_test(env=env, pre=pre, post=post, tx=tx) + post = { + contract: Account( + storage={STORED_SLOT: STORED if stores else SENTINEL}, + ), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/frontier/vmTests/test_block_info.py b/tests/ported_static/frontier/vmTests/test_block_info.py index 57fc33cc47d..c93cffc7939 100644 --- a/tests/ported_static/frontier/vmTests/test_block_info.py +++ b/tests/ported_static/frontier/vmTests/test_block_info.py @@ -1,215 +1,84 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Verify block-field opcodes store the block environment's values. Ported from: state_tests/VMTests/vmTests/blockInfoFiller.yml + +@manually-enhanced: Do not overwrite. The CALL dispatcher, hardcoded +addresses, and the near-cap transaction gas limit were dropped; each +opcode's expectation now derives from the environment fields. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, Environment, - Hash, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +BLOCK_NUMBER = 1 +TIMESTAMP = 1000 +PREV_RANDAO = 0x20000 +BLOCK_GAS_LIMIT = 100_000_000 +VALUE_SLOT = 0x0 + @pytest.mark.ported_from( ["state_tests/VMTests/vmTests/blockInfoFiller.yml"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Frontier") @pytest.mark.parametrize( - "d, g, v", + "opcode", [ - pytest.param( - 0, - 0, - 0, - id="coinbase", - ), - pytest.param( - 1, - 0, - 0, - id="difficulty", - ), - pytest.param( - 2, - 0, - 0, - id="gaslimit", - ), - pytest.param( - 3, - 0, - 0, - id="number", - ), - pytest.param( - 4, - 0, - 0, - id="timestamp", - ), + pytest.param(Op.COINBASE, id="coinbase"), + pytest.param(Op.PREVRANDAO, id="difficulty"), + pytest.param(Op.GASLIMIT, id="gaslimit"), + pytest.param(Op.NUMBER, id="number"), + pytest.param(Op.TIMESTAMP, id="timestamp"), ], ) -@pytest.mark.pre_alloc_mutable def test_block_info( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + opcode: Op, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000001000) - contract_1 = Address(0x0000000000000000000000000000000000001001) - contract_2 = Address(0x0000000000000000000000000000000000001002) - contract_3 = Address(0x0000000000000000000000000000000000001003) - contract_4 = Address(0x0000000000000000000000000000000000001004) - contract_5 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = pre.fund_eoa(amount=0xBA1A9CE0BA1A9CE) + """Store one block-field opcode's result and match it to the env.""" + coinbase = pre.fund_eoa(amount=0) env = Environment( fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, + number=BLOCK_NUMBER, + timestamp=TIMESTAMP, + prev_randao=PREV_RANDAO, + gas_limit=BLOCK_GAS_LIMIT, ) - # Source: lll - # { - # [[0]] (coinbase) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.COINBASE) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001000), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (difficulty) - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.PREVRANDAO) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001001), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (gaslimit) - # } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GASLIMIT) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001002), # noqa: E501 - ) - # Source: lll - # { ; The blocknumber - # [[0]] (number) - # } - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.NUMBER) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001003), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (timestamp) - # } - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.TIMESTAMP) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001004), # noqa: E501 + contract = pre.deploy_contract( + code=Op.SSTORE(key=VALUE_SLOT, value=opcode) + Op.STOP, ) - # Source: lll - # { - # (call 0xffffff (+ 0x1000 $4) 0 0 0 0 0) - # } - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=0xFFFFFF, - address=Op.ADD(0x1000, Op.CALLDATALOAD(offset=0x4)), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: coinbase})}, - }, - { - "indexes": {"data": [1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_1: Account(storage={0: 0x20000})}, - }, - { - "indexes": {"data": [2], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_2: Account(storage={0: 0x5F5E100})}, - }, - { - "indexes": {"data": [3], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 1})}, - }, - { - "indexes": {"data": [4], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_4: Account(storage={0: 1000})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes("693c6139") + Hash(0x0), - Bytes("693c6139") + Hash(0x1), - Bytes("693c6139") + Hash(0x2), - Bytes("693c6139") + Hash(0x3), - Bytes("693c6139") + Hash(0x4), - ] - tx_gas = [16777216] - tx_value = [1] tx = Transaction( - sender=sender, - to=contract_5, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + protected=fork.supports_protected_txs(), + sender=pre.fund_eoa(), + to=contract, ) + expected = { + Op.COINBASE: coinbase, + Op.PREVRANDAO: PREV_RANDAO, + Op.GASLIMIT: BLOCK_GAS_LIMIT, + Op.NUMBER: BLOCK_NUMBER, + Op.TIMESTAMP: TIMESTAMP, + }[opcode] + + post = {contract: Account(storage={VALUE_SLOT: expected})} + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/frontier/vmTests/test_env_info.py b/tests/ported_static/frontier/vmTests/test_env_info.py index 67c2e62f6d6..324c2832a7f 100644 --- a/tests/ported_static/frontier/vmTests/test_env_info.py +++ b/tests/ported_static/frontier/vmTests/test_env_info.py @@ -1,360 +1,146 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Verify environment-info opcodes as seen by a called contract. Ported from: state_tests/VMTests/vmTests/envInfoFiller.yml + +@manually-enhanced: Do not overwrite. Hardcoded addresses and the +calldata-indexed dispatch were dropped; each case deploys its own target +and caller pair and derives the expectation (own code bytes, code size, +caller address, gas price) instead of pinning constants. Zero-result +CODECOPY and CALLDATASIZE cases store result + 1 so a zero read is +distinguishable from a skipped store. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, + Bytecode, Bytes, - Environment, + Fork, Hash, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +VALUE_SLOT = 0x0 +CALL_VALUE = 0x10 +GAS_PRICE = 0x1234 +# Explicit sub-call budget: asking for more gas than is available would +# abort the CALL on pre-EIP-150 forks, so the caller must not forward all. +DISPATCH_GAS = 100_000 +CODECOPY_SIZE = 0x7 +# Offset so high it would wrap around if treated as a signed/modular +# index (-6 mod 2**256). +HUGE_OFFSET = 2**256 - 6 + @pytest.mark.ported_from( ["state_tests/VMTests/vmTests/envInfoFiller.yml"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Frontier") @pytest.mark.parametrize( - "d, g, v", + "case", [ - pytest.param( - 0, - 0, - 0, - id="address", - ), - pytest.param( - 1, - 0, - 0, - id="codecopy", - ), - pytest.param( - 2, - 0, - 0, - id="codecopy_len0", - ), - pytest.param( - 3, - 0, - 0, - id="codecopy_neg_offset", - ), - pytest.param( - 4, - 0, - 0, - id="caller", - ), - pytest.param( - 5, - 0, - 0, - id="callvalue", - ), - pytest.param( - 6, - 0, - 0, - id="codesize", - ), - pytest.param( - 7, - 0, - 0, - id="gasprice", - ), - pytest.param( - 8, - 0, - 0, - id="origin", - ), - pytest.param( - 9, - 0, - 0, - id="calldatasize", - ), + "address", + "codecopy", + "codecopy_len0", + "codecopy_neg_offset", + "caller", + "callvalue", + "codesize", + "gasprice", + "origin", + "calldatasize", ], ) -@pytest.mark.pre_alloc_mutable def test_env_info( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + case: str, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000001000) - contract_1 = Address(0x0000000000000000000000000000000000001001) - contract_2 = Address(0x0000000000000000000000000000000000001002) - contract_3 = Address(0x0000000000000000000000000000000000001003) - contract_4 = Address(0x0000000000000000000000000000000000001004) - contract_5 = Address(0x0000000000000000000000000000000000001005) - contract_6 = Address(0x0000000000000000000000000000000000001006) - contract_7 = Address(0x0000000000000000000000000000000000001007) - contract_8 = Address(0x0000000000000000000000000000000000001008) - contract_9 = Address(0x0000000000000000000000000000000000001009) - contract_10 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE) - # Source: lll - # { - # [[0]] (address) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADDRESS) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001000), # noqa: E501 - ) - # Source: lll - # { ; Copy first seven bytes of code to memory - # ; and from there to storage - # (codecopy 0 0 7) - # [[0]] @0 - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.CODECOPY(dest_offset=0x0, offset=0x0, size=0x7) - + Op.SSTORE(key=0x0, value=Op.MLOAD(offset=0x0)) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001001), # noqa: E501 - ) - # Source: lll - # { - # ; codecopy zero bytes - # (codecopy 0 0 0) - # [[0]] @0 - # } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.CODECOPY(dest_offset=0x0, offset=0x0, size=0x0) - + Op.SSTORE(key=0x0, value=Op.MLOAD(offset=0x0)) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001002), # noqa: E501 - ) - # Source: lll - # { - # ; codecopy from unrealistically high in the code - # ; segment (so high you wrap around) - # (def 'neg6 - # 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa) - # (codecopy 0 neg6 8) - # [[0]] @0 - # } - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.CODECOPY( - dest_offset=0x0, - offset=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA, # noqa: E501 - size=0x8, + """Store one environment-info observable in the called contract.""" + code: Bytecode + if case == "address": + code = Op.SSTORE(key=VALUE_SLOT, value=Op.ADDRESS) + Op.STOP + elif case == "codecopy": + # Copy the contract's own first bytes to memory, then to storage. + code = ( + Op.CODECOPY(dest_offset=0x0, offset=0x0, size=CODECOPY_SIZE) + + Op.SSTORE(key=VALUE_SLOT, value=Op.MLOAD(offset=0x0)) + + Op.STOP ) - + Op.SSTORE(key=0x0, value=Op.MLOAD(offset=0x0)) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001003), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (caller) - # } - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.CALLER) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001004), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (callvalue) - # } - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.CALLVALUE) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001005), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (codesize) - # } - contract_6 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.CODESIZE) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001006), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (gasprice) - # } - contract_7 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GASPRICE) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001007), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (origin) - # } - contract_8 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ORIGIN) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001008), # noqa: E501 - ) - # Source: lll - # { - # [[0]] (calldatasize) - # } - contract_9 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.CALLDATASIZE) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001009), # noqa: E501 - ) - # Source: lll - # { - # (call 0xffffff (+ 0x1000 $4) 0x10 0 0 0 0) - # } - contract_10 = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=0xFFFFFF, - address=Op.ADD(0x1000, Op.CALLDATALOAD(offset=0x4)), - value=0x10, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + elif case == "codecopy_len0": + code = ( + Op.CODECOPY(dest_offset=0x0, offset=0x0, size=0x0) + + Op.SSTORE(key=VALUE_SLOT, value=Op.ADD(Op.MLOAD(offset=0x0), 1)) + + Op.STOP + ) + elif case == "codecopy_neg_offset": + code = ( + Op.CODECOPY(dest_offset=0x0, offset=HUGE_OFFSET, size=0x8) + + Op.SSTORE(key=VALUE_SLOT, value=Op.ADD(Op.MLOAD(offset=0x0), 1)) + + Op.STOP ) + elif case == "caller": + code = Op.SSTORE(key=VALUE_SLOT, value=Op.CALLER) + Op.STOP + elif case == "callvalue": + code = Op.SSTORE(key=VALUE_SLOT, value=Op.CALLVALUE) + Op.STOP + elif case == "codesize": + code = Op.SSTORE(key=VALUE_SLOT, value=Op.CODESIZE) + Op.STOP + elif case == "gasprice": + code = Op.SSTORE(key=VALUE_SLOT, value=Op.GASPRICE) + Op.STOP + elif case == "origin": + code = Op.SSTORE(key=VALUE_SLOT, value=Op.ORIGIN) + Op.STOP + else: # calldatasize + # The caller forwards no calldata, unlike the transaction itself. + code = ( + Op.SSTORE(key=VALUE_SLOT, value=Op.ADD(Op.CALLDATASIZE, 1)) + + Op.STOP + ) + + target = pre.deploy_contract(code=code) + caller = pre.deploy_contract( + code=Op.CALL(gas=DISPATCH_GAS, address=target, value=CALL_VALUE) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 + balance=CALL_VALUE, ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: contract_0})}, - }, - { - "indexes": {"data": [1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_1: Account( - storage={ - 0: 0x6007600060003900000000000000000000000000000000000000000000000000, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [2], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_2: Account(storage={0: 0})}, - }, - { - "indexes": {"data": [3], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 0})}, - }, - { - "indexes": {"data": [4], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_4: Account(storage={0: contract_10})}, - }, - { - "indexes": {"data": [5], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_5: Account(storage={0: 16})}, - }, - { - "indexes": {"data": [6], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_6: Account(storage={0: 5})}, - }, - { - "indexes": {"data": [7], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_7: Account(storage={0: 4660})}, - }, - { - "indexes": {"data": [8], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_8: Account(storage={0: sender})}, - }, - { - "indexes": {"data": [9], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_9: Account(storage={0: 0})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes("693c6139") + Hash(0x0), - Bytes("693c6139") + Hash(0x1), - Bytes("693c6139") + Hash(0x2), - Bytes("693c6139") + Hash(0x3), - Bytes("693c6139") + Hash(0x4), - Bytes("693c6139") + Hash(0x5), - Bytes("693c6139") + Hash(0x6), - Bytes("693c6139") + Hash(0x7), - Bytes("693c6139") + Hash(0x8), - Bytes("693c6139") + Hash(0x9), - ] - tx_gas = [16777216] - tx_value = [1] + sender = pre.fund_eoa() tx = Transaction( + protected=fork.supports_protected_txs(), sender=sender, - to=contract_10, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - gas_price=4660, - error=_exc, + to=caller, + # Non-empty tx calldata, so the empty forwarded calldata observed + # by the target (calldatasize case) is a real truncation. + data=Bytes("693c6139"), + gas_price=GAS_PRICE, ) - state_test(env=env, pre=pre, post=post, tx=tx) + expected: int | bytes + if case == "address": + expected = target + elif case == "codecopy": + expected = Hash(bytes(code)[:CODECOPY_SIZE].ljust(32, b"\x00")) + elif case in ("codecopy_len0", "codecopy_neg_offset", "calldatasize"): + expected = 1 + elif case == "caller": + expected = caller + elif case == "callvalue": + expected = CALL_VALUE + elif case == "codesize": + expected = len(bytes(code)) + elif case == "gasprice": + expected = GAS_PRICE + else: # origin + expected = sender + + post = {target: Account(storage={VALUE_SLOT: expected})} + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/istanbul/stSelfBalance/test_self_balance_call_types.py b/tests/ported_static/istanbul/stSelfBalance/test_self_balance_call_types.py index 2d12d4e5179..c1f27127fdf 100644 --- a/tests/ported_static/istanbul/stSelfBalance/test_self_balance_call_types.py +++ b/tests/ported_static/istanbul/stSelfBalance/test_self_balance_call_types.py @@ -1,254 +1,156 @@ """ -SELFBALANCE tests inside CALL, DELEGATECALL, and CALLCODE. +Verify SELFBALANCE inside CALL, DELEGATECALL, and CALLCODE contexts. Ported from: state_tests/stSelfBalance/selfBalanceCallTypesFiller.json + +@manually-enhanced: Do not overwrite. The jump-table dispatcher is +replaced by a parametrize over the call opcode, the raw GAS-delta probe +by a CodeGasMeasure asserting `Op.SELFBALANCE.gas_cost(fork)`, and the +posts by relationships on the executing context's balance. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, - Hash, + Bytecode, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) -from execution_testing.vm import Op +from execution_testing.vm import Op, Opcode REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# Storage slots written by the four probe contracts. +EQ_SLOT = 0x11 +BALANCE_SLOT = 0x21 +GAS_SLOT = 0x31 +BEFORE_SLOT = 0x41 +AFTER_SLOT = 0x42 +DIFF_SLOT = 0x43 + +# Distinct balances so a probe reading the wrong context is visible. +EQ_PROBE_BALANCE = 4096 +BALANCE_PROBE_BALANCE = 4352 +GAS_PROBE_BALANCE = 4608 +TRANSFER_PROBE_BALANCE = 4864 +TARGET_BALANCE = 8192 +# The transfer probe sends this from the executing context. +TRANSFER_VALUE = 1 + @pytest.mark.ported_from( ["state_tests/stSelfBalance/selfBalanceCallTypesFiller.json"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Istanbul") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - pytest.param( - 2, - 0, - 0, - id="d2", - ), - ], + "call_op", + [Op.CALL, Op.DELEGATECALL, Op.CALLCODE], + ids=["call", "delegatecall", "callcode"], ) -@pytest.mark.pre_alloc_mutable def test_self_balance_call_types( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + call_op: Opcode, ) -> None: - """SELFBALANCE tests inside CALL, DELEGATECALL, and CALLCODE.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x3635C9ADC5DEA00000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - ) - - # Source: lll - # { [[ 0x11 ]] (EQ (SELFBALANCE) (BALANCE (ADDRESS))) } - addr = pre.deploy_contract( # noqa: F841 + """Verify SELFBALANCE reads the executing context in each call type.""" + # Probe 1: SELFBALANCE must equal BALANCE of the current address. + eq_probe = pre.deploy_contract( code=Op.SSTORE( - key=0x11, + key=EQ_SLOT, value=Op.EQ(Op.SELFBALANCE, Op.BALANCE(address=Op.ADDRESS)), ) + Op.STOP, - balance=4096, - nonce=0, - address=Address(0xA590BBF1B07B00FED987724E1DB1BF206C2BC37C), # noqa: E501 + balance=EQ_PROBE_BALANCE, ) - # Source: lll - # { [[ 0x21 ]] (SELFBALANCE) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x21, value=Op.SELFBALANCE) + Op.STOP, - balance=4352, - nonce=0, - address=Address(0x76BAC61EE2056F42F6CC29F5400ADAE3E5705237), # noqa: E501 + # Probe 2: store the raw SELFBALANCE reading. + balance_probe = pre.deploy_contract( + code=Op.SSTORE(key=BALANCE_SLOT, value=Op.SELFBALANCE) + Op.STOP, + balance=BALANCE_PROBE_BALANCE, ) - # Source: lll - # (asm GAS SELFBALANCE GAS SWAP1 POP SWAP1 SUB 2 SWAP1 SUB 0x31 SSTORE) - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.GAS - + Op.SELFBALANCE - + Op.GAS - + Op.SWAP1 - + Op.POP - + Op.SWAP1 - + Op.SUB - + Op.PUSH1[0x2] - + Op.SWAP1 - + Op.SSTORE(key=0x31, value=Op.SUB) - + Op.STOP, - balance=4608, - nonce=0, - address=Address(0x8537CE29429EA557E3903C255EE6554DD8D21D26), # noqa: E501 + # Probe 3: measure the gas SELFBALANCE consumes. + gas_probe = pre.deploy_contract( + code=CodeGasMeasure( + code=Op.SELFBALANCE, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), + balance=GAS_PROBE_BALANCE, ) - # Source: lll - # (asm SELFBALANCE DUP1 0x41 SSTORE 0 0 0 0 1 0 0 CALL POP SELFBALANCE DUP1 0x42 SSTORE SWAP1 SUB 0x43 SSTORE) # noqa: E501 - addr_4 = pre.deploy_contract( # noqa: F841 + # Probe 4: SELFBALANCE must track a value transfer out of the + # executing context (a zero-gas CALL that sends one wei). + transfer_probe = pre.deploy_contract( code=Op.SELFBALANCE - + Op.SSTORE(key=0x41, value=Op.DUP1) - + Op.POP( - Op.CALL( - gas=0x0, - address=0x0, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) + + Op.SSTORE(key=BEFORE_SLOT, value=Op.DUP1) + + Op.POP(Op.CALL(gas=0x0, address=0x0, value=TRANSFER_VALUE)) + Op.SELFBALANCE - + Op.SSTORE(key=0x42, value=Op.DUP1) + + Op.SSTORE(key=AFTER_SLOT, value=Op.DUP1) + Op.SWAP1 - + Op.SSTORE(key=0x43, value=Op.SUB) - + Op.STOP, - balance=4864, - nonce=0, - address=Address(0xE1CE93B3251FB38AE74D41AF9F865978C572CF63), # noqa: E501 - ) - # Source: lll - # {(set 'i 0) (while @@ @i {(when (eq 0x01 $0x0) (call allgas @@ @i 0 0 0 0 0)) (when (eq 0x02 $0x0) (delegatecall allgas @@ @i 0 0 0 0)) (when (eq 0x03 $0x0) (callcode allgas @@ @i 0 0 0 0 0)) [i]:(+ @i 1)})} # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x80, value=0x0) - + Op.JUMPDEST - + Op.JUMPI( - pc=0x75, condition=Op.ISZERO(Op.SLOAD(key=Op.MLOAD(offset=0x80))) - ) - + Op.JUMPI( - pc=0x2C, - condition=Op.ISZERO(Op.EQ(0x1, Op.CALLDATALOAD(offset=0x0))), - ) - + Op.POP( - Op.CALL( - gas=Op.SUB(Op.GAS, 0x15), - address=Op.SLOAD(key=Op.MLOAD(offset=0x80)), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.JUMPDEST - + Op.JUMPI( - pc=0x49, - condition=Op.ISZERO(Op.EQ(0x2, Op.CALLDATALOAD(offset=0x0))), - ) - + Op.POP( - Op.DELEGATECALL( - gas=Op.SUB(Op.GAS, 0x15), - address=Op.SLOAD(key=Op.MLOAD(offset=0x80)), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.JUMPDEST - + Op.JUMPI( - pc=0x68, - condition=Op.ISZERO(Op.EQ(0x3, Op.CALLDATALOAD(offset=0x0))), - ) - + Op.POP( - Op.CALLCODE( - gas=Op.SUB(Op.GAS, 0x15), - address=Op.SLOAD(key=Op.MLOAD(offset=0x80)), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.JUMPDEST - + Op.MSTORE(offset=0x80, value=Op.ADD(Op.MLOAD(offset=0x80), 0x1)) - + Op.JUMP(pc=0x5) - + Op.JUMPDEST + + Op.SSTORE(key=DIFF_SLOT, value=Op.SUB) + Op.STOP, - storage={0: addr, 1: addr_2, 2: addr_3, 3: addr_4}, - balance=8192, - nonce=0, - address=Address(0x84BF87FBEF135AFEA15330FDF5847EB504CFF901), # noqa: E501 + balance=TRANSFER_PROBE_BALANCE, ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr: Account(storage={17: 1}), - addr_2: Account(storage={33: 4352}), - addr_3: Account(storage={49: 5}), - addr_4: Account(storage={65: 4864, 66: 4863, 67: 1}), - }, - }, - { - "indexes": {"data": [1, 2], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - target: Account( - storage={ - 0: addr, - 1: addr_2, - 2: addr_3, - 3: addr_4, - 17: 1, - 33: 8192, - 49: 5, - 65: 8192, - 66: 8191, - 67: 1, - }, - ), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + probes = [eq_probe, balance_probe, gas_probe, transfer_probe] + target_code = Bytecode() + for probe in probes: + target_code += Op.POP(call_op(address=probe)) + target = pre.deploy_contract( + code=target_code + Op.STOP, + balance=TARGET_BALANCE, + ) - tx_data = [ - Hash(0x1), - Hash(0x2), - Hash(0x3), - ] - tx_gas = [1000000] + tx = Transaction(sender=pre.fund_eoa(), to=target) - tx = Transaction( - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, - ) + # A plain CALL runs each probe in its own context; DELEGATECALL and + # CALLCODE run the probe code in the target's context, so every slot + # lands in the target's storage and SELFBALANCE reads its balance. + selfbalance_gas = Op.SELFBALANCE.gas_cost(fork) + if call_op == Op.CALL: + post = { + eq_probe: Account(balance=EQ_PROBE_BALANCE, storage={EQ_SLOT: 1}), + balance_probe: Account( + balance=BALANCE_PROBE_BALANCE, + storage={BALANCE_SLOT: BALANCE_PROBE_BALANCE}, + ), + gas_probe: Account( + balance=GAS_PROBE_BALANCE, + storage={GAS_SLOT: selfbalance_gas}, + ), + transfer_probe: Account( + balance=TRANSFER_PROBE_BALANCE - TRANSFER_VALUE, + storage={ + BEFORE_SLOT: TRANSFER_PROBE_BALANCE, + AFTER_SLOT: TRANSFER_PROBE_BALANCE - TRANSFER_VALUE, + DIFF_SLOT: TRANSFER_VALUE, + }, + ), + target: Account(balance=TARGET_BALANCE, storage={}), + } + else: + post = { + eq_probe: Account(balance=EQ_PROBE_BALANCE, storage={}), + balance_probe: Account(balance=BALANCE_PROBE_BALANCE, storage={}), + gas_probe: Account(balance=GAS_PROBE_BALANCE, storage={}), + transfer_probe: Account( + balance=TRANSFER_PROBE_BALANCE, storage={} + ), + target: Account( + balance=TARGET_BALANCE - TRANSFER_VALUE, + storage={ + EQ_SLOT: 1, + BALANCE_SLOT: TARGET_BALANCE, + GAS_SLOT: selfbalance_gas, + BEFORE_SLOT: TARGET_BALANCE, + AFTER_SLOT: TARGET_BALANCE - TRANSFER_VALUE, + DIFF_SLOT: TRANSFER_VALUE, + }, + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_one_v_call_suicide.py b/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_one_v_call_suicide.py deleted file mode 100644 index 10d48bc1580..00000000000 --- a/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_one_v_call_suicide.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Test_call_one_v_call_suicide. - -Ported from: -state_tests/stEIP158Specific/CALL_OneVCallSuicideFiller.json - -@manually-enhanced: Do not overwrite. The measured slot captures the -regular gas of a CALL with value to a not-yet-accessed contract that -SELFDESTRUCTs to the (alive) caller. EIP-8038 reprices the cold account -access (2600 -> 3000) and the CALL value transfer (9000 -> 10300); the -beneficiary stays alive so there is no new-account write. The delta is -`(COLD_ACCOUNT_ACCESS - 2600) + (CALL_VALUE - 9000)`, exactly 0 before -EIP-8037 and tracks parameter changes; do not hardcode the Amsterdam -value. -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP158Specific/CALL_OneVCallSuicideFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_call_one_v_call_suicide( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """Test_call_one_v_call_suicide.""" - # EIP-8038 deltas, each 0 before EIP-8037. The CALL pays a cold - # account access plus a value transfer; the beneficiary stays alive. - gas_costs = fork.gas_costs() - cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 - call_value_delta = gas_costs.CALL_VALUE - 9000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [0](GAS) (CALL 60000 1 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0xEA60, - address=0x79968A94DBEDB20475585E9DD4DAE6333ADD4C01, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=100, - nonce=0, - address=Address(0xEA04224539257FBE043981AA6058FBC1D5E21B1A), # noqa: E501 - ) - # Source: lll - # { (SELFDESTRUCT ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SELFDESTRUCT( - address=0xEA04224539257FBE043981AA6058FBC1D5E21B1A - ) - + Op.STOP, - nonce=0, - address=Address(0x79968A94DBEDB20475585E9DD4DAE6333ADD4C01), # noqa: E501 - ) - - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(storage={}, balance=0), - target: Account( - storage={100: 14337 + cold_account_delta + call_value_delta}, - balance=100, - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_one_v_call_suicide2.py b/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_one_v_call_suicide2.py deleted file mode 100644 index 78ed66c3be4..00000000000 --- a/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_one_v_call_suicide2.py +++ /dev/null @@ -1,118 +0,0 @@ -""" -Test_call_one_v_call_suicide2. - -Ported from: -state_tests/stEIP158Specific/CALL_OneVCallSuicide2Filler.json - -@manually-enhanced: Do not overwrite. The measured slot captures the -regular gas of a value-1 CALL to a cold contract that then -SELFDESTRUCTs (with a zero balance) to a cold, alive beneficiary. -EIP-8038 reprices the CALL's cold account access and value transfer, -plus the SELFDESTRUCT beneficiary's cold access; the beneficiary is -alive so there is no new-account write. The delta is therefore -`2 * (COLD_ACCOUNT_ACCESS - 2600) + (CALL_VALUE - 9000)`, exactly 0 -before EIP-8038. -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP158Specific/CALL_OneVCallSuicide2Filler.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_call_one_v_call_suicide2( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """Test_call_one_v_call_suicide2.""" - # EIP-8038 deltas, each 0 before EIP-8038. The CALL pays the cold - # account reprice and the value-transfer reprice; the cold - # SELFDESTRUCT beneficiary pays a second cold account reprice. - gas_costs = fork.gas_costs() - cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 - call_value_delta = gas_costs.CALL_VALUE - 9000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - addr_2 = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) - sender = EOA( - key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - pre[addr_2] = Account(balance=0, nonce=1) - # Source: lll - # { (SELFDESTRUCT ) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SELFDESTRUCT( - address=0xEB201D2887816E041F6E807E804F64F3A7A226FE - ) - + Op.STOP, - nonce=0, - address=Address(0x99378E0DB04E57AE174AD69770E1B7A0AA805930), # noqa: E501 - ) - # Source: lll - # { [0](GAS) (CALL 60000 1 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0xEA60, - address=0x99378E0DB04E57AE174AD69770E1B7A0AA805930, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=100, - nonce=0, - address=Address(0xEA04224539257FBE043981AA6058FBC1D5E21B1A), # noqa: E501 - ) - - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(storage={}, balance=0), - target: Account( - storage={100: 16937 + 2 * cold_account_delta + call_value_delta}, - balance=99, - ), - addr_2: Account(balance=1), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_v_call_suicide.py b/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_v_call_suicide.py new file mode 100644 index 00000000000..8950266bc2a --- /dev/null +++ b/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_v_call_suicide.py @@ -0,0 +1,127 @@ +""" +Measure the gas cost of a CALL to a cold contract that SELFDESTRUCTs to +an alive beneficiary, across value-transfer and beneficiary variants. + +Ported from: +state_tests/stEIP158Specific/CALL_ZeroVCallSuicideFiller.json +state_tests/stEIP158Specific/CALL_OneVCallSuicideFiller.json +state_tests/stEIP158Specific/CALL_OneVCallSuicide2Filler.json + +@manually-enhanced: Do not overwrite. Three CALL_*VCallSuicide fillers +folded into one CodeGasMeasure parametrize; the expectation is the +fork-derived CALL + SELFDESTRUCT composite (minus the returned stipend +on value calls), and the caller-beneficiary is `Op.CALLER` instead of a +hardcoded self-referential address. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + CodeGasMeasure, + Fork, + StateTestFiller, + Transaction, +) +from execution_testing.forks import Cancun +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +GAS_SLOT = 0x64 +TARGET_BALANCE = 100 +# Non-zero so the beneficiary EOA is alive (EIP-158) and stays cold. +BENEFICIARY_BALANCE = 1 + + +@pytest.mark.ported_from( + [ + "state_tests/stEIP158Specific/CALL_ZeroVCallSuicideFiller.json", + "state_tests/stEIP158Specific/CALL_OneVCallSuicideFiller.json", + "state_tests/stEIP158Specific/CALL_OneVCallSuicide2Filler.json", + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "call_value, cold_beneficiary", + [ + pytest.param(0, False, id="call_zero_v_call_suicide"), + pytest.param(1, False, id="call_one_v_call_suicide"), + pytest.param(1, True, id="call_one_v_call_suicide2"), + ], +) +def test_call_v_call_suicide( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_value: int, + cold_beneficiary: bool, +) -> None: + """Measure a CALL whose callee SELFDESTRUCTs to an alive account.""" + gas_costs = fork.gas_costs() + + # The beneficiary is either the (warm, alive) caller — read at + # runtime via CALLER instead of a hardcoded self-address — or a + # cold, alive EOA; neither triggers a new-account write. + if cold_beneficiary: + beneficiary = pre.fund_eoa(amount=BENEFICIARY_BALANCE) + sd_body = ( + Op.SELFDESTRUCT( + address=beneficiary, address_warm=False, account_new=False + ) + + Op.STOP + ) + else: + sd_body = ( + Op.SELFDESTRUCT( + address=Op.CALLER, address_warm=True, account_new=False + ) + + Op.STOP + ) + callee = pre.deploy_contract(code=sd_body) + + call_code = Op.CALL( + address=callee, + value=call_value, + address_warm=False, + value_transfer=call_value > 0, + ) + target = pre.deploy_contract( + code=CodeGasMeasure( + code=call_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), + balance=TARGET_BALANCE, + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=target, + state_gas_reservoir=0, + ) + + # The measured window covers the CALL plus the callee's + # SELFDESTRUCT; a value-bearing CALL hands the callee the stipend, + # which comes back unused. + stipend_returned = gas_costs.CALL_STIPEND if call_value > 0 else 0 + measured_gas = ( + call_code.gas_cost(fork) + sd_body.gas_cost(fork) - stipend_returned + ) + + # The callee forwards its whole balance (the transferred value) to + # the beneficiary; before EIP-6780 (Cancun) it is also destroyed. + callee_post = Account(balance=0) if fork >= Cancun else Account.NONEXISTENT + post = { + callee: callee_post, + target: Account( + storage={GAS_SLOT: measured_gas}, + # A caller-beneficiary gets the transferred value back. + balance=TARGET_BALANCE - (call_value if cold_beneficiary else 0), + ), + } + if cold_beneficiary: + post[beneficiary] = Account(balance=BENEFICIARY_BALANCE + call_value) + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_zero_v_call_suicide.py b/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_zero_v_call_suicide.py deleted file mode 100644 index e4a32956956..00000000000 --- a/tests/ported_static/spurious_dragon/stEIP158Specific/test_call_zero_v_call_suicide.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Test_call_zero_v_call_suicide. - -Ported from: -state_tests/stEIP158Specific/CALL_ZeroVCallSuicideFiller.json - -@manually-enhanced: Do not overwrite. The measured slot captures the -regular gas of a value-0 CALL to a cold contract that then -SELFDESTRUCTs back to its (warm, alive) caller. EIP-8038 reprices the -cold account access of that CALL; the beneficiary is warm so the -SELFDESTRUCT is unchanged. The delta is therefore the fork's -`COLD_ACCOUNT_ACCESS - 2600`, exactly 0 before EIP-8038. -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP158Specific/CALL_ZeroVCallSuicideFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_call_zero_v_call_suicide( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """Test_call_zero_v_call_suicide.""" - # EIP-8038 cold account access reprice; 0 before EIP-8038. - cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [0](GAS) (CALL 60000 0 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0xEA60, - address=0x79968A94DBEDB20475585E9DD4DAE6333ADD4C01, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - address=Address(0x61385ED1C4E08AA26BE16DA3851DF1CC90ED2777), # noqa: E501 - ) - # Source: lll - # { (SELFDESTRUCT ) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SELFDESTRUCT( - address=0x61385ED1C4E08AA26BE16DA3851DF1CC90ED2777 - ) - + Op.STOP, - nonce=0, - address=Address(0x79968A94DBEDB20475585E9DD4DAE6333ADD4C01), # noqa: E501 - ) - - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(balance=0), - target: Account(storage={100: 7637 + cold_account_delta}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/tangerine_whistle/stEIP150Specific/test_call_goes_oog_on_second_level.py b/tests/ported_static/tangerine_whistle/stEIP150Specific/test_call_goes_oog_on_second_level.py index f663480b776..1bcbac6424c 100644 --- a/tests/ported_static/tangerine_whistle/stEIP150Specific/test_call_goes_oog_on_second_level.py +++ b/tests/ported_static/tangerine_whistle/stEIP150Specific/test_call_goes_oog_on_second_level.py @@ -1,25 +1,21 @@ """ -Test_call_goes_oog_on_second_level. +Verify an innermost-frame OOG (level two) reverts only that frame: both +callers observe the exact gas they were granted and complete normally. Ported from: state_tests/stEIP150Specific/CallGoesOOGOnSecondLevelFiller.json -@manually-enhanced: Do not overwrite. The `gas_limit` is derived from -the fork intrinsic calculator instead of the original hardcoded value. -The test fixes the post-intrinsic budget that the nested Op.GAS storage -assertions (8: 0x927BE, 8: 0x213FB6) depend on, so it shifts the base -2_200_000 budget by the intrinsic delta versus the pre-EIP-2780 Cancun -baseline of 21_000 (`intrinsic - 21_000`). This stays correct across -the EIP-2780 intrinsic decomposition. Do not hardcode the gas_limit. +@manually-enhanced: Do not overwrite. The gas_limit and the two stored +GAS observations are derived from the fork (intrinsic calculator and +`Op.GAS.gas_cost`) instead of hardcoded; fill-time asserts guard that +both CALL asks stay below the EIP-150 63/64 clamp so the callees +receive exactly the asked amounts on every fork. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, Fork, StateTestFiller, Transaction, @@ -29,100 +25,128 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +ENTRY_SLOT = 0x8 +RESULT_SLOT = 0x9 +DONE_SLOT = 0xC +# Both asks stay below the 63/64 clamp of the granting frame, so each +# callee receives exactly the asked amount and its entry GAS reading is +# a known constant. +FIRST_CALL_GAS = 0x927C0 +SECOND_CALL_GAS = 0x493E0 +# The top frame's budget after the intrinsic cost; the ported filler ran +# with 2_200_000 gas over a 21_000 intrinsic. +EXECUTION_BUDGET = 2_200_000 - 21_000 +# SHA3 over a ~3 MiB memory segment: the expansion cost exceeds the +# innermost frame's grant, so it always goes OOG. +HUGE_SHA3_SIZE = 0x2FFFFF + @pytest.mark.ported_from( ["state_tests/stEIP150Specific/CallGoesOOGOnSecondLevelFiller.json"], ) @pytest.mark.valid_from("Frontier") -@pytest.mark.pre_alloc_mutable def test_call_goes_oog_on_second_level( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_call_goes_oog_on_second_level.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) + """Verify only the OOG frame reverts and callers observe their grant.""" + sender = pre.fund_eoa() - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + # Every level starts by storing its remaining gas to a fresh slot. + entry_store = Op.SSTORE( + key=ENTRY_SLOT, + value=Op.GAS, + key_warm=False, + original_value=0, + new_value=1, ) - - # Source: lll - # { (SSTORE 8 (GAS)) (KECCAK256 0x00 0x2fffff) (SSTORE 9 (GAS)) (SSTORE 10 (GAS)) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.POP(Op.SHA3(offset=0x0, size=0x2FFFFF)) + addr_2 = pre.deploy_contract( + code=entry_store + + Op.POP(Op.SHA3(offset=0x0, size=HUGE_SHA3_SIZE)) + Op.SSTORE(key=0x9, value=Op.GAS) + Op.SSTORE(key=0xA, value=Op.GAS) + Op.STOP, - nonce=0, ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 300000 0 0 0 0 0)) [[12]] 1} # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x493E0, - address=addr_2, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0xC, value=0x1) - + Op.STOP, - nonce=0, + call_second = Op.CALL( + gas=SECOND_CALL_GAS, address=addr_2, address_warm=False ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 600000 0 0 0 0 0)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) + done_store = Op.SSTORE( + key=DONE_SLOT, + value=0x1, + key_warm=False, + original_value=0, + new_value=1, + ) + addr = pre.deploy_contract( + code=entry_store + + Op.SSTORE(key=RESULT_SLOT, value=call_second) + + done_store + Op.STOP, - nonce=0, ) + call_first = Op.CALL(gas=FIRST_CALL_GAS, address=addr, address_warm=False) + result_store = Op.SSTORE( + key=RESULT_SLOT, + value=call_first, + key_warm=False, + original_value=0, + new_value=1, + ) + target = pre.deploy_contract(code=entry_store + result_store + Op.STOP) - # The original test was built against Cancun's ``TX_BASE`` of - # 21_000. EIP-2780 lowers the intrinsic for non-self non-value - # txs, so shift ``gas_limit`` by the intrinsic delta to preserve - # the post-intrinsic execution budget the Op.GAS storage - # assertions depend on. - intrinsic = fork.transaction_intrinsic_cost_calculator()() - gas_limit = 2_200_000 + (intrinsic - 21_000) + gas_limit = fork.transaction_intrinsic_cost_calculator()() + ( + EXECUTION_BUDGET + ) + + # Guard the constructed relationships. The composites price older + # forks with the Berlin schedule (an overestimate there), so every + # bound below is conservative on earlier forks. + top_available = ( + EXECUTION_BUDGET + - entry_store.gas_cost(fork) + - call_first.gas_cost(fork) + ) + assert FIRST_CALL_GAS <= top_available - top_available // 64, ( + "first ask must stay below the 63/64 clamp" + ) + middle_available = ( + FIRST_CALL_GAS + - entry_store.gas_cost(fork) + - call_second.gas_cost(fork) + ) + assert SECOND_CALL_GAS <= middle_available - middle_available // 64, ( + "second ask must stay below the 63/64 clamp" + ) + # After the failed inner call, the middle frame must still afford + # its completion store; the top frame likewise. + assert middle_available - SECOND_CALL_GAS > done_store.gas_cost(fork) + assert top_available - FIRST_CALL_GAS > result_store.gas_cost(fork) tx = Transaction( protected=fork.supports_protected_txs(), sender=sender, to=target, - data=Bytes(""), gas_limit=gas_limit, ) + # Only the innermost frame reverts (its SHA3 goes OOG); each caller + # observes exactly the gas it was granted, minus the GAS opcode's + # own charge, and completes. + gas_op_cost = Op.GAS.gas_cost(fork) post = { - addr: Account(storage={8: 0x927BE, 12: 1}), addr_2: Account(storage={}), - target: Account(storage={8: 0x213FB6, 9: 1}), + addr: Account( + storage={ + ENTRY_SLOT: FIRST_CALL_GAS - gas_op_cost, + DONE_SLOT: 1, + }, + ), + target: Account( + storage={ + ENTRY_SLOT: EXECUTION_BUDGET - gas_op_cost, + RESULT_SLOT: 1, + }, + ), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/tangerine_whistle/stEIP150Specific/test_call_goes_oog_on_second_level2.py b/tests/ported_static/tangerine_whistle/stEIP150Specific/test_call_goes_oog_on_second_level2.py index e045faac14f..aece79c38af 100644 --- a/tests/ported_static/tangerine_whistle/stEIP150Specific/test_call_goes_oog_on_second_level2.py +++ b/tests/ported_static/tangerine_whistle/stEIP150Specific/test_call_goes_oog_on_second_level2.py @@ -1,17 +1,21 @@ """ -Test_call_goes_oog_on_second_level2. +Verify a two-level call chain where the innermost frame goes out of gas +and every caller's retained gas is too small to finish, so no storage +write survives at any level. Ported from: state_tests/stEIP150Specific/CallGoesOOGOnSecondLevel2Filler.json + +@manually-enhanced: Do not overwrite. The gas budget is derived from the +fork (intrinsic, SSTORE composite, CALL composite) so every level still +reaches its entry store and the cascade still fails on EIP-8037 forks, +where the entry stores' state gas comes from an explicit reservoir. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, Fork, StateTestFiller, Transaction, @@ -21,89 +25,107 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +ENTRY_SLOT = 0x8 +RESULT_SLOT = 0x9 +# Both CALLs ask for more gas than is ever available, so each callee +# receives all-but-one-64th of the caller's remaining gas (EIP-150). +OVERSIZED_GAS_ASK = 0x927C0 +# SHA3 over a ~3 MiB memory segment: the expansion cost exceeds any +# budget this test grants, so the innermost frame always goes OOG. +HUGE_SHA3_SIZE = 0x2FFFFF +# The framework prices every fork with the Berlin schedule, so actual +# pre-Berlin frames run a few thousand gas cheaper than the composite +# estimates and would retain more than intended. Aim the retention this +# far below the halt threshold to absorb that drift. +PRE_BERLIN_DRIFT_HEADROOM = 800 + @pytest.mark.ported_from( ["state_tests/stEIP150Specific/CallGoesOOGOnSecondLevel2Filler.json"], ) @pytest.mark.valid_from("Frontier") -@pytest.mark.pre_alloc_mutable def test_call_goes_oog_on_second_level2( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_call_goes_oog_on_second_level2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) + """Verify an innermost-frame OOG starves and reverts every level.""" + gas_costs = fork.gas_costs() + sender = pre.fund_eoa() - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + # Every level starts by storing its remaining gas to a fresh slot. + entry_store = Op.SSTORE( + key=ENTRY_SLOT, + value=Op.GAS, + key_warm=False, + original_value=0, + new_value=1, ) - - # Source: lll - # { (SSTORE 8 (GAS)) (KECCAK256 0x00 0x2fffff) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SHA3(offset=0x0, size=0x2FFFFF) - + Op.STOP, - nonce=0, + addr_2 = pre.deploy_contract( + code=entry_store + Op.SHA3(offset=0x0, size=HUGE_SHA3_SIZE) + Op.STOP, + ) + call_second = Op.CALL( + gas=OVERSIZED_GAS_ASK, address=addr_2, address_warm=False ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 600000 0 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr_2, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) + addr = pre.deploy_contract( + code=entry_store + + Op.SSTORE(key=RESULT_SLOT, value=call_second) + Op.STOP, - nonce=0, ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 600000 0 0 0 0 0)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) + call_first = Op.CALL( + gas=OVERSIZED_GAS_ASK, address=addr, address_warm=False + ) + target = pre.deploy_contract( + code=entry_store + + Op.SSTORE(key=RESULT_SLOT, value=call_first) + Op.STOP, - nonce=0, + ) + + # Size the top frame so that, at its CALL, just under + # CALL_STIPEND * 64 gas remains: the retained 1/64 then cannot cover + # the post-call SSTORE on any fork (EIP-2200 demands more than the + # stipend; earlier forks charge 5000), so the frame goes OOG. On + # EIP-8037 forks the entry stores' state gas is drawn from the + # reservoir, so the frame arithmetic below holds on every fork. + entry_exec = entry_store.execution_cost(fork) + state_gas_reservoir = 3 * entry_store.state_cost(fork) + top_available = (gas_costs.CALL_STIPEND - PRE_BERLIN_DRIFT_HEADROOM) * 64 + gas_limit = ( + fork.transaction_intrinsic_cost_calculator()() + + entry_exec + + call_first.gas_cost(fork) + + top_available + ) + + # The middle frame must reach its entry store and its own CALL, yet + # retain too little to finish; the innermost frame must afford its + # entry store before the SHA3 goes OOG. + forwarded_first = top_available - top_available // 64 + middle_available = ( + forwarded_first - entry_exec - call_second.gas_cost(fork) + ) + assert middle_available // 64 <= gas_costs.CALL_STIPEND, ( + "middle frame retention must not cover its post-call SSTORE" + ) + forwarded_second = middle_available - middle_available // 64 + assert forwarded_second > entry_exec, ( + "innermost frame must afford its entry store" ) tx = Transaction( protected=fork.supports_protected_txs(), sender=sender, to=target, - data=Bytes(""), - gas_limit=160000, + gas_limit=gas_limit, + state_gas_reservoir=state_gas_reservoir, ) + # The innermost OOG cascades: every frame fails, so every store is + # reverted at every level. post = { addr: Account(storage={}), addr_2: Account(storage={}), target: Account(storage={}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_exp.py b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_exp.py index 5b3b95f7c5f..a6e62adb6a5 100644 --- a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_exp.py +++ b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_exp.py @@ -1,185 +1,76 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Measure the gas cost of EXP as the exponent byte length grows +(by Ori Pomerantz qbzzt1@gmail.com). Ported from: state_tests/stEIP150singleCodeGasPrices/gasCostExpFiller.yml + +@manually-enhanced: Do not overwrite. The legacy raw GAS-delta window is +reframed as a CodeGasMeasure over the EXP opcode, asserting the +fork-derived `exp_code.gas_cost(fork)` (base plus a per-byte charge on +the exponent's minimal byte length). """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, - Hash, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x0 + +# Exponents straddling each byte-length boundary (0, 1, 2, 3, and 4 +# bytes), so the per-byte charge is exercised on both sides of each step. +EXPONENTS = [ + 0x0, + 0x1, + 0xFF, + 0x100, + 0xFFFF, + 0x10000, + 0xFFFFFF, + 0x1000000, + 0xFFFFFFFF, +] + @pytest.mark.ported_from( ["state_tests/stEIP150singleCodeGasPrices/gasCostExpFiller.yml"], ) @pytest.mark.valid_from("SpuriousDragon") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - pytest.param( - 2, - 0, - 0, - id="d2", - ), - pytest.param( - 3, - 0, - 0, - id="d3", - ), - pytest.param( - 4, - 0, - 0, - id="d4", - ), - pytest.param( - 5, - 0, - 0, - id="d5", - ), - pytest.param( - 6, - 0, - 0, - id="d6", - ), - pytest.param( - 7, - 0, - 0, - id="d7", - ), - pytest.param( - 8, - 0, - 0, - id="d8", - ), - ], + "exponent", EXPONENTS, ids=lambda e: f"exponent_{e:#x}" ) -@pytest.mark.pre_alloc_mutable def test_gas_cost_exp( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + exponent: int, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xBA1A9CE0BA1A9CE) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - # Source: lll - # { - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Initialization - # - # ; Variables (0x20 byte wide) - # (def 'powerOf 0x000) ; A to the power of @powerOf - # (def 'expectedCost 0x020) ; Expected gas cost - # (def 'gasB4 0x040) ; Before the action being measured - # (def 'gasAfter 0x060) ; After the action being measured - # - # ; Understand CALLDATA. It is four bytes of function - # ; selector (irrelevant) followed by 32 byte words - # ; of the parameters - # [powerOf] $4 - # [expectedCost] $36 - # - # - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Run the operation - # [gasB4] (gas) - # (exp 2 @powerOf) - # [gasAfter] (gas) - # - # - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Return value - # - # [[0]] (- @gasB4 @gasAfter @expectedCost) - # } - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.CALLDATALOAD(offset=0x4)) - + Op.MSTORE(offset=0x20, value=Op.CALLDATALOAD(offset=0x24)) - + Op.MSTORE(offset=0x40, value=Op.GAS) - + Op.POP(Op.EXP(0x2, Op.MLOAD(offset=0x0))) - + Op.MSTORE(offset=0x60, value=Op.GAS) - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x40), Op.MLOAD(offset=0x60)), - Op.MLOAD(offset=0x20), - ), - ) - + Op.STOP, - storage={0: 24743}, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, + """Measure the gas EXP consumes for each exponent byte length.""" + exp_code = Op.EXP(2, exponent, exponent=exponent) + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=exp_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), ) - tx_data = [ - Bytes("c5b5a1ae") + Hash(0x0) + Hash(0x20), - Bytes("c5b5a1ae") + Hash(0x1) + Hash(0x52), - Bytes("c5b5a1ae") + Hash(0xFF) + Hash(0x52), - Bytes("c5b5a1ae") + Hash(0x100) + Hash(0x84), - Bytes("c5b5a1ae") + Hash(0xFFFF) + Hash(0x84), - Bytes("c5b5a1ae") + Hash(0x10000) + Hash(0xB6), - Bytes("c5b5a1ae") + Hash(0xFFFFFF) + Hash(0xB6), - Bytes("c5b5a1ae") + Hash(0x1000000) + Hash(0xE8), - Bytes("c5b5a1ae") + Hash(0xFFFFFFFF) + Hash(0xE8), - ] - tx_gas = [16777216] - tx_value = [1] - tx = Transaction( protected=fork.supports_protected_txs(), - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], + sender=pre.fund_eoa(), + to=contract, ) - post = {target: Account(storage={0: 0})} + post = {contract: Account(storage={GAS_SLOT: exp_code.gas_cost(fork)})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_jump.py b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_jump.py index d51658c7988..2e9dd2ba878 100644 --- a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_jump.py +++ b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_jump.py @@ -1,244 +1,82 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Measure the gas cost of JUMP, JUMPI (taken and not taken), and JUMPDEST +(by Ori Pomerantz qbzzt1@gmail.com). Ported from: state_tests/stEIP150singleCodeGasPrices/gasCostJumpFiller.yml + +@manually-enhanced: Do not overwrite. The legacy raw GAS-delta windows +(action-minus-baseline differences) are reframed as one CodeGasMeasure +per case over a CALL to the jump-variant callee, asserting the +fork-derived `call cost + callee body cost` for each. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, - Hash, + Bytecode, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x0 + @pytest.mark.ported_from( ["state_tests/stEIP150singleCodeGasPrices/gasCostJumpFiller.yml"], ) -@pytest.mark.valid_from("Frontier") +@pytest.mark.valid_from("Berlin") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - pytest.param( - 2, - 0, - 0, - id="d2", - ), - ], + "case", + ["jumpdest", "jump", "jumpi_taken", "jumpi_not_taken"], ) -@pytest.mark.pre_alloc_mutable def test_gas_cost_jump( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + case: str, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000001000) - contract_1 = Address(0x0000000000000000000000000000000000002000) - contract_2 = Address(0x0000000000000000000000000000000000003000) - contract_3 = Address(0x0000000000000000000000000000000000004000) - contract_4 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) - sender = pre.fund_eoa(amount=0xBA1A9CE0BA1A9CE) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) + """Measure the gas a CALL to each jump-variant callee consumes.""" + # Every callee executes exactly the opcodes its composite lists: the + # jump target (pc=0x5) is the JUMPDEST after the pushes, and a + # not-taken JUMPI falls through to that same JUMPDEST. + body: Bytecode + if case == "jumpdest": + # The legacy baseline: two pushes and two JUMPDESTs. + body = Op.PUSH1[0x0] * 2 + Op.JUMPDEST * 2 + Op.STOP + elif case == "jump": + body = Op.PUSH1[0x0] + Op.JUMP(pc=0x5) + Op.JUMPDEST + Op.STOP + elif case == "jumpi_taken": + body = Op.JUMPI(pc=0x5, condition=0x1) + Op.JUMPDEST + Op.STOP + else: + body = Op.JUMPI(pc=0x5, condition=0x0) + Op.JUMPDEST + Op.STOP + callee = pre.deploy_contract(code=body) - # Source: raw - # 0x600060005B5B00 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x0] * 2 + Op.JUMPDEST * 2 + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001000), # noqa: E501 - ) - # Source: raw - # 0x60006005565B00 - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x0] + Op.JUMP(pc=0x5) + Op.JUMPDEST + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000002000), # noqa: E501 - ) - # Source: raw - # 0x60016005575B00 - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x5, condition=0x1) + Op.JUMPDEST + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000003000), # noqa: E501 - ) - # Source: raw - # 0x60006005575B00 - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI(pc=0x5, condition=0x0) + Op.JUMPDEST + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x0000000000000000000000000000000000004000), # noqa: E501 - ) - # Source: lll - # { - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Initialization - # - # ; Variables (0x20 byte wide) - # (def 'gasB4 0x000) ; Before the action being measured - # - # ; Gas cost for a baseline operation (call a contract that does mstore - # ; and then mload) - # (def 'gasBaseline 0x020) - # - # ; Gas for for the action intself (call a contract plus ) - # (def 'gasAction 0x040) - # - # ; Understand CALLDATA. It is four bytes of function - # ; selector (irrelevant) followed by 32 byte words - # ; of the parameters - # (def 'action $4 ) - # (def 'expectedCost $36) - # - # ; Constants - # (def 'NOP 0) ; No operation (for if statements) - # - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Define the actions - # - # ; Store the gas amount - # (def 'gas0 [gasB4] (gas)) - # - # ; Get the baseline cost - # ... (51 more lines) - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x10000, - address=contract_0, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x20, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.JUMPI(pc=0x2E, condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0x1)) - + Op.POP(0x0) - + Op.JUMP(pc=0x4E) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x10000, - address=contract_1, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x40, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.JUMPDEST - + Op.JUMPI(pc=0x5E, condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0x2)) - + Op.POP(0x0) - + Op.JUMP(pc=0x7E) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x10000, - address=contract_2, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x40, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.JUMPDEST - + Op.JUMPI(pc=0x8E, condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0x3)) - + Op.POP(0x0) - + Op.JUMP(pc=0xAE) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x10000, - address=contract_3, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x40, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.JUMPDEST - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x40), Op.MLOAD(offset=0x20)), - Op.CALLDATALOAD(offset=0x24), - ), - ) - + Op.STOP, - storage={0: 24743}, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, + call_code = Op.CALL(address=callee, address_warm=False) + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=call_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), ) - tx_data = [ - Bytes("c5b5a1ae") + Hash(0x1) + Hash(0x4), - Bytes("c5b5a1ae") + Hash(0x2) + Hash(0x6), - Bytes("c5b5a1ae") + Hash(0x3) + Hash(0x6), - ] - tx_gas = [16777216] - tx_value = [1] + tx = Transaction(sender=pre.fund_eoa(), to=contract) - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, - to=contract_4, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - ) - - post = {contract_4: Account(storage={0: 0})} + # The measured window covers the CALL plus what the callee consumed. + post = { + contract: Account( + storage={ + GAS_SLOT: call_code.gas_cost(fork) + body.gas_cost(fork), + }, + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_mem_seg.py b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_mem_seg.py index ed140f516a6..2ccbde046d2 100644 --- a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_mem_seg.py +++ b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_mem_seg.py @@ -1,793 +1,120 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Measure the gas cost of the memory-segment opcodes (SHA3, CALLDATACOPY, +CODECOPY, and LOG0-LOG4) as the touched segment grows +(by Ori Pomerantz qbzzt1@gmail.com). Ported from: state_tests/stEIP150singleCodeGasPrices/gasCostMemSegFiller.yml + +@manually-enhanced: Do not overwrite. The legacy raw GAS-delta windows +are reframed as a CodeGasMeasure per (opcode, size) case over a +pre-expanded memory (so no expansion is charged), asserting the +fork-derived `measured_code.gas_cost(fork)`. """ +from typing import Any + import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, - Hash, + Bytecode, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x0 +# MSTORE at this offset grows memory past every measured segment below, +# so the measured opcode never pays memory expansion. +PREEXPAND_OFFSET = 0x100 +PREEXPANDED = PREEXPAND_OFFSET + 0x20 + +# Per-word-priced opcodes: sizes straddling the word boundaries. +SEGMENT_SIZES = [0x1, 0x20, 0x21, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0, 0x100] +# Per-byte-priced LOG data: one case per small byte count. +LOG_SIZES = [0x0, 0x1, 0x2, 0x3, 0x4] + +CASES = [ + *[("sha3", size) for size in SEGMENT_SIZES], + *[("calldatacopy", size) for size in SEGMENT_SIZES], + *[("codecopy", size) for size in SEGMENT_SIZES], + *[(f"log{topics}", size) for topics in range(5) for size in LOG_SIZES], +] + @pytest.mark.ported_from( ["state_tests/stEIP150singleCodeGasPrices/gasCostMemSegFiller.yml"], ) @pytest.mark.valid_from("Frontier") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - pytest.param( - 2, - 0, - 0, - id="d2", - ), - pytest.param( - 3, - 0, - 0, - id="d3", - ), - pytest.param( - 4, - 0, - 0, - id="d4", - ), - pytest.param( - 5, - 0, - 0, - id="d5", - ), - pytest.param( - 6, - 0, - 0, - id="d6", - ), - pytest.param( - 7, - 0, - 0, - id="d7", - ), - pytest.param( - 8, - 0, - 0, - id="d8", - ), - pytest.param( - 9, - 0, - 0, - id="d9", - ), - pytest.param( - 10, - 0, - 0, - id="d10", - ), - pytest.param( - 11, - 0, - 0, - id="d11", - ), - pytest.param( - 12, - 0, - 0, - id="d12", - ), - pytest.param( - 13, - 0, - 0, - id="d13", - ), - pytest.param( - 14, - 0, - 0, - id="d14", - ), - pytest.param( - 15, - 0, - 0, - id="d15", - ), - pytest.param( - 16, - 0, - 0, - id="d16", - ), - pytest.param( - 17, - 0, - 0, - id="d17", - ), - pytest.param( - 18, - 0, - 0, - id="d18", - ), - pytest.param( - 19, - 0, - 0, - id="d19", - ), - pytest.param( - 20, - 0, - 0, - id="d20", - ), - pytest.param( - 21, - 0, - 0, - id="d21", - ), - pytest.param( - 22, - 0, - 0, - id="d22", - ), - pytest.param( - 23, - 0, - 0, - id="d23", - ), - pytest.param( - 24, - 0, - 0, - id="d24", - ), - pytest.param( - 25, - 0, - 0, - id="d25", - ), - pytest.param( - 26, - 0, - 0, - id="d26", - ), - pytest.param( - 27, - 0, - 0, - id="d27", - ), - pytest.param( - 28, - 0, - 0, - id="d28", - ), - pytest.param( - 29, - 0, - 0, - id="d29", - ), - pytest.param( - 30, - 0, - 0, - id="d30", - ), - pytest.param( - 31, - 0, - 0, - id="d31", - ), - pytest.param( - 32, - 0, - 0, - id="d32", - ), - pytest.param( - 33, - 0, - 0, - id="d33", - ), - pytest.param( - 34, - 0, - 0, - id="d34", - ), - pytest.param( - 35, - 0, - 0, - id="d35", - ), - pytest.param( - 36, - 0, - 0, - id="d36", - ), - pytest.param( - 37, - 0, - 0, - id="d37", - ), - pytest.param( - 38, - 0, - 0, - id="d38", - ), - pytest.param( - 39, - 0, - 0, - id="d39", - ), - pytest.param( - 40, - 0, - 0, - id="d40", - ), - pytest.param( - 41, - 0, - 0, - id="d41", - ), - pytest.param( - 42, - 0, - 0, - id="d42", - ), - pytest.param( - 43, - 0, - 0, - id="d43", - ), - pytest.param( - 44, - 0, - 0, - id="d44", - ), - pytest.param( - 45, - 0, - 0, - id="d45", - ), - pytest.param( - 46, - 0, - 0, - id="d46", - ), - pytest.param( - 47, - 0, - 0, - id="d47", - ), - pytest.param( - 48, - 0, - 0, - id="d48", - ), - pytest.param( - 49, - 0, - 0, - id="d49", - ), - pytest.param( - 50, - 0, - 0, - id="d50", - ), - pytest.param( - 51, - 0, - 0, - id="d51", - ), - pytest.param( - 52, - 0, - 0, - id="d52", - ), - pytest.param( - 53, - 0, - 0, - id="d53", - ), - pytest.param( - 54, - 0, - 0, - id="d54", - ), - ], + "opcode, size", CASES, ids=[f"{op}_size_{size:#x}" for op, size in CASES] ) -@pytest.mark.pre_alloc_mutable def test_gas_cost_mem_seg( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + opcode: str, + size: int, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xBA1A9CE0BA1A9CE) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - # Source: lll - # { - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Initialization - # - # ; Variables (0x20 byte wide) - # (def 'gasB4 0x000) ; Before the action being measured - # (def 'gasAfter 0x020) ; After the action being measured - # - # (def 'afterVars 0x100) ; Memory after the variables, - # ; safe to copy into - # - # ; Understand CALLDATA. It is four bytes of function - # ; selector (irrelevant) followed by 32 byte words - # ; of the parameters - # (def 'opcode $4 ) - # (def 'length $36) - # (def 'expectedCost $68) - # - # ; NOP for if statements - # (def 'NOP 0) - # - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Run the operation - # - # ; SHA3 - # (if (= opcode 0x20) { - # [gasB4] (gas) - # (sha3 0 length) - # [gasAfter] (gas) - # } NOP) - # ... (70 more lines) - target = pre.deploy_contract( # noqa: F841 - code=Op.JUMPI( - pc=Op.PUSH2[0x11], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0x20), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x21]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP(Op.SHA3(offset=0x0, size=Op.CALLDATALOAD(offset=0x24))) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x33], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0x37), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x45]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.CALLDATACOPY( - dest_offset=0x100, offset=0x0, size=Op.CALLDATALOAD(offset=0x24) - ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x57], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0x39), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x69]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.CODECOPY( - dest_offset=0x100, offset=0x0, size=Op.CALLDATALOAD(offset=0x24) - ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x7B], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0xA0), + """Measure the gas each memory-segment opcode consumes per size.""" + memory_sizes: dict[str, Any] = { + "old_memory_size": PREEXPANDED, + "new_memory_size": PREEXPANDED, + } + extra_stack_items = 0 + measured: Bytecode + if opcode == "sha3": + measured = Op.SHA3( + offset=0x0, size=size, data_size=size, **memory_sizes ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x8A]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.LOG0(offset=0x0, size=Op.CALLDATALOAD(offset=0x24)) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x9C], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0xA1), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0xAD]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.LOG1(offset=0x0, size=Op.CALLDATALOAD(offset=0x24), topic_1=0x1) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0xBF], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0xA2), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0xD2]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.LOG2( + extra_stack_items = 1 + elif opcode == "calldatacopy": + measured = Op.CALLDATACOPY( + dest_offset=0x0, offset=0x0, - size=Op.CALLDATALOAD(offset=0x24), - topic_1=0x1, - topic_2=0x2, - ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0xE4], - condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0xA3), + size=size, + data_size=size, + **memory_sizes, ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0xF9]) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.LOG3( + elif opcode == "codecopy": + measured = Op.CODECOPY( + dest_offset=0x0, offset=0x0, - size=Op.CALLDATALOAD(offset=0x24), - topic_1=0x1, - topic_2=0x2, - topic_3=0x3, + size=size, + data_size=size, + **memory_sizes, ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.JUMPDEST - + Op.JUMPI( - pc=0x10B, condition=Op.EQ(Op.CALLDATALOAD(offset=0x4), 0xA4) - ) - + Op.POP(0x0) - + Op.JUMP(pc=0x122) - + Op.JUMPDEST - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.LOG4( - offset=0x0, - size=Op.CALLDATALOAD(offset=0x24), - topic_1=0x1, - topic_2=0x2, - topic_3=0x3, - topic_4=0x4, + else: + topics = int(opcode[-1]) + log_op = [Op.LOG0, Op.LOG1, Op.LOG2, Op.LOG3, Op.LOG4][topics] + topic_args: dict[str, Any] = { + f"topic_{i}": i for i in range(1, topics + 1) + } + measured = log_op( + offset=0x0, size=size, data_size=size, **topic_args, **memory_sizes ) - + Op.MSTORE(offset=0x20, value=Op.GAS) - + Op.JUMPDEST - + Op.SSTORE( - key=0x0, - value=Op.SUB( - Op.SUB(Op.MLOAD(offset=0x0), Op.MLOAD(offset=0x20)), - Op.CALLDATALOAD(offset=0x44), - ), - ) - + Op.STOP, - storage={0: 24743}, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - ) - tx_data = [ - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0x1) - + Hash(0x3A) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0x20) - + Hash(0x3A) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0x21) - + Hash(0x43) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0x40) - + Hash(0x43) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0x60) - + Hash(0x4C) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0x80) - + Hash(0x55) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0xA0) - + Hash(0x5E) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0xC0) - + Hash(0x67) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0xE0) - + Hash(0x70) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x20) - + Hash(0x100) - + Hash(0x79) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0x1) - + Hash(0x35) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0x20) - + Hash(0x35) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0x21) - + Hash(0x3B) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0x40) - + Hash(0x3B) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0x60) - + Hash(0x41) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0x80) - + Hash(0x47) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0xA0) - + Hash(0x4D) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0xC0) - + Hash(0x53) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0xE0) - + Hash(0x59) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x37) - + Hash(0x100) - + Hash(0x5F) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0x1) - + Hash(0x35) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0x20) - + Hash(0x35) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0x21) - + Hash(0x3B) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0x40) - + Hash(0x3B) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0x60) - + Hash(0x41) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0x80) - + Hash(0x47) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0xA0) - + Hash(0x4D) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0xC0) - + Hash(0x53) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0xE0) - + Hash(0x59) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("98eed7a4") - + Hash(0x39) - + Hash(0x100) - + Hash(0x5F) - + Hash(0xDEAD60A7) - + Hash(0xDEADBEEF) - + Hash(0x60A7BEEF), - Bytes("d086d23d") + Hash(0xA0) + Hash(0x0) + Hash(0x18B), - Bytes("d086d23d") + Hash(0xA0) + Hash(0x1) + Hash(0x193), - Bytes("d086d23d") + Hash(0xA0) + Hash(0x2) + Hash(0x19B), - Bytes("d086d23d") + Hash(0xA0) + Hash(0x3) + Hash(0x1A3), - Bytes("d086d23d") + Hash(0xA0) + Hash(0x4) + Hash(0x1AB), - Bytes("d086d23d") + Hash(0xA1) + Hash(0x0) + Hash(0x305), - Bytes("d086d23d") + Hash(0xA1) + Hash(0x1) + Hash(0x30D), - Bytes("d086d23d") + Hash(0xA1) + Hash(0x2) + Hash(0x315), - Bytes("d086d23d") + Hash(0xA1) + Hash(0x3) + Hash(0x31D), - Bytes("d086d23d") + Hash(0xA1) + Hash(0x4) + Hash(0x325), - Bytes("d086d23d") + Hash(0xA2) + Hash(0x0) + Hash(0x47F), - Bytes("d086d23d") + Hash(0xA2) + Hash(0x1) + Hash(0x487), - Bytes("d086d23d") + Hash(0xA2) + Hash(0x2) + Hash(0x48F), - Bytes("d086d23d") + Hash(0xA2) + Hash(0x3) + Hash(0x497), - Bytes("d086d23d") + Hash(0xA2) + Hash(0x4) + Hash(0x49F), - Bytes("d086d23d") + Hash(0xA3) + Hash(0x0) + Hash(0x5F9), - Bytes("d086d23d") + Hash(0xA3) + Hash(0x1) + Hash(0x601), - Bytes("d086d23d") + Hash(0xA3) + Hash(0x2) + Hash(0x609), - Bytes("d086d23d") + Hash(0xA3) + Hash(0x3) + Hash(0x611), - Bytes("d086d23d") + Hash(0xA3) + Hash(0x4) + Hash(0x619), - Bytes("d086d23d") + Hash(0xA4) + Hash(0x0) + Hash(0x773), - Bytes("d086d23d") + Hash(0xA4) + Hash(0x1) + Hash(0x77B), - Bytes("d086d23d") + Hash(0xA4) + Hash(0x2) + Hash(0x783), - Bytes("d086d23d") + Hash(0xA4) + Hash(0x3) + Hash(0x78B), - Bytes("d086d23d") + Hash(0xA4) + Hash(0x4) + Hash(0x793), - ] - tx_gas = [16777216] - tx_value = [1] + contract = pre.deploy_contract( + code=Op.MSTORE(offset=PREEXPAND_OFFSET, value=0x1) + + CodeGasMeasure( + code=measured, + extra_stack_items=extra_stack_items, + sstore_key=GAS_SLOT, + ), + ) tx = Transaction( protected=fork.supports_protected_txs(), - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], + sender=pre.fund_eoa(), + to=contract, ) - post = {target: Account(storage={0: 0})} + post = {contract: Account(storage={GAS_SLOT: measured.gas_cost(fork)})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_return.py b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_return.py index 272ae580ce0..8f08992aead 100644 --- a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_return.py +++ b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_gas_cost_return.py @@ -1,17 +1,22 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Measure that terminating a callee via RETURN costs the same as STOP +(by Ori Pomerantz qbzzt1@gmail.com). Ported from: state_tests/stEIP150singleCodeGasPrices/gasCostReturnFiller.yml + +@manually-enhanced: Do not overwrite. The legacy raw GAS-delta windows +(whose stored difference was asserted to be zero) are reframed as two +CodeGasMeasure windows over each CALL, asserting the same fork-derived +`call_code.gas_cost(fork)` for both, with the callee addresses threaded +dynamically instead of hardcoded. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, Fork, StateTestFiller, Transaction, @@ -21,124 +26,57 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT_STOP = 0x0 +GAS_SLOT_RETURN = 0x1 + @pytest.mark.ported_from( ["state_tests/stEIP150singleCodeGasPrices/gasCostReturnFiller.yml"], ) -@pytest.mark.valid_from("Frontier") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") def test_gas_cost_return( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xBA1A9CE0BA1A9CE) + """Measure that a callee ending in RETURN costs the same as STOP.""" + # Identical callee bodies except the terminal opcode. RETURN pops the + # two pushed words as (offset=0xFF, size=0x0); a zero-size RETURN + # expands no memory, so both callees consume exactly the same gas. + stop_body = Op.PUSH1[0x0] + Op.PUSH1[0xFF] + Op.STOP + return_body = Op.PUSH1[0x0] + Op.PUSH1[0xFF] + Op.RETURN + stop_target = pre.deploy_contract(code=stop_body) + return_target = pre.deploy_contract(code=return_body) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - # Source: raw - # 0x600060FF00 - addr = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x0] + Op.PUSH1[0xFF] + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - ) - # Source: raw - # 0x600060FFF3 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.RETURN(offset=0xFF, size=0x0), - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - ) - # Source: lll - # { - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Initialization - # - # ; Variables (0x20 byte wide) - # (def 'gasB4 0x000) ; Before the action being measured - # - # ; Gas for the STOP call - # (def 'gasSTOP 0x020) - # - # ; Gas for the RETURN call - # (def 'gasRETURN 0x040) - # - # ; Play with the variables here to avoid having the memory allocation - # ; affect the gas calculation - # [gasB4] 0x60A7 - # [gasSTOP] 0x60A7 - # [gasRETURN] 0x60A7 - # - # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # ; Run the operation - # - # [gasB4] (gas) - # (call 0x10000 0x1000 0 0 0 0 0) - # [gasSTOP] (- @gasB4 (gas)) - # - # - # [gasB4] (gas) - # (call 0x10000 0x2000 0 0 0 0 0) - # [gasRETURN] (- @gasB4 (gas)) - # ... (11 more lines) - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0x60A7) - + Op.MSTORE(offset=0x20, value=0x60A7) - + Op.MSTORE(offset=0x40, value=0x60A7) - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x10000, - address=0x1000, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.MSTORE(offset=0x20, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x10000, - address=0x2000, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + call_stop = Op.CALL(address=stop_target, address_warm=False) + call_return = Op.CALL(address=return_target, address_warm=False) + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=call_stop, + extra_stack_items=1, + sstore_key=GAS_SLOT_STOP, ) - + Op.MSTORE(offset=0x40, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.SSTORE( - key=0x0, value=Op.SUB(Op.MLOAD(offset=0x20), Op.MLOAD(offset=0x40)) - ) - + Op.STOP, - storage={0: 24743}, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, + + CodeGasMeasure( + code=call_return, + extra_stack_items=1, + sstore_key=GAS_SLOT_RETURN, + ), ) - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, - to=target, - data=Bytes("00"), - gas_limit=16777216, - value=1, - ) + tx = Transaction(sender=pre.fund_eoa(), to=contract) - post = {target: Account(storage={0: 0, 1: 0})} + # Each measured window covers the CALL plus what its callee consumed; + # the two expectations are equal by construction (STOP and a + # zero-size RETURN both cost nothing). + post = { + contract: Account( + storage={ + GAS_SLOT_STOP: call_stop.gas_cost(fork) + + stop_body.gas_cost(fork), + GAS_SLOT_RETURN: call_return.gas_cost(fork) + + return_body.gas_cost(fork), + }, + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_raw_balance_gas.py b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_raw_balance_gas.py index fb6f7b8f657..975a997199e 100644 --- a/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_raw_balance_gas.py +++ b/tests/ported_static/tangerine_whistle/stEIP150singleCodeGasPrices/test_raw_balance_gas.py @@ -1,17 +1,20 @@ """ -Test_raw_balance_gas. +Measure the gas cost of a BALANCE read of the warm transaction sender. Ported from: state_tests/stEIP150singleCodeGasPrices/RawBalanceGasFiller.json + +@manually-enhanced: Do not overwrite. The legacy raw GAS-delta window is +reframed as a CodeGasMeasure over the BALANCE opcode, asserting the +fork-derived `balance_code.gas_cost(fork)` (warm: the sender is +pre-warmed as the transaction origin). """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, Fork, StateTestFiller, Transaction, @@ -21,48 +24,32 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x1 + @pytest.mark.ported_from( ["state_tests/stEIP150singleCodeGasPrices/RawBalanceGasFiller.json"], ) @pytest.mark.valid_from("Berlin") -@pytest.mark.pre_alloc_mutable def test_raw_balance_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Test_raw_balance_gas.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Measure the gas a BALANCE of the warm transaction sender consumes.""" + sender = pre.fund_eoa() + # The sender is the transaction origin, so its address is warm. + balance_code = Op.BALANCE(address=sender, address_warm=True) + target = pre.deploy_contract( + code=CodeGasMeasure( + code=balance_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), ) - # Source: lll - # { [0] (GAS) (BALANCE ) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP(Op.BALANCE(address=sender)) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - protected=fork.supports_protected_txs(), - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) + tx = Transaction(sender=sender, to=target) - post = {target: Account(storage={1: 116})} + post = {target: Account(storage={GAS_SLOT: balance_code.gas_cost(fork)})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx)