diff --git a/.claude/commands/enhance-ported-test.md b/.claude/commands/enhance-ported-test.md
index f6d3f6852e5..0d0404b2a04 100644
--- a/.claude/commands/enhance-ported-test.md
+++ b/.claude/commands/enhance-ported-test.md
@@ -42,11 +42,12 @@ so a failure is attributable.
- **Checkpoint / done:** fill the whole `valid_from` range (omit `--fork`) so all
deployed forks are exercised.
- **Probe the future fork:** explicitly `--fork Amsterdam` (or the latest fork
- that enables new EIPs). A ported test listed in `amsterdam_skip_list.txt` will
- always show `sss` there — to see its *real* behavior, temporarily remove its
- entry from that file, fill, then restore (or, once fixed, remove it for good —
- see Finishing). A gas/state-cost change there is the most likely future
- breakage.
+ that enables new EIPs). A gas/state-cost change there is the most likely
+ future breakage. (Historical note: broken tests used to be parked in a
+ `tests/ported_static/amsterdam_skip_list.txt` consumed by a local conftest;
+ the list was emptied and both were removed. If a future fork's repricing
+ breaks tests en masse, the same parking pattern — a substring-matched skip
+ list plus a `pytest_collection_modifyitems` hook — is in git history.)
- **`fill` output:** writes to `./fixtures` (`--clean` resets it), or pass
`--output
` for a scratch location. Do **not** use `-o` — that is
pytest's `--override-ini`, not the output dir.
@@ -77,8 +78,8 @@ gas the tx receives, so the body executes fully. See `write-test.md` "Transactio
- **Gas-snapshot tests are gas-sensitive.** If the post asserts a stored `GAS`
reading or a `SUB(@gas_before, GAS)` delta (legacy slots `0` / `0x64`), the
test *measures gas* — handle it under step 10 (preserve via `CodeGasMeasure`),
- do not just strip `gas_limit`. This is the dominant `amsterdam_skip_list.txt`
- shape: the stored gas value is exactly what EIP-8037 re-prices and breaks.
+ do not just strip `gas_limit`. This was the dominant skip-list shape:
+ the stored gas value is exactly what EIP-8037 re-prices and breaks.
- **EIP-8037 caveat:** when you omit `gas_limit` on a test that *measures* an
operation incurring **state gas** (account creation, storage writes), add
`state_gas_reservoir=0` to the tx, or that state gas is silently dropped from
@@ -294,8 +295,8 @@ ties a `CREATE`'s `size` operand to the memory/gas math that depends on it.
on `test_make_money`.
### 10. (Gas-subject / gas-snapshot tests) Replace hardcoded gas with dynamic calculation
-Covers both tests that *assert* a gas amount and the dominant
-`amsterdam_skip_list.txt` shape: a legacy `GAS` snapshot / `SUB(@gas_before,
+Covers both tests that *assert* a gas amount and the dominant broken-port
+shape: a legacy `GAS` snapshot / `SUB(@gas_before,
GAS)` delta stored to slot `0`/`0x64`. That stored value is *why* EIP-8037
breaks the test, but it is real coverage — **preserve and fork-robustify it, do
not drop it.**
@@ -386,6 +387,20 @@ previous_bytes=)`; EIP-3860 init-code words → `fork.gas_costs().CODE_INIT_PER_
* ceil(size/32)`. You can also call `.gas_cost` / `.regular_cost` / `.state_cost`
on exactly the measured bytecode.
+**Reservoir-less sub-calls pay state gas from their regular grant.** With
+the tx reservoir at 0, a sub-frame's state charges spill from its own
+`gas_left` — a delegate that does one first-set SSTORE needs its *whole*
+~111k inside the forwarded grant on Amsterdam, not just the ~13k regular
+part. Size derived sub-call budgets from the callee composite's full
+`gas_cost(fork)`. Corollaries: (a) a *failed* sub-frame contributes its
+entire forfeited grant to the parent's measured window, not its "cost";
+(b) `SSTORE(flag, )` silently degrades to a ~3k no-op store when
+the call fails — the flag reads 0 and no state gas is charged, which can
+mask a broken callee behind a plausible-looking measurement. Validated on
+`test_new_gas_price_for_codes` (delegate budget derived; failed value
+calls return their stipends: subtract one `CALL_STIPEND` per failed
+value-bearing call from window measurements).
+
**Nested / callee-side measurements.** When the measured op is a `CALL` whose
callee does real work, the measured cost = `call_code.gas_cost(fork) +
callee_code.gas_cost(fork)` (the CALL's own cost plus what the callee consumed).
@@ -402,6 +417,88 @@ transition, not the magnitude — which also breaks the `forward_gas`/`new_value
circularity.) Set `state_gas_reservoir=0` so the state gas is captured.
Validated on `test_raw_call_gas`.
+**An expensive store after a callee that eats all forwarded gas — pre-write
+the slot.** When a frame must SSTORE a result *after* a subcall that
+deliberately consumes its whole 63/64 grant (an OOG-probe callee), the frame
+retains only 1/64 — under EIP-8037 that cannot afford a cold zero→nonzero
+store (~111k), and pre-8037 it often couldn't afford the cold 2.2k either
+(making the ported `{slot: 0}` expectation vacuous: caller-OOG and
+callee-failure were indistinguishable). Fix: write a sentinel to the slot
+*before* the call (paying cold + state with the full budget), then store
+`BASE + result` after it — now a dirty-warm write (100 gas) the retention
+always covers, and the three outcomes (success `BASE+1`, failure `BASE`,
+caller OOG `sentinel`) are all distinct. Validated on
+`test_static_execute_call_that_ask_fore_gas_then_trabsaction_has`.
+**Caveat — EIP-2200's stipend rule caps this trick.** Any SSTORE (even a
+100-gas dirty-warm one) exceptionally halts unless `gas_left > 2300`
+(Istanbul+), so the 1/64 retention must exceed ~2400, i.e. the pre-call
+budget must exceed ~154k. When the scenario *requires* a smaller budget
+(e.g. a starved arm whose forwarded gas must undercut the callee's cost),
+no post-call SSTORE is possible at all: write the sentinel *before* the
+call and put nothing but a `POP` after it — frame completion (the account
+persists with the sentinel) plus the callee-side observable already
+separate the outcomes. Validated on
+`test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided`.
+
+**Refund-cap derivations need the EIP-7623 kwarg.** The EIP-3529 cap's
+base is the gas deducted before execution, which excludes the calldata
+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`.
+
+**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
+build always-failing creator frames with predictable consumption.
+Validated on `test_revert_depth_create_address_collision`.
+
+**Loop-to-depth-1024 cannot replace loop-to-OOG.** With 63/64
+attenuation, reaching depth 1024 needs ~e^16 × the terminal gas — no
+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`.
+
+**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.
+
+**EIP-8037 repriced the code deposit's regular part — boundaries beware.**
+On 8037 forks the deposit charges only the keccak word cost
+(`OPCODE_KECCAK256_PER_WORD * ceil32(len)/32`, ~6 gas) as regular gas plus
+`len * 1530` state; `fork.gas_costs().CODE_DEPOSIT_PER_BYTE` (200) is the
+*pre-8037* constant. Using 200/byte in a *sufficiency* budget merely
+overshoots (safe); using it in a one-gas-short *boundary* silently funds
+the deposit on Amsterdam. Branch on `fork.is_eip_enabled(8037)` for exact
+deposit boundaries. Validated on
+`test_create_oo_gafter_init_code_returndata_size`.
+
+**Match the intrinsic calculator's kwargs to the transaction's shape.**
+`fork.transaction_intrinsic_cost_calculator()()` defaults to
+`sends_value=False`; under EIP-2780 a value-bearing transaction's intrinsic
+includes the folded value-transfer cost (~5.9k), so a derived budget or
+GAS-observation formula silently skews by that amount on Amsterdam only.
+Pass `sends_value=True` when the tx carries value — or drop an incidental
+tx `value` entirely (step 5) so the default holds. Validated on
+`test_store_gas_on_create`.
+
+**A creation transaction's top frame pays new-account state gas
+(EIP-8037) — but only for a fresh target.** When deriving a create-tx
+budget, the intrinsic calculator does not include the created account's
+state gas — add
+`fork.transaction_top_frame_state_gas(contract_creation=True)` (183,600 on
+Amsterdam, 0 before) or the whole creation silently OOGs only on the
+future fork. Exception: `prepare_dispatch` charges it only when the
+target's *pre-state* account is `EMPTY_ACCOUNT` — a prefunded create
+address pays nothing (validated on
+`test_out_of_gas_prefunded_contract_creation`, whose budgets omit the
+term). A nested CREATE's new-account state is charged to the parent
+before the 63/64 withhold and refunded if the child fails, so a derived
+budget must cover its *peak* (use the composite `gas_cost(fork)`), even
+on paths where the net is zero.
+
**Measuring forwarded gas / the EIP-150 63/64 rule (the `*_gas_ask` shape).**
Ported fillers probe "how much gas does a subcall receive when it asks for more
than is available" by pinning an absolute forwarded amount — fork-fragile,
@@ -499,11 +596,10 @@ Not yet covered by a validated walkthrough; figure out and append when hit:
## Finishing
-**Remove the skip-list entry.** Once the test passes on the future fork, delete
-its line from `tests/ported_static/amsterdam_skip_list.txt` and decrement both
-its per-directory count header (`# stXxx (N)`) and the `# Total entries:` count.
-Confirm with a full-range fill (`--fork` omitted) with the entry gone — that is
-the definition of done.
+**Confirm with a full-range fill** (`--fork` omitted) — every deployed fork
+green is the definition of done. (If a skip list is ever reintroduced for a
+future fork, also delete the test's entry and keep its count headers
+accurate.)
**Final sweep checklist** — each of these has been missed in practice; check
them one by one before calling the test done:
diff --git a/tests/ported_static/amsterdam_skip_list.txt b/tests/ported_static/amsterdam_skip_list.txt
index 7aba6f38bb0..2943eddc540 100644
--- a/tests/ported_static/amsterdam_skip_list.txt
+++ b/tests/ported_static/amsterdam_skip_list.txt
@@ -8,23 +8,19 @@
# Entries are substring-matched against each pytest nodeid (after
# stripping the fixture-format suffix in conftest.py).
#
-# Total entries: 153
+# Total entries: 109
# stAttackTest (1)
stAttackTest/test_crashing_transaction.py::test_crashing_transaction[fork_Amsterdam]
-# stBadOpcode (4)
-stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE2]
-stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE]
-stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE2]
-stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE]
+# stBadOpcode (0)
# stCallCodes (3)
stCallCodes/test_callcode_in_initcode_to_existing_contract.py::test_callcode_in_initcode_to_existing_contract[fork_Amsterdam-d0]
stCallCodes/test_callcode_in_initcode_to_existing_contract.py::test_callcode_in_initcode_to_existing_contract[fork_Amsterdam-d1]
stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py::test_callcode_in_initcode_to_existing_contract_with_value_transfer[fork_Amsterdam]
-# stCallCreateCallCodeTest (11)
+# stCallCreateCallCodeTest (8)
stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0]
stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1]
stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g2]
@@ -33,9 +29,6 @@ stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Am
stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Amsterdam--g1]
stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py::test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided[fork_Amsterdam--g0]
stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py::test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided[fork_Amsterdam--g1]
-stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g0]
-stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g1]
-stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py::test_create_name_registrator_pre_store1_not_enough_gas[fork_Amsterdam]
# stCallDelegateCodesCallCodeHomestead (1)
stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py::test_callcallcallcode_001_suicide_end[fork_Amsterdam]
@@ -73,7 +66,7 @@ stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_dept
stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v0]
stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v1]
-# stCreateTest (36)
+# stCreateTest (13)
stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-0xef-v1]
stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-contructor-revert-v1]
stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-high-nonce-v1]
@@ -87,29 +80,6 @@ stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_af
stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-ok-v1]
stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-constructor-v1]
stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-post-constr-v1]
-stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g0]
-stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g1]
-stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py::test_create_e_contract_then_call_to_non_existent_acc[fork_Amsterdam]
-stCreateTest/test_create_empty_contract_with_storage.py::test_create_empty_contract_with_storage[fork_Amsterdam]
-stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py::test_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam]
-stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py::test_create_empty_contract_with_storage_and_call_it_1wei[fork_Amsterdam]
-stCreateTest/test_create_oo_gafter_init_code_returndata_size.py::test_create_oo_gafter_init_code_returndata_size[fork_Amsterdam]
-stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Create2_Refund_NoOoG]
-stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Create_Refund_NoOoG]
-stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Refund_NoOoG2]
-stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Refund_NoOoG3]
-stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d0]
-stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d1]
-stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d2]
-stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d4]
-stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d5]
-stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d6]
-stCreateTest/test_transaction_collision_to_empty2.py::test_transaction_collision_to_empty2[fork_Amsterdam--g1-v0]
-stCreateTest/test_transaction_collision_to_empty2.py::test_transaction_collision_to_empty2[fork_Amsterdam--g1-v1]
-stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_collision_to_empty_but_code[fork_Amsterdam--g1-v0]
-stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_collision_to_empty_but_code[fork_Amsterdam--g1-v1]
-stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v0]
-stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v1]
# stDelegatecallTestHomestead (4)
stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0]
@@ -117,10 +87,8 @@ stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterd
stDelegatecallTestHomestead/test_delegatecall1024_oog.py::test_delegatecall1024_oog[fork_Amsterdam]
stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py::test_delegatecall_in_initcode_to_existing_contract[fork_Amsterdam]
-# stEIP150Specific (7)
+# stEIP150Specific (5)
stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py::test_call_ask_more_gas_on_depth2_then_transaction_has[fork_Amsterdam]
-stEIP150Specific/test_create_and_gas_inside_create.py::test_create_and_gas_inside_create[fork_Amsterdam]
-stEIP150Specific/test_delegate_call_on_eip.py::test_delegate_call_on_eip[fork_Amsterdam]
stEIP150Specific/test_new_gas_price_for_codes.py::test_new_gas_price_for_codes[fork_Amsterdam]
stEIP150Specific/test_transaction64_rule_d64e0.py::test_transaction64_rule_d64e0[fork_Amsterdam]
stEIP150Specific/test_transaction64_rule_d64m1.py::test_transaction64_rule_d64m1[fork_Amsterdam]
@@ -136,19 +104,10 @@ stEIP158Specific/test_exp_empty.py::test_exp_empty[fork_Amsterdam]
# stHomesteadSpecific (1)
stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py::test_contract_creation_oo_gdont_leave_empty_contract_via_transaction[fork_Amsterdam]
-# stInitCodeTest (7)
-stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d0-g0]
-stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d0-g1]
-stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d1-g0]
-stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d1-g1]
-stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g0]
-stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g1]
-stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g2]
+# stInitCodeTest (0)
-# stMemExpandingEIP150Calls (4)
+# stMemExpandingEIP150Calls (2)
stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py::test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls[fork_Amsterdam]
-stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py::test_call_goes_oog_on_second_level_with_mem_expanding_calls[fork_Amsterdam]
-stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py::test_create_and_gas_inside_create_with_mem_expanding_calls[fork_Amsterdam]
stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py::test_new_gas_price_for_codes_with_mem_expanding_calls[fork_Amsterdam]
# stMemoryTest (2)
@@ -189,10 +148,7 @@ stSolidityTest/test_recursive_create_contracts.py::test_recursive_create_contrac
stSolidityTest/test_test_contract_interaction.py::test_test_contract_interaction[fork_Amsterdam]
stSolidityTest/test_test_contract_suicide.py::test_test_contract_suicide[fork_Amsterdam]
-# stStaticCall (3)
-stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py::test_static_create_empty_contract_and_call_it_0wei[fork_Amsterdam]
-stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py::test_static_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam]
-stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py::test_static_execute_call_that_ask_fore_gas_then_trabsaction_has[fork_Amsterdam-d0]
+# stStaticCall (0)
# stSystemOperationsTest (5)
stSystemOperationsTest/test_ab_acalls0.py::test_ab_acalls0[fork_Amsterdam]
diff --git a/tests/ported_static/stBadOpcode/test_measure_gas.py b/tests/ported_static/stBadOpcode/test_measure_gas.py
index 5f9d16570a6..59832975c4a 100644
--- a/tests/ported_static/stBadOpcode/test_measure_gas.py
+++ b/tests/ported_static/stBadOpcode/test_measure_gas.py
@@ -1,17 +1,21 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Measure the minimum gas each opcode needs to succeed via a binary
+search (by Ori Pomerantz qbzzt1@gmail.com).
Ported from:
state_tests/stBadOpcode/measureGasFiller.yml
@manually-enhanced: Do not overwrite. A binary search measures the gas
-an opcode needs to succeed. Only the EXTCODE case shifts: it runs a
-warm `EXTCODESIZE` plus a warm `EXTCODECOPY` (the target is warmed by
-earlier search iterations), and EIP-8038 adds a flat +100 to each warm
-extcode access. The stored threshold therefore grows by the sum of the
-two opcodes' warm `(Amsterdam - Cancun)` cost deltas, derived from the
-fork's own gas model so it is exactly 0 before EIP-8038; do not
-hardcode the Amsterdam number.
+an opcode needs to succeed. The EXTCODE case runs a warm `EXTCODESIZE`
+plus a warm `EXTCODECOPY` (the target is warmed by earlier search
+iterations), and EIP-8038 adds a flat +100 to each warm extcode
+access; its threshold grows by the two opcodes' warm cost deltas. The
+CREATE/CREATE2 thresholds equal the probe bytecode's own
+`gas_cost(fork)` (EIP-8037 adds new-account state gas), and the search
+bound is supplied via calldata (same 3-byte width as the ported PUSH2
+60000, keeping JUMP targets and the CODESIZE trick intact) so those
+cases cannot saturate. All values derive from the fork's own gas
+model; do not hardcode them.
"""
import pytest
@@ -240,7 +244,12 @@ def test_measure_gas(
# sstore(0, max)
# }
contract_12 = pre.deploy_contract( # noqa: F841
- code=Op.PUSH2[0xEA60]
+ # The search's upper bound comes from calldata (word at 0x24):
+ # EIP-8037's state gas pushes the CREATE/CREATE2 thresholds past
+ # the ported PUSH2 60000 bound, and CALLDATALOAD keeps the same
+ # 3-byte width so the hand-coded JUMP targets and the CODESIZE
+ # constant trick below are unaffected.
+ code=Op.CALLDATALOAD(offset=0x24)
+ Op.ADD(Op.CALLDATALOAD(offset=0x4), 0xC0DE00)
+ Op.PUSH1[0x0]
+ Op.JUMPDEST
@@ -393,16 +402,36 @@ def test_measure_gas(
- 103
)
+ # The measured threshold for the CREATE/CREATE2 probes is exactly the
+ # probe bytecode's own cost (operand pushes + opcode); mirroring the
+ # deployed code in the metadata keeps the expectation fork-derived —
+ # EIP-8037 adds the new-account state gas and reprices the base.
+ create_probe_cost = Op.CREATE(
+ value=Op.DUP1,
+ offset=0x0,
+ size=0x200,
+ new_memory_size=0x200,
+ init_code_size=0x200,
+ ).gas_cost(fork)
+ create2_probe_cost = Op.CREATE2(
+ value=Op.DUP1,
+ offset=0x0,
+ size=0x200,
+ salt=Op.ADD(0x5A17, Op.GAS),
+ new_memory_size=0x200,
+ init_code_size=0x200,
+ ).gas_cost(fork)
+
expect_entries_: list[dict] = [
{
"indexes": {"data": [0], "gas": -1, "value": -1},
"network": [">=Cancun"],
- "result": {contract_12: Account(storage={0: 32089})},
+ "result": {contract_12: Account(storage={0: create_probe_cost})},
},
{
"indexes": {"data": [1], "gas": -1, "value": -1},
"network": [">=Cancun"],
- "result": {contract_12: Account(storage={0: 32193})},
+ "result": {contract_12: Account(storage={0: create2_probe_cost})},
},
{
"indexes": {"data": [2, 3], "gas": -1, "value": -1},
@@ -440,18 +469,22 @@ def test_measure_gas(
post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ # Second calldata word: the binary search's upper bound. The bisection
+ # boundary is independent of the starting bound, so one generous value
+ # (covering EIP-8037's ~216k CREATE threshold) works on every fork.
+ search_max = Hash(0x100000)
tx_data = [
- Bytes("693c6139") + Hash(0xF0),
- Bytes("693c6139") + Hash(0xF5),
- Bytes("693c6139") + Hash(0xF1),
- Bytes("693c6139") + Hash(0xF2),
- Bytes("693c6139") + Hash(0xF4),
- Bytes("693c6139") + Hash(0xFA),
- Bytes("693c6139") + Hash(0x51),
- Bytes("693c6139") + Hash(0x52),
- Bytes("693c6139") + Hash(0x53),
- Bytes("693c6139") + Hash(0x20),
- Bytes("693c6139") + Hash(0x3B),
+ Bytes("693c6139") + Hash(0xF0) + search_max,
+ Bytes("693c6139") + Hash(0xF5) + search_max,
+ Bytes("693c6139") + Hash(0xF1) + search_max,
+ Bytes("693c6139") + Hash(0xF2) + search_max,
+ Bytes("693c6139") + Hash(0xF4) + search_max,
+ Bytes("693c6139") + Hash(0xFA) + search_max,
+ Bytes("693c6139") + Hash(0x51) + search_max,
+ Bytes("693c6139") + Hash(0x52) + search_max,
+ Bytes("693c6139") + Hash(0x53) + search_max,
+ Bytes("693c6139") + Hash(0x20) + search_max,
+ Bytes("693c6139") + Hash(0x3B) + search_max,
]
tx_gas = [16777216]
diff --git a/tests/ported_static/stBadOpcode/test_operation_diff_gas.py b/tests/ported_static/stBadOpcode/test_operation_diff_gas.py
index bb80de6c8d9..241f8d4a4a5 100644
--- a/tests/ported_static/stBadOpcode/test_operation_diff_gas.py
+++ b/tests/ported_static/stBadOpcode/test_operation_diff_gas.py
@@ -1,11 +1,17 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Measure the minimum gas each opcode needs to succeed via a linear
+search in 100-gas steps (by Ori Pomerantz qbzzt1@gmail.com).
Ported from:
state_tests/stBadOpcode/operationDiffGasFiller.yml
@manually-enhanced: Do not overwrite. A search measures the gas an
-opcode needs to succeed. Two access classes shift under EIP-8038: the
+opcode needs to succeed. The CREATE/CREATE2 thresholds equal the probe
+bytecode's own `gas_cost(fork)` rounded up to the search step —
+EIP-8037 adds new-account and storage-set state gas — and their search
+start is supplied via calldata a few steps below the threshold so the
+linear probe loop cannot exhaust the transaction's gas on Amsterdam.
+Two access classes also shift under EIP-8038: the
CALL-family probes (`CALL`/`CALLCODE`/`DELEGATECALL`/`STATICCALL`) make
one cold account access to the callee, repricing by
`COLD_ACCOUNT_ACCESS - 2600`; the EXTCODE probe runs a cold
@@ -378,6 +384,44 @@ def test_operation_diff_gas(
# memory) so only the account-access component varies across forks.
gas_costs = fork.gas_costs()
cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600
+ # The CREATE/CREATE2 probes wrap the create in a cold zero->nonzero
+ # SSTORE of the returned address (cost depends only on the
+ # transition, so new_value=1 stands in for the address). The stored
+ # threshold is the first search step (multiples of GAS_DIFF) at or
+ # above the probe bytecode's own fork-derived cost; EIP-8037 adds
+ # the new-account and storage-set state gas. The search starts a few
+ # steps below the expected threshold (via calldata) so the linear
+ # probe loop cannot exhaust the transaction's gas on Amsterdam.
+ gas_diff = 0x64
+ create_probe_cost = Op.SSTORE(
+ key=0x0,
+ value=Op.CREATE(
+ value=Op.DUP1,
+ offset=0x0,
+ size=0x200,
+ new_memory_size=0x200,
+ init_code_size=0x200,
+ ),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ ).gas_cost(fork)
+ create2_probe_cost = Op.SSTORE(
+ key=0x0,
+ value=Op.CREATE2(
+ value=Op.DUP1,
+ offset=0x0,
+ size=0x200,
+ salt=0x5A17,
+ new_memory_size=0x200,
+ init_code_size=0x200,
+ ),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ ).gas_cost(fork)
+ create_threshold = -(-create_probe_cost // gas_diff) * gas_diff
+ create2_threshold = -(-create2_probe_cost // gas_diff) * gas_diff
extcode_probe_delta = (
Op.EXTCODESIZE.with_metadata(address_warm=False).gas_cost(fork) - 2600
) + (
@@ -394,12 +438,12 @@ def test_operation_diff_gas(
{
"indexes": {"data": [0], "gas": -1, "value": -1},
"network": [">=Cancun"],
- "result": {contract_12: Account(storage={0: 54200})},
+ "result": {contract_12: Account(storage={0: create_threshold})},
},
{
"indexes": {"data": [1], "gas": -1, "value": -1},
"network": [">=Cancun"],
- "result": {contract_12: Account(storage={0: 54300})},
+ "result": {contract_12: Account(storage={0: create2_threshold})},
},
{
"indexes": {"data": [2, 3, 4, 5], "gas": -1, "value": -1},
@@ -430,8 +474,14 @@ def test_operation_diff_gas(
post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
tx_data = [
- Bytes("048071d3") + Hash(0xF0) + Hash(0x0) + Hash(0x64),
- Bytes("048071d3") + Hash(0xF5) + Hash(0x0) + Hash(0x64),
+ Bytes("048071d3")
+ + Hash(0xF0)
+ + Hash(create_threshold - 5 * gas_diff)
+ + Hash(gas_diff),
+ Bytes("048071d3")
+ + Hash(0xF5)
+ + Hash(create2_threshold - 5 * gas_diff)
+ + Hash(gas_diff),
Bytes("048071d3") + Hash(0xF1) + Hash(0x0) + Hash(0x64),
Bytes("048071d3") + Hash(0xF2) + Hash(0x0) + Hash(0x64),
Bytes("048071d3") + Hash(0xF4) + Hash(0x0) + Hash(0x64),
diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py
index 87fb08afa78..48a1e1efced 100644
--- a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py
+++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py
@@ -1,104 +1,71 @@
"""
-Legacy Test from Christoph. J.
+Verify a name-registrator contract creation succeeds or fails with the
+transaction budget: the init code writes a storage slot and deposits the
+registrar's runtime code.
Ported from:
state_tests/stCallCreateCallCodeTest/createNameRegistratorPerTxsNotEnoughGasFiller.json
+
+@manually-enhanced: Do not overwrite. Both budgets are derived from the
+fork (intrinsic + top-frame state gas + init code execution + code deposit
+regular and state costs); the success arm also pins the deposited code and
+transferred balance, which the ported post never checked.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+TX_VALUE = 100_000
+COPY_OFFSET = 0xC
+DEPOSITED_SIZE = 0x10
+
@pytest.mark.ported_from(
[
"state_tests/stCallCreateCallCodeTest/createNameRegistratorPerTxsNotEnoughGasFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
+ "enough_gas",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1",
- ),
+ pytest.param(False, id="g0"),
+ pytest.param(True, id="g1"),
],
)
def test_create_name_registrator_per_txs_not_enough_gas(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ enough_gas: bool,
) -> None:
- """Legacy Test from Christoph."""
- 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=10000000000,
+ """An under-budgeted registrar creation leaves no account behind."""
+ # The ported init code: write slot 1, then deposit 16 bytes of
+ # registrar runtime copied from the init code's own bytes.
+ store = Op.SSTORE(
+ key=0x1, value=0x1, key_warm=False, original_value=0, new_value=1
)
-
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- compute_create_address(
- address=sender, nonce=0
- ): Account.NONEXISTENT,
- },
- },
- {
- "indexes": {"data": -1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- compute_create_address(address=sender, nonce=0): Account(
- storage={1: 1}
- ),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Op.SSTORE(key=0x1, value=0x1)
- + Op.PUSH1[0x10]
- + Op.CODECOPY(dest_offset=0x0, offset=0xC, size=Op.DUP1)
+ initcode = (
+ store
+ + Op.PUSH1[DEPOSITED_SIZE]
+ + Op.CODECOPY(
+ dest_offset=0x0,
+ offset=COPY_OFFSET,
+ size=Op.DUP1,
+ data_size=DEPOSITED_SIZE,
+ new_memory_size=0x20,
+ )
+ Op.PUSH1[0x0]
+ Op.RETURN
+ Op.STOP
@@ -109,19 +76,50 @@ def test_create_name_registrator_per_txs_not_enough_gas(
+ Op.STOP
+ Op.JUMPDEST
+ Op.SSTORE(
- key=Op.CALLDATALOAD(offset=0x0), value=Op.CALLDATALOAD(offset=0x20)
- ),
- ]
- tx_gas = [56157, 86157]
- tx_value = [100000]
+ key=Op.CALLDATALOAD(offset=0x0),
+ value=Op.CALLDATALOAD(offset=0x20),
+ )
+ )
+ deposited = bytes(initcode)[COPY_OFFSET : COPY_OFFSET + DEPOSITED_SIZE]
+
+ # Fork-derived budgets: the sufficient one covers the init code, the
+ # code deposit (regular and EIP-8037 state), and the created account's
+ # top-frame state gas; the insufficient one dies mid-init-code.
+ overhead = fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ ) + fork.transaction_top_frame_state_gas(contract_creation=True)
+ execution_cost = (
+ initcode.gas_cost(fork)
+ + DEPOSITED_SIZE * fork.gas_costs().CODE_DEPOSIT_PER_BYTE
+ + fork.code_deposit_state_gas(code_size=DEPOSITED_SIZE)
+ )
+ gas_limit = overhead + (
+ execution_cost + 5_000 if enough_gas else execution_cost // 2
+ )
+ sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ data=initcode,
+ gas_limit=gas_limit,
+ value=TX_VALUE,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ created = compute_create_address(address=sender, nonce=0)
+ if enough_gas:
+ created_account: Account | None = Account(
+ nonce=1,
+ code=deposited,
+ balance=TX_VALUE,
+ storage={1: 1},
+ )
+ else:
+ created_account = Account.NONEXISTENT
+ post = {
+ sender: Account(nonce=1),
+ created: created_account,
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py
index cc76b3587f5..212b244d922 100644
--- a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py
+++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py
@@ -1,17 +1,22 @@
"""
-Legacy Test from Christoph. J.
+Verify a nested CREATE of the name registrar whose child grant cannot cover
+the init code: the child account never materializes, while the creating
+frame completes (its nonce still advances).
Ported from:
state_tests/stCallCreateCallCodeTest/createNameRegistratorPreStore1NotEnoughGasFiller.json
+
+@manually-enhanced: Do not overwrite. The registrar init code is composed
+(not a hex blob) and the transaction budget is derived from the fork so
+the child's 63/64 grant undercuts its cost on every fork; the creator's
+balance is asserted (the endowment returns on failure).
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -21,60 +26,111 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+TX_VALUE = 0x186A0
+CREATE_VALUE = 0x17
+INITIAL_BALANCE = 10**15
+COPY_OFFSET = 0xC
+DEPOSITED_SIZE = 0x10
+
@pytest.mark.ported_from(
[
"state_tests/stCallCreateCallCodeTest/createNameRegistratorPreStore1NotEnoughGasFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_create_name_registrator_pre_store1_not_enough_gas(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Legacy Test from Christoph."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87)
- 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=100000000,
+ """A starved nested registrar creation leaves no account behind."""
+ # The registrar init code (same as the per-txs sibling): write slot 1,
+ # deposit 16 bytes of runtime copied from its own bytes.
+ initcode = (
+ Op.SSTORE(
+ key=0x1, value=0x1, key_warm=False, original_value=0, new_value=1
+ )
+ + Op.PUSH1[DEPOSITED_SIZE]
+ + Op.CODECOPY(
+ dest_offset=0x0,
+ offset=COPY_OFFSET,
+ size=Op.DUP1,
+ data_size=DEPOSITED_SIZE,
+ new_memory_size=0x20,
+ )
+ + Op.PUSH1[0x0]
+ + Op.RETURN
+ + Op.STOP
+ + Op.JUMPI(
+ pc=0x9,
+ condition=Op.ISZERO(Op.SLOAD(key=Op.CALLDATALOAD(offset=0x0))),
+ )
+ + Op.STOP
+ + Op.JUMPDEST
+ + Op.SSTORE(
+ key=Op.CALLDATALOAD(offset=0x0),
+ value=Op.CALLDATALOAD(offset=0x20),
+ )
)
+ initcode_bytes = bytes(initcode)
+ assert len(initcode_bytes) == 0x22, "ported init code is 34 bytes"
- # Source: lll
- # {(MSTORE 0 0x6001600155601080600c6000396000f3006000355415600957005b6020356000 ) (MSTORE8 32 0x35) (MSTORE8 33 0x55) (CREATE 23 0 34) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(
+ # Memory setup derived from the composed bytes (one word plus two
+ # trailing byte stores, as in the ported filler).
+ setup = (
+ Op.MSTORE(
offset=0x0,
- value=0x6001600155601080600C6000396000F3006000355415600957005B6020356000, # noqa: E501
+ value=int.from_bytes(initcode_bytes[:0x20], "big"),
+ new_memory_size=0x20,
)
- + Op.MSTORE8(offset=0x20, value=0x35)
- + Op.MSTORE8(offset=0x21, value=0x55)
- + Op.CREATE(value=0x17, offset=0x0, size=0x22)
- + Op.STOP,
- balance=0xDE0B6B3A7640000,
- nonce=0,
+ + Op.MSTORE8(
+ offset=0x20, value=initcode_bytes[0x20], new_memory_size=0x40
+ )
+ + Op.MSTORE8(
+ offset=0x21, value=initcode_bytes[0x21], new_memory_size=0x40
+ )
+ )
+ create_code = Op.CREATE(
+ value=CREATE_VALUE,
+ offset=0x0,
+ size=len(initcode_bytes),
+ new_memory_size=0x40,
+ old_memory_size=0x40,
+ init_code_size=len(initcode_bytes),
+ )
+ creator = pre.deploy_contract(
+ code=setup + Op.POP(create_code) + Op.STOP,
+ balance=INITIAL_BALANCE,
+ )
+
+ # Budget: covers the frame's own work and the CREATE's peak charge,
+ # but the child's 63/64 grant undercuts the init code plus deposit.
+ child_needed = (
+ initcode.gas_cost(fork)
+ + DEPOSITED_SIZE * fork.gas_costs().CODE_DEPOSIT_PER_BYTE
+ + fork.code_deposit_state_gas(code_size=DEPOSITED_SIZE)
+ )
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = (
+ intrinsic
+ + setup.gas_cost(fork)
+ + create_code.gas_cost(fork)
+ + child_needed // 2
)
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=73071,
- value=0x186A0,
+ sender=pre.fund_eoa(),
+ to=creator,
+ gas_limit=gas_limit,
+ value=TX_VALUE,
)
post = {
- contract_0: Account(nonce=1),
- compute_create_address(
- address=contract_0, nonce=0
- ): Account.NONEXISTENT,
+ # The CREATE advanced the nonce even though its child failed, and
+ # the endowment returned.
+ creator: Account(nonce=2, balance=INITIAL_BALANCE + TX_VALUE),
+ compute_create_address(address=creator, nonce=1): 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/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py b/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py
index 943824d3990..bac280a1984 100644
--- a/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py
+++ b/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py
@@ -1,141 +1,154 @@
"""
-Test_create_e_contract_create_ne_contract_in_init_oog_tr.
+Verify a contract-creation transaction whose init code first calls an
+existing contract and then CREATEs a child: with a full budget both the
+call and the nested creation land (the child at the creator's nonce-1
+address, not nonce 0); with a starved budget the callee and the whole
+creation fail together.
Ported from:
state_tests/stCreateTest/CREATE_EContractCreateNEContractInInitOOG_TrFiller.json
+
+@manually-enhanced: Do not overwrite. Budgets are derived from the fork
+(intrinsic + EIP-8037 top-frame and nested-create state gas + composed
+code costs), the callee call forwards all gas instead of a ported fixed
+budget, and the nested child is now asserted at its real nonce-1 address
+(the port only checked the vacuous nonce-0 address).
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CALLEE_STORED = 0xC
+
@pytest.mark.ported_from(
[
"state_tests/stCreateTest/CREATE_EContractCreateNEContractInInitOOG_TrFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@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("oog", [False, True], ids=["enough-gas", "oog"])
def test_create_e_contract_create_ne_contract_in_init_oog_tr(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ oog: bool,
) -> None:
- """Test_create_e_contract_create_ne_contract_in_init_oog_tr."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
+ """Budget decides how far a creation's call-then-CREATE init gets."""
+ sender = pre.fund_eoa()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ # Callee: one cold zero->non-zero store observed in the post.
+ callee_code = (
+ Op.SSTORE(
+ key=0x1,
+ value=CALLEE_STORED,
+ key_warm=False,
+ original_value=0,
+ new_value=CALLEE_STORED,
+ )
+ + Op.STOP
)
+ callee = pre.deploy_contract(code=callee_code)
- # Source: lll
- # {[[1]]12}
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP,
- balance=0xE8D4A51000,
- nonce=0,
+ # Child init code: return a small runtime code from memory.
+ child_runtime = Op.SSTORE(key=0x0, value=CALLEE_STORED)
+ child_initcode = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(bytes(child_runtime), "big"),
+ new_memory_size=0x20,
+ ) + Op.RETURN(
+ offset=32 - len(bytes(child_runtime)),
+ size=len(bytes(child_runtime)),
)
+ child_initcode_bytes = bytes(child_initcode)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(storage={1: 12}),
- compute_create_address(address=sender, nonce=0): Account(
- nonce=2
- ),
- compute_create_address(
- address=compute_create_address(address=sender, nonce=0),
- nonce=0,
- ): Account.NONEXISTENT,
- },
- },
- {
- "indexes": {"data": -1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(storage={1: 0}),
- compute_create_address(
- address=sender, nonce=0
- ): Account.NONEXISTENT,
- compute_create_address(
- address=compute_create_address(address=sender, nonce=0),
- nonce=0,
- ): Account.NONEXISTENT,
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ # Transaction init code: call the callee (forwarding all gas), then
+ # CREATE the child from memory; deploys nothing itself.
+ call_code = Op.POP(Op.CALL(address=callee))
+ stage_code = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(child_initcode_bytes, "big"),
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ )
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=32 - len(child_initcode_bytes),
+ size=len(child_initcode_bytes),
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ init_code_size=len(child_initcode_bytes),
+ )
+ initcode = call_code + stage_code + create_code
- tx_data = [
- Op.POP(
- Op.CALL(
- gas=0xEA60,
- address=contract_0,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
+ intrinsic = fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ )
+ if oog:
+ # Enough to start executing, but the forwarded 63/64 undercuts
+ # the callee's store and the CREATE is unaffordable after it.
+ gas_limit = intrinsic + callee_code.gas_cost(fork) // 2
+ else:
+ # Everything must land: the fresh create target's top-frame
+ # state gas (EIP-8037), the callee, and the nested creation's
+ # peak charge plus the child's execution and code deposit.
+ runtime_size = len(bytes(child_runtime))
+ child_total = (
+ child_initcode.gas_cost(fork)
+ + runtime_size * fork.gas_costs().CODE_DEPOSIT_PER_BYTE
+ + fork.code_deposit_state_gas(code_size=runtime_size)
)
- + Op.MSTORE(offset=0x0, value=0x64600C6000556000526005601BF3)
- + Op.CREATE(value=0x0, offset=0x12, size=0xE),
- ]
- tx_gas = [160000, 60000]
+ needed = (
+ intrinsic
+ + fork.transaction_top_frame_state_gas(contract_creation=True)
+ + initcode.gas_cost(fork)
+ + callee_code.gas_cost(fork)
+ + child_total
+ )
+ # Headroom for the 63/64 withhold at the call and the CREATE.
+ gas_limit = needed + needed // 63
tx = Transaction(
sender=sender,
to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- error=_exc,
+ data=initcode,
+ gas_limit=gas_limit,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ created = compute_create_address(address=sender, nonce=0)
+ # The nested CREATE runs while the creator's nonce is 1 (EIP-161),
+ # so the child lands at the nonce-1 address and the nonce-0 address
+ # must stay empty.
+ child = compute_create_address(address=created, nonce=1)
+ child_at_nonce0 = compute_create_address(address=created, nonce=0)
+
+ if oog:
+ post = {
+ sender: Account(nonce=1),
+ callee: Account(storage={1: 0}),
+ created: Account.NONEXISTENT,
+ child: Account.NONEXISTENT,
+ child_at_nonce0: Account.NONEXISTENT,
+ }
+ else:
+ post = {
+ sender: Account(nonce=1),
+ callee: Account(storage={1: CALLEE_STORED}),
+ created: Account(nonce=2, code=b""),
+ child: Account(nonce=1, code=bytes(child_runtime), storage={}),
+ child_at_nonce0: Account.NONEXISTENT,
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py b/tests/ported_static/stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py
index f752c8b1f6b..b85370ef6d2 100644
--- a/tests/ported_static/stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py
+++ b/tests/ported_static/stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py
@@ -1,18 +1,24 @@
"""
-Test_create_e_contract_then_call_to_non_existent_acc.
+Verify a CREATE of an empty contract followed by a CALL to a non-existent
+account: both operations are gas-measured, the created address and the
+call's success flag are stored, and the absent callee stays non-existent.
Ported from:
state_tests/stCreateTest/CREATE_EContract_ThenCALLToNonExistentAccFiller.json
+
+@manually-enhanced: Do not overwrite. The ported absolute GAS snapshots
+(slots 0/2/100) are re-expressed as two CodeGasMeasure windows asserted
+via the fork's gas model, the created address and call flag stay in the
+measured windows' SSTOREs, and the callee is a dynamic non-existent
+account called with all gas forwarded.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ CodeGasMeasure,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -22,80 +28,95 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CREATE_GAS_SLOT = 0x0
+ADDRESS_SLOT = 0x1
+CALL_GAS_SLOT = 0x2
+FLAG_SLOT = 0x3
+
@pytest.mark.ported_from(
[
"state_tests/stCreateTest/CREATE_EContract_ThenCALLToNonExistentAccFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_create_e_contract_then_call_to_non_existent_acc(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_create_e_contract_then_call_to_non_existent_acc."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """Measure a CREATE of an empty contract and a call to no account."""
+ absent = pre.nonexistent_account()
+
+ # CREATE over never-written memory: the all-STOP init code deposits
+ # nothing, leaving an empty account with nonce 1.
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=0x0,
+ size=0x20,
+ new_memory_size=0x20,
+ init_code_size=0x20,
+ )
+ # Storing the created address keeps it observable and folds the
+ # store into the measured window (the address is non-zero, so the
+ # placeholder new_value only sizes the zero->non-zero transition).
+ store_create = Op.SSTORE(
+ ADDRESS_SLOT,
+ create_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ # A value-less call to an absent account creates nothing on any
+ # fork; the callee consumes no gas, so the window measures only the
+ # cold CALL itself. Storing the success flag keeps it observable.
+ call_code = Op.CALL(
+ address=absent,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
+ )
+ store_flag = Op.SSTORE(
+ FLAG_SLOT,
+ call_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[0]](GAS) [[1]] (CREATE 0 0 32) [[2]](GAS) [[3]] (CALL 60000 0xe1ecf98489fa9ed60a664fc4998db699cfa39d40 0 0 0 0 0) [[100]] (GAS) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.GAS)
- + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x20))
- + Op.SSTORE(key=0x2, value=Op.GAS)
- + Op.SSTORE(
- key=0x3,
- value=Op.CALL(
- gas=0xEA60,
- address=0xE1ECF98489FA9ED60A664FC4998DB699CFA39D40,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
+ contract = pre.deploy_contract(
+ code=CodeGasMeasure(
+ code=store_create,
+ sstore_key=CREATE_GAS_SLOT,
+ )
+ + CodeGasMeasure(
+ code=store_flag,
+ sstore_key=CALL_GAS_SLOT,
)
- + Op.SSTORE(key=0x64, value=Op.GAS)
- + Op.STOP,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
)
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=600000,
+ sender=pre.fund_eoa(),
+ to=contract,
+ state_gas_reservoir=0,
)
post = {
- contract_0: Account(
+ contract: Account(
storage={
- 0: 0x8D5B6,
- 1: compute_create_address(address=contract_0, nonce=0),
- 2: 0x7ABF8,
- 3: 1,
- 100: 0x6F50B,
+ CREATE_GAS_SLOT: store_create.gas_cost(fork),
+ ADDRESS_SLOT: compute_create_address(
+ address=contract, nonce=1
+ ),
+ CALL_GAS_SLOT: store_flag.gas_cost(fork),
+ FLAG_SLOT: 1,
},
),
- compute_create_address(address=contract_0, nonce=0): Account(nonce=1),
- Address(
- 0xE1ECF98489FA9ED60A664FC4998DB699CFA39D40
- ): Account.NONEXISTENT,
+ compute_create_address(address=contract, nonce=1): Account(
+ nonce=1, code=b"", balance=0
+ ),
+ absent: 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/stCreateTest/test_create_empty_contract_with_storage.py b/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage.py
index 9ca299282ef..1f406840b63 100644
--- a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage.py
+++ b/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage.py
@@ -1,18 +1,29 @@
"""
-Test_create_empty_contract_with_storage.
+Measure CREATE of a codeless-but-storage-writing contract, and optionally
+a following CALL to it, via CodeGasMeasure.
+
+The init code writes the created account's own storage and calls a
+storage-writer contract, then deposits no code: the result is an "empty"
+(codeless) account with storage and nonce 1.
Ported from:
state_tests/stCreateTest/CREATE_EmptyContractWithStorageFiller.json
+state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json
+state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_1weiFiller.json
+
+@manually-enhanced: Do not overwrite. Three fillers folded into one
+parametrize; the init code is composed (not hex blobs) so the measured
+CREATE/CALL expectations derive from the same bytecode; the init code's
+inner CALL forwards all gas (the ported 0xEA60 budget OOGs under
+EIP-8037); the CALL success flag stays inside the measured window.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ CodeGasMeasure,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -22,78 +33,169 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+ADDRESS_SLOT = 0x1
+CREATE_GAS_SLOT = 0x2
+CALL_FLAG_SLOT = 0x3
+CALL_GAS_SLOT = 0x64
+STORED_VALUE = 0xC
+
+FORWARDED_GAS = 0xEA60
+
@pytest.mark.ported_from(
- ["state_tests/stCreateTest/CREATE_EmptyContractWithStorageFiller.json"],
+ [
+ "state_tests/stCreateTest/CREATE_EmptyContractWithStorageFiller.json",
+ "state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json", # noqa: E501
+ "state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_1weiFiller.json", # noqa: E501
+ ],
+)
+@pytest.mark.valid_from("Berlin")
+@pytest.mark.parametrize(
+ "call_created, call_value",
+ [
+ pytest.param(False, 0, id="with_storage"),
+ pytest.param(True, 0, id="and_call_it_0wei"),
+ pytest.param(True, 1, id="and_call_it_1wei"),
+ ],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
def test_create_empty_contract_with_storage(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
+ call_created: bool,
+ call_value: int,
) -> None:
- """Test_create_empty_contract_with_storage."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """Measure CREATE (and optionally CALL) gas for a storage-only account."""
+ # Called by the init code below; writes one cold fresh slot.
+ writer_store = Op.SSTORE(
+ key=0x1,
+ value=STORED_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=STORED_VALUE,
)
+ writer = pre.deploy_contract(code=writer_store + Op.STOP)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ # The init code writes the created account's own slot 0 and calls the
+ # writer, then runs off its end (STOP) so no code is deposited. The
+ # inner CALL forwards all remaining gas (default Op.GAS operand).
+ initcode = Op.SSTORE(
+ key=0x0,
+ value=STORED_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=STORED_VALUE,
+ ) + Op.CALL(
+ address=writer,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
)
+ initcode_bytes = bytes(initcode)
+ assert len(initcode_bytes) <= 0x40, "init code must fit two MSTORE words"
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[0]](GAS) (MSTORE 0 0x600c6000556000600060006000600073c94f5374fce5edbc8e2a8697c1533167) (MSTORE 32 0x7e6ebf0b61ea60f1000000000000000000000000000000000000000000000000) [[1]] (CREATE 0 0 64) [[100]] (GAS) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.GAS)
- + Op.MSTORE(
- offset=0x0,
- value=0x600C6000556000600060006000600073C94F5374FCE5EDBC8E2A8697C1533167, # noqa: E501
- )
- + Op.MSTORE(
- offset=0x20,
- value=0x7E6EBF0B61EA60F1000000000000000000000000000000000000000000000000, # noqa: E501
- )
- + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x40))
- + Op.SSTORE(key=0x64, value=Op.GAS)
- + Op.STOP,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ # Memory is populated (and expanded to 0x40) before the measured
+ # window, so the CREATE itself expands nothing.
+ setup = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(
+ initcode_bytes[:0x20].ljust(0x20, b"\x00"), "big"
+ ),
+ ) + Op.MSTORE(
+ offset=0x20,
+ value=int.from_bytes(
+ initcode_bytes[0x20:].ljust(0x20, b"\x00"), "big"
+ ),
+ )
+
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=0x0,
+ size=len(initcode_bytes),
+ new_memory_size=0x40,
+ old_memory_size=0x40,
+ init_code_size=len(initcode_bytes),
)
- # Source: lll
- # {[[1]]12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP,
- balance=0xE8D4A51000,
- nonce=0,
- address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ # The created address is stored inside the measured window (as in the
+ # ported filler) so the optional CALL can target it at runtime.
+ create_store = Op.SSTORE(
+ key=ADDRESS_SLOT,
+ value=create_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
+ # The created account exists (nonce 1) and is warm (CREATE accessed
+ # it); the CALL success flag is stored inside the measured window — a
+ # wrongly failed call would otherwise be unobservable for the 0wei arm.
+ call_code = Op.CALL(
+ gas=FORWARDED_GAS,
+ address=Op.SLOAD(key=ADDRESS_SLOT, key_warm=True),
+ value=call_value,
+ address_warm=True,
+ value_transfer=call_value > 0,
+ account_new=False,
+ )
+ call_store = Op.SSTORE(
+ key=CALL_FLAG_SLOT,
+ value=call_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+
+ code = setup + CodeGasMeasure(
+ code=create_store,
+ extra_stack_items=0,
+ sstore_key=CREATE_GAS_SLOT,
+ )
+ if call_created:
+ code += CodeGasMeasure(
+ code=call_store,
+ extra_stack_items=0,
+ sstore_key=CALL_GAS_SLOT,
+ )
+ contract = pre.deploy_contract(code=code, balance=call_value)
+
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=600000,
+ sender=pre.fund_eoa(),
+ to=contract,
+ state_gas_reservoir=0,
+ )
+
+ # The measured CREATE includes the child's work: the init code's own
+ # consumption plus the writer's store it calls.
+ measured_create = (
+ create_store.gas_cost(fork)
+ + initcode.gas_cost(fork)
+ + writer_store.gas_cost(fork)
)
+ # A value-bearing CALL whose codeless callee consumes nothing measures
+ # gas_cost minus the stipend (forwarded then returned unused).
+ stipend = fork.gas_costs().CALL_STIPEND if call_value else 0
+ measured_call = call_store.gas_cost(fork) - stipend
+
+ created = compute_create_address(address=contract, nonce=1)
+ contract_storage: dict = {
+ ADDRESS_SLOT: created,
+ CREATE_GAS_SLOT: measured_create,
+ }
+ if call_created:
+ contract_storage[CALL_FLAG_SLOT] = 1
+ contract_storage[CALL_GAS_SLOT] = measured_call
post = {
- contract_0: Account(
- storage={
- 0: 0x8D5B6,
- 1: compute_create_address(address=contract_0, nonce=0),
- 100: 0x6F4F0,
- },
+ contract: Account(storage=contract_storage, balance=0),
+ # Codeless, but with storage and (for the 1wei arm) the value the
+ # measured CALL transferred — proving both the init code and the
+ # CALL executed.
+ created: Account(
+ nonce=1,
+ balance=call_value if call_created else 0,
+ storage={0: STORED_VALUE},
),
- compute_create_address(address=contract_0, nonce=0): Account(nonce=1),
- contract_1: Account(storage={1: 12}),
+ writer: Account(storage={1: STORED_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/stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py b/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py
deleted file mode 100644
index d7940716427..00000000000
--- a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py
+++ /dev/null
@@ -1,112 +0,0 @@
-"""
-Test_create_empty_contract_with_storage_and_call_it_0wei.
-
-Ported from:
-state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json
-"""
-
-import pytest
-from execution_testing import (
- Account,
- Address,
- Alloc,
- Bytes,
- Environment,
- StateTestFiller,
- Transaction,
- compute_create_address,
-)
-from execution_testing.vm import Op
-
-REFERENCE_SPEC_GIT_PATH = "N/A"
-REFERENCE_SPEC_VERSION = "N/A"
-
-
-@pytest.mark.ported_from(
- [
- "state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json" # noqa: E501
- ],
-)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
-def test_create_empty_contract_with_storage_and_call_it_0wei(
- state_test: StateTestFiller,
- pre: Alloc,
-) -> None:
- """Test_create_empty_contract_with_storage_and_call_it_0wei."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- 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) (MSTORE 0 0x600c6000556000600060006000600073c94f5374fce5edbc8e2a8697c1533167) (MSTORE 32 0x7e6ebf0b61ea60f1000000000000000000000000000000000000000000000000) [[1]] (CREATE 0 0 64) [[2]] (GAS) [[3]] (CALL 60000 (SLOAD 1) 0 0 0 0 0) [[100]] (GAS) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.GAS)
- + Op.MSTORE(
- offset=0x0,
- value=0x600C6000556000600060006000600073C94F5374FCE5EDBC8E2A8697C1533167, # noqa: E501
- )
- + Op.MSTORE(
- offset=0x20,
- value=0x7E6EBF0B61EA60F1000000000000000000000000000000000000000000000000, # noqa: E501
- )
- + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x40))
- + Op.SSTORE(key=0x2, value=Op.GAS)
- + Op.SSTORE(
- key=0x3,
- value=Op.CALL(
- gas=0xEA60,
- address=Op.SLOAD(key=0x1),
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x64, value=Op.GAS)
- + Op.STOP,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
- )
- # Source: lll
- # {[[1]]12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP,
- balance=0xE8D4A51000,
- nonce=0,
- address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
- )
-
- tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=600000,
- )
-
- post = {
- contract_0: Account(
- storage={
- 0: 0x8D5B6,
- 1: compute_create_address(address=contract_0, nonce=0),
- 2: 0x6F4F0,
- 3: 1,
- 100: 0x64763,
- },
- ),
- compute_create_address(address=contract_0, nonce=0): Account(nonce=1),
- contract_1: Account(storage={1: 12}),
- }
-
- state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py b/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py
deleted file mode 100644
index cbd15afeba3..00000000000
--- a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""
-Test_create_empty_contract_with_storage_and_call_it_1wei.
-
-Ported from:
-state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_1weiFiller.json
-"""
-
-import pytest
-from execution_testing import (
- Account,
- Address,
- Alloc,
- Bytes,
- Environment,
- StateTestFiller,
- Transaction,
- compute_create_address,
-)
-from execution_testing.vm import Op
-
-REFERENCE_SPEC_GIT_PATH = "N/A"
-REFERENCE_SPEC_VERSION = "N/A"
-
-
-@pytest.mark.ported_from(
- [
- "state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_1weiFiller.json" # noqa: E501
- ],
-)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
-def test_create_empty_contract_with_storage_and_call_it_1wei(
- state_test: StateTestFiller,
- pre: Alloc,
-) -> None:
- """Test_create_empty_contract_with_storage_and_call_it_1wei."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- 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) (MSTORE 0 0x600c6000556000600060006000600073c94f5374fce5edbc8e2a8697c1533167) (MSTORE 32 0x7e6ebf0b61ea60f1000000000000000000000000000000000000000000000000) [[1]] (CREATE 0 0 64) [[2]] (GAS) [[3]] (CALL 60000 (SLOAD 1) 1 0 0 0 0) [[100]] (GAS) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.GAS)
- + Op.MSTORE(
- offset=0x0,
- value=0x600C6000556000600060006000600073C94F5374FCE5EDBC8E2A8697C1533167, # noqa: E501
- )
- + Op.MSTORE(
- offset=0x20,
- value=0x7E6EBF0B61EA60F1000000000000000000000000000000000000000000000000, # noqa: E501
- )
- + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x40))
- + Op.SSTORE(key=0x2, value=Op.GAS)
- + Op.SSTORE(
- key=0x3,
- value=Op.CALL(
- gas=0xEA60,
- address=Op.SLOAD(key=0x1),
- value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x64, value=Op.GAS)
- + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
- )
- # Source: lll
- # {[[1]]12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP,
- balance=0xE8D4A51000,
- nonce=0,
- address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
- )
-
- tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=600000,
- )
-
- post = {
- contract_0: Account(
- storage={
- 0: 0x8D5B6,
- 1: compute_create_address(address=contract_0, nonce=0),
- 2: 0x6F4F0,
- 3: 1,
- 100: 0x62D37,
- },
- ),
- compute_create_address(address=contract_0, nonce=0): Account(
- storage={0: 12}, balance=1
- ),
- contract_1: Account(storage={1: 12}),
- }
-
- state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata_size.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata_size.py
index 6f458740e9d..aaa2962d0ec 100644
--- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata_size.py
+++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata_size.py
@@ -1,17 +1,23 @@
"""
-Calls a contract that runs CREATE which deploy a code. then OOG happens...
+Verify a CREATE whose child completes its init code but cannot afford the
+code deposit: the creation fails (no account is deployed), yet the parent
+frame survives on its 63/64 retention and the transaction succeeds.
Ported from:
state_tests/stCreateTest/CreateOOGafterInitCodeReturndataSizeFiller.json
+
+@manually-enhanced: Do not overwrite. The gas limit is derived from the
+fork so the child's 63/64 grant covers init execution but not the code
+deposit (including EIP-8037 deposit state gas), and the budget carries
+the CREATE's peak new-account state charge, refunded when the child
+fails.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -21,57 +27,99 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+TX_VALUE = 1
+
@pytest.mark.ported_from(
[
"state_tests/stCreateTest/CreateOOGafterInitCodeReturndataSizeFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_create_oo_gafter_init_code_returndata_size(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Calls a contract that runs CREATE which deploy a code."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
+ """CREATE fails at the code deposit; the parent frame completes."""
+ # The child would deploy two stores; it never runs, only its deposit
+ # price matters.
+ child_runtime = Op.SSTORE(key=0x1, value=0x1) + Op.SSTORE(
+ key=0x2, value=0x1
+ )
+ runtime_size = len(bytes(child_runtime))
+
+ # Child init code: return the runtime code from memory.
+ child_initcode = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(bytes(child_runtime), "big"),
+ new_memory_size=0x20,
+ ) + Op.RETURN(
+ offset=32 - runtime_size,
+ size=runtime_size,
+ )
+ initcode_bytes = bytes(child_initcode)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ # Parent: stage the init code in memory, CREATE from it, then read
+ # RETURNDATASIZE (zero after the deposit failure) before stopping.
+ stage_code = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(initcode_bytes, "big"),
+ new_memory_size=0x20,
)
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=32 - len(initcode_bytes),
+ size=len(initcode_bytes),
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ init_code_size=len(initcode_bytes),
+ )
+ tail_code = Op.POP + Op.EXP(0x2, Op.RETURNDATASIZE) + Op.STOP
+ contract = pre.deploy_contract(code=stage_code + create_code + tail_code)
- # Source: lll
- # { (MSTORE 0 0x6960016001556001600255600052600a6016f3) (CREATE 0 13 19) (EXP 2 (RETURNDATASIZE)) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(
- offset=0x0, value=0x6960016001556001600255600052600A6016F3
+ # Grant the child enough for its init execution but one gas short of
+ # the code deposit, so the deposit is what fails. Under EIP-8037 the
+ # regular deposit cost is only the keccak word cost (the per-byte
+ # price moved into deposit state gas); before it, 200 per byte.
+ child_exec = child_initcode.gas_cost(fork)
+ if fork.is_eip_enabled(8037):
+ deposit_regular = fork.gas_costs().OPCODE_KECCAK256_PER_WORD * (
+ (runtime_size + 31) // 32
)
- + Op.POP(Op.CREATE(value=0x0, offset=0xD, size=0x13))
- + Op.EXP(0x2, Op.RETURNDATASIZE)
- + Op.STOP,
- nonce=0,
+ else:
+ deposit_regular = runtime_size * fork.gas_costs().CODE_DEPOSIT_PER_BYTE
+ deposit = deposit_regular + fork.code_deposit_state_gas(
+ code_size=runtime_size
+ )
+ available = (child_exec + deposit - 1) * 64 // 63
+ forwarded = available - available // 64
+ assert child_exec <= forwarded < child_exec + deposit, (
+ "63/64 grant must cover init execution but not the deposit"
+ )
+ # The parent's 1/64 retention must still afford the tail.
+ assert available // 64 > tail_code.gas_cost(fork), (
+ "retention must cover the post-CREATE tail"
+ )
+
+ gas_limit = (
+ fork.transaction_intrinsic_cost_calculator()(sends_value=True)
+ + stage_code.gas_cost(fork)
+ + create_code.gas_cost(fork)
+ + available
)
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=55054,
- value=1,
+ sender=pre.fund_eoa(),
+ to=contract,
+ gas_limit=gas_limit,
+ value=TX_VALUE,
)
post = {
- contract_0: Account(balance=1),
- compute_create_address(
- address=contract_0, nonce=0
- ): Account.NONEXISTENT,
+ # The transferred value proves the parent frame completed.
+ contract: Account(balance=TX_VALUE, storage={}),
+ compute_create_address(address=contract, nonce=1): 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/stCreateTest/test_create_oog_from_call_refunds.py b/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py
index 2f7088ae13d..307bc3d7c06 100644
--- a/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py
+++ b/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py
@@ -1,8 +1,17 @@
"""
-Test_create_oog_from_call_refunds.
+Verify that gas refunds earned during (or via calls made from) a CREATE's
+init code cannot rescue the creation from an out-of-gas failure: each OoG
+variant burns the whole budget through the dispatcher's INVALID, while
+the NoOoG variants deploy and keep their refunds.
Ported from:
state_tests/stCreateTest/CreateOOGFromCallRefundsFiller.yml
+
+@manually-enhanced: Do not overwrite. The gas limit and the sender's
+exact prefund are derived from the fork so the child's 63/64 grant
+covers the deepest nested-create chain (EIP-8037 state gas included)
+yet stays below the 5000-byte code-deposit price that drives the OoG
+arms.
"""
import pytest
@@ -12,7 +21,6 @@
Address,
Alloc,
Bytes,
- Environment,
Hash,
StateTestFiller,
Transaction,
@@ -28,6 +36,11 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# Returning this much code makes the deposit unaffordable in the OoG
+# variants; the gas limit below is derived against it.
+OOG_DEPOSIT_SIZE = 0x1388
+TX_GAS_PRICE = 10
+
@pytest.mark.ported_from(
["state_tests/stCreateTest/CreateOOGFromCallRefundsFiller.yml"],
@@ -191,8 +204,7 @@ def test_create_oog_from_call_refunds(
g: int,
v: int,
) -> None:
- """Test_create_oog_from_call_refunds."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
+ """Refunds earned inside a creation cannot avert its OOG."""
contract_0 = Address(0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)
contract_1 = Address(0x000000000000000000000000000000000000001A)
contract_2 = Address(0x000000000000000000000000000000000000001B)
@@ -226,15 +238,38 @@ def test_create_oog_from_call_refunds(
key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
+ # Budget: covers the deepest NoOoG chain (a nested CREATE with
+ # EIP-8037 peak state gas at each level) while any init frame's
+ # 63/64 grant stays below the OoG arms' code-deposit price. The
+ # intrinsic bound uses all-non-zero calldata (selector + address).
+ gas_costs = fork.gas_costs()
+ intrinsic = fork.transaction_intrinsic_cost_calculator()(
+ calldata=b"\xff" * 36
+ )
+ create_op = Op.CREATE(
+ value=0x0,
+ offset=0x0,
+ size=0x40,
+ new_memory_size=0x40,
+ init_code_size=0x40,
+ )
+ gas_limit = (
+ intrinsic
+ + create_op.gas_cost(fork)
+ + OOG_DEPOSIT_SIZE * gas_costs.CODE_DEPOSIT_PER_BYTE
+ )
+ deposit_price = (
+ OOG_DEPOSIT_SIZE * gas_costs.CODE_DEPOSIT_PER_BYTE
+ + fork.code_deposit_state_gas(code_size=OOG_DEPOSIT_SIZE)
+ )
+ # No init frame can receive enough to pay the OoG arms' deposit.
+ grant_bound = gas_limit - intrinsic - create_op.execution_cost(fork)
+ assert grant_bound * 63 // 64 < deposit_price, (
+ "63/64 grant must stay below the OoG deposit price"
)
- pre[sender] = Account(balance=0x3D0900, nonce=1)
+ # The exact prefund makes "all gas burned" observable as balance 0.
+ pre[sender] = Account(balance=gas_limit * TX_GAS_PRICE, nonce=1)
# Source: yul
# berlin
# {
@@ -294,7 +329,7 @@ def test_create_oog_from_call_refunds(
code=Op.SSTORE(key=0x0, value=0x1)
+ Op.SSTORE(key=Op.DUP1, value=0x1)
+ Op.SSTORE(key=0x1, value=0x0)
- + Op.RETURN(offset=0x0, size=0x1388),
+ + Op.RETURN(offset=0x0, size=OOG_DEPOSIT_SIZE),
nonce=0,
address=Address(0x000000000000000000000000000000000000001B), # noqa: E501
)
@@ -464,7 +499,7 @@ def test_create_oog_from_call_refunds(
ret_offset=Op.DUP1,
ret_size=0x0,
)
- + Op.RETURN(offset=0x0, size=0x1388),
+ + Op.RETURN(offset=0x0, size=OOG_DEPOSIT_SIZE),
nonce=0,
address=Address(0x000000000000000000000000000000000000002B), # noqa: E501
)
@@ -560,7 +595,7 @@ def test_create_oog_from_call_refunds(
ret_offset=Op.DUP1,
ret_size=0x0,
)
- + Op.RETURN(offset=0x0, size=0x1388),
+ + Op.RETURN(offset=0x0, size=OOG_DEPOSIT_SIZE),
nonce=0,
address=Address(0x000000000000000000000000000000000000004B), # noqa: E501
)
@@ -583,7 +618,7 @@ def test_create_oog_from_call_refunds(
ret_offset=Op.DUP1,
ret_size=0x0,
)
- + Op.RETURN(offset=0x0, size=0x1388),
+ + Op.RETURN(offset=0x0, size=OOG_DEPOSIT_SIZE),
nonce=0,
address=Address(0x000000000000000000000000000000000000003B), # noqa: E501
)
@@ -655,7 +690,7 @@ def test_create_oog_from_call_refunds(
ret_offset=Op.DUP1,
ret_size=0x0,
)
- + Op.RETURN(offset=0x0, size=0x1388),
+ + Op.RETURN(offset=0x0, size=OOG_DEPOSIT_SIZE),
nonce=0,
address=Address(0x000000000000000000000000000000000000005B), # noqa: E501
)
@@ -745,7 +780,7 @@ def test_create_oog_from_call_refunds(
ret_offset=Op.DUP1,
ret_size=0x0,
)
- + Op.RETURN(offset=0x0, size=0x1388),
+ + Op.RETURN(offset=0x0, size=OOG_DEPOSIT_SIZE),
nonce=0,
address=Address(0x000000000000000000000000000000000000006B), # noqa: E501
)
@@ -789,7 +824,7 @@ def test_create_oog_from_call_refunds(
code=Op.SSTORE(key=0x0, value=0x1)
+ Op.SSTORE(key=Op.DUP1, value=0x1)
+ Op.SSTORE(key=0x1, value=0x0)
- + Op.PUSH2[0x1388]
+ + Op.PUSH2[OOG_DEPOSIT_SIZE]
+ Op.PUSH1[0x1]
+ Op.PUSH1[0x0]
+ Op.PUSH3[0xC0DE1]
@@ -855,7 +890,7 @@ def test_create_oog_from_call_refunds(
code=Op.SSTORE(key=0x0, value=0x1)
+ Op.SSTORE(key=Op.DUP1, value=0x1)
+ Op.SSTORE(key=0x1, value=0x0)
- + Op.PUSH2[0x1388]
+ + Op.PUSH2[OOG_DEPOSIT_SIZE]
+ Op.PUSH1[0x1]
+ Op.PUSH1[0x0]
+ Op.PUSH3[0xC0DE1]
@@ -1123,15 +1158,14 @@ def test_create_oog_from_call_refunds(
Bytes("693c6139") + Hash(contract_23, left_padding=True),
Bytes("693c6139") + Hash(contract_24, left_padding=True),
]
- tx_gas = [400000]
-
tx = Transaction(
sender=sender,
to=contract_0,
data=tx_data[d],
- gas_limit=tx_gas[g],
+ gas_limit=gas_limit,
+ gas_price=TX_GAS_PRICE,
nonce=1,
error=_exc,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreateTest/test_create_results.py b/tests/ported_static/stCreateTest/test_create_results.py
index a18d11e9cc6..1a92cd79ad2 100644
--- a/tests/ported_static/stCreateTest/test_create_results.py
+++ b/tests/ported_static/stCreateTest/test_create_results.py
@@ -1,224 +1,139 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Verify the value CREATE/CREATE2 leaves on the stack, the returndata, and
+the deployed code for each constructor outcome — success, OOG, empty
+revert, revert with data, empty deploy, and in-init SELFDESTRUCT — plus
+each CALL-kind's result when calling the successfully created contract,
+and the frame-aborting RETURNDATACOPY past an empty return buffer.
+
+Written by Ori Pomerantz (qbzzt1@gmail.com).
Ported from:
state_tests/stCreateTest/CreateResultsFiller.yml
+
+@manually-enhanced: Do not overwrite. The ported PUSH2 0xFFFF sub-call
+budgets are replaced by length-preserving forward-all-gas sequences (the
+fixed budget starves EIP-8037 state gas), the created accounts are now
+asserted per case (code, nonce, or non-existence), and the per-case
+posts are an explicit switch over the decoded calldata triple.
"""
import pytest
from execution_testing import (
- EOA,
Account,
Address,
Alloc,
Bytes,
- Environment,
+ Fork,
Hash,
StateTestFiller,
Transaction,
+ compute_create2_address,
+ compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# The dispatcher's LLL-derived bytecode hardcodes every jump target and
+# code-copy offset, so all edits below preserve instruction lengths.
+CONTRACT_1_ADDRESS = 0x60A7
+CREATE2_SALT = 0x5A17
+# PC values the dispatcher snapshots right after the create (slot 0x20)
+# and after the call section (slot 0x21); fixed by the code layout.
+CREATE_PC = 295
+CALL_PC = 551
+# Below the SHA3-OOG constructor's memory-expansion cost (which must
+# fail) and above Amsterdam's state-gas needs (which must not).
+TX_GAS = 9_437_184
+
+# Calldata triples (creation kind, call kind, constructor kind) in the
+# ported data order. creation: 1=CREATE, 2=CREATE2. call: 0=none,
+# 1=CALL, 2=CALLCODE, 3=DELEGATECALL, 4=STATICCALL. constructor:
+# 0/4=success, 1=OOG, 2=revert, 3=revert-with-data, 5=empty deploy,
+# 6=SELFDESTRUCT in init (4 also RETURNDATACOPYs past the empty
+# return buffer, aborting the whole dispatcher frame).
+CASES: list[tuple[int, int, int]] = [
+ (1, 1, 0),
+ (1, 2, 0),
+ (1, 3, 0),
+ (1, 4, 0),
+ (2, 1, 0),
+ (2, 2, 0),
+ (2, 3, 0),
+ (2, 4, 0),
+ (1, 0, 1),
+ (2, 0, 1),
+ (1, 0, 2),
+ (2, 0, 2),
+ (1, 0, 5),
+ (2, 0, 5),
+ (1, 0, 6),
+ (2, 0, 6),
+ (1, 0, 3),
+ (2, 0, 3),
+ (1, 1, 4),
+ (1, 2, 4),
+ (1, 3, 4),
+ (1, 4, 4),
+ (2, 1, 4),
+ (2, 2, 4),
+ (2, 3, 4),
+ (2, 4, 4),
+]
+
+# Constructor fragment (offset, size) within the dispatcher's code:
+# the dispatcher CODECOPYs these windows as the init code it creates
+# from, keyed by the constructor kind.
+FRAGMENTS: dict[int, tuple[int, int]] = {
+ 0: (0x250, 0x21),
+ 1: (0x271, 0x29),
+ 2: (0x29A, 0x26),
+ 3: (0x2C0, 0x2C),
+ 4: (0x250, 0x21),
+ 5: (0x2EC, 0x28),
+ 6: (0x314, 0x2A),
+}
+
@pytest.mark.ported_from(
["state_tests/stCreateTest/CreateResultsFiller.yml"],
)
@pytest.mark.valid_from("Cancun")
-@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.mark.parametrize("d", range(len(CASES)), ids=lambda d: f"d{d}")
@pytest.mark.pre_alloc_mutable
def test_create_results(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
d: int,
- g: int,
- v: int,
) -> None:
- """Ori Pomerantz qbzzt1@gmail."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC)
- contract_1 = Address(0x00000000000000000000000000000000000060A7)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
- )
+ """Verify create results and follow-up calls per constructor kind."""
+ creation, call_kind, constructor = CASES[d]
+ contract_1 = Address(CONTRACT_1_ADDRESS)
+ sender = pre.fund_eoa()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
+ # Length-preserving stand-in for the ported PUSH2 0xFFFF gas
+ # operand: two JUMPDESTs pad the 3-byte slot so every hardcoded
+ # jump target and code-copy offset stays valid, while GAS forwards
+ # everything (a fixed budget starves EIP-8037 state gas).
+ forward_all_gas = Op.JUMPDEST + Op.JUMPDEST + Op.GAS
+
+ # The 18-byte contract each successful constructor deploys: call
+ # contract_1 (as a 2-byte push, part of the fixed layout) and stop.
+ contract_code = (
+ Op.CALL(
+ gas=forward_all_gas,
+ address=CONTRACT_1_ADDRESS,
+ value=0x0,
+ args_offset=0x0,
+ args_size=0x0,
+ ret_offset=0x0,
+ ret_size=0x0,
+ )
+ + Op.STOP
)
- pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE)
# Source: lll
# {
# ; Variables are 0x20 bytes (= 256 bits) apart, except for
@@ -251,8 +166,8 @@ def test_create_results(
# )
# ; I did not want to rely on knowing the address at which the contract
# ... (138 more lines)
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x100, value=Op.CALLDATALOAD(offset=0x4))
+ dispatcher_code = (
+ Op.MSTORE(offset=0x100, value=Op.CALLDATALOAD(offset=0x4))
+ Op.MSTORE(offset=0x120, value=Op.CALLDATALOAD(offset=0x24))
+ Op.MSTORE(offset=0x140, value=Op.CALLDATALOAD(offset=0x44))
+ Op.JUMPI(
@@ -386,7 +301,7 @@ def test_create_results(
+ Op.MSTORE(
offset=0x640,
value=Op.CALL(
- gas=0xFFFF,
+ gas=forward_all_gas,
address=Op.MLOAD(offset=0x600),
value=0x0,
args_offset=0x0,
@@ -403,7 +318,7 @@ def test_create_results(
+ Op.MSTORE(
offset=0x640,
value=Op.CALLCODE(
- gas=0xFFFF,
+ gas=forward_all_gas,
address=Op.MLOAD(offset=0x600),
value=0x0,
args_offset=0x0,
@@ -420,7 +335,7 @@ def test_create_results(
+ Op.MSTORE(
offset=0x640,
value=Op.DELEGATECALL(
- gas=0xFFFF,
+ gas=forward_all_gas,
address=Op.MLOAD(offset=0x600),
args_offset=0x0,
args_size=0x0,
@@ -436,7 +351,7 @@ def test_create_results(
+ Op.MSTORE(
offset=0x640,
value=Op.STATICCALL(
- gas=0xFFFF,
+ gas=forward_all_gas,
address=Op.MLOAD(offset=0x600),
args_offset=0x0,
args_size=0x0,
@@ -463,16 +378,7 @@ def test_create_results(
+ Op.RETURN
+ Op.STOP
+ Op.INVALID
- + Op.CALL(
- gas=0xFFFF,
- address=0x60A7,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP
+ + contract_code
+ Op.POP(Op.SHA3(offset=0x0, size=0x2FFFFF))
+ Op.PUSH1[0x12]
+ Op.CODECOPY(dest_offset=0x200, offset=0x17, size=Op.DUP1)
@@ -480,16 +386,7 @@ def test_create_results(
+ Op.RETURN
+ Op.STOP
+ Op.INVALID
- + Op.CALL(
- gas=0xFFFF,
- address=0x60A7,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP
+ + contract_code
+ Op.REVERT(offset=0x0, size=0x0)
+ Op.PUSH1[0x12]
+ Op.CODECOPY(dest_offset=0x200, offset=0x14, size=Op.DUP1)
@@ -497,16 +394,7 @@ def test_create_results(
+ Op.RETURN
+ Op.STOP
+ Op.INVALID
- + Op.CALL(
- gas=0xFFFF,
- address=0x60A7,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP
+ + contract_code
+ Op.MSTORE(offset=0x0, value=0x60A7)
+ Op.REVERT(offset=0x0, size=0x20)
+ Op.PUSH1[0x12]
@@ -515,16 +403,7 @@ def test_create_results(
+ Op.RETURN
+ Op.STOP
+ Op.INVALID
- + Op.CALL(
- gas=0xFFFF,
- address=0x60A7,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP
+ + contract_code
+ Op.MSTORE(offset=0x0, value=0x60A7)
+ Op.STOP
+ Op.PUSH1[0x12]
@@ -533,16 +412,7 @@ def test_create_results(
+ Op.RETURN
+ Op.STOP
+ Op.INVALID
- + Op.CALL(
- gas=0xFFFF,
- address=0x60A7,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP
+ + contract_code
+ Op.MSTORE(offset=0x0, value=0x60A7)
+ Op.SELFDESTRUCT(address=0x0)
+ Op.PUSH1[0x12]
@@ -551,26 +421,26 @@ def test_create_results(
+ Op.RETURN
+ Op.STOP
+ Op.INVALID
- + Op.CALL(
- gas=0xFFFF,
- address=0x60A7,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP
- + Op.CALL(
- gas=0xFFFF,
- address=0x60A7,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP,
+ + contract_code
+ + contract_code
+ )
+
+ # Guard the hardcoded layout the bytecode's jump targets and the
+ # FRAGMENTS table rely on.
+ dispatcher_bytes = bytes(dispatcher_code)
+ assert len(dispatcher_bytes) == 0x350, "dispatcher layout drifted"
+ assert dispatcher_bytes[0x33E:0x350] == bytes(contract_code), (
+ "reference contract code slot drifted"
+ )
+ # The SHA3-OOG constructor must stay unaffordable.
+ assert TX_GAS < fork.memory_expansion_gas_calculator()(
+ new_bytes=0x2FFFFF
+ ), "budget must not afford the SHA3-OOG constructor"
+
+ # Slots 16-33 hold non-zero sentinels so every overwrite (even with
+ # zero) is observable.
+ contract_0 = pre.deploy_contract(
+ code=dispatcher_code,
storage={
16: contract_1,
18: contract_1,
@@ -580,135 +450,99 @@ def test_create_results(
32: contract_1,
33: contract_1,
},
- balance=0xBA1A9CE0BA1A9CE,
- nonce=0,
- address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501
)
# Source: lll
# {
# [[0]] 0x60A7
# } ; end of LLL code
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=0x60A7) + Op.STOP,
- balance=0xBA1A9CE0BA1A9CE,
- nonce=0,
- address=Address(0x00000000000000000000000000000000000060A7), # noqa: E501
+ pre.deploy_contract(
+ code=Op.SSTORE(key=0x0, value=CONTRACT_1_ADDRESS) + Op.STOP,
+ address=contract_1,
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": [0, 1, 2, 4, 5, 6], "gas": 0, "value": 0},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(storage={32: 295, 33: 551}),
- contract_1: Account(storage={0: contract_1}),
- },
- },
- {
- "indexes": {"data": [3, 7], "gas": 0, "value": 0},
- "network": [">=Cancun"],
- "result": {contract_0: Account(storage={32: 295, 33: 551})},
- },
- {
- "indexes": {
- "data": [8, 9, 10, 11, 12, 13, 14, 15],
- "gas": 0,
- "value": 0,
- },
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(
- storage={
- 18: 18,
- 19: 0x600060006000600060006160A761FFFFF1000000000000000000000000000000, # noqa: E501
- 20: contract_1,
- 21: contract_1,
- 32: 295,
- 33: 551,
- },
- ),
- },
- },
- {
- "indexes": {"data": [16, 17], "gas": 0, "value": 0},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(
- storage={
- 16: 32,
- 17: contract_1,
- 18: 18,
- 19: 0x600060006000600060006160A761FFFFF1000000000000000000000000000000, # noqa: E501
- 20: contract_1,
- 21: contract_1,
- 32: 295,
- 33: 551,
- },
- ),
- },
- },
- {
- "indexes": {
- "data": [18, 19, 20, 21, 22, 23, 24, 25],
- "gas": 0,
- "value": 0,
- },
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(
- storage={
- 16: contract_1,
- 17: 0,
- 18: contract_1,
- 19: contract_1,
- 20: contract_1,
- 21: contract_1,
- 32: contract_1,
- 33: contract_1,
- },
- ),
- },
- },
- ]
+ # Decode the case into the created account's address and the
+ # expected post-state.
+ if creation == 1:
+ created = compute_create_address(address=contract_0, nonce=1)
+ else:
+ frag_offset, frag_size = FRAGMENTS[constructor]
+ created = compute_create2_address(
+ contract_0,
+ CREATE2_SALT,
+ dispatcher_bytes[frag_offset : frag_offset + frag_size],
+ )
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ # The word the dispatcher stores when comparing its reference copy
+ # of the contract code against a non-existent account's ext code.
+ contract_code_word = int.from_bytes(
+ bytes(contract_code).ljust(32, b"\x00"), "big"
+ )
- tx_data = [
- Bytes("048071d3") + Hash(0x1) + Hash(0x1) + Hash(0x0),
- Bytes("048071d3") + Hash(0x1) + Hash(0x2) + Hash(0x0),
- Bytes("048071d3") + Hash(0x1) + Hash(0x3) + Hash(0x0),
- Bytes("048071d3") + Hash(0x1) + Hash(0x4) + Hash(0x0),
- Bytes("048071d3") + Hash(0x2) + Hash(0x1) + Hash(0x0),
- Bytes("048071d3") + Hash(0x2) + Hash(0x2) + Hash(0x0),
- Bytes("048071d3") + Hash(0x2) + Hash(0x3) + Hash(0x0),
- Bytes("048071d3") + Hash(0x2) + Hash(0x4) + Hash(0x0),
- Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x1),
- Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x1),
- Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x2),
- Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x2),
- Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x5),
- Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x5),
- Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x6),
- Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x6),
- Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x3),
- Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x3),
- Bytes("048071d3") + Hash(0x1) + Hash(0x1) + Hash(0x4),
- Bytes("048071d3") + Hash(0x1) + Hash(0x2) + Hash(0x4),
- Bytes("048071d3") + Hash(0x1) + Hash(0x3) + Hash(0x4),
- Bytes("048071d3") + Hash(0x1) + Hash(0x4) + Hash(0x4),
- Bytes("048071d3") + Hash(0x2) + Hash(0x1) + Hash(0x4),
- Bytes("048071d3") + Hash(0x2) + Hash(0x2) + Hash(0x4),
- Bytes("048071d3") + Hash(0x2) + Hash(0x3) + Hash(0x4),
- Bytes("048071d3") + Hash(0x2) + Hash(0x4) + Hash(0x4),
- ]
- tx_gas = [9437184]
+ post: dict = {}
+ if constructor == 4:
+ # The create succeeds with an empty return buffer, so the
+ # forced RETURNDATACOPY of 32 bytes aborts the whole dispatcher
+ # frame: every sentinel survives and nothing was created.
+ post[contract_0] = Account(
+ storage={
+ 16: contract_1,
+ 18: contract_1,
+ 19: contract_1,
+ 20: contract_1,
+ 21: contract_1,
+ 32: contract_1,
+ 33: contract_1,
+ },
+ )
+ post[contract_1] = Account(storage={})
+ post[created] = Account.NONEXISTENT
+ elif constructor == 0:
+ # Successful creation and a follow-up call to the new contract,
+ # which calls contract_1. Every sentinel is overwritten (the
+ # zero results are observable), and only the non-static call
+ # kinds let contract_1 store its own address.
+ post[contract_0] = Account(
+ storage={32: CREATE_PC, 33: CALL_PC},
+ )
+ post[contract_1] = Account(
+ storage={} if call_kind == 4 else {0: contract_1},
+ )
+ post[created] = Account(code=bytes(contract_code), nonce=1, storage={})
+ else:
+ # No follow-up call: slots 20/21 keep their sentinels, and the
+ # dispatcher records the code/length differences against the
+ # created (or never-created) account's empty ext code.
+ storage = {
+ 18: len(bytes(contract_code)),
+ 19: contract_code_word,
+ 20: contract_1,
+ 21: contract_1,
+ 32: CREATE_PC,
+ 33: CALL_PC,
+ }
+ if constructor == 3:
+ # The constructor reverted 32 bytes holding contract_1's
+ # address; the dispatcher copied them out.
+ storage[16] = 32
+ storage[17] = contract_1
+ post[contract_0] = Account(storage=storage)
+ post[contract_1] = Account(storage={})
+ if constructor == 5:
+ # Empty deploy: the account exists with no code.
+ post[created] = Account(code=b"", nonce=1, storage={})
+ else:
+ # OOG (1), reverts (2, 3), and an in-init SELFDESTRUCT (6,
+ # destroyed in its creation transaction per EIP-6780).
+ post[created] = Account.NONEXISTENT
tx = Transaction(
sender=sender,
to=contract_0,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- error=_exc,
+ data=Bytes("048071d3")
+ + Hash(creation)
+ + Hash(call_kind)
+ + Hash(constructor),
+ gas_limit=TX_GAS,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py
index e49cd593e8f..c697e7352be 100644
--- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py
+++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py
@@ -1,134 +1,96 @@
"""
-Test_transaction_collision_to_empty2.
+Verify a contract-creation transaction targeting an address that holds
+only a balance: the prefund is not a collision, so creation proceeds and
+the budget alone decides whether the init code completes.
Ported from:
state_tests/stCreateTest/TransactionCollisionToEmpty2Filler.json
+
+@manually-enhanced: Do not overwrite. Budgets are derived from the fork
+(intrinsic + init code cost, success arm exact), pinning that a prefunded
+create target incurs no EIP-8037 top-frame new-account state gas.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
+ compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+PREFUND = 10
+
@pytest.mark.ported_from(
["state_tests/stCreateTest/TransactionCollisionToEmpty2Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="-g1-v1",
- ),
- ],
-)
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
+@pytest.mark.parametrize("oog", [False, True], ids=["enough-gas", "oog"])
+@pytest.mark.parametrize("tx_value", [0, 1], ids=["v0", "v1"])
def test_transaction_collision_to_empty2(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ oog: bool,
+ tx_value: int,
) -> None:
- """Test_transaction_collision_to_empty2."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """Prefunded create target is no collision; budget decides the rest."""
+ # Init code: one cold zero->non-zero store, deploys nothing.
+ initcode = Op.SSTORE(
+ key=0x1,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
+ # The prefunded target is not EMPTY_ACCOUNT in the pre-state, so
+ # EIP-8037 charges no top-frame new-account state gas: the exact
+ # success budget below would OOG if it were charged.
+ success_gas = fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ sends_value=tx_value > 0,
+ ) + initcode.gas_cost(fork)
+ # The OOG arm misses half the store's cost rather than one gas: the
+ # intrinsic calculator over-estimates by the initcode word cost on
+ # pre-Shanghai forks, so a one-gas boundary is not portable.
+ gas_limit = success_gas
+ if oog:
+ gas_limit -= initcode.gas_cost(fork) // 2
- pre[sender] = Account(balance=0xE8D4A51000)
- pre[contract_0] = Account(balance=10)
-
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": 0, "value": 0},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- contract_0: Account(storage={1: 1}, balance=10, nonce=1),
- },
- },
- {
- "indexes": {"data": -1, "gas": 0, "value": 1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- contract_0: Account(storage={1: 1}, balance=11, nonce=1),
- },
- },
- {
- "indexes": {"data": -1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- contract_0: Account(storage={}, balance=10, nonce=0),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Op.SSTORE(key=0x1, value=0x1),
- ]
- tx_gas = [600000, 54000]
- tx_value = [0, 1]
+ sender = pre.fund_eoa()
+ created = compute_create_address(address=sender, nonce=0)
+ pre.fund_address(created, PREFUND)
tx = Transaction(
sender=sender,
to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ data=initcode,
+ gas_limit=gas_limit,
+ value=tx_value,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ if oog:
+ # Creation rolled back: prefund kept, no value, nonce untouched.
+ created_account = Account(
+ storage={}, code=b"", nonce=0, balance=PREFUND
+ )
+ else:
+ created_account = Account(
+ storage={1: 1}, code=b"", nonce=1, balance=PREFUND + tx_value
+ )
+
+ post = {
+ sender: Account(nonce=1),
+ created: created_account,
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py
index 468a026fc98..ff51bba2eee 100644
--- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py
+++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py
@@ -1,149 +1,99 @@
"""
-Test_transaction_collision_to_empty_but_code.
+Verify a contract-creation transaction whose target address already holds
+code: the collision aborts the creation, consumes the whole gas limit,
+transfers no value, and leaves the existing account untouched.
Ported from:
state_tests/stCreateTest/TransactionCollisionToEmptyButCodeFiller.json
+
+@manually-enhanced: Do not overwrite. Budgets are derived from the fork
+(bare intrinsic and a fully-funded creation); the post asserts the
+colliding account's code, nonce, and unchanged zero balance.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
Header,
StateTestFiller,
Transaction,
+ compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# Any non-empty code at the target address triggers the collision.
+COLLIDING_CODE = bytes.fromhex("1122334455")
+
@pytest.mark.ported_from(
["state_tests/stCreateTest/TransactionCollisionToEmptyButCodeFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="-g1-v1",
- ),
- ],
+ "full_budget", [True, False], ids=["full-budget", "intrinsic-only"]
)
+@pytest.mark.parametrize("tx_value", [0, 1], ids=["v0", "v1"])
@pytest.mark.pre_alloc_mutable
def test_transaction_collision_to_empty_but_code(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ full_budget: bool,
+ tx_value: int,
) -> None:
- """Test_transaction_collision_to_empty_but_code."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """Creation collision with code burns the whole gas limit."""
+ # Init code that would store a flag if it ever ran.
+ initcode = Op.SSTORE(
+ key=0x1,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ intrinsic = fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ sends_value=tx_value > 0,
)
+ if full_budget:
+ # Enough to fund the whole creation (even at the fresh-target
+ # EIP-8037 price) — the collision must still consume all of it.
+ gas_limit = (
+ intrinsic
+ + fork.transaction_top_frame_state_gas(contract_creation=True)
+ + initcode.gas_cost(fork)
+ )
+ else:
+ gas_limit = intrinsic
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: raw
- # 0x1122334455
- contract_0 = pre.deploy_contract( # noqa: F841
- code=bytes.fromhex("1122334455"),
- nonce=0,
- address=Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F), # noqa: E501
- )
-
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- contract_0: Account(
- storage={1: 0},
- code=bytes.fromhex("1122334455"),
- nonce=0,
- ),
- },
- },
- {
- "indexes": {"data": -1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- contract_0: Account(
- storage={},
- code=bytes.fromhex("1122334455"),
- nonce=0,
- ),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Op.SSTORE(key=0x1, value=0x1),
- ]
- tx_gas = [600000, 54000]
- tx_value = [0, 1]
+ sender = pre.fund_eoa()
+ created = compute_create_address(address=sender, nonce=0)
+ pre[created] = Account(code=COLLIDING_CODE)
tx = Transaction(
sender=sender,
to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ data=initcode,
+ gas_limit=gas_limit,
+ value=tx_value,
)
+ post = {
+ sender: Account(nonce=1),
+ # The colliding account is untouched: the init code never ran and
+ # the transferred value never arrived.
+ created: Account(storage={}, code=COLLIDING_CODE, nonce=0, balance=0),
+ }
+
state_test(
- env=env,
pre=pre,
post=post,
tx=tx,
- blockchain_test_header_verify=Header(
- gas_used=tx_gas[g],
- ),
+ blockchain_test_header_verify=Header(gas_used=gas_limit),
)
diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py
index 14a3c066470..a4b4c40e06a 100644
--- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py
+++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py
@@ -1,22 +1,26 @@
"""
-Test_transaction_collision_to_empty_but_nonce.
+Verify a contract-creation transaction whose target address already has a
+non-zero nonce: the collision aborts the creation, consumes the whole gas
+limit, transfers no value, and leaves the existing account untouched.
Ported from:
state_tests/stCreateTest/TransactionCollisionToEmptyButNonceFiller.json
+
+@manually-enhanced: Do not overwrite. Budgets are derived from the fork
+(bare intrinsic and a fully-funded creation); the post asserts the
+colliding account's empty code, nonce, and unchanged zero balance.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
Header,
StateTestFiller,
Transaction,
+ compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
@@ -28,89 +32,67 @@
"state_tests/stCreateTest/TransactionCollisionToEmptyButNonceFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="-g1-v1",
- ),
- ],
+ "full_budget", [True, False], ids=["full-budget", "intrinsic-only"]
)
+@pytest.mark.parametrize("tx_value", [0, 1], ids=["v0", "v1"])
@pytest.mark.pre_alloc_mutable
def test_transaction_collision_to_empty_but_nonce(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ full_budget: bool,
+ tx_value: int,
) -> None:
- """Test_transaction_collision_to_empty_but_nonce."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """Creation collision with a nonce burns the whole gas limit."""
+ # Init code that would store a flag if it ever ran.
+ initcode = Op.SSTORE(
+ key=0x1,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ intrinsic = fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ sends_value=tx_value > 0,
)
+ if full_budget:
+ # Enough to fund the whole creation (even at the fresh-target
+ # EIP-8037 price) — the collision must still consume all of it.
+ gas_limit = (
+ intrinsic
+ + fork.transaction_top_frame_state_gas(contract_creation=True)
+ + initcode.gas_cost(fork)
+ )
+ else:
+ gas_limit = intrinsic
- pre[sender] = Account(balance=0xE8D4A51000)
- pre[contract_0] = Account(balance=0, nonce=1)
-
- tx_data = [
- Op.SSTORE(key=0x1, value=0x1),
- ]
- tx_gas = [600000, 54000]
- tx_value = [0, 1]
+ sender = pre.fund_eoa()
+ created = compute_create_address(address=sender, nonce=0)
+ pre[created] = Account(nonce=1)
tx = Transaction(
sender=sender,
to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
+ data=initcode,
+ gas_limit=gas_limit,
+ value=tx_value,
)
post = {
sender: Account(nonce=1),
- contract_0: Account(storage={1: 0}, nonce=1),
+ # The colliding account is untouched: the init code never ran and
+ # the transferred value never arrived.
+ created: Account(storage={}, code=b"", nonce=1, balance=0),
}
state_test(
- env=env,
pre=pre,
post=post,
tx=tx,
- blockchain_test_header_verify=Header(
- gas_used=tx_gas[g],
- ),
+ blockchain_test_header_verify=Header(gas_used=gas_limit),
)
diff --git a/tests/ported_static/stEIP150Specific/test_create_and_gas_inside_create.py b/tests/ported_static/stEIP150Specific/test_create_and_gas_inside_create.py
index 0bcf52c7d85..47a20e3a725 100644
--- a/tests/ported_static/stEIP150Specific/test_create_and_gas_inside_create.py
+++ b/tests/ported_static/stEIP150Specific/test_create_and_gas_inside_create.py
@@ -1,17 +1,23 @@
"""
-Test_create_and_gas_inside_create.
+Verify the gas a CREATE's init code observes: the child receives all but
+one 64th of what remains in the creating frame, and the parent's CREATE
+cost is measured alongside it.
Ported from:
state_tests/stEIP150Specific/CreateAndGasInsideCreateFiller.json
+
+@manually-enhanced: Do not overwrite. An outer call pins the creating
+frame's budget so the child's stored GAS observation is fork-derived
+(`63/64` of the derived base); the parent measures the CREATE with
+CodeGasMeasure instead of raw snapshots.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ CodeGasMeasure,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -21,58 +27,105 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+ADDRESS_SLOT = 0xB
+GAS_SLOT = 0x9
+CHILD_GAS_SLOT = 0xFD
+
+# The creating frame's pinned budget (the ported transaction's).
+CALLER_GAS = 600_000
+
@pytest.mark.ported_from(
["state_tests/stEIP150Specific/CreateAndGasInsideCreateFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_create_and_gas_inside_create(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_create_and_gas_inside_create."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
+ """A CREATE's init code observes 63/64 of the creating frame's gas."""
+ # Child init code: stores the gas it observes into its own storage
+ # and deposits no code.
+ child_code = Op.SSTORE(
+ key=CHILD_GAS_SLOT,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ child_bytes = bytes(child_code)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ # The child bytes sit right-aligned in the first memory word.
+ setup = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(child_bytes, "big"),
+ new_memory_size=0x20,
+ )
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=0x20 - len(child_bytes),
+ size=len(child_bytes),
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ init_code_size=len(child_bytes),
+ )
+ create_store = Op.SSTORE(
+ key=ADDRESS_SLOT,
+ value=create_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ creator = pre.deploy_contract(
+ code=setup
+ + CodeGasMeasure(
+ code=create_store,
+ extra_stack_items=0,
+ sstore_key=GAS_SLOT,
+ ),
)
- # Source: lll
- # { [100] (GAS) (MSTORE 0 0x5a60fd55) (SSTORE 11 (CREATE 0 28 4)) (SSTORE 9 (SUB @100 (GAS))) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x64, value=Op.GAS)
- + Op.MSTORE(offset=0x0, value=0x5A60FD55)
- + Op.SSTORE(key=0xB, value=Op.CREATE(value=0x0, offset=0x1C, size=0x4))
- + Op.SSTORE(key=0x9, value=Op.SUB(Op.MLOAD(offset=0x64), Op.GAS))
+ # The outer call pins the creating frame's budget so the child's
+ # observation does not depend on the tx gas limit.
+ entry = pre.deploy_contract(
+ code=Op.SSTORE(key=0x0, value=Op.CALL(gas=CALLER_GAS, address=creator))
+ Op.STOP,
- nonce=0,
)
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=600000,
+ sender=pre.fund_eoa(),
+ to=entry,
+ state_gas_reservoir=0,
+ )
+
+ # The child receives all but one 64th of what remains after the
+ # setup, the measuring GAS read, and the CREATE's own charges (its
+ # new-account state gas is taken before the withhold).
+ base = (
+ CALLER_GAS
+ - setup.gas_cost(fork)
+ - Op.GAS.gas_cost(fork)
+ - create_code.gas_cost(fork)
)
+ assert base > 0, "CALLER_GAS must cover the CREATE's charges"
+ child_observed = (base - base // 64) - Op.GAS.gas_cost(fork)
+ measured_create = create_store.gas_cost(fork) + child_code.gas_cost(fork)
+ created = compute_create_address(address=creator, nonce=1)
post = {
- contract_0: Account(
+ entry: Account(storage={0: 1}),
+ creator: Account(
storage={
- 9: 0x129DB,
- 11: compute_create_address(address=contract_0, nonce=0),
+ ADDRESS_SLOT: created,
+ GAS_SLOT: measured_create,
},
),
- compute_create_address(address=contract_0, nonce=0): Account(
- storage={253: 0x83729}
+ created: Account(
+ nonce=1,
+ code=b"",
+ storage={CHILD_GAS_SLOT: child_observed},
),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stEIP150Specific/test_delegate_call_on_eip.py b/tests/ported_static/stEIP150Specific/test_delegate_call_on_eip.py
index 5156516411b..89d33ae0248 100644
--- a/tests/ported_static/stEIP150Specific/test_delegate_call_on_eip.py
+++ b/tests/ported_static/stEIP150Specific/test_delegate_call_on_eip.py
@@ -1,17 +1,23 @@
"""
-Test_delegate_call_on_eip.
+Measure a DELEGATECALL that asks for more gas than its frame holds: the
+EIP-150 clamp decides the grant, the delegate writes into the caller's
+storage, and the measured cost is the call plus the delegate's work.
Ported from:
state_tests/stEIP150Specific/DelegateCallOnEIPFiller.json
+
+@manually-enhanced: Do not overwrite. An outer call pins the frame budget
+so the oversized ask always clamps; the DELEGATECALL is measured with
+CodeGasMeasure (success flag inside the window) and the expectation is the
+composite plus the delegate's fork-priced store.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ CodeGasMeasure,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,62 +26,79 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+DELEGATE_VALUE = 0x12
+FLAG_SLOT = 0x9
+GAS_SLOT = 0x8
+
+# The ported ask (600000): above the pinned frame budget, so the EIP-150
+# clamp decides the grant on every fork.
+ASK_GAS = 0x927C0
+CALLER_GAS = 400_000
+
@pytest.mark.ported_from(
["state_tests/stEIP150Specific/DelegateCallOnEIPFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_delegate_call_on_eip(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_delegate_call_on_eip."""
- 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 a clamped DELEGATECALL running a store in the caller."""
+ # Runs in the caller's storage context: one cold fresh store.
+ delegate_store = Op.SSTORE(
+ key=0x0,
+ value=DELEGATE_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=DELEGATE_VALUE,
)
+ delegate = pre.deploy_contract(code=delegate_store + Op.STOP)
- # Source: lll
- # { (SSTORE 0 0x12) }
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=0x12) + Op.STOP,
- nonce=0,
+ delegatecall_code = Op.DELEGATECALL(
+ gas=ASK_GAS,
+ address=delegate,
+ address_warm=False,
)
- # Source: lll
- # { [8] (GAS) (SSTORE 9 (DELEGATECALL 600000 0 0 0 0)) [[8]] (SUB @8 (GAS)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x8, value=Op.GAS)
- + Op.SSTORE(
- key=0x9,
- value=Op.DELEGATECALL(
- gas=0x927C0,
- address=addr,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x8, value=Op.SUB(Op.MLOAD(offset=0x8), Op.GAS))
+ flag_store = Op.SSTORE(
+ key=FLAG_SLOT,
+ value=delegatecall_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ target = pre.deploy_contract(
+ code=CodeGasMeasure(
+ code=flag_store,
+ extra_stack_items=0,
+ sstore_key=GAS_SLOT,
+ ),
+ )
+
+ assert CALLER_GAS < ASK_GAS, "the 63/64 clamp must apply"
+ entry = pre.deploy_contract(
+ code=Op.SSTORE(key=0x0, value=Op.CALL(gas=CALLER_GAS, address=target))
+ Op.STOP,
- nonce=0,
)
tx = Transaction(
- sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=600000,
+ sender=pre.fund_eoa(),
+ to=entry,
+ state_gas_reservoir=0,
)
- post = {target: Account(storage={0: 18, 8: 46841, 9: 1})}
+ measured = flag_store.gas_cost(fork) + delegate_store.gas_cost(fork)
+
+ post = {
+ entry: Account(storage={0: 1}),
+ target: Account(
+ storage={
+ 0: DELEGATE_VALUE,
+ GAS_SLOT: measured,
+ FLAG_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/stInitCodeTest/test_out_of_gas_contract_creation.py b/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py
index f346844b949..078fb6506fe 100644
--- a/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py
+++ b/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py
@@ -1,150 +1,132 @@
"""
-Test_out_of_gas_contract_creation.
+Verify a contract-creation transaction whose init code runs out of gas (or
+halts on invalid code) leaves no account behind, while a sufficient budget
+creates it.
Ported from:
state_tests/stInitCodeTest/OutOfGasContractCreationFiller.json
+
+@manually-enhanced: Do not overwrite. Both transaction budgets are derived
+from the fork (intrinsic + the created account's top-frame state gas + the
+init code's metadata-priced cost), so the insufficient arm keeps running
+out mid-init-code and the sufficient arm keeps succeeding on every fork;
+the success post pins the final storage value, not just the nonce.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Environment,
+ Bytecode,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+def storage_writes_initcode() -> Bytecode:
+ """Six stores to one slot: one cold set, then five dirty warm writes."""
+ code = Op.SSTORE(
+ key=0x1, value=0x1, key_warm=False, original_value=0, new_value=1
+ )
+ for value in range(2, 7):
+ code += Op.SSTORE(
+ key=0x1,
+ value=value,
+ key_warm=True,
+ original_value=0,
+ current_value=value - 1,
+ new_value=value,
+ )
+ return code
+
+
+def stack_underflow_initcode() -> Bytecode:
+ """The ported junk init code: CALLCODE underflows the stack."""
+ return (
+ Op.PUSH1[0xA]
+ + Op.CODECOPY(dest_offset=0x0, offset=0xC, size=Op.DUP1)
+ + Op.PUSH1[0x0]
+ + Op.CALLCODE
+ + Op.STOP
+ + Op.PUSH1[0x1]
+ + Op.PUSH1[0x0]
+ + Op.BYTE(Op.DUP2, Op.CALLDATALOAD(offset=Op.DUP1))
+ + Op.DUP2
+ + Op.STOP
+ )
+
+
@pytest.mark.ported_from(
["state_tests/stInitCodeTest/OutOfGasContractCreationFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
+ "invalid_initcode",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="d0-g0",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="d0-g1",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1-g0",
- ),
- pytest.param(
- 1,
- 1,
- 0,
- id="d1-g1",
- ),
+ pytest.param(True, id="d0"),
+ pytest.param(False, id="d1"),
+ ],
+)
+@pytest.mark.parametrize(
+ "enough_gas",
+ [
+ pytest.param(False, id="g0"),
+ pytest.param(True, id="g1"),
],
)
def test_out_of_gas_contract_creation(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ invalid_initcode: bool,
+ enough_gas: bool,
) -> None:
- """Test_out_of_gas_contract_creation."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(
- amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501
- )
+ """An under-budgeted or invalid init code creates no account."""
+ if invalid_initcode:
+ initcode = stack_underflow_initcode()
+ else:
+ initcode = storage_writes_initcode()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=100000000000000,
+ # The insufficient budget runs out midway through the init code; the
+ # sufficient one covers it with margin. EIP-8037 charges the created
+ # account's state gas to the creation transaction's top frame.
+ overhead = fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ ) + fork.transaction_top_frame_state_gas(contract_creation=True)
+ # The sufficient margin must exceed the EIP-2200 stipend (2300), or
+ # the final SSTOREs of the init code fail their minimum-gas check.
+ initcode_cost = storage_writes_initcode().gas_cost(fork)
+ gas_limit = overhead + (
+ initcode_cost + 5_000 if enough_gas else initcode_cost // 2
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 0, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- compute_create_address(
- address=sender, nonce=0
- ): Account.NONEXISTENT,
- },
- },
- {
- "indexes": {"data": 1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- compute_create_address(address=sender, nonce=0): Account(
- nonce=1
- ),
- },
- },
- {
- "indexes": {"data": -1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- compute_create_address(
- address=sender, nonce=0
- ): Account.NONEXISTENT,
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Op.PUSH1[0xA]
- + Op.CODECOPY(dest_offset=0x0, offset=0xC, size=Op.DUP1)
- + Op.PUSH1[0x0]
- + Op.CALLCODE
- + Op.STOP
- + Op.PUSH1[0x1]
- + Op.PUSH1[0x0]
- + Op.BYTE(Op.DUP2, Op.CALLDATALOAD(offset=Op.DUP1))
- + Op.DUP2
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.SSTORE(key=0x1, value=0x2)
- + Op.SSTORE(key=0x1, value=0x3)
- + Op.SSTORE(key=0x1, value=0x4)
- + Op.SSTORE(key=0x1, value=0x5)
- + Op.SSTORE(key=0x1, value=0x6),
- ]
- tx_gas = [56000, 150000]
- tx_value = [1]
-
+ sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ data=initcode,
+ gas_limit=gas_limit,
+ value=1,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ created = compute_create_address(address=sender, nonce=0)
+ if enough_gas and not invalid_initcode:
+ created_account: Account | None = Account(
+ nonce=1, code=b"", storage={1: 6}, balance=1
+ )
+ else:
+ # OOG / invalid init code: the creation is rolled back entirely.
+ created_account = Account.NONEXISTENT
+ post = {
+ sender: Account(nonce=1),
+ created: created_account,
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py b/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py
index 93f7f151c9e..c72984e1633 100644
--- a/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py
+++ b/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py
@@ -1,137 +1,168 @@
"""
-Test_out_of_gas_prefunded_contract_creation.
+Verify a contract-creation transaction targeting a prefunded address, whose
+init code CREATEs a value-bearing child: the budget decides whether the
+outer creation fails (prefund untouched), the child fails (value stays),
+or the child succeeds (one wei moves into it).
Ported from:
state_tests/stInitCodeTest/OutOfGasPrefundedContractCreationFiller.json
+
+@manually-enhanced: Do not overwrite. All three budgets are derived from
+the fork (intrinsic + top-frame state gas + the composed init/child code
+costs), and the child account is asserted, disambiguating the ported
+"balance 1" outcomes (outer-failure vs child-success) that were previously
+indistinguishable.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
+ compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+PREFUND = 1
+TX_VALUE = 1
+CHILD_VALUE = 1
+CHILD_STORED = 0x112233
+
@pytest.mark.ported_from(
[
"state_tests/stInitCodeTest/OutOfGasPrefundedContractCreationFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
+ "outcome",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1",
- ),
- pytest.param(
- 0,
- 2,
- 0,
- id="-g2",
- ),
+ pytest.param("child_succeeds", id="g0"),
+ pytest.param("outer_oog", id="g1"),
+ pytest.param("child_oog", id="g2"),
],
)
-@pytest.mark.pre_alloc_mutable
def test_out_of_gas_prefunded_contract_creation(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ outcome: str,
) -> None:
- """Test_out_of_gas_prefunded_contract_creation."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """Budget decides how deep a prefunded creation's child CREATE gets."""
+ # Child init code: one cold store, deposits nothing.
+ child_code = (
+ Op.SSTORE(
+ key=0x0,
+ value=CHILD_STORED,
+ key_warm=False,
+ original_value=0,
+ new_value=CHILD_STORED,
+ )
+ + Op.STOP * 2
)
+ child_bytes = bytes(child_code)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000000,
+ # Outer init code: copy the child init code from its own tail, then
+ # CREATE a value-bearing child from it; deposits nothing. The copy
+ # window and memory usage stay within one word.
+ inner_create = Op.CREATE(
+ value=CHILD_VALUE,
+ offset=0x0,
+ size=len(child_bytes),
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ init_code_size=len(child_bytes),
)
-
- pre[sender] = Account(balance=0xF424000)
- # Source: hex
- # 0x
- contract_0 = pre.deploy_contract( # noqa: F841
- code="",
- balance=1,
- nonce=0,
- address=Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F), # noqa: E501
+ prefix = Op.CODECOPY(
+ dest_offset=0x0,
+ offset=0x1A, # placeholder; recomputed below
+ size=len(child_bytes),
+ data_size=len(child_bytes),
+ new_memory_size=0x20,
)
+ body = prefix + Op.POP(inner_create) + Op.STOP
+ # The child code sits immediately after the executable body.
+ initcode_prefix_len = len(bytes(body))
+ prefix = Op.CODECOPY(
+ dest_offset=0x0,
+ offset=initcode_prefix_len,
+ size=len(child_bytes),
+ data_size=len(child_bytes),
+ new_memory_size=0x20,
+ )
+ body = prefix + Op.POP(inner_create) + Op.STOP
+ assert len(bytes(body)) == initcode_prefix_len, "stable code layout"
+ initcode = body + child_code
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": [0, 1], "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- contract_0: Account(balance=1),
- },
- },
- {
- "indexes": {"data": -1, "gas": [2], "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- contract_0: Account(balance=2),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ # Fork-derived budgets. The prefunded target is not EMPTY_ACCOUNT in
+ # the pre-state, so EIP-8037 charges no top-frame new-account state
+ # gas for this creation — an Amsterdam behavior this test pins. The
+ # inner CREATE's composite cost covers its peak charge (its
+ # new-account state gas is refunded if the child fails, but must be
+ # affordable when charged).
+ overhead = (
+ fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ )
+ + prefix.gas_cost(fork)
+ + inner_create.gas_cost(fork)
+ )
+ child_needed = child_code.gas_cost(fork)
+ if outcome == "outer_oog":
+ # Dies charging the inner CREATE.
+ gas_limit = overhead - inner_create.gas_cost(fork) // 2
+ elif outcome == "child_oog":
+ # Outer completes; the child's 63/64 grant undercuts its cost.
+ gas_limit = overhead + child_needed // 2
+ else:
+ # Child completes too and keeps the transferred wei.
+ gas_limit = overhead + -(-child_needed * 64 // 63) + 2_000
- tx_data = [
- Op.PUSH1[0x9]
- + Op.CODECOPY(dest_offset=0x0, offset=0x11, size=Op.DUP1)
- + Op.PUSH1[0x0]
- + Op.PUSH1[0x1]
- + Op.POP(Op.CREATE)
- + Op.STOP * 2
- + Op.INVALID
- + Op.SSTORE(key=0x0, value=0x112233)
- + Op.STOP * 2,
- ]
- tx_gas = [154000, 65000, 95000]
- tx_value = [1]
+ sender = pre.fund_eoa()
+ created = compute_create_address(address=sender, nonce=0)
+ pre.fund_address(created, PREFUND)
tx = Transaction(
sender=sender,
to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ data=initcode,
+ gas_limit=gas_limit,
+ value=TX_VALUE,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ child = compute_create_address(address=created, nonce=1)
+ if outcome == "outer_oog":
+ # Creation rolled back: only the prefund remains, nonce untouched.
+ created_account = Account(nonce=0, balance=PREFUND)
+ child_account: Account | None = Account.NONEXISTENT
+ elif outcome == "child_oog":
+ # The inner CREATE increments the creator's nonce even when the
+ # child fails.
+ created_account = Account(
+ nonce=2, code=b"", balance=PREFUND + TX_VALUE
+ )
+ child_account = Account.NONEXISTENT
+ else:
+ created_account = Account(
+ nonce=2, code=b"", balance=PREFUND + TX_VALUE - CHILD_VALUE
+ )
+ child_account = Account(
+ nonce=1,
+ balance=CHILD_VALUE,
+ storage={0: CHILD_STORED},
+ )
+
+ post = {
+ sender: Account(nonce=1),
+ created: created_account,
+ child: child_account,
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py
index 5f31ce42a8a..5163dea2f5e 100644
--- a/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py
+++ b/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py
@@ -1,17 +1,23 @@
"""
-Test_call_goes_oog_on_second_level_with_mem_expanding_calls.
+Verify a two-level call chain (with memory-expanding call windows) where
+the second-level frame runs out of gas: its own frame and everything below
+it revert, while the top frame survives and records the failure.
Ported from:
state_tests/stMemExpandingEIP150Calls/CallGoesOOGOnSecondLevelWithMemExpandingCallsFiller.json
+
+@manually-enhanced: Do not overwrite. The first-level budget is pinned and
+derived from the fork so the second level keeps starving on every fork
+(its 1/64 retention cannot afford the post-call store); the second-level
+ask stays oversized; the top frame's entry snapshot is derived and pins
+the transaction intrinsic.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,88 +26,142 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+SNAPSHOT_SLOT = 0x8
+FLAG_SLOT = 0x9
+# The ported second-level ask: far above the pinned budget.
+ASK_GAS = 0x927C0
+# The ported calls' argument window, driving the memory expansion.
+MEM_OFFSET = 0xFF
+MEM_SIZE = 0xFF
+
@pytest.mark.ported_from(
[
"state_tests/stMemExpandingEIP150Calls/CallGoesOOGOnSecondLevelWithMemExpandingCallsFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_call_goes_oog_on_second_level_with_mem_expanding_calls(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_call_goes_oog_on_second_level_with_mem_expanding_calls."""
- 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,
+ """A starved second-level frame reverts itself and everything below."""
+ # Deepest contract: snapshots and creates twice; its cost anchors the
+ # starvation budget.
+ deep_snapshot = Op.SSTORE(
+ key=SNAPSHOT_SLOT,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
-
- # Source: hex
- # 0x5a600855600060006000f050600060006000f0505a6009555a600a55
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x8, value=Op.GAS)
+ deep = pre.deploy_contract(
+ code=deep_snapshot
+ Op.POP(Op.CREATE(value=0x0, offset=0x0, size=0x0)) * 2
- + Op.SSTORE(key=0x9, value=Op.GAS)
+ + Op.SSTORE(key=FLAG_SLOT, value=Op.GAS)
+ Op.SSTORE(key=0xA, value=Op.GAS),
- nonce=0,
)
- # Source: hex
- # 0x5a60085560ff60ff60ff60ff600073620927c0f1600955 # noqa: E501
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x8, value=Op.GAS)
+
+ # Second level: snapshots, then asks far more than it holds; the ported
+ # memory-expanding argument window is kept.
+ mid = pre.deploy_contract(
+ code=Op.SSTORE(key=SNAPSHOT_SLOT, value=Op.GAS)
+ Op.SSTORE(
- key=0x9,
+ key=FLAG_SLOT,
value=Op.CALL(
- gas=0x927C0,
- address=addr,
- value=0x0,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ gas=ASK_GAS,
+ address=deep,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
),
),
- nonce=0,
)
- # Source: hex
- # 0x5a60085560ff60ff60ff60ff600073620927c0f1600955 # 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_2,
- value=0x0,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
- ),
+
+ # Pin the second level's budget so it starves on every fork: enough
+ # to pay its own snapshot and call, but its grant to the deep frame
+ # undercuts the deep frame's first store, and its 1/64 retention
+ # cannot afford its own post-call flag store.
+ mid_snapshot_cost = Op.SSTORE(
+ key=SNAPSHOT_SLOT,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ ).gas_cost(fork)
+ mid_call_cost = Op.CALL(
+ gas=ASK_GAS,
+ address=deep,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=False,
+ account_new=False,
+ new_memory_size=MEM_OFFSET + MEM_SIZE,
+ ).gas_cost(fork)
+ deep_needed = deep_snapshot.gas_cost(fork)
+ caller_gas = mid_snapshot_cost + mid_call_cost + deep_needed // 2
+ assert caller_gas < ASK_GAS, "the second-level ask must exceed its frame"
+
+ # Top frame: derived entry snapshot (pins the intrinsic), the pinned
+ # call, and the failure flag.
+ entry_snapshot = Op.SSTORE(
+ key=SNAPSHOT_SLOT,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ flag_store = Op.SSTORE(
+ key=FLAG_SLOT,
+ value=Op.CALL(
+ gas=caller_gas,
+ address=mid,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=False,
+ account_new=False,
+ new_memory_size=MEM_OFFSET + MEM_SIZE,
),
- nonce=0,
+ key_warm=False,
+ original_value=0,
+ new_value=0,
+ )
+ target = pre.deploy_contract(
+ code=entry_snapshot + flag_store + Op.STOP,
+ )
+
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = (
+ intrinsic
+ + entry_snapshot.gas_cost(fork)
+ + flag_store.gas_cost(fork)
+ + caller_gas
+ + 5_000
)
tx = Transaction(
- sender=sender,
+ sender=pre.fund_eoa(),
to=target,
- data=Bytes(""),
- gas_limit=220000,
+ gas_limit=gas_limit,
)
post = {
- sender: Account(nonce=1),
- target: Account(storage={8: 0x30956}),
- addr_2: Account(storage={}),
- addr: Account(storage={}),
+ # The failed call's flag slot stays zero; the entry snapshot pins
+ # the intrinsic.
+ target: Account(
+ storage={
+ SNAPSHOT_SLOT: gas_limit - intrinsic - Op.GAS.gas_cost(fork),
+ },
+ ),
+ # Both lower frames reverted entirely.
+ mid: Account(storage={}),
+ deep: 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/stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py
index 705e9e760f9..74f0be4227b 100644
--- a/tests/ported_static/stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py
+++ b/tests/ported_static/stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py
@@ -1,17 +1,22 @@
"""
-Test_create_and_gas_inside_create_with_mem_expanding_calls.
+Verify the gas a CREATE's init code observes when the creating frame also
+expands memory: the child receives all but one 64th of what remains, and
+the creating frame's entry and post-CREATE gas readings are asserted.
Ported from:
state_tests/stMemExpandingEIP150Calls/CreateAndGasInsideCreateWithMemExpandingCallsFiller.json
+
+@manually-enhanced: Do not overwrite. The ported bytecode is kept, but the
+transaction budget and every stored gas reading (entry snapshot, child
+observation, post-CREATE reading) are derived from the fork instead of
+pinned — the entry snapshot doubles as a transaction-intrinsic pin.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -21,62 +26,129 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+ENTRY_GAS_SLOT = 0xA
+ADDRESS_SLOT = 0xB
+AFTER_GAS_SLOT = 0x9
+CHILD_GAS_SLOT = 0xFD
+
@pytest.mark.ported_from(
[
"state_tests/stMemExpandingEIP150Calls/CreateAndGasInsideCreateWithMemExpandingCallsFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_create_and_gas_inside_create_with_mem_expanding_calls(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_create_and_gas_inside_create_with_mem_expanding_calls."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- 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,
+ """A CREATE's init code observes 63/64 of the creating frame's gas."""
+ # Child init code: stores the gas it observes, deposits no code.
+ child_code = Op.SSTORE(
+ key=CHILD_GAS_SLOT,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
+ child_bytes = bytes(child_code)
- # Source: hex
- # 0x5a600a55635a60fd556000526004601c6000f0600b555a600955
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0xA, value=Op.GAS)
- + Op.MSTORE(offset=0x0, value=0x5A60FD55)
- + Op.SSTORE(key=0xB, value=Op.CREATE(value=0x0, offset=0x1C, size=0x4))
- + Op.SSTORE(key=0x9, value=Op.GAS),
- nonce=0,
+ entry_snapshot = Op.SSTORE(
+ key=ENTRY_GAS_SLOT,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ setup = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(child_bytes, "big"),
+ new_memory_size=0x20,
+ )
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=0x20 - len(child_bytes),
+ size=len(child_bytes),
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ init_code_size=len(child_bytes),
+ )
+ create_store = Op.SSTORE(
+ key=ADDRESS_SLOT,
+ value=create_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ after_snapshot = Op.SSTORE(
+ key=AFTER_GAS_SLOT,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ creator = pre.deploy_contract(
+ code=entry_snapshot + setup + create_store + after_snapshot + Op.STOP,
)
+ # Fork-derived budget: the ported 600000 no longer covers the three
+ # state-priced stores plus the CREATE under EIP-8037. The margin
+ # keeps the final store above the EIP-2200 stipend.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ tx_gas = (
+ intrinsic
+ + entry_snapshot.gas_cost(fork)
+ + setup.gas_cost(fork)
+ + create_store.gas_cost(fork)
+ + child_code.gas_cost(fork)
+ + after_snapshot.gas_cost(fork)
+ + 5_000
+ )
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=600000,
+ sender=pre.fund_eoa(),
+ to=creator,
+ gas_limit=tx_gas,
)
+ # Entry reading: everything after the intrinsic, minus the GAS opcode
+ # itself (it executes first in the store's operand order).
+ entry_observed = tx_gas - intrinsic - Op.GAS.gas_cost(fork)
+ # The child receives all but one 64th of what remains after the entry
+ # store, the setup, and the CREATE's own charges.
+ base = (
+ tx_gas
+ - intrinsic
+ - entry_snapshot.gas_cost(fork)
+ - setup.gas_cost(fork)
+ - create_code.gas_cost(fork)
+ )
+ assert base > 0, "the budget must cover the CREATE's charges"
+ child_observed = (base - base // 64) - Op.GAS.gas_cost(fork)
+ # After the CREATE: the child's consumption and the address store are
+ # gone; the address store's own cost is the composite minus the
+ # CREATE it wraps.
+ after_observed = (
+ base
+ - child_code.gas_cost(fork)
+ - (create_store.gas_cost(fork) - create_code.gas_cost(fork))
+ - Op.GAS.gas_cost(fork)
+ )
+
+ created = compute_create_address(address=creator, nonce=1)
post = {
- sender: Account(nonce=1),
- contract_0: Account(
+ creator: Account(
storage={
- 9: 0x75596,
- 10: 0x8D5B6,
- 11: compute_create_address(address=contract_0, nonce=0),
+ ENTRY_GAS_SLOT: entry_observed,
+ ADDRESS_SLOT: created,
+ AFTER_GAS_SLOT: after_observed,
},
- nonce=1,
),
- compute_create_address(address=contract_0, nonce=0): Account(
- storage={253: 0x7E23D}
+ created: Account(
+ nonce=1,
+ code=b"",
+ storage={CHILD_GAS_SLOT: child_observed},
),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py b/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py
index d426d271f6e..95c600b91ce 100644
--- a/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py
+++ b/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py
@@ -1,106 +1,198 @@
"""
-Test_static_create_empty_contract_and_call_it_0wei.
+Measure CREATE of a codeless contract (optionally writing storage in its
+init code) followed by a STATICCALL to it, via CodeGasMeasure.
Ported from:
state_tests/stStaticCall/static_CREATE_EmptyContractAndCallIt_0weiFiller.json
+state_tests/stStaticCall/static_CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json
+
+@manually-enhanced: Do not overwrite. Two fillers folded into one
+parametrize; the storage-writing init code is composed (not hex blobs) so
+the measured CREATE/STATICCALL expectations derive from the same bytecode;
+the init code's inner CALL forwards all gas (the ported 0xEA60 budget OOGs
+under EIP-8037); the STATICCALL success flag stays inside the measured
+window. Replaces the prior EIP-8037 expect-any band-aid with fork-derived
+gas assertions.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Bytecode,
+ CodeGasMeasure,
+ Fork,
StateTestFiller,
- Storage,
Transaction,
compute_create_address,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+ADDRESS_SLOT = 0x1
+CREATE_GAS_SLOT = 0x2
+STATICCALL_FLAG_SLOT = 0x3
+STATICCALL_GAS_SLOT = 0x64
+STORED_VALUE = 0xC
+
+FORWARDED_GAS = 0xEA60
+
@pytest.mark.ported_from(
[
- "state_tests/stStaticCall/static_CREATE_EmptyContractAndCallIt_0weiFiller.json" # noqa: E501
+ "state_tests/stStaticCall/static_CREATE_EmptyContractAndCallIt_0weiFiller.json", # noqa: E501
+ "state_tests/stStaticCall/static_CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json", # noqa: E501
+ ],
+)
+@pytest.mark.valid_from("Berlin")
+@pytest.mark.parametrize(
+ "with_storage",
+ [
+ pytest.param(False, id="empty_contract"),
+ pytest.param(True, id="with_storage"),
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.slow
-@pytest.mark.pre_alloc_mutable
def test_static_create_empty_contract_and_call_it_0wei(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
+ with_storage: bool,
) -> None:
- """Test_static_create_empty_contract_and_call_it_0wei."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- 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 CREATE and STATICCALL gas for a created codeless account."""
+ if with_storage:
+ # Called by the init code below; writes one cold fresh slot.
+ writer_store = Op.SSTORE(
+ key=0x1,
+ value=STORED_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=STORED_VALUE,
+ )
+ writer = pre.deploy_contract(code=writer_store + Op.STOP)
+
+ # The init code writes the created account's own slot 0 and calls
+ # the writer, then runs off its end (STOP) so no code is deposited.
+ # The inner CALL forwards all remaining gas (default Op.GAS).
+ initcode = Op.SSTORE(
+ key=0x0,
+ value=STORED_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=STORED_VALUE,
+ ) + Op.CALL(
+ address=writer,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
+ )
+ initcode_bytes = bytes(initcode)
+ assert len(initcode_bytes) <= 0x40, "init code must fit two words"
- # Source: lll
- # { [[0]](GAS) [[1]] (CREATE 0 0 32) [[2]](GAS) [[3]] (STATICCALL 60000 (SLOAD 1) 0 0 0 0) [[100]] (GAS) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.GAS)
- + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x20))
- + Op.SSTORE(key=0x2, value=Op.GAS)
- + Op.SSTORE(
- key=0x3,
- value=Op.STATICCALL(
- gas=0xEA60,
- address=Op.SLOAD(key=0x1),
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ # Memory is populated (and expanded to 0x40) before the measured
+ # window, so the CREATE itself expands nothing.
+ setup = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(
+ initcode_bytes[:0x20].ljust(0x20, b"\x00"), "big"
),
+ ) + Op.MSTORE(
+ offset=0x20,
+ value=int.from_bytes(
+ initcode_bytes[0x20:].ljust(0x20, b"\x00"), "big"
+ ),
+ )
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=0x0,
+ size=len(initcode_bytes),
+ new_memory_size=0x40,
+ old_memory_size=0x40,
+ init_code_size=len(initcode_bytes),
)
- + Op.SSTORE(key=0x64, value=Op.GAS)
- + Op.STOP,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ # The measured CREATE includes the child's work: the init code's
+ # own consumption plus the writer's store it calls.
+ child_cost = initcode.gas_cost(fork) + writer_store.gas_cost(fork)
+ else:
+ # CREATE over never-written memory runs 32 zero bytes as init code
+ # (STOP on the first byte), depositing no code and consuming
+ # nothing; the memory expansion happens inside the window.
+ setup = Bytecode()
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=0x0,
+ size=0x20,
+ new_memory_size=0x20,
+ init_code_size=0x20,
+ )
+ child_cost = 0
+
+ # The created address is stored inside the measured window (as in the
+ # ported filler) so the STATICCALL can target it at runtime.
+ create_store = Op.SSTORE(
+ key=ADDRESS_SLOT,
+ value=create_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=600000,
+ # The created account exists (nonce 1) and is warm (CREATE accessed
+ # it); the success flag is stored inside the measured window — a
+ # wrongly failed STATICCALL would otherwise be unobservable.
+ staticcall_code = Op.STATICCALL(
+ gas=FORWARDED_GAS,
+ address=Op.SLOAD(key=ADDRESS_SLOT, key_warm=True),
+ address_warm=True,
+ )
+ staticcall_store = Op.SSTORE(
+ key=STATICCALL_FLAG_SLOT,
+ value=staticcall_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- if fork.is_eip_enabled(8037):
- contract_0_storage = Storage.model_validate(
- {1: compute_create_address(address=contract_0, nonce=0), 3: 1}
- )
- contract_0_storage.set_expect_any(0)
- contract_0_storage.set_expect_any(2)
- contract_0_storage.set_expect_any(100)
- else:
- contract_0_storage = Storage.model_validate(
- {
- 0: 0x8D5B6,
- 1: compute_create_address(address=contract_0, nonce=0),
- 2: 0x7ABF8,
- 3: 1,
- 100: 0x6FE6E,
- }
+ contract = pre.deploy_contract(
+ code=setup
+ + CodeGasMeasure(
+ code=create_store,
+ extra_stack_items=0,
+ sstore_key=CREATE_GAS_SLOT,
)
+ + CodeGasMeasure(
+ code=staticcall_store,
+ extra_stack_items=0,
+ sstore_key=STATICCALL_GAS_SLOT,
+ ),
+ )
+
+ tx = Transaction(
+ sender=pre.fund_eoa(),
+ to=contract,
+ state_gas_reservoir=0,
+ )
+
+ measured_create = create_store.gas_cost(fork) + child_cost
+ measured_staticcall = staticcall_store.gas_cost(fork)
+
+ created = compute_create_address(address=contract, nonce=1)
post = {
- contract_0: Account(storage=contract_0_storage),
- compute_create_address(address=contract_0, nonce=0): Account(nonce=1),
+ contract: Account(
+ storage={
+ ADDRESS_SLOT: created,
+ CREATE_GAS_SLOT: measured_create,
+ STATICCALL_FLAG_SLOT: 1,
+ STATICCALL_GAS_SLOT: measured_staticcall,
+ },
+ ),
+ created: Account(
+ nonce=1,
+ storage={0: STORED_VALUE} if with_storage else {},
+ ),
}
+ if with_storage:
+ post[writer] = Account(storage={1: STORED_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/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py b/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py
deleted file mode 100644
index 91623e4efe9..00000000000
--- a/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py
+++ /dev/null
@@ -1,124 +0,0 @@
-"""
-Test_static_create_empty_contract_with_storage_and_call_it_0wei.
-
-Ported from:
-state_tests/stStaticCall/static_CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json
-"""
-
-import pytest
-from execution_testing import (
- Account,
- Address,
- Alloc,
- Bytes,
- Environment,
- StateTestFiller,
- Storage,
- Transaction,
- compute_create_address,
-)
-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/stStaticCall/static_CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json" # noqa: E501
- ],
-)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.slow
-@pytest.mark.pre_alloc_mutable
-def test_static_create_empty_contract_with_storage_and_call_it_0wei(
- state_test: StateTestFiller,
- pre: Alloc,
- fork: Fork,
-) -> None:
- """Test_static_create_empty_contract_with_storage_and_call_it_0wei."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- 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) (MSTORE 0 0x600c6000556000600060006000600073c94f5374fce5edbc8e2a8697c1533167) (MSTORE 32 0x7e6ebf0b61ea60f1000000000000000000000000000000000000000000000000) [[1]] (CREATE 0 0 64) [[2]] (GAS) [[3]] (STATICCALL 60000 (SLOAD 1) 0 0 0 0) [[100]] (GAS) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.GAS)
- + Op.MSTORE(
- offset=0x0,
- value=0x600C6000556000600060006000600073C94F5374FCE5EDBC8E2A8697C1533167, # noqa: E501
- )
- + Op.MSTORE(
- offset=0x20,
- value=0x7E6EBF0B61EA60F1000000000000000000000000000000000000000000000000, # noqa: E501
- )
- + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x40))
- + Op.SSTORE(key=0x2, value=Op.GAS)
- + Op.SSTORE(
- key=0x3,
- value=Op.STATICCALL(
- gas=0xEA60,
- address=Op.SLOAD(key=0x1),
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x64, value=Op.GAS)
- + Op.STOP,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
- )
- # Source: lll
- # {[[1]]12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP,
- balance=0xE8D4A51000,
- nonce=0,
- address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
- )
-
- tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=600000,
- )
-
- if fork.is_eip_enabled(8037):
- contract_0_storage = Storage.model_validate(
- {1: compute_create_address(address=contract_0, nonce=0), 3: 1}
- )
- contract_0_storage.set_expect_any(0)
- contract_0_storage.set_expect_any(2)
- contract_0_storage.set_expect_any(100)
- else:
- contract_0_storage = Storage.model_validate(
- {
- 0: 0x8D5B6,
- 1: compute_create_address(address=contract_0, nonce=0),
- 2: 0x6F4F0,
- 3: 1,
- 100: 0x64766,
- }
- )
- post = {
- contract_0: Account(storage=contract_0_storage),
- compute_create_address(address=contract_0, nonce=0): Account(nonce=1),
- contract_1: Account(storage={1: 12}),
- }
-
- state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py b/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py
index b5965a5da21..3ea3f5959b4 100644
--- a/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py
+++ b/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py
@@ -1,162 +1,131 @@
"""
-Test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.
+Verify a STATICCALL that asks for more gas than is available is clamped to
+63/64 of the remaining gas (EIP-150), across callees that succeed, out-of-gas,
+and violate the static context.
Ported from:
state_tests/stStaticCall/static_ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json
+
+@manually-enhanced: Do not overwrite. An outer call caps the caller frame so
+the callee budgets are fork-independent; the flag slot is pre-written so the
+post-call store is a cheap dirty-warm write affordable from the 1/64
+retention even under EIP-8037; distinct flag values discriminate success,
+callee failure, and caller OOG (the ported {1: 0} expectation could not).
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Environment,
- Hash,
+ Fork,
StateTestFiller,
Transaction,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+FLAG_SLOT = 0x1
+# Pre-written sentinel: if the caller frame dies after the call, the slot
+# keeps this value instead of reverting to an ambiguous zero.
+FLAG_PREWRITE = 0xFF
+# Stored flag = 0x10 + STATICCALL result: 0x11 success, 0x10 failure.
+FLAG_BASE = 0x10
+
+# Far larger than any gas the caller frame can hold, so the EIP-150 clamp
+# (not the operand) decides what the callee receives.
+OVERSIZED_GAS_ASK = 2**61
+# The outer call pins the caller frame's budget: large enough to cover the
+# caller's own cold flag store (~111k under EIP-8037) and the successful
+# callee, small enough that the looping callee (~6.5M) still runs out.
+CALLER_GAS = 1_000_000
+
@pytest.mark.ported_from(
[
"state_tests/stStaticCall/static_ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.slow
+@pytest.mark.valid_from("Byzantium")
@pytest.mark.parametrize(
- "d, g, v",
+ "callee_kind, callee_succeeds",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="d0",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1",
- ),
- pytest.param(
- 2,
- 0,
- 0,
- id="d2",
- ),
+ pytest.param("mstore", True, id="d0"),
+ pytest.param("extcodesize_loop", False, id="d1"),
+ pytest.param("sstore_static_violation", False, id="d2"),
],
)
-@pytest.mark.pre_alloc_mutable
def test_static_execute_call_that_ask_fore_gas_then_trabsaction_has(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ callee_kind: str,
+ callee_succeeds: bool,
) -> None:
- """Test_static_execute_call_that_ask_fore_gas_then_trabsaction_has."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(amount=0x989680)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
+ """A STATICCALL asking for more gas than available gets 63/64 of it."""
+ if callee_kind == "mstore":
+ # Trivial: succeeds well within the forwarded gas.
+ callee = pre.deploy_contract(
+ code=Op.MSTORE(offset=0x1, value=0x1) + Op.STOP
+ )
+ elif callee_kind == "extcodesize_loop":
+ # 50000 EXTCODESIZE iterations (~6.5M gas): must exhaust the
+ # clamped forwarded gas, proving the callee did not receive the
+ # oversized ask.
+ callee = 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,
+ )
+ else:
+ # SSTORE inside a static context: exceptional halt regardless of
+ # gas.
+ callee = pre.deploy_contract(
+ code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP
+ )
- # Source: lll
- # { [[1]] (STATICCALL 600000 (CALLDATALOAD 0) 0 0 0 0) }
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(
- key=0x1,
- value=Op.STATICCALL(
- gas=0x927C0,
- address=Op.CALLDATALOAD(offset=0x0),
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ # The flag slot is written twice: the pre-write pays the cold/state
+ # cost with the full budget, so the post-call store is a dirty-warm
+ # write the 1/64 retention can always afford.
+ caller = pre.deploy_contract(
+ code=Op.SSTORE(key=FLAG_SLOT, value=FLAG_PREWRITE)
+ + Op.SSTORE(
+ key=FLAG_SLOT,
+ value=Op.ADD(
+ FLAG_BASE,
+ Op.STATICCALL(gas=OVERSIZED_GAS_ASK, address=callee),
),
)
+ Op.STOP,
- nonce=0,
- address=Address(0xA256EBCC5536CDA56E04C39FE9584ECC7594A438), # noqa: E501
- )
- # Source: lll
- # { (MSTORE 1 1) }
- addr = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x1, value=0x1) + Op.STOP,
- balance=0x186A0,
- nonce=0,
- address=Address(0x3DC16A13CF554533F380CC938A2C1AB04DAC534F), # noqa: E501
)
- # Source: lll
- # { (def 'i 0x80) (for {} (< @i 50000) [i](+ @i 1) (EXTCODESIZE 1)) }
- addr_2 = 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
+
+ # The outer call pins the caller frame's gas so the callee budgets do
+ # not depend on the tx gas limit; the clamp must always bite.
+ assert CALLER_GAS < OVERSIZED_GAS_ASK, "the 63/64 clamp must apply"
+ entry = pre.deploy_contract(
+ code=Op.SSTORE(key=0x0, value=Op.CALL(gas=CALLER_GAS, address=caller))
+ Op.STOP,
- balance=0x186A0,
- nonce=0,
- address=Address(0x73EF1878A0F2C9629DEDC1B1E9BE8D77DCF93688), # noqa: E501
- )
- # Source: lll
- # { (SSTORE 1 1) }
- addr_3 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP,
- balance=0x186A0,
- nonce=0,
- address=Address(0xCE4CCBFFAF450AE2126EB96DCD7C891F37764F20), # noqa: E501
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": [1, 2], "gas": -1, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={1: 0})},
- },
- {
- "indexes": {"data": [0], "gas": -1, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={1: 1})},
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Hash(addr, left_padding=True),
- Hash(addr_2, left_padding=True),
- Hash(addr_3, left_padding=True),
- ]
- tx_gas = [100000]
-
tx = Transaction(
- sender=sender,
- to=target,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- error=_exc,
+ sender=pre.fund_eoa(),
+ to=entry,
+ state_gas_reservoir=0,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ post = {
+ entry: Account(storage={0: 1}),
+ caller: Account(
+ storage={FLAG_SLOT: FLAG_BASE + (1 if callee_succeeds else 0)},
+ ),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)