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
deleted file mode 100644
index 7aba6f38bb0..00000000000
--- a/tests/ported_static/amsterdam_skip_list.txt
+++ /dev/null
@@ -1,208 +0,0 @@
-# Amsterdam ported static skip list.
-#
-# Test cases in this list are temporarily skipped for the Amsterdam
-# fork due to EIP-8037's two-dimensional gas model. Gas limits in the
-# underlying ported static tests have not yet been updated to account
-# for state gas.
-#
-# Entries are substring-matched against each pytest nodeid (after
-# stripping the fixture-format suffix in conftest.py).
-#
-# Total entries: 153
-
-# 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]
-
-# 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/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]
-stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g3]
-stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Amsterdam--g0]
-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]
-
-# stCreate2 (31)
-stCreate2/test_create2_oo_gafter_init_code_revert2.py::test_create2_oo_gafter_init_code_revert2[fork_Amsterdam]
-stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_CallCode_Refund_NoOoG]
-stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_Create2_Refund_NoOoG]
-stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_Create_Refund_NoOoG]
-stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_DelegateCall_Refund_NoOoG]
-stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d0]
-stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d1]
-stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d2]
-stCreate2/test_create2no_cash.py::test_create2no_cash[fork_Amsterdam-d1]
-stCreate2/test_create_message_reverted_oog_in_init2.py::test_create_message_reverted_oog_in_init2[fork_Amsterdam--g0]
-stCreate2/test_create_message_reverted_oog_in_init2.py::test_create_message_reverted_oog_in_init2[fork_Amsterdam--g1]
-stCreate2/test_revert_depth_create2_oog.py::test_revert_depth_create2_oog[fork_Amsterdam-d0-g1-v0]
-stCreate2/test_revert_depth_create2_oog.py::test_revert_depth_create2_oog[fork_Amsterdam-d0-g1-v1]
-stCreate2/test_revert_depth_create2_oog.py::test_revert_depth_create2_oog[fork_Amsterdam-d1-g1-v0]
-stCreate2/test_revert_depth_create2_oog.py::test_revert_depth_create2_oog[fork_Amsterdam-d1-g1-v1]
-stCreate2/test_revert_depth_create2_oog_berlin.py::test_revert_depth_create2_oog_berlin[fork_Amsterdam-d0-g1-v0]
-stCreate2/test_revert_depth_create2_oog_berlin.py::test_revert_depth_create2_oog_berlin[fork_Amsterdam-d0-g1-v1]
-stCreate2/test_revert_depth_create2_oog_berlin.py::test_revert_depth_create2_oog_berlin[fork_Amsterdam-d1-g1-v0]
-stCreate2/test_revert_depth_create2_oog_berlin.py::test_revert_depth_create2_oog_berlin[fork_Amsterdam-d1-g1-v1]
-stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g0-v0]
-stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g0-v1]
-stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g0-v0]
-stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g0-v1]
-stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g1-v0]
-stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g1-v1]
-stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d0-g0-v0]
-stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d0-g0-v1]
-stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g0-v0]
-stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g0-v1]
-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/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]
-stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-invalid-opcode-v1]
-stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-ok-v1]
-stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-constructor-v1]
-stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-post-constr-v1]
-stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-0xef-v1]
-stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-contructor-revert-v1]
-stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-invalid-opcode-v1]
-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]
-stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1]
-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/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]
-stEIP150Specific/test_transaction64_rule_d64p1.py::test_transaction64_rule_d64p1[fork_Amsterdam]
-
-# stEIP150singleCodeGasPrices (2)
-stEIP150singleCodeGasPrices/test_gas_cost.py::test_gas_cost[fork_Amsterdam-d40]
-stEIP150singleCodeGasPrices/test_gas_cost_berlin.py::test_gas_cost_berlin[fork_Amsterdam-d40]
-
-# stEIP158Specific (1)
-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]
-
-# stMemExpandingEIP150Calls (4)
-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)
-stMemoryTest/test_oog.py::test_oog[fork_Amsterdam-success14]
-stMemoryTest/test_oog.py::test_oog[fork_Amsterdam-success15]
-
-# stRefundTest (7)
-stRefundTest/test_refund50_2.py::test_refund50_2[fork_Amsterdam]
-stRefundTest/test_refund50percent_cap.py::test_refund50percent_cap[fork_Amsterdam]
-stRefundTest/test_refund600.py::test_refund600[fork_Amsterdam]
-stRefundTest/test_refund_call_a.py::test_refund_call_a[fork_Amsterdam]
-stRefundTest/test_refund_suicide50procent_cap.py::test_refund_suicide50procent_cap[fork_Amsterdam-d0]
-stRefundTest/test_refund_suicide50procent_cap.py::test_refund_suicide50procent_cap[fork_Amsterdam-d1]
-stRefundTest/test_refund_tx_to_suicide.py::test_refund_tx_to_suicide[fork_Amsterdam]
-
-# stRevertTest (12)
-stRevertTest/test_loop_calls_depth_then_revert.py::test_loop_calls_depth_then_revert[fork_Amsterdam]
-stRevertTest/test_loop_delegate_calls_depth_then_revert.py::test_loop_delegate_calls_depth_then_revert[fork_Amsterdam]
-stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g1-v0]
-stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g1-v1]
-stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d0-g1-v0]
-stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d0-g1-v1]
-stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d1-g1-v0]
-stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d1-g1-v1]
-stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d0-g0]
-stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d1-g0]
-stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d2-g0]
-stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d3-g0]
-
-# stSStoreTest (4)
-stSStoreTest/test_sstore_gas.py::test_sstore_gas[fork_Amsterdam]
-stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d2]
-stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d5]
-stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d8]
-
-# stSolidityTest (3)
-stSolidityTest/test_recursive_create_contracts.py::test_recursive_create_contracts[fork_Amsterdam]
-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]
-
-# stSystemOperationsTest (5)
-stSystemOperationsTest/test_ab_acalls0.py::test_ab_acalls0[fork_Amsterdam]
-stSystemOperationsTest/test_ab_acalls3.py::test_ab_acalls3[fork_Amsterdam]
-stSystemOperationsTest/test_call_recursive_bomb3.py::test_call_recursive_bomb3[fork_Amsterdam]
-stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v1]
-stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v2]
-
-# stTransactionTest (4)
-stTransactionTest/test_opcodes_transaction_init.py::test_opcodes_transaction_init[fork_Amsterdam-d120]
-stTransactionTest/test_opcodes_transaction_init.py::test_opcodes_transaction_init[fork_Amsterdam-side_effects]
-stTransactionTest/test_store_gas_on_create.py::test_store_gas_on_create[fork_Amsterdam]
-stTransactionTest/test_suicides_and_internal_call_suicides_success.py::test_suicides_and_internal_call_suicides_success[fork_Amsterdam-d1]
diff --git a/tests/ported_static/conftest.py b/tests/ported_static/conftest.py
deleted file mode 100644
index c5d9aff2a9d..00000000000
--- a/tests/ported_static/conftest.py
+++ /dev/null
@@ -1,68 +0,0 @@
-"""
-Conftest for ported static tests.
-
-Temporarily skip ported static tests that fail on Amsterdam and its
-descendant forks due to EIP-8037's two-dimensional gas model. The gas
-limits in these ported static test cases have not yet been updated to
-account for state gas.
-
-TODO: Update gas limits in the 3452 failing ported static test cases and
-remove this skip list.
-"""
-
-from pathlib import Path
-
-import pytest
-from execution_testing.forks import Amsterdam
-
-_SKIP_LIST_PATH = Path(__file__).parent / "amsterdam_skip_list.txt"
-_AMSTERDAM_SKIP_CASES: frozenset[str] = frozenset(
- line.strip()
- for line in _SKIP_LIST_PATH.read_text().splitlines()
- if line.strip() and not line.lstrip().startswith("#")
-)
-
-# Fixture format suffixes pytest appends inside the parametrize id. These
-# must be stripped from the nodeid before substring-matching against the
-# skip list, because the skip list predates these suffixes.
-_FIXTURE_FORMAT_TOKENS: tuple[str, ...] = (
- "-blockchain_test_engine_from_state_test",
- "-blockchain_test_from_state_test",
- "-blockchain_test_engine",
- "-blockchain_test",
- "-state_test",
-)
-
-
-def _normalize_nodeid(nodeid: str) -> str:
- """Strip pytest fixture-format suffixes to match the skip list format."""
- for token in _FIXTURE_FORMAT_TOKENS:
- nodeid = nodeid.replace(token, "")
- return nodeid
-
-
-def pytest_collection_modifyitems(
- config: pytest.Config, items: list[pytest.Item]
-) -> None:
- """Skip ported static test cases listed in amsterdam_skip_list.txt."""
- skip_marker = pytest.mark.skip(
- reason="Ported static test gas limits not yet updated for EIP-8037"
- )
- for item in items:
- if "ported_static" not in item.nodeid:
- continue
- callspec = getattr(item, "callspec", None)
- fork = callspec.params.get("fork") if callspec else None
- if fork is None or not fork >= Amsterdam:
- continue
- # The skip list is written against fork_Amsterdam, but the
- # EIP-8037 breakage applies equally to its descendant forks.
- # Rewriting the item's fork token to Amsterdam's lets one list
- # cover them all.
- normalized = _normalize_nodeid(item.nodeid).replace(
- f"fork_{fork.name()}", "fork_Amsterdam"
- )
- for skip_case in _AMSTERDAM_SKIP_CASES:
- if skip_case in normalized:
- item.add_marker(skip_marker)
- break
diff --git a/tests/ported_static/stAttackTest/test_crashing_transaction.py b/tests/ported_static/stAttackTest/test_crashing_transaction.py
index 8f8320dd1b8..a7874ced2d1 100644
--- a/tests/ported_static/stAttackTest/test_crashing_transaction.py
+++ b/tests/ported_static/stAttackTest/test_crashing_transaction.py
@@ -1,8 +1,17 @@
"""
-Https://ropsten.etherscan.io/tx/0x8ec445380649f6c75a042a438ea9256c2fab2a...
+Verify the Ropsten "crashing transaction" attack replay: a creation
+transaction whose init code CREATEs children in a loop while more than
+50000 gas remains, then deposits its runtime code.
Ported from:
state_tests/stAttackTest/CrashingTransactionFiller.json
+
+@manually-enhanced: Do not overwrite. On pre-EIP-8037 forks the loop
+drains to the ported child count (created nonce 124); under EIP-8037 an
+iteration costs more than the loop's 50000-gas guard (new-account plus
+code-deposit state gas spill from the frame), so the loop enters an
+iteration it cannot afford and the whole creation deterministically
+reverts — the split post pins both behaviors.
"""
import pytest
@@ -11,6 +20,7 @@
Address,
Alloc,
Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -25,12 +35,14 @@
["state_tests/stAttackTest/CrashingTransactionFiller.json"],
)
@pytest.mark.valid_from("Cancun")
+# Required: the sender is funded at the attack's historical nonce 3270.
@pytest.mark.pre_alloc_mutable
def test_crashing_transaction(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Https://ropsten."""
+ """Replay the attack loop; EIP-8037 makes the creation revert."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000, nonce=3270)
@@ -95,13 +107,20 @@ def test_crashing_transaction(
gas_price=11,
)
- post = {
- sender: Account(nonce=3271),
- compute_create_address(address=sender, nonce=3270): Account(
+ created = compute_create_address(address=sender, nonce=3270)
+ if fork.is_eip_enabled(8037):
+ # An iteration's state gas exceeds the loop's 50000-gas guard,
+ # so the init frame dies mid-CREATE and no account survives.
+ created_account: Account | None = Account.NONEXISTENT
+ else:
+ created_account = Account(
code=bytes.fromhex("60606040526008565b00"),
balance=1,
nonce=124,
- ),
+ )
+ post = {
+ sender: Account(nonce=3271),
+ created: created_account,
}
state_test(env=env, pre=pre, post=post, tx=tx)
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/stCallCodes/test_callcode_in_initcode_to_existing_contract.py b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py
index 900c2e78bda..5e2547c7a1f 100644
--- a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py
+++ b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py
@@ -1,199 +1,132 @@
"""
-Callcode inside create/create2 contract init to existing contract.
+Verify a CALLCODE made from inside init code to an existing contract.
+
+The created account's init code CALLCODEs an already-deployed contract,
+so that contract's code runs in the freshly created account's context:
+its storage write lands in the created account (never in the existing
+contract), and the transferred value stays with the created account.
Ported from:
state_tests/stCallCodes/callcodeInInitcodeToExistingContractFiller.json
+
+@manually-enhanced: Do not overwrite. The calldata-dispatch entry
+contract is collapsed into a direct transaction to the create-runner,
+the init code is composed and shared with the CREATE2 address
+computation, sub-calls forward all gas (EIP-8037-proof), and the post
+also pins the created account's code/nonce/balance and that the
+existing contract's own storage stays untouched.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
- Hash,
+ Bytecode,
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,
-)
+from execution_testing.vm import Op, Opcode
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# Endowment the runner sends into the creation; the init code's CALLCODE
+# then names the same amount, a self-to-self transfer the created
+# account's balance must cover and keep.
+CREATE_ENDOWMENT = 1
+CALLCODE_VALUE = CREATE_ENDOWMENT
+RUNNER_BALANCE = 10_000
+CREATE2_SALT = 0
+
+# Written by the init code with the CALLCODE's success flag.
+SUCCESS_FLAG_SLOT = 1
+# Written by the existing contract's code, in the caller's context.
+DELEGATE_SLOT = 2
+
+
+def memory_stores(data: bytes) -> Bytecode:
+ """Write the given bytes to memory starting at offset zero."""
+ code = Bytecode()
+ for offset in range(0, len(data), 32):
+ chunk = data[offset : offset + 32].ljust(32, b"\x00")
+ code += Op.MSTORE(offset, int.from_bytes(chunk, "big"))
+ return code
+
@pytest.mark.ported_from(
[
"state_tests/stCallCodes/callcodeInInitcodeToExistingContractFiller.json" # noqa: E501
],
)
-@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.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Constantinople")
+@pytest.mark.parametrize("opcode", [Op.CREATE, Op.CREATE2])
def test_callcode_in_initcode_to_existing_contract(
state_test: StateTestFiller,
pre: Alloc,
- fork: Fork,
- d: int,
- g: int,
- v: int,
+ opcode: Opcode,
) -> None:
- """Callcode inside create/create2 contract init to existing contract."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x1100000000000000000000000000000000000000)
- contract_1 = Address(0x1000000000000000000000000000000000000000)
- contract_2 = Address(0x2000000000000000000000000000000000000000)
- contract_3 = Address(0x1000000000000000000000000000000000000001)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """A CALLCODE in init code runs in the created account's context."""
+ existing = pre.deploy_contract(
+ code=Op.SSTORE(key=DELEGATE_SLOT, value=1) + Op.STOP,
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
- )
-
- pre[sender] = Account(balance=0x2386F26FC10000)
- # Source: lll
- # { (CALL 300000 (CALLDATALOAD 0) 0 0 0 0 0) }
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.CALL(
- gas=0x493E0,
- address=Op.CALLDATALOAD(offset=0x0),
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ initcode = (
+ Op.SSTORE(
+ key=SUCCESS_FLAG_SLOT,
+ value=Op.CALLCODE(address=existing, value=CALLCODE_VALUE),
)
- + Op.STOP,
- nonce=0,
- address=Address(0x1100000000000000000000000000000000000000), # noqa: E501
- )
- # Source: lll
- # { (SSTORE 2 1) }
- contract_3 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x1) + Op.STOP,
- nonce=0,
- address=Address(0x1000000000000000000000000000000000000001), # noqa: E501
- )
- # Source: lll
- # {(seq (CREATE2 1 0 (lll (seq [[1]] (CALLCODE 50000 0x1000000000000000000000000000000000000001 1 0 0 0 0)) 0) 0) )} # noqa: E501
- contract_2 = pre.deploy_contract( # noqa: F841
- code=Op.PUSH1[0x0]
- + Op.PUSH1[0x27]
- + Op.CODECOPY(dest_offset=0x0, offset=0x11, size=Op.DUP1)
- + Op.PUSH1[0x0]
- + Op.PUSH1[0x1]
- + Op.CREATE2
+ Op.STOP
- + Op.INVALID
- + Op.SSTORE(
- key=0x1,
- value=Op.CALLCODE(
- gas=0xC350,
- address=0x1000000000000000000000000000000000000001,
- value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.STOP,
- balance=10000,
- nonce=0,
- address=Address(0x2000000000000000000000000000000000000000), # noqa: E501
)
- # Source: lll
- # {(seq (CREATE 1 0 (lll (seq [[1]] (CALLCODE 50000 0x1000000000000000000000000000000000000001 1 0 0 0 0)) 0) ) )} # noqa: E501
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.PUSH1[0x27]
- + Op.CODECOPY(dest_offset=0x0, offset=0xF, size=Op.DUP1)
- + Op.PUSH1[0x0]
- + Op.PUSH1[0x1]
- + Op.CREATE
- + Op.STOP
- + Op.INVALID
- + Op.SSTORE(
- key=0x1,
- value=Op.CALLCODE(
- gas=0xC350,
- address=0x1000000000000000000000000000000000000001,
- value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
+ initcode_bytes = bytes(initcode)
+
+ if opcode == Op.CREATE2:
+ create_call = Op.CREATE2(
+ value=CREATE_ENDOWMENT,
+ offset=0,
+ size=len(initcode_bytes),
+ salt=CREATE2_SALT,
+ )
+ else:
+ create_call = Op.CREATE(
+ value=CREATE_ENDOWMENT, offset=0, size=len(initcode_bytes)
)
- + Op.STOP,
- balance=10000,
- nonce=0,
- address=Address(0x1000000000000000000000000000000000000000), # noqa: E501
+ runner = pre.deploy_contract(
+ code=memory_stores(initcode_bytes) + create_call + Op.STOP,
+ balance=RUNNER_BALANCE,
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 0, "gas": -1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- compute_create_address(address=contract_1, nonce=0): Account(
- storage={1: 1, 2: 1}, balance=1
- ),
- },
- },
- {
- "indexes": {"data": 1, "gas": -1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- Address(0x11B62573BE8F72B4085BAFE5B675B3E7F08ED522): Account(
- storage={1: 1, 2: 1}, balance=1
- ),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Hash(contract_1, left_padding=True),
- Hash(contract_2, left_padding=True),
- ]
- tx_gas = [1000000]
+ if opcode == Op.CREATE2:
+ created = compute_create_address(
+ address=runner,
+ salt=CREATE2_SALT,
+ initcode=initcode,
+ opcode=Op.CREATE2,
+ )
+ else:
+ # Deployed contracts start at nonce 1.
+ created = compute_create_address(address=runner, nonce=1)
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- error=_exc,
+ sender=pre.fund_eoa(),
+ to=runner,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ post = {
+ created: Account(
+ # The init code deploys no code but writes its own storage.
+ code=b"",
+ nonce=1,
+ balance=CREATE_ENDOWMENT,
+ storage={SUCCESS_FLAG_SLOT: 1, DELEGATE_SLOT: 1},
+ ),
+ runner: Account(
+ nonce=2,
+ balance=RUNNER_BALANCE - CREATE_ENDOWMENT,
+ storage={},
+ ),
+ # The existing contract's own storage must stay untouched.
+ existing: Account(storage={}),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py
index 4822486a1ea..f58442f3cf0 100644
--- a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py
+++ b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py
@@ -1,18 +1,28 @@
"""
-Callcode inside create/create2 contract init to existing contract.
+Verify a value-bearing CALLCODE made from inside init code to an
+existing contract.
+
+The runner endows the creation with value; the init code CALLCODEs an
+already-deployed contract naming that same value, so the existing
+contract's code runs in the created account's context: its storage
+write lands in the created account, and the value transfer is
+self-to-self, leaving the endowment with the created account.
Ported from:
state_tests/stCallCodes/callcodeInInitcodeToExistingContractWithValueTransferFiller.json
+
+@manually-enhanced: Do not overwrite. The raw-word init code is
+composed, sub-calls forward all gas (EIP-8037-proof), the transaction
+budget is maxed, and the post also pins the created account's
+code/nonce/balance and that the existing contract's own storage stays
+untouched.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Bytecode,
StateTestFiller,
Transaction,
compute_create_address,
@@ -22,72 +32,82 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# Endowment the runner sends into the creation; the init code's CALLCODE
+# then names the same amount, a self-to-self transfer the created
+# account's balance must cover and keep.
+CREATE_ENDOWMENT = 5
+CALLCODE_VALUE = CREATE_ENDOWMENT
+RUNNER_BALANCE = 10_000
+
+# Written by the init code with the CALLCODE's success flag.
+SUCCESS_FLAG_SLOT = 0
+# Written by the existing contract's code, in the caller's context.
+DELEGATE_SLOT = 2
+
+
+def memory_stores(data: bytes) -> Bytecode:
+ """Write the given bytes to memory starting at offset zero."""
+ code = Bytecode()
+ for offset in range(0, len(data), 32):
+ chunk = data[offset : offset + 32].ljust(32, b"\x00")
+ code += Op.MSTORE(offset, int.from_bytes(chunk, "big"))
+ return code
+
@pytest.mark.ported_from(
[
"state_tests/stCallCodes/callcodeInInitcodeToExistingContractWithValueTransferFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("SpuriousDragon")
def test_callcode_in_initcode_to_existing_contract_with_value_transfer(
state_test: StateTestFiller,
pre: Alloc,
) -> None:
- """Callcode inside create/create2 contract init to existing contract."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x1000000000000000000000000000000000000000)
- contract_1 = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """A value-bearing CALLCODE in init code keeps the endowment."""
+ existing = pre.deploy_contract(
+ code=Op.SSTORE(key=DELEGATE_SLOT, value=1) + Op.STOP,
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ initcode = (
+ Op.SSTORE(
+ key=SUCCESS_FLAG_SLOT,
+ value=Op.CALLCODE(address=existing, value=CALLCODE_VALUE),
+ )
+ + Op.STOP
)
+ initcode_bytes = bytes(initcode)
- pre[sender] = Account(balance=0x2386F26FC10000)
- # Source: lll
- # { (MSTORE 0 0x6040600060406000600573945304eb96065b2a98b57a48a06ae28d285a71b562) (MSTORE 32 0x0186a0f260005500000000000000000000000000000000000000000000000000) (CREATE 5 0 64) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(
- offset=0x0,
- value=0x6040600060406000600573945304EB96065B2A98B57A48A06AE28D285A71B562, # noqa: E501
- )
- + Op.MSTORE(
- offset=0x20,
- value=0x186A0F260005500000000000000000000000000000000000000000000000000, # noqa: E501
- )
- + Op.CREATE(value=0x5, offset=0x0, size=0x40)
+ runner = pre.deploy_contract(
+ code=memory_stores(initcode_bytes)
+ + Op.CREATE(value=CREATE_ENDOWMENT, offset=0, size=len(initcode_bytes))
+ Op.STOP,
- balance=10000,
- nonce=0,
- address=Address(0x1000000000000000000000000000000000000000), # noqa: E501
- )
- # Source: lll
- # { (SSTORE 2 1) }
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x1) + Op.STOP,
- nonce=0,
- address=Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5), # noqa: E501
+ balance=RUNNER_BALANCE,
)
+ # Deployed contracts start at nonce 1.
+ created = compute_create_address(address=runner, nonce=1)
+
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=453081,
+ sender=pre.fund_eoa(),
+ to=runner,
)
post = {
- compute_create_address(address=contract_0, nonce=0): Account(
- storage={0: 1, 2: 1}, balance=5
+ created: Account(
+ # The init code deploys no code but writes its own storage.
+ code=b"",
+ nonce=1,
+ balance=CREATE_ENDOWMENT,
+ storage={SUCCESS_FLAG_SLOT: 1, DELEGATE_SLOT: 1},
+ ),
+ runner: Account(
+ nonce=2,
+ balance=RUNNER_BALANCE - CREATE_ENDOWMENT,
+ storage={},
),
+ # The existing contract's own storage must stay untouched.
+ existing: 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/stCallCreateCallCodeTest/test_call1024_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py
index 16729e411a8..c73e13d0235 100644
--- a/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py
+++ b/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py
@@ -1,153 +1,249 @@
"""
-Calldepth with oog.
+Verify a self-recursive CALL chain that terminates by out-of-gas.
+
+Each level bumps a shared depth counter, forwards almost all its gas to
+a call to itself (keeping a 10,000 reserve for its post-call stores),
+then records the call's success flag and a depth marker. Levels too deep
+to afford their stores halt and roll back, so the surviving storage pins
+the exact depth the budget reaches under the EIP-150 63/64 rule.
Ported from:
state_tests/stCallCreateCallCodeTest/Call1024OOGFiller.json
+
+@manually-enhanced: Do not overwrite. The post state is predicted by an
+exact fork-derived replay of the recursion's gas flow (EIP-150 grants,
+warm/cold and SSTORE pricing via opcode metadata, EIP-8037 state-gas
+spill), validated against the ported Cancun depths; the hardcoded
+self-address is replaced by ADDRESS.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+COUNTER_SLOT = 0
+RESULT_SLOT = 1
+MARKER_SLOT = 2
+# Gas each level keeps back for its post-call stores.
+GAS_RESERVE = 10_000
+# The ask factor zeroes out at the call-depth limit (never reached here;
+# the recursion always dies of out-of-gas first).
+DEPTH_CUTOFF = 1025
+# The marker store writes 1 + DEPTH_MARKER * depth.
+DEPTH_MARKER = 1000
+
+RECURSIVE_CALL_OP = Op.CALL
+
+RECURSION_CODE = (
+ Op.SSTORE(
+ key=COUNTER_SLOT,
+ value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1),
+ )
+ + Op.SSTORE(
+ key=RESULT_SLOT,
+ value=RECURSIVE_CALL_OP(
+ gas=Op.MUL(
+ Op.SUB(Op.GAS, GAS_RESERVE),
+ Op.SUB(1, Op.DIV(Op.SLOAD(key=COUNTER_SLOT), DEPTH_CUTOFF)),
+ ),
+ address=Op.ADDRESS,
+ ),
+ )
+ + Op.SSTORE(
+ key=MARKER_SLOT,
+ value=Op.ADD(1, Op.MUL(Op.SLOAD(key=COUNTER_SLOT), DEPTH_MARKER)),
+ )
+ + Op.STOP
+)
+
+
+def predict_recursion_storage(fork: Fork, tx_gas_limit: int) -> dict[int, int]:
+ """
+ Replay the recursion's gas flow and return the surviving storage.
+
+ Descend the self-call chain computing each level's EIP-150 grant,
+ then unwind: a level that cannot afford its post-call stores halts
+ and forfeits its entire grant to its parent, so the deepest level
+ that completes fixes the surviving depth counter (deeper levels'
+ writes and warmth all revert). Every cost is derived from the fork
+ via opcode metadata, including EIP-8037 state gas: with a sub-cap
+ gas limit the state reservoir is zero, so state charges spill from
+ the charging frame's own gas.
+ """
+ push_cost = Op.PUSH1[0].gas_cost(fork)
+ # The SUB and MUL of the ask expression run after GAS reads gas_left.
+ post_gas_read = Op.SUB.gas_cost(fork) + Op.MUL.gas_cost(fork)
+ # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally.
+ stipend = fork.gas_costs().CALL_STIPEND
+
+ def raw_store_cost(key_warm: bool, current: int, new: int) -> int:
+ """Cost of a bare SSTORE; original value is always zero here."""
+ return Op.SSTORE(
+ key_warm=key_warm,
+ original_value=0,
+ current_value=current,
+ new_value=new,
+ ).gas_cost(fork)
+
+ sstore_warm_set = raw_store_cost(True, 0, 1)
+ sstore_warm_dirty = raw_store_cost(True, 1, 2)
+ sstore_warm_noop = raw_store_cost(True, 1, 1)
+ sstore_cold_noop = raw_store_cost(False, 0, 0)
+ sstore_cold_set = raw_store_cost(False, 0, 1)
+
+ def bump_statics(key_warm: bool) -> int:
+ """Counter-bump costs before its SSTORE (value expr plus key)."""
+ return (
+ Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost(
+ fork
+ )
+ + push_cost
+ )
+
+ bump_statics_cold = bump_statics(False)
+ bump_statics_warm = bump_statics(True)
+
+ ask_expr = Op.MUL(
+ Op.SUB(Op.GAS, GAS_RESERVE),
+ Op.SUB(
+ 1,
+ Op.DIV(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_CUTOFF),
+ ),
+ )
+ call_upfront = RECURSIVE_CALL_OP(address_warm=True).gas_cost(fork)
+ # Everything charged before GAS reads gas_left: the call's argument
+ # pushes, ADDRESS, and the ask expression through the GAS opcode.
+ pre_gas_read = (
+ RECURSIVE_CALL_OP(
+ gas=ask_expr, address=Op.ADDRESS, address_warm=True
+ ).gas_cost(fork)
+ - call_upfront
+ - post_gas_read
+ )
+
+ marker_statics = (
+ Op.ADD(
+ 1,
+ Op.MUL(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_MARKER),
+ ).gas_cost(fork)
+ + push_cost
+ )
+
+ # Descend: compute each level's grant until a level dies mid-frame.
+ gas = (
+ tx_gas_limit
+ - fork.transaction_intrinsic_cost_calculator()()
+ - fork.transaction_top_frame_state_gas()
+ )
+ levels: list[tuple[int, int]] = []
+ level = 0
+ while True:
+ level += 1
+ first = level == 1
+ gas -= bump_statics_cold if first else bump_statics_warm
+ if gas < 0 or gas <= stipend:
+ break
+ gas -= sstore_warm_set if first else sstore_warm_dirty
+ if gas < 0:
+ break
+ gas -= pre_gas_read
+ if gas < 0:
+ break
+ gas_read = gas
+ gas -= post_gas_read + call_upfront
+ if gas < 0:
+ break
+ assert level < DEPTH_CUTOFF, "recursion must die of gas, not depth"
+ # A reserve underflow wraps mod 2**256: an effectively infinite
+ # ask, clamped to the 63/64 forwardable maximum.
+ ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256
+ forwarded = min(ask, gas - gas // 64)
+ levels.append((gas, forwarded))
+ gas = forwarded
+
+ # Unwind: a failed level forfeits its whole grant to its parent.
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ for lvl in range(len(levels), 0, -1):
+ available, forwarded = levels[lvl - 1]
+ gas = available - forwarded + (leftover if child_ok else 0)
+ # Result store: push the slot key, then store the success flag.
+ # Below the deepest completing level everything reverts, so its
+ # own stores find cold slots and zero current values.
+ gas -= push_cost
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ if not child_ok:
+ result_store = sstore_cold_noop
+ elif result_below == 0:
+ result_store = sstore_warm_set
+ else:
+ result_store = sstore_warm_noop
+ gas -= result_store
+ ok = gas >= 0
+ # Marker store: parents rewrite the same surviving marker value.
+ if ok:
+ gas -= marker_statics
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ gas -= sstore_warm_noop if child_ok else sstore_cold_set
+ ok = gas >= 0
+ if ok:
+ if not child_ok:
+ survivor = lvl
+ result_below = 1 if child_ok else 0
+ leftover = gas
+ child_ok = True
+ else:
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ assert child_ok and survivor > 0, "the top level must complete"
+ return {
+ COUNTER_SLOT: survivor,
+ RESULT_SLOT: result_below,
+ MARKER_SLOT: 1 + DEPTH_MARKER * survivor,
+ }
+
@pytest.mark.ported_from(
["state_tests/stCallCreateCallCodeTest/Call1024OOGFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1",
- ),
- pytest.param(
- 0,
- 2,
- 0,
- id="-g2",
- ),
- pytest.param(
- 0,
- 3,
- 0,
- id="-g3",
- ),
- ],
+ # Ported budgets; each pins a distinct OOG-terminated depth.
+ "tx_gas_limit",
+ [13_120_826, 9_320_826, 15_720_826, 11_220_826],
)
-@pytest.mark.pre_alloc_mutable
def test_call1024_oog(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ tx_gas_limit: int,
) -> None:
- """Calldepth with oog."""
- coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=9223372036854775807,
- )
-
- addr = pre.fund_eoa(amount=7000) # noqa: F841
- # Source: lll
- # { [[ 0 ]] (ADD @@0 1) [[ 1 ]] (CALL (MUL (SUB (GAS) 10000) (SUB 1 (DIV @@0 1025))) 0 0 0 0 0) [[ 2 ]] (ADD 1(MUL @@0 1000)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.SSTORE(
- key=0x1,
- value=Op.CALL(
- gas=Op.MUL(
- Op.SUB(Op.GAS, 0x2710),
- Op.SUB(0x1, Op.DIV(Op.SLOAD(key=0x0), 0x401)),
- ),
- address=0x878BC1C3D660907B056E31C854A309F7EF1B4C4,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(
- key=0x2, value=Op.ADD(0x1, Op.MUL(Op.SLOAD(key=0x0), 0x3E8))
- )
- + Op.STOP,
- balance=1024,
- nonce=0,
- address=Address(0x0878BC1C3D660907B056E31C854A309F7EF1B4C4), # noqa: E501
- )
-
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={0: 134, 1: 1, 2: 0x20B71})},
- },
- {
- "indexes": {"data": -1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={0: 113, 1: 1, 2: 0x1B969})},
- },
- {
- "indexes": {"data": -1, "gas": 2, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={0: 146, 1: 1, 2: 0x23A51})},
- },
- {
- "indexes": {"data": -1, "gas": 3, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={0: 124, 1: 1, 2: 0x1E461})},
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Bytes(""),
- ]
- tx_gas = [13120826, 9320826, 15720826, 11220826]
- tx_value = [10]
+ """Pin the depth an OOG-terminated CALL self-recursion reaches."""
+ target = pre.deploy_contract(code=RECURSION_CODE)
tx = Transaction(
- sender=sender,
+ sender=pre.fund_eoa(),
to=target,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ gas_limit=tx_gas_limit,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ post = {
+ target: Account(storage=predict_recursion_storage(fork, tx_gas_limit)),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py
index c67dfb77762..c475d6e6a48 100644
--- a/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py
+++ b/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py
@@ -1,131 +1,250 @@
"""
-Calldepth and oog.
+Verify a self-recursive CALLCODE chain that terminates by out-of-gas.
+
+Each level bumps a shared depth counter, forwards almost all its gas to
+a CALLCODE to its own address (same code, same storage context, keeping
+a 10,000 reserve for its post-call stores), then records the call's
+success flag and a depth marker. Levels too deep to afford their stores
+halt and roll back, so the surviving storage pins the exact depth the
+budget reaches under the EIP-150 63/64 rule.
Ported from:
state_tests/stCallCreateCallCodeTest/Callcode1024OOGFiller.json
+
+@manually-enhanced: Do not overwrite. The post state is predicted by an
+exact fork-derived replay of the recursion's gas flow (EIP-150 grants,
+warm/cold and SSTORE pricing via opcode metadata, EIP-8037 state-gas
+spill), validated against the ported Cancun depths; the hardcoded
+self-address is replaced by ADDRESS.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+COUNTER_SLOT = 0
+RESULT_SLOT = 1
+MARKER_SLOT = 2
+# Gas each level keeps back for its post-call stores.
+GAS_RESERVE = 10_000
+# The ask factor zeroes out at the call-depth limit (never reached here;
+# the recursion always dies of out-of-gas first).
+DEPTH_CUTOFF = 1025
+# The marker store writes 1 + DEPTH_MARKER * depth.
+DEPTH_MARKER = 1000
+
+RECURSIVE_CALL_OP = Op.CALLCODE
+
+RECURSION_CODE = (
+ Op.SSTORE(
+ key=COUNTER_SLOT,
+ value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1),
+ )
+ + Op.SSTORE(
+ key=RESULT_SLOT,
+ value=RECURSIVE_CALL_OP(
+ gas=Op.MUL(
+ Op.SUB(Op.GAS, GAS_RESERVE),
+ Op.SUB(1, Op.DIV(Op.SLOAD(key=COUNTER_SLOT), DEPTH_CUTOFF)),
+ ),
+ address=Op.ADDRESS,
+ ),
+ )
+ + Op.SSTORE(
+ key=MARKER_SLOT,
+ value=Op.ADD(1, Op.MUL(Op.SLOAD(key=COUNTER_SLOT), DEPTH_MARKER)),
+ )
+ + Op.STOP
+)
+
+
+def predict_recursion_storage(fork: Fork, tx_gas_limit: int) -> dict[int, int]:
+ """
+ Replay the recursion's gas flow and return the surviving storage.
+
+ Descend the self-call chain computing each level's EIP-150 grant,
+ then unwind: a level that cannot afford its post-call stores halts
+ and forfeits its entire grant to its parent, so the deepest level
+ that completes fixes the surviving depth counter (deeper levels'
+ writes and warmth all revert). Every cost is derived from the fork
+ via opcode metadata, including EIP-8037 state gas: with a sub-cap
+ gas limit the state reservoir is zero, so state charges spill from
+ the charging frame's own gas.
+ """
+ push_cost = Op.PUSH1[0].gas_cost(fork)
+ # The SUB and MUL of the ask expression run after GAS reads gas_left.
+ post_gas_read = Op.SUB.gas_cost(fork) + Op.MUL.gas_cost(fork)
+ # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally.
+ stipend = fork.gas_costs().CALL_STIPEND
+
+ def raw_store_cost(key_warm: bool, current: int, new: int) -> int:
+ """Cost of a bare SSTORE; original value is always zero here."""
+ return Op.SSTORE(
+ key_warm=key_warm,
+ original_value=0,
+ current_value=current,
+ new_value=new,
+ ).gas_cost(fork)
+
+ sstore_warm_set = raw_store_cost(True, 0, 1)
+ sstore_warm_dirty = raw_store_cost(True, 1, 2)
+ sstore_warm_noop = raw_store_cost(True, 1, 1)
+ sstore_cold_noop = raw_store_cost(False, 0, 0)
+ sstore_cold_set = raw_store_cost(False, 0, 1)
+
+ def bump_statics(key_warm: bool) -> int:
+ """Counter-bump costs before its SSTORE (value expr plus key)."""
+ return (
+ Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost(
+ fork
+ )
+ + push_cost
+ )
+
+ bump_statics_cold = bump_statics(False)
+ bump_statics_warm = bump_statics(True)
+
+ ask_expr = Op.MUL(
+ Op.SUB(Op.GAS, GAS_RESERVE),
+ Op.SUB(
+ 1,
+ Op.DIV(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_CUTOFF),
+ ),
+ )
+ call_upfront = RECURSIVE_CALL_OP(address_warm=True).gas_cost(fork)
+ # Everything charged before GAS reads gas_left: the call's argument
+ # pushes, ADDRESS, and the ask expression through the GAS opcode.
+ pre_gas_read = (
+ RECURSIVE_CALL_OP(
+ gas=ask_expr, address=Op.ADDRESS, address_warm=True
+ ).gas_cost(fork)
+ - call_upfront
+ - post_gas_read
+ )
+
+ marker_statics = (
+ Op.ADD(
+ 1,
+ Op.MUL(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_MARKER),
+ ).gas_cost(fork)
+ + push_cost
+ )
+
+ # Descend: compute each level's grant until a level dies mid-frame.
+ gas = (
+ tx_gas_limit
+ - fork.transaction_intrinsic_cost_calculator()()
+ - fork.transaction_top_frame_state_gas()
+ )
+ levels: list[tuple[int, int]] = []
+ level = 0
+ while True:
+ level += 1
+ first = level == 1
+ gas -= bump_statics_cold if first else bump_statics_warm
+ if gas < 0 or gas <= stipend:
+ break
+ gas -= sstore_warm_set if first else sstore_warm_dirty
+ if gas < 0:
+ break
+ gas -= pre_gas_read
+ if gas < 0:
+ break
+ gas_read = gas
+ gas -= post_gas_read + call_upfront
+ if gas < 0:
+ break
+ assert level < DEPTH_CUTOFF, "recursion must die of gas, not depth"
+ # A reserve underflow wraps mod 2**256: an effectively infinite
+ # ask, clamped to the 63/64 forwardable maximum.
+ ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256
+ forwarded = min(ask, gas - gas // 64)
+ levels.append((gas, forwarded))
+ gas = forwarded
+
+ # Unwind: a failed level forfeits its whole grant to its parent.
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ for lvl in range(len(levels), 0, -1):
+ available, forwarded = levels[lvl - 1]
+ gas = available - forwarded + (leftover if child_ok else 0)
+ # Result store: push the slot key, then store the success flag.
+ # Below the deepest completing level everything reverts, so its
+ # own stores find cold slots and zero current values.
+ gas -= push_cost
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ if not child_ok:
+ result_store = sstore_cold_noop
+ elif result_below == 0:
+ result_store = sstore_warm_set
+ else:
+ result_store = sstore_warm_noop
+ gas -= result_store
+ ok = gas >= 0
+ # Marker store: parents rewrite the same surviving marker value.
+ if ok:
+ gas -= marker_statics
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ gas -= sstore_warm_noop if child_ok else sstore_cold_set
+ ok = gas >= 0
+ if ok:
+ if not child_ok:
+ survivor = lvl
+ result_below = 1 if child_ok else 0
+ leftover = gas
+ child_ok = True
+ else:
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ assert child_ok and survivor > 0, "the top level must complete"
+ return {
+ COUNTER_SLOT: survivor,
+ RESULT_SLOT: result_below,
+ MARKER_SLOT: 1 + DEPTH_MARKER * survivor,
+ }
+
@pytest.mark.ported_from(
["state_tests/stCallCreateCallCodeTest/Callcode1024OOGFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1",
- ),
- ],
+ # Ported budgets; each pins a distinct OOG-terminated depth.
+ "tx_gas_limit",
+ [15_720_826, 13_120_826],
)
-@pytest.mark.pre_alloc_mutable
def test_callcode1024_oog(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ tx_gas_limit: int,
) -> None:
- """Calldepth and oog."""
- coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=9223372036854775807,
- )
-
- addr = pre.fund_eoa(amount=7000) # noqa: F841
- # Source: lll
- # { [[ 0 ]] (ADD @@0 1) [[ 1 ]] (CALLCODE (MUL (SUB (GAS) 10000) (SUB 1 (DIV @@0 1025))) 0 0 0 0 0) [[ 2 ]] (ADD 1(MUL @@0 1000)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.SSTORE(
- key=0x1,
- value=Op.CALLCODE(
- gas=Op.MUL(
- Op.SUB(Op.GAS, 0x2710),
- Op.SUB(0x1, Op.DIV(Op.SLOAD(key=0x0), 0x401)),
- ),
- address=0x1B803058288DC00000F98311B059597434253374,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(
- key=0x2, value=Op.ADD(0x1, Op.MUL(Op.SLOAD(key=0x0), 0x3E8))
- )
- + Op.STOP,
- balance=1024,
- nonce=0,
- address=Address(0x1B803058288DC00000F98311B059597434253374), # noqa: E501
- )
-
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={0: 146, 1: 1, 2: 0x23A51})},
- },
- {
- "indexes": {"data": -1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={0: 134, 1: 1, 2: 0x20B71})},
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Bytes(""),
- ]
- tx_gas = [15720826, 13120826]
- tx_value = [10]
+ """Pin the depth an OOG-terminated CALLCODE self-recursion reaches."""
+ target = pre.deploy_contract(code=RECURSION_CODE)
tx = Transaction(
- sender=sender,
+ sender=pre.fund_eoa(),
to=target,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ gas_limit=tx_gas_limit,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ post = {
+ target: Account(storage=predict_recursion_storage(fork, tx_gas_limit)),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py b/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py
index 281a5b080b4..5e3075039ea 100644
--- a/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py
+++ b/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py
@@ -1,152 +1,133 @@
"""
-Test_contract_creation_make_call_that_ask_more_gas_then_transaction_prov...
+Verify a CALL made inside a contract-creation transaction's init code that
+asks for more gas than the transaction provided: the EIP-150 clamp decides
+what the callee receives, and the transaction budget decides whether that
+grant covers the callee's work.
Ported from:
state_tests/stCallCreateCallCodeTest/contractCreationMakeCallThatAskMoreGasThenTransactionProvidedFiller.json
+
+@manually-enhanced: Do not overwrite. The ask is explicitly oversized (the
+ported 50000 was schedule-sized); both transaction budgets are derived from
+the fork so the clamped grant lands above/below the callee's cost on every
+fork; the init code writes a canary before the call (nothing after it needs
+more than a POP — the 1/64 retention cannot afford an SSTORE, whose
+EIP-2200 stipend rule would kill the creation), so a failed call and a
+failed creation stay distinguishable.
"""
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"
+CANARY_SLOT = 0x2
+CANARY = 0xFF
+
+# Far larger than any gas the init frame can hold: the clamp always
+# applies, which is the scenario the ported filler names.
+OVERSIZED_GAS_ASK = 2**61
+
@pytest.mark.ported_from(
[
"state_tests/stCallCreateCallCodeTest/contractCreationMakeCallThatAskMoreGasThenTransactionProvidedFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
+ "call_covered",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1",
- ),
+ pytest.param(True, id="enough_gas"),
+ pytest.param(False, id="not_enough_gas"),
],
)
-@pytest.mark.pre_alloc_mutable
def test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided( # noqa: E501
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ call_covered: bool,
) -> None:
- """Test_contract_creation_make_call_that_ask_more_gas_then_transaction...""" # noqa: E501
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- contract_1 = Address(0x1000000000000000000000000000000000000001)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """An init-code CALL asking above the tx budget gets the 63/64 clamp."""
+ # Success indicator: writes one cold fresh slot when called.
+ writer_store = Op.SSTORE(
+ key=0x1,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
+ 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 a completion canary before the call (a failed
+ # creation persists nothing, so the canary distinguishes it from a
+ # failed call), makes the oversized ask, and deposits no code. Only a
+ # POP runs after the call: the 1/64 retention on the starved arm is
+ # far below the EIP-2200 stipend an SSTORE would require.
+ canary_store = Op.SSTORE(
+ key=CANARY_SLOT,
+ value=CANARY,
+ key_warm=False,
+ original_value=0,
+ new_value=CANARY,
)
-
- pre[sender] = Account(balance=0x10C8E0)
- # Source: lll
- # {(SSTORE 1 1)}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP,
- balance=0x186A0,
- nonce=0,
- address=Address(0x1000000000000000000000000000000000000001), # noqa: E501
+ ask_call = Op.CALL(
+ gas=OVERSIZED_GAS_ASK,
+ address=writer,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
)
- # Source: lll
- # {(CALL 50000 0x1000000000000000000000000000000000000001 0 0 64 0 64)}
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.CALL(
- gas=0xC350,
- address=0x1000000000000000000000000000000000000001,
- value=0x0,
- args_offset=0x0,
- args_size=0x40,
- ret_offset=0x0,
- ret_size=0x40,
+ initcode = canary_store + Op.POP(ask_call) + Op.STOP
+
+ # Derive the two budgets around the callee's fork-priced cost: the
+ # clamped grant (63/64 of the base left after the charges made before
+ # the forward point) lands above it on one arm and below it on the
+ # other. The post-call flag write runs on the 1/64 retention.
+ overhead = (
+ fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
)
- + Op.STOP,
- balance=0x186A0,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ # EIP-8037 charges the created account's state gas to the
+ # creation transaction's top frame (zero before Amsterdam).
+ + fork.transaction_top_frame_state_gas(contract_creation=True)
+ + canary_store.gas_cost(fork)
+ + ask_call.gas_cost(fork)
)
+ callee_needed = writer_store.gas_cost(fork)
+ if call_covered:
+ base = -(-callee_needed * 64 // 63) + 2_000
+ else:
+ base = callee_needed // 2
+ assert base < OVERSIZED_GAS_ASK, "the 63/64 clamp must apply"
+ gas_limit = overhead + base
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": [0], "value": -1},
- "network": [">=Cancun"],
- "result": {
- compute_create_address(address=sender, nonce=0): Account(
- balance=0
- ),
- contract_1: Account(storage={1: 1}),
- },
- },
- {
- "indexes": {"data": -1, "gas": [1], "value": -1},
- "network": [">=Cancun"],
- "result": {
- compute_create_address(address=sender, nonce=0): Account(
- balance=0
- ),
- contract_1: Account(storage={1: 0}),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Op.CALL(
- gas=0xC350,
- address=contract_1,
- value=0x0,
- args_offset=0x0,
- args_size=0x40,
- ret_offset=0x0,
- ret_size=0x40,
- ),
- ]
- tx_gas = [96000, 60000]
-
+ sender = pre.fund_eoa()
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)
+ post = {
+ created: Account(
+ nonce=1,
+ code=b"",
+ storage={CANARY_SLOT: CANARY},
+ ),
+ writer: Account(storage={1: 1 if call_covered else 0}),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
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/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py
index 9d540998144..ae588b130d8 100644
--- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py
+++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py
@@ -1,12 +1,15 @@
"""
-Test_callcallcallcode_001_suicide_end.
+Verify a CALLCODE -> CALLCODE -> (DELEGATECALL + SELFDESTRUCT) chain:
+every store lands in the outermost target's storage and the SELFDESTRUCT
+(running in the target's context) destroys the target.
Ported from:
state_tests/stCallDelegateCodesCallCodeHomestead/callcallcallcode_001_SuicideEndFiller.json
-@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas
-values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget.
-
+@manually-enhanced: Do not overwrite. The three call budgets are derived
+bottom-up from the fork (each frame pays its stores' state gas from its
+own grant under EIP-8037), and the target's post code is coupled to the
+composed bytecode (the gas operand varies by fork).
"""
import pytest
@@ -38,17 +41,16 @@ def test_callcallcallcode_001_suicide_end(
pre: Alloc,
fork: Fork,
) -> None:
- """Test_callcallcallcode_001_suicide_end."""
- # EIP-8037 inner-CALL gas bumps: original values restored for
- # pre-EIP-8037 forks; bumped values cover the per-storage state-
- # gas spill into regular gas on Amsterdam.
- outer_call_gas = 150000
- middle_call_gas = 100000
- inner_call_gas = 50000
- if fork.is_eip_enabled(8037):
- outer_call_gas = 1000000
- middle_call_gas = 800000
- inner_call_gas = 100000
+ """Chained callcode stores land in the target; it self-destructs."""
+ # Derived bottom-up call budgets: with a zero state-gas reservoir
+ # each frame pays its stores' state gas from its own grant, so every
+ # level's budget covers its callee plus its own work with margin.
+ store_cost = Op.SSTORE(
+ key=0x3, value=0x1, key_warm=False, original_value=0, new_value=1
+ ).gas_cost(fork)
+ inner_call_gas = store_cost + 5_000
+ middle_call_gas = inner_call_gas + 2 * store_cost + 30_000
+ outer_call_gas = middle_call_gas + store_cost + 30_000
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
@@ -72,8 +74,8 @@ def test_callcallcallcode_001_suicide_end(
)
# Source: lll
# { [[ 0 ]] (CALLCODE 150000 0 0 64 0 64 ) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(
+ target_code = (
+ Op.SSTORE(
key=0x0,
value=Op.CALLCODE(
gas=outer_call_gas,
@@ -85,7 +87,10 @@ def test_callcallcallcode_001_suicide_end(
ret_size=0x40,
),
)
- + Op.STOP,
+ + Op.STOP
+ )
+ target = pre.deploy_contract( # noqa: F841
+ code=target_code,
balance=0xDE0B6B3A7640000,
nonce=0,
address=Address(0xA74CA10B765DCDA3B60687F73F2881E2A56EDA64), # noqa: E501
@@ -141,9 +146,10 @@ def test_callcallcallcode_001_suicide_end(
post = {
target: Account(
storage={0: 1, 1: 1, 2: 1, 3: 1},
- code=bytes.fromhex(
- "6040600060406000600073eaf8c2ae0d01a880cea4e1aa88def5edd153d57b620249f0f260005500" # noqa: E501
- ),
+ # Coupled to the deployed bytecode (the gas operand varies
+ # by fork), proving SELFDESTRUCT in a CALLCODE context kills
+ # nothing here.
+ code=target_code,
balance=0,
nonce=0,
),
diff --git a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert2.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert2.py
index 2b48b4cb0bd..8ca16e80b5b 100644
--- a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert2.py
+++ b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert2.py
@@ -1,98 +1,158 @@
"""
-Calls a contract that runs CREATE2 which deploy a code. then after...
+Verify a CREATE2 whose child completes its init code but runs out of gas
+at the code-deposit charge, inside a frame that then REVERTs: the revert
+payload carries the CREATE2 result (zero) back to the caller and every
+side effect of the creating frame is rolled back.
Ported from:
state_tests/stCreate2/Create2OOGafterInitCodeRevert2Filler.json
+
+@manually-enhanced: Do not overwrite. The forwarded grant is derived from
+fork composites so the child fails exactly at the deposit charge on every
+fork; the revert payload now also carries the CREATE2 result, and the
+caller stores the call result plus both payload words.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
- compute_create_address,
+ compute_create2_address,
)
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+PAYLOAD_SLOT = 0x1
+CREATE2_RESULT_SLOT = 0x2
+CALL_RESULT_SLOT = 0x3
+
+# The init code returns this many memory bytes as the code to deposit;
+# the grant is sized so this charge is exactly what the child cannot pay.
+DEPOSIT_SIZE = 0x40
+
@pytest.mark.ported_from(
["state_tests/stCreate2/Create2OOGafterInitCodeRevert2Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Constantinople")
def test_create2_oo_gafter_init_code_revert2(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Calls a contract that runs CREATE2 which deploy a code."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- contract_1 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """A CREATE2 child dies at the deposit charge; its creator reverts."""
+ # The child's init code: one word of scratch data, then a deposit
+ # request the sized grant cannot cover. The would-be deposited code
+ # (an SSTORE) never runs.
+ child_mstore = Op.MSTORE(
+ offset=0x0,
+ value=0x6001600155,
+ new_memory_size=0x20,
)
+ child_return = Op.RETURN(
+ offset=0x0,
+ size=DEPOSIT_SIZE,
+ new_memory_size=DEPOSIT_SIZE,
+ old_memory_size=0x20,
+ )
+ initcode = child_mstore + child_return
+ initcode_bytes = bytes(initcode)
+ assert len(initcode_bytes) <= 0x20, "init code must fit one word"
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ # The creator writes the init code into memory (right-aligned in the
+ # first word), runs CREATE2, appends the CREATE2 result to memory and
+ # reverts both words back to the caller.
+ creator_setup = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(initcode_bytes, "big"),
+ new_memory_size=0x20,
+ )
+ create2_code = Op.CREATE2(
+ value=0x0,
+ offset=0x20 - len(initcode_bytes),
+ size=len(initcode_bytes),
+ salt=0x0,
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ init_code_size=len(initcode_bytes),
+ )
+ creator = pre.deploy_contract(
+ code=creator_setup
+ + Op.MSTORE(
+ offset=0x20,
+ value=create2_code,
+ new_memory_size=0x40,
+ old_memory_size=0x20,
+ )
+ + Op.REVERT(offset=0x0, size=0x40)
)
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { (MSTORE 0 0x6460016001556000526005601bf3) (CREATE2 0 18 14 0) (REVERT 0 32) } # noqa: E501
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=0x6460016001556000526005601BF3)
- + Op.POP(Op.CREATE2(value=0x0, offset=0x12, size=0xE, salt=0x0))
- + Op.REVERT(offset=0x0, size=0x20)
- + Op.STOP,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ # Size the grant so the child's grant covers its init-code execution
+ # but not the deposit charge, and the creator's 1/64 retention still
+ # covers its tail (the result MSTORE and the REVERT).
+ child_exec = child_mstore.gas_cost(fork) + child_return.gas_cost(fork)
+ deposit_cost = DEPOSIT_SIZE * fork.gas_costs().CODE_DEPOSIT_PER_BYTE
+ deposit_cost += fork.code_deposit_state_gas(code_size=DEPOSIT_SIZE)
+ child_grant = child_exec + deposit_cost // 2
+ rem_after_create = -(-child_grant * 64 // 63)
+ forwarded = (
+ creator_setup.gas_cost(fork)
+ + create2_code.gas_cost(fork)
+ + rem_after_create
+ )
+ granted = rem_after_create - rem_after_create // 64
+ assert child_exec + 10 <= granted <= child_exec + deposit_cost - 10, (
+ "the child grant must die exactly at the deposit charge"
)
- # Source: lll
- # { (CALL 33000 0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b 0 0 0 0 32) [[ 1 ]] (MLOAD 0) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.POP(
- Op.CALL(
- gas=0x80E8,
- address=contract_1,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x20,
- )
+ creator_tail = Op.MSTORE(
+ offset=0x20,
+ value=0x0,
+ new_memory_size=0x40,
+ old_memory_size=0x20,
+ ).gas_cost(fork) + Op.REVERT(offset=0x0, size=0x40).gas_cost(fork)
+ assert rem_after_create // 64 >= creator_tail + 5, (
+ "the creator's retention must cover its tail"
+ )
+
+ # The caller forwards the sized grant, then stores the call result
+ # and both words of the revert payload.
+ call_code = Op.CALL(gas=forwarded, address=creator, ret_size=0x40)
+ caller = pre.deploy_contract(
+ code=Op.SSTORE(key=CALL_RESULT_SLOT, value=Op.ADD(0x1, call_code))
+ + Op.SSTORE(key=PAYLOAD_SLOT, value=Op.MLOAD(offset=0x0))
+ + Op.SSTORE(
+ key=CREATE2_RESULT_SLOT,
+ value=Op.ADD(0x1, Op.MLOAD(offset=0x20)),
)
- + Op.SSTORE(key=0x1, value=Op.MLOAD(offset=0x0))
+ Op.STOP,
- storage={1: 1},
- nonce=0,
- address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ storage={PAYLOAD_SLOT: 0x1},
)
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=75000,
+ sender=pre.fund_eoa(),
+ to=caller,
+ state_gas_reservoir=0,
)
post = {
- contract_0: Account(storage={1: 0x6460016001556000526005601BF3}),
- compute_create_address(
- address=contract_1, nonce=0
- ): Account.NONEXISTENT,
+ caller: Account(
+ storage={
+ # The creator reverted: the call result is 0.
+ CALL_RESULT_SLOT: 0x1,
+ # First payload word: the init code the creator staged.
+ PAYLOAD_SLOT: int.from_bytes(initcode_bytes, "big"),
+ # Second payload word: the failed CREATE2 returned 0.
+ CREATE2_RESULT_SLOT: 0x1,
+ }
+ ),
+ # Everything inside the creator was rolled back.
+ creator: Account(nonce=1, storage={}),
+ compute_create2_address(creator, 0, initcode): 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/stCreate2/test_create2_oog_from_call_refunds.py b/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py
index 9efd489bbb9..9aef43337a4 100644
--- a/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py
+++ b/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py
@@ -1,8 +1,17 @@
"""
-Test_create2_oog_from_call_refunds.
+Verify gas refunds earned inside a CREATE2's init code (storage clears,
+via direct stores and CALL/CALLCODE/DELEGATECALL helpers, selfdestructs,
+and nested creations) against out-of-gas boundaries: each scenario runs
+once completing normally and twice dying — on an oversized code deposit
+and on an INVALID that pins the refund bookkeeping.
Ported from:
state_tests/stCreate2/Create2OOGFromCallRefundsFiller.yml
+
+@manually-enhanced: Do not overwrite. The transaction budget and the
+sender's funding derive from the fork: the ported 400k regular budget
+plus the deepest arm's peak outstanding EIP-8037 state gas, guarded to
+stay below the 5000-byte deposit charge that starves the OoG arms.
"""
import pytest
@@ -27,6 +36,13 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+GAS_PRICE = 10
+# The ported budget, proven to cover every NoOoG arm's regular gas.
+PORTED_GAS_LIMIT = 400_000
+# The OoG arms return this much memory as code; the deposit charge is
+# what must exceed the transaction budget so they starve.
+OOG_DEPOSIT_SIZE = 0x1388
+
@pytest.mark.ported_from(
["state_tests/stCreate2/Create2OOGFromCallRefundsFiller.yml"],
@@ -233,7 +249,34 @@ def test_create2_oog_from_call_refunds(
base_fee_per_gas=10,
)
- pre[sender] = Account(balance=0x3D0900, nonce=1)
+ # EIP-8037 charges state gas on top of the ported regular budget.
+ # The headroom is the deepest arm's (create-inside-create2) peak
+ # outstanding state gas: a NEW_ACCOUNT charge and one live fresh
+ # slot set at each creation depth, plus the one-byte code deposits;
+ # all terms are zero before Amsterdam.
+ fresh_set_state = Op.SSTORE(
+ key=0x0, value=0x1, key_warm=False, original_value=0, new_value=1
+ ).state_cost(fork)
+ new_account_state = Op.CREATE2(
+ value=0x0, offset=0x0, size=0x0, salt=0x0
+ ).state_cost(fork)
+ tx_gas_limit = (
+ PORTED_GAS_LIMIT
+ + 2 * new_account_state
+ + 2 * fresh_set_state
+ + 3 * fork.code_deposit_state_gas(code_size=1)
+ + 20_000
+ )
+ # The budget must stay below the oversized deposit charge so the
+ # OoG arms keep starving on it on every fork.
+ oog_deposit = (
+ OOG_DEPOSIT_SIZE * fork.gas_costs().CODE_DEPOSIT_PER_BYTE
+ + fork.code_deposit_state_gas(code_size=OOG_DEPOSIT_SIZE)
+ )
+ assert tx_gas_limit < oog_deposit, "the OoG arms must stay starved"
+
+ # The exact funding makes the OoG arms' post-state balance zero.
+ pre[sender] = Account(balance=tx_gas_limit * GAS_PRICE, nonce=1)
# Source: yul
# berlin
# {
@@ -1189,13 +1232,14 @@ def test_create2_oog_from_call_refunds(
Bytes("693c6139") + Hash(contract_23, left_padding=True),
Bytes("693c6139") + Hash(contract_24, left_padding=True),
]
- tx_gas = [400000]
+ tx_gas = [tx_gas_limit]
tx = Transaction(
sender=sender,
to=contract_0,
data=tx_data[d],
gas_limit=tx_gas[g],
+ gas_price=GAS_PRICE,
nonce=1,
error=_exc,
)
diff --git a/tests/ported_static/stCreate2/test_create2collision_selfdestructed_oog.py b/tests/ported_static/stCreate2/test_create2collision_selfdestructed_oog.py
index 4def59bad89..70ae08055a7 100644
--- a/tests/ported_static/stCreate2/test_create2collision_selfdestructed_oog.py
+++ b/tests/ported_static/stCreate2/test_create2collision_selfdestructed_oog.py
@@ -1,52 +1,57 @@
"""
-Collision with address that has been selfdestructed in the same...
+Verify a CREATE2 whose target address holds a pre-existing account that
+SELFDESTRUCTed earlier in the same transaction: the collision stands
+(the account is only emptied, not freed), consuming the child's grant,
+and the sized budget then runs the creating init code out of gas so the
+whole creation transaction rolls back — including the selfdestruct's
+balance transfer.
Ported from:
state_tests/stCreate2/create2collisionSelfdestructedOOGFiller.json
+
+@manually-enhanced: Do not overwrite. Collider and beneficiary addresses
+are computed instead of hardcoded, the budget is derived from fork
+composites, and the post-collision work is sized above the collision's
+1/64 retention on every fork (the alive collider means the CREATE2
+charges — and refunds — no new-account state gas).
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Bytecode,
+ Fork,
StateTestFiller,
Transaction,
+ compute_create2_address,
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"
+COLLIDER_BALANCE = 1
+# Gas left for the collision to consume: the CREATE2's child grant. Its
+# 1/64 retention (plus any state refund) must stay below the two-store
+# victim cost, which the guard below asserts.
+CHILD_GRANT_SLACK = 30_000
+
@pytest.mark.ported_from(
["state_tests/stCreate2/create2collisionSelfdestructedOOGFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Constantinople")
@pytest.mark.parametrize(
- "d, g, v",
+ "inner_initcode",
[
+ pytest.param(Bytecode(), id="empty_initcode"),
+ pytest.param(Op.SSTORE(key=0x1, value=0x1), id="storing_initcode"),
pytest.param(
- 0,
- 0,
- 0,
- id="d0",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1",
- ),
- pytest.param(
- 2,
- 0,
- 0,
- id="d2",
+ Op.MSTORE(offset=0x0, value=0x6001600155)
+ + Op.RETURN(offset=0x1B, size=0x5),
+ id="depositing_initcode",
),
],
)
@@ -55,120 +60,118 @@ def test_create2collision_selfdestructed_oog(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ inner_initcode: Bytecode,
) -> None:
- """Collision with address that has been selfdestructed in the same..."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xE2B35478FDD26477CC576DD906E6277761246A3C)
- contract_1 = Address(0xAF3ECBA2FE09A4F6C19F16A9D119E44E08C2DA01)
- contract_2 = Address(0xEC2C6832D00680ECE8FF9254F81FDAB0A5A2AC50)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
- )
+ """A selfdestructed account still collides, and its grant is lost."""
+ sender = pre.fund_eoa()
+ outer_created = compute_create_address(address=sender, nonce=0)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ # The collider occupies the CREATE2 target (which depends on the
+ # init code below through its hash) and selfdestructs when called;
+ # its address is derived from the pre-funded sender, so the pre
+ # allocation must stay mutable.
+ beneficiary = pre.nonexistent_account()
+ collider_work = Op.SELFDESTRUCT(
+ address=beneficiary,
+ address_warm=False,
+ account_new=True,
+ )
+ collider = compute_create2_address(outer_created, 0, inner_initcode)
+ pre.deploy_contract(
+ code=collider_work,
+ balance=COLLIDER_BALANCE,
+ address=collider,
)
- pre[sender] = Account(balance=0xDE0B6B3A7640000)
- # Source: lll
- # { (SELFDESTRUCT 0x10) }
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SELFDESTRUCT(address=0x10) + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0xE2B35478FDD26477CC576DD906E6277761246A3C), # noqa: E501
+ # The outer init code selfdestructs the collider, stages the child's
+ # init code (never executed: the collision aborts before dispatch)
+ # and runs the CREATE2 into the collision; the two stores after it
+ # are the victims the burned grant leaves unaffordable.
+ inner_bytes = bytes(inner_initcode)
+ assert len(inner_bytes) <= 0x20, "inner init code must fit one word"
+ call_code = Op.CALL(
+ address=collider,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
)
- # Source: lll
- # { (SELFDESTRUCT 0x10) }
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SELFDESTRUCT(address=0x10) + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0xAF3ECBA2FE09A4F6C19F16A9D119E44E08C2DA01), # noqa: E501
+ setup = (
+ Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(inner_bytes, "big"),
+ new_memory_size=0x20,
+ )
+ if inner_bytes
+ else Bytecode()
+ )
+ create2_code = Op.CREATE2(
+ value=0x0,
+ offset=0x20 - len(inner_bytes) if inner_bytes else 0x0,
+ size=len(inner_bytes),
+ salt=0x0,
+ new_memory_size=0x20 if inner_bytes else 0x0,
+ old_memory_size=0x20 if inner_bytes else 0x0,
+ init_code_size=len(inner_bytes),
+ account_new=False,
)
- # Source: lll
- # { (SELFDESTRUCT 0x10) }
- contract_2 = pre.deploy_contract( # noqa: F841
- code=Op.SELFDESTRUCT(address=0x10) + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0xEC2C6832D00680ECE8FF9254F81FDAB0A5A2AC50), # noqa: E501
+ victim_stores = Op.SSTORE(
+ key=0x0,
+ value=0x112233,
+ key_warm=False,
+ original_value=0,
+ new_value=0x112233,
+ ) + Op.SSTORE(
+ key=0x1,
+ value=0x112233,
+ key_warm=False,
+ original_value=0,
+ new_value=0x112233,
+ )
+ outer_initcode = (
+ Op.POP(call_code) + setup + Op.POP(create2_code) + victim_stores
)
- tx_data = [
- Op.POP(
- Op.CALL(
- gas=0xC350,
- address=contract_0,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- )
- + Op.POP(Op.CREATE2(value=0x0, offset=0x0, size=0x0, salt=0x0))
- + Op.SSTORE(key=0x0, value=0x112233)
- + Op.STOP,
- Op.POP(
- Op.CALL(
- gas=0xC350,
- address=contract_1,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
+ # The budget covers everything up to and including the CREATE2's own
+ # charges plus the slack the collision consumes; the guard proves
+ # the victims exceed what the collision leaves behind, so the outer
+ # frame must die and the whole creation rolls back. The collider is
+ # alive at the CREATE2 (only emptied by its selfdestruct), so no
+ # new-account state gas is charged — or refunded — there.
+ gas_limit = (
+ fork.transaction_intrinsic_cost_calculator()(
+ calldata=outer_initcode,
+ contract_creation=True,
)
- + Op.MSTORE(offset=0x0, value=0x6001600155)
- + Op.POP(Op.CREATE2(value=0x0, offset=0x1B, size=0x5, salt=0x0))
- + Op.SSTORE(key=0x0, value=0x112233)
- + Op.STOP,
- Op.POP(
- Op.CALL(
- gas=0xC350,
- address=contract_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- )
- + Op.MSTORE(offset=0x0, value=0x6460016001556000526005601BF3)
- + Op.POP(Op.CREATE2(value=0x0, offset=0x12, size=0xE, salt=0x0))
- + Op.SSTORE(key=0x0, value=0x112233)
- + Op.STOP,
- ]
- tx_gas = [200000]
- tx_value = [1]
+ + fork.transaction_top_frame_state_gas(contract_creation=True)
+ + call_code.gas_cost(fork)
+ + collider_work.gas_cost(fork)
+ + setup.gas_cost(fork)
+ + create2_code.gas_cost(fork)
+ + CHILD_GRANT_SLACK
+ )
+ leftover = CHILD_GRANT_SLACK // 64
+ assert leftover + 2_500 < victim_stores.gas_cost(fork), (
+ "the collision's leavings must not afford the victim stores"
+ )
tx = Transaction(
sender=sender,
to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
+ data=outer_initcode,
+ gas_limit=gas_limit,
)
post = {
- contract_0: Account(code=bytes.fromhex("6010ff00"), balance=1),
- contract_1: Account(code=bytes.fromhex("6010ff00"), balance=1),
- contract_2: Account(code=bytes.fromhex("6010ff00"), balance=1),
- Address(
- 0x0000000000000000000000000000000000000010
- ): Account.NONEXISTENT,
- compute_create_address(address=sender, nonce=0): Account.NONEXISTENT,
sender: Account(nonce=1),
+ # Rolled back wholesale: the collider keeps its code and its
+ # balance, the beneficiary was never credited, nothing created.
+ collider: Account(
+ code=collider_work,
+ balance=COLLIDER_BALANCE,
+ nonce=1,
+ ),
+ beneficiary: Account.NONEXISTENT,
+ outer_created: 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/stCreate2/test_create2no_cash.py b/tests/ported_static/stCreate2/test_create2no_cash.py
index 587c64889c1..c61a5f6fed3 100644
--- a/tests/ported_static/stCreate2/test_create2no_cash.py
+++ b/tests/ported_static/stCreate2/test_create2no_cash.py
@@ -1,161 +1,107 @@
"""
-Create2 fails with not enough cash (endowment of a new account) +...
+Verify CREATE2's endowment balance preflight: a creator one wei short of
+the endowment fails without creating (and without a nonce bump), a
+one-wei top-up sent with the call makes the same CREATE2 succeed, and in
+a static context the CREATE2 faults the whole frame instead.
Ported from:
state_tests/stCreate2/create2noCashFiller.json
+
+@manually-enhanced: Do not overwrite. The creation-transaction wrapper
+and tuned gas budgets are replaced by a deployed entry contract that
+records the call result; the created account and the creator's nonce
+(no bump on the balance preflight) are asserted explicitly.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
StateTestFiller,
Transaction,
+ compute_create2_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"
+CALL_RESULT_SLOT = 0x0
+CREATE2_ENDOWMENT = 0x65
+
@pytest.mark.ported_from(
["state_tests/stCreate2/create2noCashFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Constantinople")
@pytest.mark.parametrize(
- "d, g, v",
+ "opcode, top_up",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="d0",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1",
- ),
- pytest.param(
- 2,
- 0,
- 0,
- id="d2",
- ),
+ pytest.param(Op.CALL, 0, id="call_insufficient_balance"),
+ pytest.param(Op.CALL, 1, id="call_topped_up_balance"),
+ pytest.param(Op.STATICCALL, 0, id="staticcall_write_protection"),
],
)
-@pytest.mark.pre_alloc_mutable
def test_create2no_cash(
state_test: StateTestFiller,
pre: Alloc,
- fork: Fork,
- d: int,
- g: int,
- v: int,
+ opcode: Op,
+ top_up: int,
) -> None:
- """Create2 fails with not enough cash (endowment of a new account) +..."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xE2B35478FDD26477CC576DD906E6277761246A3C)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """A CREATE2 endowment beyond the creator's balance cannot create."""
+ # The creator holds one wei less than the endowment it attempts to
+ # transfer; only the topped-up arm can afford it.
+ creator = pre.deploy_contract(
+ code=Op.POP(
+ Op.CREATE2(value=CREATE2_ENDOWMENT, offset=0x0, size=0x0, salt=0x0)
+ )
+ + Op.STOP,
+ balance=CREATE2_ENDOWMENT - 1,
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ # The entry contract forwards the transaction's value (the optional
+ # top-up) and records the call result, shifted so that a failed call
+ # (1), a successful call (2) and no call at all (0) all differ.
+ if opcode == Op.CALL:
+ call_code = Op.CALL(address=creator, value=top_up)
+ else:
+ call_code = Op.STATICCALL(address=creator)
+ entry = pre.deploy_contract(
+ code=Op.SSTORE(key=CALL_RESULT_SLOT, value=Op.ADD(0x1, call_code))
+ + Op.STOP,
)
- pre[sender] = Account(balance=0xDE0B6B3A7640000)
- # Source: lll
- # { (CREATE2 101 0 0 0) }
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.CREATE2(value=0x65, offset=0x0, size=0x0, salt=0x0) + Op.STOP,
- balance=100,
- nonce=0,
- address=Address(0xE2B35478FDD26477CC576DD906E6277761246A3C), # noqa: E501
+ tx = Transaction(
+ sender=pre.fund_eoa(),
+ to=entry,
+ value=top_up,
+ state_gas_reservoir=0,
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": [0, 2], "gas": -1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(balance=100),
- Address(
- 0x12AAEFBC0350A026228076E5369E6CE148CE67BE
- ): Account.NONEXISTENT,
- sender: Account(nonce=1),
- },
- },
- {
- "indexes": {"data": 1, "gas": -1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(balance=0),
- Address(0x12AAEFBC0350A026228076E5369E6CE148CE67BE): Account(
- balance=101
- ),
- sender: Account(nonce=1),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Op.CALL(
- gas=0x249F0,
- address=contract_0,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP,
- Op.CALL(
- gas=0x249F0,
- address=contract_0,
- value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP,
- Op.STATICCALL(
- gas=0x249F0,
- address=contract_0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ created = compute_create2_address(creator, 0, b"")
+ create_succeeds = opcode == Op.CALL and top_up > 0
+ if create_succeeds:
+ # The whole (topped-up) balance moved into the created account,
+ # and the creator's nonce was consumed by the creation.
+ creator_account = Account(nonce=2, balance=0)
+ created_account: Account | None = Account(
+ nonce=1, code=b"", balance=CREATE2_ENDOWMENT
)
- + Op.STOP,
- ]
- tx_gas = [400000]
- tx_value = [1]
+ else:
+ # The balance preflight (or the static fault) aborts before any
+ # account is touched: no creation and no nonce bump.
+ creator_account = Account(nonce=1, balance=CREATE2_ENDOWMENT - 1)
+ created_account = Account.NONEXISTENT
- tx = Transaction(
- sender=sender,
- to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
- )
+ # A static frame faults on CREATE2, so only that arm's call fails.
+ call_result = 0 if opcode == Op.STATICCALL else 1
+
+ post = {
+ entry: Account(
+ storage={CALL_RESULT_SLOT: 0x1 + call_result}, balance=0
+ ),
+ creator: creator_account,
+ created: created_account,
+ }
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreate2/test_create_message_reverted_oog_in_init2.py b/tests/ported_static/stCreate2/test_create_message_reverted_oog_in_init2.py
index 4e4ef237ca4..e75adcb8de8 100644
--- a/tests/ported_static/stCreate2/test_create_message_reverted_oog_in_init2.py
+++ b/tests/ported_static/stCreate2/test_create_message_reverted_oog_in_init2.py
@@ -1,127 +1,145 @@
"""
-Create2 oog during the init code, + when create2 is from transaction...
+Verify a CREATE2 issued from a contract-creation transaction's init
+code: the transaction budget decides whether the CREATE2's child
+completes its storage-writing init code or dies out of gas, while the
+outer creation completes either way.
Ported from:
state_tests/stCreate2/CreateMessageRevertedOOGInInit2Filler.json
+
+@manually-enhanced: Do not overwrite. Both budgets are derived from fork
+composites (intrinsic + top-frame state gas + the composed init code);
+the outer created account is asserted on both arms with a pre-CREATE2
+canary, and the child account's storage on the success arm.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
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"
+CANARY_SLOT = 0x2
+CANARY = 0xFF
+TX_VALUE = 100
+CHILD_STORED = {0x0: 0xC, 0x1: 0xD}
+
@pytest.mark.ported_from(
["state_tests/stCreate2/CreateMessageRevertedOOGInInit2Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Constantinople")
@pytest.mark.parametrize(
- "d, g, v",
+ "child_covered",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1",
- ),
+ pytest.param(False, id="child_oog"),
+ pytest.param(True, id="child_succeeds"),
],
)
-@pytest.mark.pre_alloc_mutable
def test_create_message_reverted_oog_in_init2(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ child_covered: bool,
) -> None:
- """Create2 oog during the init code, + when create2 is from..."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """The budget decides how far an init-code CREATE2's child gets."""
+ # The child's init code writes two fresh slots and deposits nothing.
+ inner_initcode = Op.SSTORE(
+ key=0x0,
+ value=CHILD_STORED[0x0],
+ key_warm=False,
+ original_value=0,
+ new_value=CHILD_STORED[0x0],
+ ) + Op.SSTORE(
+ key=0x1,
+ value=CHILD_STORED[0x1],
+ key_warm=False,
+ original_value=0,
+ new_value=CHILD_STORED[0x1],
)
+ inner_bytes = bytes(inner_initcode)
+ assert len(inner_bytes) <= 0x20, "inner init code must fit one word"
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000000000,
+ # The outer init code writes a completion canary before the CREATE2
+ # (only a POP runs after it: on the starved arm the 1/64 retention
+ # cannot afford an SSTORE), stages the child's init code in memory
+ # and runs the CREATE2; it deposits no code.
+ canary_store = Op.SSTORE(
+ key=CANARY_SLOT,
+ value=CANARY,
+ key_warm=False,
+ original_value=0,
+ new_value=CANARY,
)
-
- pre[sender] = Account(balance=0x2DC6C0)
- # Source: hex
- # 0x
- contract_0 = pre.deploy_contract( # noqa: F841
- code="",
- balance=10,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ setup = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(inner_bytes, "big"),
+ new_memory_size=0x20,
)
+ create2_code = Op.CREATE2(
+ value=0x0,
+ offset=0x20 - len(inner_bytes),
+ size=len(inner_bytes),
+ salt=0x0,
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ init_code_size=len(inner_bytes),
+ )
+ outer_initcode = canary_store + setup + Op.POP(create2_code) + Op.STOP
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- Address(
- 0xF3059E18A327C662766F6BA11808C400635847EF
- ): Account.NONEXISTENT,
- },
- },
- {
- "indexes": {"data": -1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- sender: Account(nonce=1),
- Address(0xF3059E18A327C662766F6BA11808C400635847EF): Account(
- storage={0: 12, 1: 13}, balance=0, nonce=1
- ),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Op.MSTORE(offset=0x0, value=0x600C600055600D600155)
- + Op.CREATE2(value=0x0, offset=0x16, size=0xA, salt=0x0)
- + Op.STOP,
- ]
- tx_gas = [110000, 150000]
- tx_value = [100]
+ # Both budgets derive from the same overhead: everything the outer
+ # frame pays before (and including) the CREATE2's own charges. The
+ # child's grant is what remains after the 1/64 withhold.
+ overhead = (
+ fork.transaction_intrinsic_cost_calculator()(
+ calldata=outer_initcode,
+ contract_creation=True,
+ sends_value=True,
+ )
+ + fork.transaction_top_frame_state_gas(contract_creation=True)
+ + canary_store.gas_cost(fork)
+ + setup.gas_cost(fork)
+ + create2_code.gas_cost(fork)
+ )
+ child_needed = inner_initcode.gas_cost(fork)
+ if child_covered:
+ gas_limit = overhead + -(-child_needed * 64 // 63) + 3_000
+ else:
+ gas_limit = overhead + child_needed // 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=outer_initcode,
+ gas_limit=gas_limit,
+ value=TX_VALUE,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ outer_created = compute_create_address(address=sender, nonce=0)
+ child = compute_create2_address(outer_created, 0, inner_initcode)
+ post = {
+ sender: Account(nonce=1),
+ # The outer creation completes on both arms: the CREATE2 always
+ # bumps its nonce, and a failed child costs it only the grant.
+ outer_created: Account(
+ nonce=2,
+ code=b"",
+ balance=TX_VALUE,
+ storage={CANARY_SLOT: CANARY},
+ ),
+ child: Account(nonce=1, code=b"", balance=0, storage=CHILD_STORED)
+ if child_covered
+ else Account.NONEXISTENT,
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py b/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py
index 00ddb3595a4..17782417272 100644
--- a/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py
+++ b/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py
@@ -1,200 +1,235 @@
"""
-Test_revert_depth_create2_oog.
+Verify a CREATE2 two frames deep under out-of-gas pressure: the calldata
+sets the grant a caller forwards to a creating contract, and the two
+budgets decide whether the creation completes, the creator dies mid-way,
+or the whole outer frame runs dry — each with a distinct post-state.
Ported from:
state_tests/stCreate2/RevertDepthCreate2OOGFiller.json
+state_tests/stCreate2/RevertDepthCreate2OOGBerlinFiller.json
+
+@manually-enhanced: Do not overwrite. The byte-identical Berlin twin is
+folded in; every budget derives from fork composites; the creator now
+stores the CREATE2 result so a wrongly failed (or wrongly succeeding)
+creation is visible beyond the created account itself.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
Hash,
StateTestFiller,
Transaction,
+ compute_create2_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"
+# Caller slots (as in the ported filler).
+CALLER_START_SLOT = 0x0
+CALL_RESULT_SLOT = 0x1
+CALLER_DONE_SLOT = 0x4
+# Creator slots: 0x2/0x3 as ported, plus the CREATE2 result.
+CREATOR_START_SLOT = 0x2
+CREATOR_DONE_SLOT = 0x3
+CREATE2_RESULT_SLOT = 0x5
+
+# Gas available in the caller frame at the CALL on the starved-outer
+# arms: far below the creator's needs, and retaining under 1/64th of the
+# EIP-2200 stipend so no post-call store can run — the caller must die.
+STARVED_AVAILABLE = 20_000
+
@pytest.mark.ported_from(
- ["state_tests/stCreate2/RevertDepthCreate2OOGFiller.json"],
+ [
+ "state_tests/stCreate2/RevertDepthCreate2OOGFiller.json",
+ "state_tests/stCreate2/RevertDepthCreate2OOGBerlinFiller.json",
+ ],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Constantinople")
@pytest.mark.parametrize(
- "d, g, v",
+ "creator_covered",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="d0-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="d0-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="d0-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="d0-g1-v1",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1-g0-v0",
- ),
- pytest.param(
- 1,
- 0,
- 1,
- id="d1-g0-v1",
- ),
- pytest.param(
- 1,
- 1,
- 0,
- id="d1-g1-v0",
- ),
- pytest.param(
- 1,
- 1,
- 1,
- id="d1-g1-v1",
- ),
+ pytest.param(False, id="creator_oog"),
+ pytest.param(True, id="creator_ok"),
],
)
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.parametrize(
+ "outer_covered",
+ [
+ pytest.param(False, id="outer_oog"),
+ pytest.param(True, id="outer_ok"),
+ ],
+)
+@pytest.mark.parametrize("tx_value", [1, 0])
def test_revert_depth_create2_oog(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ creator_covered: bool,
+ outer_covered: bool,
+ tx_value: int,
) -> None:
- """Test_revert_depth_create2_oog."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xA000000000000000000000000000000000000000)
- contract_1 = Address(0xB000000000000000000000000000000000000000)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """Two stacked budgets decide how deep a nested CREATE2 gets."""
+ # The creator: entry marker, an empty-init-code CREATE2 whose result
+ # is stored (success leaves the created address plus one, a failure
+ # leaves exactly one), and a completion marker.
+ sstore_2 = Op.SSTORE(
+ key=CREATOR_START_SLOT,
+ value=0x8,
+ key_warm=False,
+ original_value=0,
+ new_value=0x8,
+ )
+ result_store = Op.SSTORE(
+ key=CREATE2_RESULT_SLOT,
+ value=Op.ADD(
+ 0x1, Op.CREATE2(value=0x0, offset=0x0, size=0x0, salt=0x0)
+ ),
+ key_warm=False,
+ original_value=0,
+ new_value=0x1,
+ )
+ sstore_3 = Op.SSTORE(
+ key=CREATOR_DONE_SLOT,
+ value=0xC,
+ key_warm=False,
+ original_value=0,
+ new_value=0xC,
+ )
+ creator = pre.deploy_contract(
+ code=sstore_2 + result_store + sstore_3 + Op.STOP
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
+ # The empty-init-code child consumes nothing and returns its whole
+ # grant, so the creator's needs are just its composite costs.
+ creator_needed = (
+ sstore_2.gas_cost(fork)
+ + result_store.gas_cost(fork)
+ + sstore_3.gas_cost(fork)
)
+ if creator_covered:
+ forwarded = creator_needed + 1_000
+ else:
+ forwarded = creator_needed // 2
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[2]] 8 (CREATE2 0 0 0 0) [[3]] 12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x8)
- + Op.POP(Op.CREATE2(value=0x0, offset=0x0, size=0x0, salt=0x0))
- + Op.SSTORE(key=0x3, value=0xC)
- + Op.STOP,
- nonce=0,
- address=Address(0xB000000000000000000000000000000000000000), # noqa: E501
+ # The caller: entry marker, the CALL with its grant taken from
+ # calldata (as in the ported filler), result store, completion
+ # marker.
+ sstore_0 = Op.SSTORE(
+ key=CALLER_START_SLOT,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=0x1,
+ )
+ call_code = Op.CALL(
+ gas=Op.CALLDATALOAD(offset=0x0),
+ address=creator,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
+ )
+ sstore_1 = Op.SSTORE(
+ key=CALL_RESULT_SLOT,
+ value=call_code,
+ key_warm=False,
+ original_value=0,
+ new_value=0x1,
)
- # Source: lll
- # { [[0]] 1 [[1]] (CALL (CALLDATALOAD 0) 0xb000000000000000000000000000000000000000 0 0 0 0 0) [[4]] 12 } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=0x1)
- + Op.SSTORE(
- key=0x1,
- value=Op.CALL(
- gas=Op.CALLDATALOAD(offset=0x0),
- address=contract_1,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
+ sstore_4 = Op.SSTORE(
+ key=CALLER_DONE_SLOT,
+ value=0xC,
+ key_warm=False,
+ original_value=0,
+ new_value=0xC,
+ )
+ caller_code = sstore_0 + sstore_1 + sstore_4 + Op.STOP
+ caller = pre.deploy_contract(code=caller_code)
+
+ tx_data = Hash(forwarded)
+ overhead = fork.transaction_intrinsic_cost_calculator()(
+ calldata=tx_data,
+ sends_value=tx_value > 0,
+ ) + sstore_0.gas_cost(fork)
+ if outer_covered:
+ # Enough at the CALL that the EIP-150 clamp still grants the
+ # full ask, plus the caller's post-call stores.
+ available = -(-forwarded * 64 // 63) + 64
+ assert available - available // 64 >= forwarded, (
+ "the full ask must be granted"
+ )
+ gas_limit = (
+ overhead
+ + sstore_1.gas_cost(fork)
+ + sstore_4.gas_cost(fork)
+ + available
)
- + Op.SSTORE(key=0x4, value=0xC)
- + Op.STOP,
- balance=5,
- nonce=54,
- address=Address(0xA000000000000000000000000000000000000000), # noqa: E501
+ else:
+ # The clamped grant starves the creator, and the 1/64 retention
+ # cannot run any store afterwards: the caller must die too.
+ granted = STARVED_AVAILABLE - STARVED_AVAILABLE // 64
+ assert granted < creator_needed, "the creator must be starved"
+ assert STARVED_AVAILABLE // 64 <= fork.gas_costs().CALL_STIPEND, (
+ "the retention must not afford the post-call store"
+ )
+ gas_limit = overhead + call_code.gas_cost(fork) + STARVED_AVAILABLE
+
+ sender = pre.fund_eoa()
+ tx = Transaction(
+ sender=sender,
+ to=caller,
+ data=tx_data,
+ gas_limit=gas_limit,
+ value=tx_value,
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- Address(0x05A28FC366483258507BCF739658573CB47E4FAD): Account(
- nonce=1
- ),
- contract_0: Account(storage={0: 1, 1: 1, 4: 12}),
- contract_1: Account(storage={2: 8, 3: 12}),
+ created = compute_create2_address(creator, 0, b"")
+ if not outer_covered:
+ # The whole transaction ran dry: only the code survives.
+ caller_account = Account(storage={}, code=caller_code, balance=0)
+ creator_account = Account(storage={}, nonce=1)
+ created_account: Account | None = Account.NONEXISTENT
+ elif not creator_covered:
+ # The creator died mid-creation and was rolled back.
+ caller_account = Account(
+ storage={
+ CALLER_START_SLOT: 0x1,
+ CALL_RESULT_SLOT: 0x0,
+ CALLER_DONE_SLOT: 0xC,
},
- },
- {
- "indexes": {"data": 0, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- Address(
- 0x05A28FC366483258507BCF739658573CB47E4FAD
- ): Account.NONEXISTENT,
- contract_0: Account(storage={0: 1, 4: 12}),
- contract_1: Account(storage={}),
+ balance=tx_value,
+ )
+ creator_account = Account(storage={}, nonce=1)
+ created_account = Account.NONEXISTENT
+ else:
+ caller_account = Account(
+ storage={
+ CALLER_START_SLOT: 0x1,
+ CALL_RESULT_SLOT: 0x1,
+ CALLER_DONE_SLOT: 0xC,
},
- },
- {
- "indexes": {"data": [0, 1], "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- Address(
- 0x05A28FC366483258507BCF739658573CB47E4FAD
- ): Account.NONEXISTENT,
- contract_0: Account(storage={}),
- contract_1: Account(storage={}),
+ balance=tx_value,
+ )
+ creator_account = Account(
+ storage={
+ CREATOR_START_SLOT: 0x8,
+ CREATE2_RESULT_SLOT: int.from_bytes(bytes(created), "big") + 1,
+ CREATOR_DONE_SLOT: 0xC,
},
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Hash(0xEA60),
- Hash(0x1EA60),
- ]
- tx_gas = [110000, 170000]
- tx_value = [1, 0]
+ nonce=2,
+ )
+ created_account = Account(nonce=1, code=b"", balance=0)
- tx = Transaction(
- sender=sender,
- to=contract_0,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
- )
+ post = {
+ sender: Account(nonce=1),
+ caller: caller_account,
+ creator: creator_account,
+ created: created_account,
+ }
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py b/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py
deleted file mode 100644
index c15b8760d49..00000000000
--- a/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py
+++ /dev/null
@@ -1,200 +0,0 @@
-"""
-Test_revert_depth_create2_oog_berlin.
-
-Ported from:
-state_tests/stCreate2/RevertDepthCreate2OOGBerlinFiller.json
-"""
-
-import pytest
-from execution_testing import (
- EOA,
- Account,
- Address,
- Alloc,
- Environment,
- Hash,
- 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"
-
-
-@pytest.mark.ported_from(
- ["state_tests/stCreate2/RevertDepthCreate2OOGBerlinFiller.json"],
-)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="d0-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="d0-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="d0-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="d0-g1-v1",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1-g0-v0",
- ),
- pytest.param(
- 1,
- 0,
- 1,
- id="d1-g0-v1",
- ),
- pytest.param(
- 1,
- 1,
- 0,
- id="d1-g1-v0",
- ),
- pytest.param(
- 1,
- 1,
- 1,
- id="d1-g1-v1",
- ),
- ],
-)
-@pytest.mark.pre_alloc_mutable
-def test_revert_depth_create2_oog_berlin(
- state_test: StateTestFiller,
- pre: Alloc,
- fork: Fork,
- d: int,
- g: int,
- v: int,
-) -> None:
- """Test_revert_depth_create2_oog_berlin."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xA000000000000000000000000000000000000000)
- contract_1 = Address(0xB000000000000000000000000000000000000000)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
- )
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- )
-
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[2]] 8 (CREATE2 0 0 0 0) [[3]] 12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x8)
- + Op.POP(Op.CREATE2(value=0x0, offset=0x0, size=0x0, salt=0x0))
- + Op.SSTORE(key=0x3, value=0xC)
- + Op.STOP,
- nonce=0,
- address=Address(0xB000000000000000000000000000000000000000), # noqa: E501
- )
- # Source: lll
- # { [[0]] 1 [[1]] (CALL (CALLDATALOAD 0) 0xb000000000000000000000000000000000000000 0 0 0 0 0) [[4]] 12 } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=0x1)
- + Op.SSTORE(
- key=0x1,
- value=Op.CALL(
- gas=Op.CALLDATALOAD(offset=0x0),
- address=contract_1,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x4, value=0xC)
- + Op.STOP,
- balance=5,
- nonce=54,
- address=Address(0xA000000000000000000000000000000000000000), # noqa: E501
- )
-
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- Address(0x05A28FC366483258507BCF739658573CB47E4FAD): Account(
- nonce=1
- ),
- contract_0: Account(storage={0: 1, 1: 1, 4: 12}),
- contract_1: Account(storage={2: 8, 3: 12}),
- },
- },
- {
- "indexes": {"data": 0, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- Address(
- 0x05A28FC366483258507BCF739658573CB47E4FAD
- ): Account.NONEXISTENT,
- contract_0: Account(storage={0: 1, 4: 12}),
- contract_1: Account(storage={}),
- },
- },
- {
- "indexes": {"data": [0, 1], "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- Address(
- 0x05A28FC366483258507BCF739658573CB47E4FAD
- ): Account.NONEXISTENT,
- contract_0: Account(storage={}),
- contract_1: Account(storage={}),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Hash(0xEA60),
- Hash(0x1EA60),
- ]
- tx_gas = [110000, 170000]
- tx_value = [1, 0]
-
- tx = Transaction(
- sender=sender,
- to=contract_0,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
- )
-
- state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py
index 38873c202b1..ac9cbaf99d6 100644
--- a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py
+++ b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py
@@ -1,222 +1,264 @@
"""
-Copy of this test for CREATE2.
+Verify a CREATE2 that collides with a live account — the very caller
+that funded the attempt: the collision burns the child's grant and bumps
+the creator's nonce without creating anything, and the two stacked
+budgets decide whether the creator survives its aftermath, dies on it,
+or the whole outer frame runs dry.
Ported from:
state_tests/stCreate2/RevertDepthCreateAddressCollisionFiller.json
+state_tests/stCreate2/RevertDepthCreateAddressCollisionBerlinFiller.json
-@manually-enhanced: Do not overwrite. `tx_gas` raised on Amsterdam to
-cover EIP-8037 NEW_ACCOUNT state-gas spill on the CREATE2-via-revert
-path. Pre-EIP-8037 keeps the original [110_000, 170_000] tuned budgets;
-post-state expectations unchanged on all forks.
-
+@manually-enhanced: Do not overwrite. The byte-identical Berlin twin is
+folded in, and the legacy fillers' vacancy is repaired: they kept the
+collider at contract_1's CREATE address while the code runs CREATE2, so
+nothing ever collided — the caller now occupies the CREATE2 target. The
+creator pre-writes its result slot so the post-collision store is a
+dirty-warm write its 1/64 retention can afford, and all budgets derive
+from fork composites.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
Hash,
StateTestFiller,
Transaction,
+ compute_create2_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"
+# Caller slots (as in the ported filler).
+CALLER_START_SLOT = 0x0
+CALL_RESULT_SLOT = 0x1
+CALLER_DONE_SLOT = 0x4
+# Creator slots: 0x2 as ported, plus the pre-written CREATE2 result.
+CREATOR_START_SLOT = 0x2
+CREATE2_RESULT_SLOT = 0x5
+# Pre-written sentinel, overwritten by the CREATE2 result plus one: a
+# surviving creator must show 1 (collision), never 0xFF or an address.
+RESULT_PREWRITE = 0xFF
+
+# Post-collision slack for the starved-creator arm: retains under 1/64th
+# of the EIP-2200 stipend, so the result store cannot run.
+STARVED_SLACK = 10_000
+# The SSTORE composite prices a dirty re-store at the Berlin-era 100 on
+# every fork, but the un-metered pre-Berlin schedule charges up to 5000
+# (EIP-1283 was reverted in ConstantinopleFix); the covered arm's
+# retention carries this headroom so those forks stay covered too.
+DIRTY_STORE_HEADROOM = 5_000
+# Gas available in the caller frame at the CALL on the starved-outer
+# arms: too little for the creator, and retaining too little for any
+# post-call store — the caller must die.
+STARVED_AVAILABLE = 20_000
+
@pytest.mark.ported_from(
- ["state_tests/stCreate2/RevertDepthCreateAddressCollisionFiller.json"],
+ [
+ "state_tests/stCreate2/RevertDepthCreateAddressCollisionFiller.json",
+ "state_tests/stCreate2/RevertDepthCreateAddressCollisionBerlinFiller.json", # noqa: E501
+ ],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Constantinople")
@pytest.mark.parametrize(
- "d, g, v",
+ "creator_covered",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="d0-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="d0-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="d0-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="d0-g1-v1",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1-g0-v0",
- ),
- pytest.param(
- 1,
- 0,
- 1,
- id="d1-g0-v1",
- ),
- pytest.param(
- 1,
- 1,
- 0,
- id="d1-g1-v0",
- ),
- pytest.param(
- 1,
- 1,
- 1,
- id="d1-g1-v1",
- ),
+ pytest.param(False, id="creator_oog"),
+ pytest.param(True, id="creator_ok"),
],
)
+@pytest.mark.parametrize(
+ "outer_covered",
+ [
+ pytest.param(False, id="outer_oog"),
+ pytest.param(True, id="outer_ok"),
+ ],
+)
+@pytest.mark.parametrize("tx_value", [1, 0])
@pytest.mark.pre_alloc_mutable
def test_revert_depth_create_address_collision(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ creator_covered: bool,
+ outer_covered: bool,
+ tx_value: int,
) -> None:
- """Copy of this test for CREATE2."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x3E180B1862F9D158ABB5E519A6D8605540C23682)
- contract_1 = Address(0xB000000000000000000000000000000000000000)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """A CREATE2 collision burns the grant; budgets decide what's left."""
+ # The creator: entry marker, result-slot pre-write, then the CREATE2
+ # aimed at the caller's (occupied) address, whose result overwrites
+ # the sentinel as a dirty-warm store the 1/64 retention can pay.
+ sstore_2 = Op.SSTORE(
+ key=CREATOR_START_SLOT,
+ value=0x8,
+ key_warm=False,
+ original_value=0,
+ new_value=0x8,
+ )
+ sentinel_store = Op.SSTORE(
+ key=CREATE2_RESULT_SLOT,
+ value=RESULT_PREWRITE,
+ key_warm=False,
+ original_value=0,
+ new_value=RESULT_PREWRITE,
+ )
+ create2_code = Op.CREATE2(
+ value=0x0,
+ offset=0x0,
+ size=0x0,
+ salt=0x0,
+ account_new=False,
+ )
+ result_store = Op.SSTORE(
+ key=CREATE2_RESULT_SLOT,
+ value=Op.ADD(0x1, create2_code),
+ key_warm=True,
+ original_value=0,
+ current_value=RESULT_PREWRITE,
+ new_value=0x1,
+ )
+ creator = pre.deploy_contract(
+ code=sstore_2 + sentinel_store + result_store + Op.STOP
)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
+ # The caller occupies the creator's CREATE2 target (this is the
+ # repaired collision, and why the pre allocation must stay mutable).
+ sstore_0 = Op.SSTORE(
+ key=CALLER_START_SLOT,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=0x1,
+ )
+ call_code = Op.CALL(
+ gas=Op.CALLDATALOAD(offset=0x0),
+ address=creator,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
+ )
+ sstore_1 = Op.SSTORE(
+ key=CALL_RESULT_SLOT,
+ value=call_code,
+ key_warm=False,
+ original_value=0,
+ new_value=0x1,
)
+ sstore_4 = Op.SSTORE(
+ key=CALLER_DONE_SLOT,
+ value=0xC,
+ key_warm=False,
+ original_value=0,
+ new_value=0xC,
+ )
+ caller_code = sstore_0 + sstore_1 + sstore_4 + Op.STOP
+ caller = compute_create2_address(creator, 0, b"")
+ pre.deploy_contract(code=caller_code, address=caller)
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[2]] 8 (CREATE2 0 0 0 0) [[3]] 12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x8)
- + Op.POP(Op.CREATE2(value=0x0, offset=0x0, size=0x0, salt=0x0))
- + Op.SSTORE(key=0x3, value=0xC)
- + Op.STOP,
- nonce=0,
- address=Address(0xB000000000000000000000000000000000000000), # noqa: E501
+ # The collision consumes everything left after the CREATE2's charges
+ # but one 64th (the live target means no new-account state gas moves
+ # in either direction), so the creator's fate is set by the slack
+ # riding on top of its pre-collision costs.
+ create2_charge = create2_code.gas_cost(fork)
+ result_tail = result_store.gas_cost(fork) - create2_charge
+ stipend = fork.gas_costs().CALL_STIPEND
+ if creator_covered:
+ slack = 64 * (stipend + result_tail + DIRTY_STORE_HEADROOM + 100)
+ else:
+ slack = STARVED_SLACK
+ assert slack // 64 <= stipend, (
+ "the retention must not afford the result store"
+ )
+ forwarded = (
+ sstore_2.gas_cost(fork)
+ + sentinel_store.gas_cost(fork)
+ + create2_charge
+ + slack
)
- # Source: lll
- # { [[0]] 1 [[1]] (CALL (CALLDATALOAD 0) 0xb000000000000000000000000000000000000000 0 0 0 0 0) [[4]] 12 } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=0x1)
- + Op.SSTORE(
- key=0x1,
- value=Op.CALL(
- gas=Op.CALLDATALOAD(offset=0x0),
- address=0xB000000000000000000000000000000000000000,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
+
+ tx_data = Hash(forwarded)
+ overhead = fork.transaction_intrinsic_cost_calculator()(
+ calldata=tx_data,
+ sends_value=tx_value > 0,
+ ) + sstore_0.gas_cost(fork)
+ if outer_covered:
+ # Enough at the CALL that the EIP-150 clamp still grants the
+ # full ask, plus the caller's post-call stores.
+ available = -(-forwarded * 64 // 63) + 64
+ assert available - available // 64 >= forwarded, (
+ "the full ask must be granted"
+ )
+ gas_limit = (
+ overhead
+ + sstore_1.gas_cost(fork)
+ + sstore_4.gas_cost(fork)
+ + available
+ )
+ else:
+ granted = STARVED_AVAILABLE - STARVED_AVAILABLE // 64
+ assert granted < sstore_2.gas_cost(fork) + sentinel_store.gas_cost(
+ fork
+ ), "the creator must die before its CREATE2"
+ assert STARVED_AVAILABLE // 64 <= stipend, (
+ "the retention must not afford the post-call store"
)
- + Op.SSTORE(key=0x4, value=0xC)
- + Op.STOP,
- balance=5,
- nonce=54,
- address=Address(0x3E180B1862F9D158ABB5E519A6D8605540C23682), # noqa: E501
+ gas_limit = overhead + call_code.gas_cost(fork) + STARVED_AVAILABLE
+
+ sender = pre.fund_eoa()
+ tx = Transaction(
+ sender=sender,
+ to=caller,
+ data=tx_data,
+ gas_limit=gas_limit,
+ value=tx_value,
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(storage={0: 1, 1: 1, 4: 12}, nonce=54),
- contract_1: Account(storage={2: 8, 3: 12}),
- },
- },
- {
- "indexes": {"data": 0, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(storage={0: 1, 4: 12}, nonce=54),
- contract_1: Account(storage={}),
+ if not outer_covered:
+ # The whole transaction ran dry: only the code survives.
+ caller_account = Account(storage={}, code=caller_code, balance=0)
+ creator_account = Account(storage={}, nonce=1)
+ elif not creator_covered:
+ # The creator reached the collision but died on its aftermath
+ # and was rolled back — including the collision's nonce bump.
+ caller_account = Account(
+ storage={
+ CALLER_START_SLOT: 0x1,
+ CALL_RESULT_SLOT: 0x0,
+ CALLER_DONE_SLOT: 0xC,
},
- },
- {
- "indexes": {"data": 1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(
- storage={},
- code=bytes.fromhex(
- "60016000556000600060006000600073b000000000000000000000000000000000000000600035f1600155600c60045500" # noqa: E501
- ),
- balance=5,
- nonce=54,
- ),
- contract_1: Account(storage={}),
+ code=caller_code,
+ balance=tx_value,
+ )
+ creator_account = Account(storage={}, nonce=1)
+ else:
+ # The collision's signature: a nonce bump with nothing created,
+ # a zero CREATE2 result, and the caller's account untouched.
+ caller_account = Account(
+ storage={
+ CALLER_START_SLOT: 0x1,
+ CALL_RESULT_SLOT: 0x1,
+ CALLER_DONE_SLOT: 0xC,
},
- },
- {
- "indexes": {"data": 0, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(
- storage={},
- code=bytes.fromhex(
- "60016000556000600060006000600073b000000000000000000000000000000000000000600035f1600155600c60045500" # noqa: E501
- ),
- nonce=54,
- ),
- contract_1: Account(storage={}),
+ code=caller_code,
+ balance=tx_value,
+ )
+ creator_account = Account(
+ storage={
+ CREATOR_START_SLOT: 0x8,
+ CREATE2_RESULT_SLOT: 0x1,
},
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Hash(0xEA60),
- Hash(0x1EA60),
- ]
- # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam exceeds the
- # original tuned tx_gas budgets; pre-EIP-8037 keeps the originals.
- tx_gas = [110000, 170000]
- if fork.is_eip_enabled(8037):
- tx_gas = [500_000, 700_000]
- tx_value = [1, 0]
+ nonce=2,
+ )
- tx = Transaction(
- sender=sender,
- to=contract_0,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
- )
+ post = {
+ sender: Account(nonce=1),
+ caller: caller_account,
+ creator: creator_account,
+ }
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py
deleted file mode 100644
index 907358ab624..00000000000
--- a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py
+++ /dev/null
@@ -1,224 +0,0 @@
-"""
-Copy of this test for CREATE2.
-
-Ported from:
-state_tests/stCreate2/RevertDepthCreateAddressCollisionBerlinFiller.json
-
-@manually-enhanced: Do not overwrite. `tx_gas` raised on Amsterdam to
-cover EIP-8037 NEW_ACCOUNT state-gas spill on the CREATE2-via-revert
-path. Pre-EIP-8037 keeps the original [110_000, 170_000] tuned budgets;
-post-state expectations unchanged on all forks.
-
-"""
-
-import pytest
-from execution_testing import (
- EOA,
- Account,
- Address,
- Alloc,
- Environment,
- Hash,
- 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"
-
-
-@pytest.mark.ported_from(
- [
- "state_tests/stCreate2/RevertDepthCreateAddressCollisionBerlinFiller.json" # noqa: E501
- ],
-)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="d0-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="d0-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="d0-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="d0-g1-v1",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1-g0-v0",
- ),
- pytest.param(
- 1,
- 0,
- 1,
- id="d1-g0-v1",
- ),
- pytest.param(
- 1,
- 1,
- 0,
- id="d1-g1-v0",
- ),
- pytest.param(
- 1,
- 1,
- 1,
- id="d1-g1-v1",
- ),
- ],
-)
-@pytest.mark.pre_alloc_mutable
-def test_revert_depth_create_address_collision_berlin(
- state_test: StateTestFiller,
- pre: Alloc,
- fork: Fork,
- d: int,
- g: int,
- v: int,
-) -> None:
- """Copy of this test for CREATE2."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x3E180B1862F9D158ABB5E519A6D8605540C23682)
- contract_1 = Address(0xB000000000000000000000000000000000000000)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
- )
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- )
-
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[2]] 8 (CREATE2 0 0 0 0) [[3]] 12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x8)
- + Op.POP(Op.CREATE2(value=0x0, offset=0x0, size=0x0, salt=0x0))
- + Op.SSTORE(key=0x3, value=0xC)
- + Op.STOP,
- nonce=0,
- address=Address(0xB000000000000000000000000000000000000000), # noqa: E501
- )
- # Source: lll
- # { [[0]] 1 [[1]] (CALL (CALLDATALOAD 0) 0xb000000000000000000000000000000000000000 0 0 0 0 0) [[4]] 12 } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=0x1)
- + Op.SSTORE(
- key=0x1,
- value=Op.CALL(
- gas=Op.CALLDATALOAD(offset=0x0),
- address=0xB000000000000000000000000000000000000000,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x4, value=0xC)
- + Op.STOP,
- balance=5,
- nonce=54,
- address=Address(0x3E180B1862F9D158ABB5E519A6D8605540C23682), # noqa: E501
- )
-
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(storage={0: 1, 1: 1, 4: 12}, nonce=54),
- contract_1: Account(storage={2: 8, 3: 12}),
- },
- },
- {
- "indexes": {"data": 0, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(storage={0: 1, 4: 12}, nonce=54),
- contract_1: Account(storage={}),
- },
- },
- {
- "indexes": {"data": 1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(
- storage={},
- code=bytes.fromhex(
- "60016000556000600060006000600073b000000000000000000000000000000000000000600035f1600155600c60045500" # noqa: E501
- ),
- balance=5,
- nonce=54,
- ),
- contract_1: Account(storage={}),
- },
- },
- {
- "indexes": {"data": 0, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- contract_0: Account(
- storage={},
- code=bytes.fromhex(
- "60016000556000600060006000600073b000000000000000000000000000000000000000600035f1600155600c60045500" # noqa: E501
- ),
- nonce=54,
- ),
- contract_1: Account(storage={}),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Hash(0xEA60),
- Hash(0x1EA60),
- ]
- # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam exceeds the
- # original tuned tx_gas budgets; pre-EIP-8037 keeps the originals.
- tx_gas = [110000, 170000]
- if fork.is_eip_enabled(8037):
- tx_gas = [500_000, 700_000]
- tx_value = [1, 0]
-
- tx = Transaction(
- sender=sender,
- to=contract_0,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
- )
-
- state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py
index dd8381ad14c..a6f301f95bf 100644
--- a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py
+++ b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py
@@ -10,13 +10,13 @@
Ported from:
state_tests/stCreateTest/CreateAddressWarmAfterFailFiller.yml
-@manually-enhanced: Do not overwrite. The post-state records the
-measured cost of accessing the create address after a failed CREATE,
-which is a cold account access. EIP-8038 reprices a cold account
-access from 2 600 to 3 000, so each such measurement gains 400 at
-Amsterdam. Derive that delta from the fork's gas model so it is
-exactly 0 pre-EIP-8037 and tracks parameter changes; do not hardcode
-the Amsterdam value.
+@manually-enhanced: Do not overwrite. The post-state records measured
+probe-CALL costs; derive them from the fork's gas model (CALL regular
+cost with warm/cold, value-transfer, and new-account metadata, minus
+the returned stipend) plus the dispatcher's fixed framing gas, so
+EIP-2929/8037/8038 repricings track automatically. The transaction
+gas limit carries reservoir headroom for the state gas the dispatcher
+incurs on EIP-8037 forks, keeping state gas out of the measurements.
"""
import pytest
@@ -390,10 +390,43 @@ def test_create_address_warm_after_fail(
address=Address(0x00000000000000000000000000000000000C0DEC), # noqa: E501
)
- # The create address access after a failed CREATE is cold here;
- # EIP-8038 reprices a cold account access from 2 600 to 3 000.
- # Derive the delta from the fork so it is 0 pre-EIP-8037.
- cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600
+ # The dispatcher measures each probe CALL with a GAS-delta window.
+ # The window's framing (stack shuffling, the two dirty-warm SSTOREs
+ # bracketing the call, and the closing GAS read) is baked into the
+ # ported bytecode blob and fork-stable; the CALL's own cost is
+ # derived from the fork so warm/cold, value-transfer, and
+ # new-account repricings (EIP-2929, EIP-8037, EIP-8038) track
+ # automatically. On EIP-8037 forks the state-gas component is paid
+ # from the transaction's reservoir (see the gas limit below), so
+ # the windows observe only the regular cost.
+ first_call_frame = 228
+ repeat_call_frame = 216
+
+ def measured_call(frame: int, *, warm: bool, new: bool) -> int:
+ """Compute the gas one dispatcher probe-CALL window measures."""
+ call = Op.CALL(
+ address_warm=warm,
+ value_transfer=bool(v),
+ account_new=new and bool(v),
+ )
+ measured = frame + call.execution_cost(fork)
+ if v:
+ # The callee is empty (or STOP-only), so the stipend
+ # forwarded with the value returns unused.
+ measured -= fork.gas_costs().CALL_STIPEND
+ return measured
+
+ # Slot 12: first call to the CREATE target. Warm if a failed CREATE
+ # accessed it (the subject, EIP-2929), cold if the creating frame
+ # itself failed (or CREATE aborted on a nonce overflow), and an
+ # existing account when the CREATE succeeded.
+ warm_new_call = measured_call(first_call_frame, warm=True, new=True)
+ cold_new_call = measured_call(first_call_frame, warm=False, new=True)
+ warm_existing_call = measured_call(first_call_frame, warm=True, new=False)
+ # Slots 13/15: repeated calls, always warm to an existing account.
+ repeat_call = measured_call(repeat_call_frame, warm=True, new=False)
+ # Slot 14: first call to the never-created empty address.
+ empty_call = cold_new_call
expect_entries_: list[dict] = [
{
@@ -408,10 +441,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -432,10 +465,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -459,10 +492,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -483,10 +516,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -510,10 +543,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -534,10 +567,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -561,10 +594,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -585,10 +618,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -612,10 +645,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -636,10 +669,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -663,10 +696,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 2828 + cold_account_delta,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: cold_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=0,
),
@@ -690,10 +723,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 34528,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: cold_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=0,
),
@@ -717,10 +750,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 2828 + cold_account_delta,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: cold_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=0,
),
@@ -741,10 +774,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 34528,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: cold_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=0,
),
@@ -765,10 +798,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_existing_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -789,10 +822,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 7028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_existing_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -816,10 +849,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_existing_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -840,10 +873,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 7028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_existing_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -876,12 +909,22 @@ def test_create_address_warm_after_fail(
Bytes("52c3fd24") + Hash(0x7),
Bytes("52c3fd24") + Hash(0x11),
]
- # The dispatcher writes to ~14 fresh storage slots; under EIP-8037
- # each slot's 32-byte cost is settled at frame end out of the
- # reservoir/`gas_left` (~37_500 gas/slot on Amsterdam). Add that
- # headroom — `sstore_state_gas` is 0 pre-EIP-8037, so the budget
- # is unchanged on older forks.
- tx_gas = [16777216 + 14 * Op.SSTORE(new_value=1).state_cost(fork)]
+ # Under EIP-8037 the gas above the execution cap becomes the
+ # state-gas reservoir. Size it to cover every state charge the
+ # dispatcher can incur — nine 0→non-zero SSTOREs, the CREATE's
+ # peak new-account charge plus up to two accounts created by the
+ # value-bearing probe calls (one spare), and the code deposit —
+ # so no state gas spills into the measured windows. The headroom
+ # is 0 pre-EIP-8037, leaving the budget unchanged on older forks.
+ state_gas_headroom = (
+ 9 * Op.SSTORE(new_value=1).state_cost(fork)
+ + 4
+ * Op.CALL(
+ address_warm=True, value_transfer=True, account_new=True
+ ).state_cost(fork)
+ + fork.code_deposit_state_gas(code_size=1)
+ )
+ tx_gas = [16777216 + state_gas_headroom]
tx_value = [0, 1]
tx = Transaction(
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/stDelegatecallTestHomestead/test_call1024_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py
index 0f6b6855f24..96a5cf26fab 100644
--- a/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py
+++ b/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py
@@ -1,130 +1,251 @@
"""
-Test_call1024_oog.
+Verify a self-recursive DELEGATECALL chain that terminates by
+out-of-gas (the Homestead delegatecall suite's variant of Call1024OOG).
+
+Each level bumps a shared depth counter, forwards almost all its gas to
+a DELEGATECALL to its own address (same code, same storage context,
+keeping a 10,000 reserve for its post-call stores), then records the
+call's success flag and a depth marker. Levels too deep to afford their
+stores halt and roll back, so the surviving storage pins the exact
+depth the budget reaches under the EIP-150 63/64 rule.
Ported from:
state_tests/stDelegatecallTestHomestead/Call1024OOGFiller.json
+
+@manually-enhanced: Do not overwrite. The post state is predicted by an
+exact fork-derived replay of the recursion's gas flow (EIP-150 grants,
+warm/cold and SSTORE pricing via opcode metadata, EIP-8037 state-gas
+spill), validated against the ported Cancun depths; the hardcoded
+self-address is replaced by ADDRESS.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+COUNTER_SLOT = 0
+RESULT_SLOT = 1
+MARKER_SLOT = 2
+# Gas each level keeps back for its post-call stores.
+GAS_RESERVE = 10_000
+# The ask factor zeroes out at the call-depth limit (never reached here;
+# the recursion always dies of out-of-gas first).
+DEPTH_CUTOFF = 1025
+# The marker store writes 1 + DEPTH_MARKER * depth.
+DEPTH_MARKER = 1000
+
+RECURSIVE_CALL_OP = Op.DELEGATECALL
+
+RECURSION_CODE = (
+ Op.SSTORE(
+ key=COUNTER_SLOT,
+ value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1),
+ )
+ + Op.SSTORE(
+ key=RESULT_SLOT,
+ value=RECURSIVE_CALL_OP(
+ gas=Op.MUL(
+ Op.SUB(Op.GAS, GAS_RESERVE),
+ Op.SUB(1, Op.DIV(Op.SLOAD(key=COUNTER_SLOT), DEPTH_CUTOFF)),
+ ),
+ address=Op.ADDRESS,
+ ),
+ )
+ + Op.SSTORE(
+ key=MARKER_SLOT,
+ value=Op.ADD(1, Op.MUL(Op.SLOAD(key=COUNTER_SLOT), DEPTH_MARKER)),
+ )
+ + Op.STOP
+)
+
+
+def predict_recursion_storage(fork: Fork, tx_gas_limit: int) -> dict[int, int]:
+ """
+ Replay the recursion's gas flow and return the surviving storage.
+
+ Descend the self-call chain computing each level's EIP-150 grant,
+ then unwind: a level that cannot afford its post-call stores halts
+ and forfeits its entire grant to its parent, so the deepest level
+ that completes fixes the surviving depth counter (deeper levels'
+ writes and warmth all revert). Every cost is derived from the fork
+ via opcode metadata, including EIP-8037 state gas: with a sub-cap
+ gas limit the state reservoir is zero, so state charges spill from
+ the charging frame's own gas.
+ """
+ push_cost = Op.PUSH1[0].gas_cost(fork)
+ # The SUB and MUL of the ask expression run after GAS reads gas_left.
+ post_gas_read = Op.SUB.gas_cost(fork) + Op.MUL.gas_cost(fork)
+ # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally.
+ stipend = fork.gas_costs().CALL_STIPEND
+
+ def raw_store_cost(key_warm: bool, current: int, new: int) -> int:
+ """Cost of a bare SSTORE; original value is always zero here."""
+ return Op.SSTORE(
+ key_warm=key_warm,
+ original_value=0,
+ current_value=current,
+ new_value=new,
+ ).gas_cost(fork)
+
+ sstore_warm_set = raw_store_cost(True, 0, 1)
+ sstore_warm_dirty = raw_store_cost(True, 1, 2)
+ sstore_warm_noop = raw_store_cost(True, 1, 1)
+ sstore_cold_noop = raw_store_cost(False, 0, 0)
+ sstore_cold_set = raw_store_cost(False, 0, 1)
+
+ def bump_statics(key_warm: bool) -> int:
+ """Counter-bump costs before its SSTORE (value expr plus key)."""
+ return (
+ Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost(
+ fork
+ )
+ + push_cost
+ )
+
+ bump_statics_cold = bump_statics(False)
+ bump_statics_warm = bump_statics(True)
+
+ ask_expr = Op.MUL(
+ Op.SUB(Op.GAS, GAS_RESERVE),
+ Op.SUB(
+ 1,
+ Op.DIV(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_CUTOFF),
+ ),
+ )
+ call_upfront = RECURSIVE_CALL_OP(address_warm=True).gas_cost(fork)
+ # Everything charged before GAS reads gas_left: the call's argument
+ # pushes, ADDRESS, and the ask expression through the GAS opcode.
+ pre_gas_read = (
+ RECURSIVE_CALL_OP(
+ gas=ask_expr, address=Op.ADDRESS, address_warm=True
+ ).gas_cost(fork)
+ - call_upfront
+ - post_gas_read
+ )
+
+ marker_statics = (
+ Op.ADD(
+ 1,
+ Op.MUL(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_MARKER),
+ ).gas_cost(fork)
+ + push_cost
+ )
+
+ # Descend: compute each level's grant until a level dies mid-frame.
+ gas = (
+ tx_gas_limit
+ - fork.transaction_intrinsic_cost_calculator()()
+ - fork.transaction_top_frame_state_gas()
+ )
+ levels: list[tuple[int, int]] = []
+ level = 0
+ while True:
+ level += 1
+ first = level == 1
+ gas -= bump_statics_cold if first else bump_statics_warm
+ if gas < 0 or gas <= stipend:
+ break
+ gas -= sstore_warm_set if first else sstore_warm_dirty
+ if gas < 0:
+ break
+ gas -= pre_gas_read
+ if gas < 0:
+ break
+ gas_read = gas
+ gas -= post_gas_read + call_upfront
+ if gas < 0:
+ break
+ assert level < DEPTH_CUTOFF, "recursion must die of gas, not depth"
+ # A reserve underflow wraps mod 2**256: an effectively infinite
+ # ask, clamped to the 63/64 forwardable maximum.
+ ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256
+ forwarded = min(ask, gas - gas // 64)
+ levels.append((gas, forwarded))
+ gas = forwarded
+
+ # Unwind: a failed level forfeits its whole grant to its parent.
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ for lvl in range(len(levels), 0, -1):
+ available, forwarded = levels[lvl - 1]
+ gas = available - forwarded + (leftover if child_ok else 0)
+ # Result store: push the slot key, then store the success flag.
+ # Below the deepest completing level everything reverts, so its
+ # own stores find cold slots and zero current values.
+ gas -= push_cost
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ if not child_ok:
+ result_store = sstore_cold_noop
+ elif result_below == 0:
+ result_store = sstore_warm_set
+ else:
+ result_store = sstore_warm_noop
+ gas -= result_store
+ ok = gas >= 0
+ # Marker store: parents rewrite the same surviving marker value.
+ if ok:
+ gas -= marker_statics
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ gas -= sstore_warm_noop if child_ok else sstore_cold_set
+ ok = gas >= 0
+ if ok:
+ if not child_ok:
+ survivor = lvl
+ result_below = 1 if child_ok else 0
+ leftover = gas
+ child_ok = True
+ else:
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ assert child_ok and survivor > 0, "the top level must complete"
+ return {
+ COUNTER_SLOT: survivor,
+ RESULT_SLOT: result_below,
+ MARKER_SLOT: 1 + DEPTH_MARKER * survivor,
+ }
+
@pytest.mark.ported_from(
["state_tests/stDelegatecallTestHomestead/Call1024OOGFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="-g0",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="-g1",
- ),
- ],
+ # Ported budgets; each pins a distinct OOG-terminated depth.
+ "tx_gas_limit",
+ [13_120_826, 15_720_826],
)
-@pytest.mark.pre_alloc_mutable
def test_call1024_oog(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ tx_gas_limit: int,
) -> None:
- """Test_call1024_oog."""
- coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=9223372036854775807,
- )
-
- addr = pre.fund_eoa(amount=7000) # noqa: F841
- # Source: lll
- # { [[ 0 ]] (ADD @@0 1) [[ 1 ]] (DELEGATECALL (MUL (SUB (GAS) 10000) (SUB 1 (DIV @@0 1025))) 0 0 0 0) [[ 2 ]] (ADD 1(MUL @@0 1000)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.SSTORE(
- key=0x1,
- value=Op.DELEGATECALL(
- gas=Op.MUL(
- Op.SUB(Op.GAS, 0x2710),
- Op.SUB(0x1, Op.DIV(Op.SLOAD(key=0x0), 0x401)),
- ),
- address=0x62C5C9278DA01E6594D6FEDE061838CF5E597F2B,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(
- key=0x2, value=Op.ADD(0x1, Op.MUL(Op.SLOAD(key=0x0), 0x3E8))
- )
- + Op.STOP,
- balance=1024,
- nonce=0,
- address=Address(0x62C5C9278DA01E6594D6FEDE061838CF5E597F2B), # noqa: E501
- )
-
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": -1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={0: 134, 1: 1, 2: 0x20B71})},
- },
- {
- "indexes": {"data": -1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {target: Account(storage={0: 146, 1: 1, 2: 0x23A51})},
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
-
- tx_data = [
- Bytes(""),
- ]
- tx_gas = [13120826, 15720826]
- tx_value = [10]
+ """Pin the depth an OOG-terminated DELEGATECALL recursion reaches."""
+ target = pre.deploy_contract(code=RECURSION_CODE)
tx = Transaction(
- sender=sender,
+ sender=pre.fund_eoa(),
to=target,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ gas_limit=tx_gas_limit,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ post = {
+ target: Account(storage=predict_recursion_storage(fork, tx_gas_limit)),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall1024_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall1024_oog.py
index 42959c58111..2bc71a23c44 100644
--- a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall1024_oog.py
+++ b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall1024_oog.py
@@ -1,17 +1,29 @@
"""
-Test_delegatecall1024_oog.
+Verify a self-recursive DELEGATECALL chain that terminates by
+out-of-gas.
+
+Each level bumps a shared depth counter, forwards almost all its gas to
+a DELEGATECALL to its own address (same code, same storage context,
+keeping a 10,000 reserve for its post-call stores), then records the
+call's success flag and a depth marker. Levels too deep to afford their
+stores halt and roll back, so the surviving storage pins the exact
+depth the budget reaches under the EIP-150 63/64 rule.
Ported from:
state_tests/stDelegatecallTestHomestead/Delegatecall1024OOGFiller.json
+
+@manually-enhanced: Do not overwrite. The post state is predicted by an
+exact fork-derived replay of the recursion's gas flow (EIP-150 grants,
+warm/cold and SSTORE pricing via opcode metadata, EIP-8037 state-gas
+spill), validated against the ported Cancun depths; the hardcoded
+self-address is replaced by ADDRESS.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,65 +32,220 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+COUNTER_SLOT = 0
+RESULT_SLOT = 1
+MARKER_SLOT = 2
+# Gas each level keeps back for its post-call stores.
+GAS_RESERVE = 10_000
+# The ask factor zeroes out at the call-depth limit (never reached here;
+# the recursion always dies of out-of-gas first).
+DEPTH_CUTOFF = 1025
+# The marker store writes 1 + DEPTH_MARKER * depth.
+DEPTH_MARKER = 1000
+
+RECURSIVE_CALL_OP = Op.DELEGATECALL
+
+RECURSION_CODE = (
+ Op.SSTORE(
+ key=COUNTER_SLOT,
+ value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1),
+ )
+ + Op.SSTORE(
+ key=RESULT_SLOT,
+ value=RECURSIVE_CALL_OP(
+ gas=Op.MUL(
+ Op.SUB(Op.GAS, GAS_RESERVE),
+ Op.SUB(1, Op.DIV(Op.SLOAD(key=COUNTER_SLOT), DEPTH_CUTOFF)),
+ ),
+ address=Op.ADDRESS,
+ ),
+ )
+ + Op.SSTORE(
+ key=MARKER_SLOT,
+ value=Op.ADD(1, Op.MUL(Op.SLOAD(key=COUNTER_SLOT), DEPTH_MARKER)),
+ )
+ + Op.STOP
+)
+
+
+def predict_recursion_storage(fork: Fork, tx_gas_limit: int) -> dict[int, int]:
+ """
+ Replay the recursion's gas flow and return the surviving storage.
+
+ Descend the self-call chain computing each level's EIP-150 grant,
+ then unwind: a level that cannot afford its post-call stores halts
+ and forfeits its entire grant to its parent, so the deepest level
+ that completes fixes the surviving depth counter (deeper levels'
+ writes and warmth all revert). Every cost is derived from the fork
+ via opcode metadata, including EIP-8037 state gas: with a sub-cap
+ gas limit the state reservoir is zero, so state charges spill from
+ the charging frame's own gas.
+ """
+ push_cost = Op.PUSH1[0].gas_cost(fork)
+ # The SUB and MUL of the ask expression run after GAS reads gas_left.
+ post_gas_read = Op.SUB.gas_cost(fork) + Op.MUL.gas_cost(fork)
+ # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally.
+ stipend = fork.gas_costs().CALL_STIPEND
+
+ def raw_store_cost(key_warm: bool, current: int, new: int) -> int:
+ """Cost of a bare SSTORE; original value is always zero here."""
+ return Op.SSTORE(
+ key_warm=key_warm,
+ original_value=0,
+ current_value=current,
+ new_value=new,
+ ).gas_cost(fork)
+
+ sstore_warm_set = raw_store_cost(True, 0, 1)
+ sstore_warm_dirty = raw_store_cost(True, 1, 2)
+ sstore_warm_noop = raw_store_cost(True, 1, 1)
+ sstore_cold_noop = raw_store_cost(False, 0, 0)
+ sstore_cold_set = raw_store_cost(False, 0, 1)
+
+ def bump_statics(key_warm: bool) -> int:
+ """Counter-bump costs before its SSTORE (value expr plus key)."""
+ return (
+ Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost(
+ fork
+ )
+ + push_cost
+ )
+
+ bump_statics_cold = bump_statics(False)
+ bump_statics_warm = bump_statics(True)
+
+ ask_expr = Op.MUL(
+ Op.SUB(Op.GAS, GAS_RESERVE),
+ Op.SUB(
+ 1,
+ Op.DIV(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_CUTOFF),
+ ),
+ )
+ call_upfront = RECURSIVE_CALL_OP(address_warm=True).gas_cost(fork)
+ # Everything charged before GAS reads gas_left: the call's argument
+ # pushes, ADDRESS, and the ask expression through the GAS opcode.
+ pre_gas_read = (
+ RECURSIVE_CALL_OP(
+ gas=ask_expr, address=Op.ADDRESS, address_warm=True
+ ).gas_cost(fork)
+ - call_upfront
+ - post_gas_read
+ )
+
+ marker_statics = (
+ Op.ADD(
+ 1,
+ Op.MUL(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_MARKER),
+ ).gas_cost(fork)
+ + push_cost
+ )
+
+ # Descend: compute each level's grant until a level dies mid-frame.
+ gas = (
+ tx_gas_limit
+ - fork.transaction_intrinsic_cost_calculator()()
+ - fork.transaction_top_frame_state_gas()
+ )
+ levels: list[tuple[int, int]] = []
+ level = 0
+ while True:
+ level += 1
+ first = level == 1
+ gas -= bump_statics_cold if first else bump_statics_warm
+ if gas < 0 or gas <= stipend:
+ break
+ gas -= sstore_warm_set if first else sstore_warm_dirty
+ if gas < 0:
+ break
+ gas -= pre_gas_read
+ if gas < 0:
+ break
+ gas_read = gas
+ gas -= post_gas_read + call_upfront
+ if gas < 0:
+ break
+ assert level < DEPTH_CUTOFF, "recursion must die of gas, not depth"
+ # A reserve underflow wraps mod 2**256: an effectively infinite
+ # ask, clamped to the 63/64 forwardable maximum.
+ ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256
+ forwarded = min(ask, gas - gas // 64)
+ levels.append((gas, forwarded))
+ gas = forwarded
+
+ # Unwind: a failed level forfeits its whole grant to its parent.
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ for lvl in range(len(levels), 0, -1):
+ available, forwarded = levels[lvl - 1]
+ gas = available - forwarded + (leftover if child_ok else 0)
+ # Result store: push the slot key, then store the success flag.
+ # Below the deepest completing level everything reverts, so its
+ # own stores find cold slots and zero current values.
+ gas -= push_cost
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ if not child_ok:
+ result_store = sstore_cold_noop
+ elif result_below == 0:
+ result_store = sstore_warm_set
+ else:
+ result_store = sstore_warm_noop
+ gas -= result_store
+ ok = gas >= 0
+ # Marker store: parents rewrite the same surviving marker value.
+ if ok:
+ gas -= marker_statics
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ gas -= sstore_warm_noop if child_ok else sstore_cold_set
+ ok = gas >= 0
+ if ok:
+ if not child_ok:
+ survivor = lvl
+ result_below = 1 if child_ok else 0
+ leftover = gas
+ child_ok = True
+ else:
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ assert child_ok and survivor > 0, "the top level must complete"
+ return {
+ COUNTER_SLOT: survivor,
+ RESULT_SLOT: result_below,
+ MARKER_SLOT: 1 + DEPTH_MARKER * survivor,
+ }
+
@pytest.mark.ported_from(
["state_tests/stDelegatecallTestHomestead/Delegatecall1024OOGFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
+@pytest.mark.parametrize(
+ # Ported budgets; each pins a distinct OOG-terminated depth.
+ "tx_gas_limit",
+ [15_720_826],
+)
def test_delegatecall1024_oog(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
+ tx_gas_limit: int,
) -> None:
- """Test_delegatecall1024_oog."""
- coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=9223372036854775807,
- )
-
- addr = pre.fund_eoa(amount=7000) # noqa: F841
- # Source: lll
- # { [[ 0 ]] (ADD @@0 1) [[ 1 ]] (DELEGATECALL (MUL (SUB (GAS) 10000) (SUB 1 (DIV @@0 1025))) 0 0 0 0) [[ 2 ]] (ADD 1(MUL @@0 1000)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.SSTORE(
- key=0x1,
- value=Op.DELEGATECALL(
- gas=Op.MUL(
- Op.SUB(Op.GAS, 0x2710),
- Op.SUB(0x1, Op.DIV(Op.SLOAD(key=0x0), 0x401)),
- ),
- address=0x62C5C9278DA01E6594D6FEDE061838CF5E597F2B,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(
- key=0x2, value=Op.ADD(0x1, Op.MUL(Op.SLOAD(key=0x0), 0x3E8))
- )
- + Op.STOP,
- balance=1024,
- nonce=0,
- address=Address(0x62C5C9278DA01E6594D6FEDE061838CF5E597F2B), # noqa: E501
- )
+ """Pin the depth an OOG-terminated DELEGATECALL recursion reaches."""
+ target = pre.deploy_contract(code=RECURSION_CODE)
tx = Transaction(
- sender=sender,
+ sender=pre.fund_eoa(),
to=target,
- data=Bytes(""),
- gas_limit=15720826,
- value=10,
+ gas_limit=tx_gas_limit,
)
- post = {target: Account(storage={0: 146, 1: 1, 2: 0x23A51})}
+ post = {
+ target: Account(storage=predict_recursion_storage(fork, tx_gas_limit)),
+ }
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py
index e20c162ae59..2ceeefd5137 100644
--- a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py
+++ b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py
@@ -1,18 +1,28 @@
"""
-Test_delegatecall_in_initcode_to_existing_contract.
+Verify a DELEGATECALL made from inside init code to an existing
+contract.
+
+The created account's init code DELEGATECALLs an already-deployed
+contract, so that contract's code runs in the freshly created account's
+context with the init frame's caller preserved: both the delegate and
+the init code itself observe the creating contract as CALLER, and every
+storage write lands in the created account, never in the delegate.
Ported from:
state_tests/stDelegatecallTestHomestead/delegatecallInInitcodeToExistingContractFiller.json
+
+@manually-enhanced: Do not overwrite. The port's unused second creator
+contract is deleted, the raw-word init code is composed, the delegate
+call forwards all gas (EIP-8037-proof), the transaction budget is
+maxed, and the post also pins the created account's code/nonce/balance
+and that the delegate's own storage stays untouched.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Bytecode,
StateTestFiller,
Transaction,
compute_create_address,
@@ -22,86 +32,91 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CREATE_ENDOWMENT = 1
+RUNNER_BALANCE = 10_000
+
+# Written by the init code with the DELEGATECALL's success flag.
+DELEGATE_RESULT_SLOT = 0
+# Written by the init code with the CALLER it observes (the runner).
+INITCODE_CALLER_SLOT = 1
+# Written by the delegate's code, in the created account's context.
+DELEGATE_WRITE_SLOT = 2
+# Written by the delegate with the CALLER it observes (still the
+# runner: DELEGATECALL preserves the init frame's caller).
+DELEGATE_CALLER_SLOT = 0xB
+
+
+def memory_stores(data: bytes) -> Bytecode:
+ """Write the given bytes to memory starting at offset zero."""
+ code = Bytecode()
+ for offset in range(0, len(data), 32):
+ chunk = data[offset : offset + 32].ljust(32, b"\x00")
+ code += Op.MSTORE(offset, int.from_bytes(chunk, "big"))
+ return code
+
@pytest.mark.ported_from(
[
"state_tests/stDelegatecallTestHomestead/delegatecallInInitcodeToExistingContractFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("SpuriousDragon")
def test_delegatecall_in_initcode_to_existing_contract(
state_test: StateTestFiller,
pre: Alloc,
) -> None:
- """Test_delegatecall_in_initcode_to_existing_contract."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0x1000000000000000000000000000000000000000)
- contract_1 = Address(0x1000000000000000000000000000000000000001)
- contract_2 = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
- )
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ """A DELEGATECALL in init code runs in the created account."""
+ existing = pre.deploy_contract(
+ code=Op.SSTORE(key=DELEGATE_WRITE_SLOT, value=1)
+ + Op.SSTORE(key=DELEGATE_CALLER_SLOT, value=Op.CALLER)
+ + Op.STOP,
)
- pre[sender] = Account(balance=0x2386F26FC10000)
- # Source: lll
- # { (MSTORE 0 0x604060006040600073945304eb96065b2a98b57a48a06ae28d285a71b5620186) (MSTORE 32 0xa0f4600055336001550000000000000000000000000000000000000000000000) (CREATE 1 0 64) } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(
- offset=0x0,
- value=0x604060006040600073945304EB96065B2A98B57A48A06AE28D285A71B5620186, # noqa: E501
- )
- + Op.MSTORE(
- offset=0x20,
- value=0xA0F4600055336001550000000000000000000000000000000000000000000000, # noqa: E501
+ initcode = (
+ Op.SSTORE(
+ key=DELEGATE_RESULT_SLOT,
+ value=Op.DELEGATECALL(address=existing),
)
- + Op.CREATE(value=0x1, offset=0x0, size=0x40)
- + Op.STOP,
- balance=10000,
- nonce=0,
- address=Address(0x1000000000000000000000000000000000000000), # noqa: E501
- )
- # Source: lll
- # { (MSTORE 0 0x6001600055) (CREATE 1 27 5) }
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=0x6001600055)
- + Op.CREATE(value=0x1, offset=0x1B, size=0x5)
- + Op.STOP,
- balance=1000,
- nonce=0,
- address=Address(0x1000000000000000000000000000000000000001), # noqa: E501
+ + Op.SSTORE(key=INITCODE_CALLER_SLOT, value=Op.CALLER)
+ + Op.STOP
)
- # Source: lll
- # { (SSTORE 2 1) [[ 11 ]] (CALLER) }
- contract_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x1)
- + Op.SSTORE(key=0xB, value=Op.CALLER)
+ initcode_bytes = bytes(initcode)
+
+ runner = pre.deploy_contract(
+ code=memory_stores(initcode_bytes)
+ + Op.CREATE(value=CREATE_ENDOWMENT, offset=0, size=len(initcode_bytes))
+ Op.STOP,
- nonce=0,
- address=Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5), # noqa: E501
+ balance=RUNNER_BALANCE,
)
+ # Deployed contracts start at nonce 1.
+ created = compute_create_address(address=runner, nonce=1)
+
tx = Transaction(
- sender=sender,
- to=contract_0,
- data=Bytes(""),
- gas_limit=453081,
+ sender=pre.fund_eoa(),
+ to=runner,
)
post = {
- compute_create_address(address=contract_0, nonce=0): Account(
- storage={0: 1, 1: contract_0, 2: 1, 11: contract_0},
- balance=1,
+ created: Account(
+ # The init code deploys no code but writes its own storage.
+ code=b"",
+ nonce=1,
+ balance=CREATE_ENDOWMENT,
+ storage={
+ DELEGATE_RESULT_SLOT: 1,
+ INITCODE_CALLER_SLOT: runner,
+ DELEGATE_WRITE_SLOT: 1,
+ DELEGATE_CALLER_SLOT: runner,
+ },
+ ),
+ runner: Account(
+ nonce=2,
+ balance=RUNNER_BALANCE - CREATE_ENDOWMENT,
+ storage={},
),
+ # The delegate's own storage must stay untouched.
+ existing: 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/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py b/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py
index 61fd1052dec..20371a9ce34 100644
--- a/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py
+++ b/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py
@@ -1,17 +1,23 @@
"""
-Test_call_ask_more_gas_on_depth2_then_transaction_has.
+Verify the EIP-150 63/64 clamp at call depth 2: a first-level call receives
+its exact (affordable) ask, and its own oversized ask is clamped to 63/64
+of what remains in that frame.
Ported from:
state_tests/stEIP150Specific/CallAskMoreGasOnDepth2ThenTransactionHasFiller.json
+
+@manually-enhanced: Do not overwrite. The lower frames return their
+observed GAS up the stack instead of SSTORE-ing it (the ported lower-frame
+gas snapshots are EIP-8037 state-gas traps), and both expectations are
+derived from the fork: the depth-1 frame sees exactly its asked budget,
+the depth-2 frame sees `base - base // 64` of the depth-1 remainder.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,86 +26,86 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+FLAG_SLOT = 0x0
+DEPTH2_GAS_SLOT = 0x1
+DEPTH1_GAS_SLOT = 0x2
+
+# The ported depth-1 budget: affordable, so it is forwarded exactly.
+CALLER_GAS = 0x30D40
+# The ported depth-2 ask: above anything the depth-1 frame can hold, so
+# the 63/64 clamp decides what the depth-2 frame receives.
+ASK_GAS = 0x927C0
+
@pytest.mark.ported_from(
[
"state_tests/stEIP150Specific/CallAskMoreGasOnDepth2ThenTransactionHasFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_call_ask_more_gas_on_depth2_then_transaction_has(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_call_ask_more_gas_on_depth2_then_transaction_has."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ """A depth-2 call asking above the frame budget gets 63/64 of it."""
+ # Depth 2: returns the gas it observed on entry.
+ gas_return_contract = pre.deploy_contract(
+ code=Op.MSTORE(0, Op.GAS, new_memory_size=0x20) + Op.RETURN(0, 0x20),
)
- # Source: lll
- # { (SSTORE 8 (GAS))}
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x8, value=Op.GAS) + Op.STOP,
- nonce=0,
+ # Depth 1: records its own entry gas, then asks depth 2 for more gas
+ # than this frame holds; both observations return to the top frame.
+ entry_snapshot = Op.MSTORE(0x20, Op.GAS, new_memory_size=0x40)
+ depth2_call = Op.CALL(
+ gas=ASK_GAS,
+ address=gas_return_contract,
+ ret_size=0x20,
+ address_warm=False,
+ account_new=False,
+ new_memory_size=0x40,
+ old_memory_size=0x40,
)
- # Source: lll
- # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 600000 0 0 0 0 0)) } # noqa: E501
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x8, value=Op.GAS)
- + Op.SSTORE(
- key=0x9,
- value=Op.CALL(
- gas=0x927C0,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.STOP,
- nonce=0,
+ caller = pre.deploy_contract(
+ code=entry_snapshot + depth2_call + Op.RETURN(0, 0x40),
)
- # Source: lll
- # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 200000 0 0 0 0 0)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x8, value=Op.GAS)
- + Op.SSTORE(
- key=0x9,
- value=Op.CALL(
- gas=0x30D40,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
+
+ # Top frame: forwards the exact (affordable) depth-1 budget and stores
+ # the success flag plus both returned observations.
+ entry = pre.deploy_contract(
+ code=Op.SSTORE(
+ key=FLAG_SLOT,
+ value=Op.CALL(gas=CALLER_GAS, address=caller, ret_size=0x40),
)
- + Op.STOP,
- nonce=0,
+ + Op.SSTORE(key=DEPTH2_GAS_SLOT, value=Op.MLOAD(0))
+ + Op.SSTORE(key=DEPTH1_GAS_SLOT, value=Op.MLOAD(0x20)),
)
tx = Transaction(
- sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=600000,
+ sender=pre.fund_eoa(),
+ to=entry,
+ state_gas_reservoir=0,
+ )
+
+ # Depth 1 received exactly CALLER_GAS; its snapshot reads it minus the
+ # GAS opcode itself. The depth-2 base is what remains after the
+ # snapshot and the call's own costs, clamped by EIP-150.
+ depth1_observed = CALLER_GAS - Op.GAS.gas_cost(fork)
+ base = (
+ CALLER_GAS - entry_snapshot.gas_cost(fork) - depth2_call.gas_cost(fork)
)
+ assert 0 < base < ASK_GAS, "the 63/64 clamp must apply at depth 2"
+ forwarded = base - base // 64
+ depth2_observed = forwarded - Op.GAS.gas_cost(fork)
post = {
- addr: Account(storage={8: 0x30D3E, 9: 1}),
- addr_2: Account(storage={8: 0x2A1F6}),
+ entry: Account(
+ storage={
+ FLAG_SLOT: 1,
+ DEPTH2_GAS_SLOT: depth2_observed,
+ DEPTH1_GAS_SLOT: depth1_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_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/stEIP150Specific/test_new_gas_price_for_codes.py b/tests/ported_static/stEIP150Specific/test_new_gas_price_for_codes.py
index 0be45107816..6ed52393b2d 100644
--- a/tests/ported_static/stEIP150Specific/test_new_gas_price_for_codes.py
+++ b/tests/ported_static/stEIP150Specific/test_new_gas_price_for_codes.py
@@ -1,18 +1,25 @@
"""
-Test_new_gas_price_for_codes.
+Verify the EIP-150 repriced code/account operations in one frame:
+EXTCODESIZE, EXTCODECOPY, SLOAD, failing value CALL/CALLCODE (insufficient
+balance), DELEGATECALL that writes the caller's storage, a call to a
+nonexistent account, BALANCE, and the whole window's measured gas.
Ported from:
state_tests/stEIP150Specific/NewGasPriceForCodesFiller.json
+
+@manually-enhanced: Do not overwrite. The ported bytecode shape is kept,
+but the window delta, the mid-execution sender balance, and the copied
+code word are derived (opcode metadata, fee formula, the deployed bytes);
+the delegate's budget is derived so its store — state-priced under
+EIP-8037 — fits inside the grant (a reservoir-less sub-call pays state
+gas from its regular grant); each failed value call returns its stipend.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -21,133 +28,196 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+EXTCODE_BYTES = bytes.fromhex(
+ "1122334455667788991011121314151617181920212223242526272829303132"
+)
+COPY_SIZE = 0x14
+DELEGATE_VALUE = 0x11
+# Budget for the calls whose outcome does not depend on it (the value
+# calls fail on insufficient balance; the absent target runs nothing).
+FORWARDED_GAS = 0x7530
+GAS_PRICE = 10
+INITIAL_BALANCE = 10**15
+
@pytest.mark.ported_from(
["state_tests/stEIP150Specific/NewGasPriceForCodesFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_new_gas_price_for_codes(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_new_gas_price_for_codes."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = EOA(
- key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52
+ """Measure a frame exercising every repriced code/account operation."""
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
+ code_target = pre.deploy_contract(code=EXTCODE_BYTES, balance=111)
+ delegate_store = Op.SSTORE(
+ key=0x64,
+ value=DELEGATE_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=DELEGATE_VALUE,
)
+ storage_writer = pre.deploy_contract(code=delegate_store + Op.STOP)
+ absent = pre.nonexistent_account()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
+ # The delegate must succeed: with a zero reservoir its state-priced
+ # store is paid from the regular grant, so the budget is derived.
+ delegate_budget = delegate_store.gas_cost(fork) + 2_000
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: raw
- # 0x1122334455667788991011121314151617181920212223242526272829303132
- addr = pre.deploy_contract( # noqa: F841
- code=bytes.fromhex(
- "1122334455667788991011121314151617181920212223242526272829303132"
- ),
- balance=111,
- nonce=0,
- address=Address(0xC572A70AFAAB9D01D0A2AFB855BFBAFB47C8211B), # noqa: E501
- )
- # Source: lll
- # { (SSTORE 100 0x11) }
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x64, value=0x11) + Op.STOP,
- nonce=0,
- address=Address(0xAD9D325B811CB0701839C07C6F139F3799476798), # noqa: E501
- )
- # Source: lll
- # { [999] (GAS) (SSTORE 1 (EXTCODESIZE )) (EXTCODECOPY 0 0 20) (SSTORE 2 (MLOAD 0)) (SSTORE 4 (SLOAD 0)) (SSTORE 5 (CALL 30000 1 0 0 0 0)) (SSTORE 6 (CALLCODE 30000 1 0 0 0 0)) (SSTORE 7 (DELEGATECALL 30000 0 0 0 0)) (SSTORE 8 (CALL 30000 0x1000000000000000000000000000000000000013 0 0 0 0 0)) (SSTORE 3 (BALANCE )) (SSTORE 10 (SUB (MLOAD 999) (GAS))) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x3E7, value=Op.GAS)
- + Op.SSTORE(key=0x1, value=Op.EXTCODESIZE(address=addr))
- + Op.EXTCODECOPY(address=addr, dest_offset=0x0, offset=0x0, size=0x14)
- + Op.SSTORE(key=0x2, value=Op.MLOAD(offset=0x0))
- + Op.SSTORE(key=0x4, value=Op.SLOAD(key=0x0))
+ # The measured window: entry GAS snapshot through the closing GAS.
+ # The value-bearing CALL and CALLCODE fail on insufficient balance
+ # (this contract holds nothing), costing their access and transfer
+ # charges minus the returned stipend; the DELEGATECALL runs the
+ # writer against this contract's storage.
+ window = (
+ Op.MSTORE(offset=0x3E7, value=Op.GAS, new_memory_size=0x407)
+ + Op.SSTORE(
+ key=0x1,
+ value=Op.EXTCODESIZE(address=code_target, address_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.EXTCODECOPY(
+ address=code_target,
+ dest_offset=0x0,
+ offset=0x0,
+ size=COPY_SIZE,
+ address_warm=True,
+ data_size=COPY_SIZE,
+ new_memory_size=0x407,
+ old_memory_size=0x407,
+ )
+ + Op.SSTORE(
+ key=0x2,
+ value=Op.MLOAD(offset=0x0),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.SSTORE(
+ key=0x4,
+ value=Op.SLOAD(key=0x0, key_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ Op.SSTORE(
key=0x5,
value=Op.CALL(
- gas=0x7530,
- address=addr_2,
+ gas=FORWARDED_GAS,
+ address=storage_writer,
value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ address_warm=False,
+ value_transfer=True,
+ account_new=False,
),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
)
+ Op.SSTORE(
key=0x6,
value=Op.CALLCODE(
- gas=0x7530,
- address=addr_2,
+ gas=FORWARDED_GAS,
+ address=storage_writer,
value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ address_warm=True,
+ value_transfer=True,
+ account_new=False,
),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
)
+ Op.SSTORE(
key=0x7,
value=Op.DELEGATECALL(
- gas=0x7530,
- address=addr_2,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ gas=delegate_budget,
+ address=storage_writer,
+ address_warm=True,
),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
+ Op.SSTORE(
key=0x8,
value=Op.CALL(
- gas=0x7530,
- address=0x1000000000000000000000000000000000000013,
+ gas=FORWARDED_GAS,
+ address=absent,
value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- + Op.SSTORE(key=0x3, value=Op.BALANCE(address=sender))
- + Op.SSTORE(key=0xA, value=Op.SUB(Op.MLOAD(offset=0x3E7), Op.GAS))
- + Op.STOP,
+ + Op.SSTORE(
+ key=0x3,
+ value=Op.BALANCE(address=sender, address_warm=True),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ )
+ delta_store = Op.SSTORE(
+ key=0xA,
+ value=Op.SUB(Op.MLOAD(offset=0x3E7), Op.GAS),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ target = pre.deploy_contract(
+ code=window + delta_store + Op.STOP,
storage={0: 18},
- nonce=0,
- address=Address(0xFD9AFC8315A88141164E2A753157EA3E0F72C707), # noqa: E501
)
+ # Window delta: everything from the entry GAS read to the closing
+ # one; the lead GAS and the closing GAS cancel out of the composite,
+ # the delegate's work is added on top, and each failed value call
+ # hands back its stipend along with the unused grant.
+ measured = (
+ window.gas_cost(fork)
+ + delegate_store.gas_cost(fork)
+ - 2 * fork.gas_costs().CALL_STIPEND
+ )
+
+ # Fork-derived budget with an EIP-2200 stipend margin for the
+ # trailing delta store.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = intrinsic + measured + delta_store.gas_cost(fork) + 5_000
+
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=600000,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
+ copied_word = int.from_bytes(
+ EXTCODE_BYTES[:COPY_SIZE].ljust(0x20, b"\x00"), "big"
+ )
post = {
target: Account(
storage={
- 0: 18,
- 1: 32,
- 2: 0x1122334455667788991011121314151617181920000000000000000000000000, # noqa: E501
- 3: 0xE8D4498280,
- 4: 18,
- 7: 1,
- 8: 1,
- 10: 0x2CB0A,
- 100: 17,
+ 0x0: 18,
+ 0x1: len(EXTCODE_BYTES),
+ 0x2: copied_word,
+ # Mid-execution balance: the full fee is charged upfront.
+ 0x3: INITIAL_BALANCE - gas_limit * GAS_PRICE,
+ 0x4: 18,
+ # Slots 5 and 6 stay zero: the value calls failed.
+ 0x7: 1,
+ 0x8: 1,
+ 0xA: measured,
+ 0x64: DELEGATE_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/stEIP150Specific/test_transaction64_rule.py b/tests/ported_static/stEIP150Specific/test_transaction64_rule.py
new file mode 100644
index 00000000000..5930e8a26c2
--- /dev/null
+++ b/tests/ported_static/stEIP150Specific/test_transaction64_rule.py
@@ -0,0 +1,109 @@
+"""
+Verify the EIP-150 "all but one 64th" rounding at the transaction level: the
+gas available when a subcall asks for more than the transaction provided is
+floored as `base - base // 64`, probed with the base exactly divisible by
+64 and one gas below/above it.
+
+Ported from:
+state_tests/stEIP150Specific/Transaction64Rule_d64e0Filler.json
+state_tests/stEIP150Specific/Transaction64Rule_d64m1Filler.json
+state_tests/stEIP150Specific/Transaction64Rule_d64p1Filler.json
+
+@manually-enhanced: Do not overwrite. Three fillers folded into one
+parametrize; the callee reports its observed GAS so the exact forwarded
+amount is asserted (`base - base // 64` differs from `base * 63 // 64` by
+one whenever the base is not a multiple of 64 — the ported posts could not
+see that difference); the tx gas limit is derived from the fork so the
+divisibility residue holds on every fork.
+"""
+
+import pytest
+from execution_testing import (
+ Account,
+ Alloc,
+ Fork,
+ StateTestFiller,
+ Transaction,
+)
+from execution_testing.vm import Op
+
+REFERENCE_SPEC_GIT_PATH = "N/A"
+REFERENCE_SPEC_VERSION = "N/A"
+
+GAS_SLOT = 0x1
+# Far larger than any gas the frame can hold: the clamp always applies.
+OVERSIZED_GAS_ASK = 2**61
+
+
+@pytest.mark.ported_from(
+ [
+ "state_tests/stEIP150Specific/Transaction64Rule_d64e0Filler.json",
+ "state_tests/stEIP150Specific/Transaction64Rule_d64m1Filler.json",
+ "state_tests/stEIP150Specific/Transaction64Rule_d64p1Filler.json",
+ ],
+)
+@pytest.mark.valid_from("Berlin")
+@pytest.mark.parametrize(
+ "residue",
+ [
+ pytest.param(0, id="d64e0"),
+ pytest.param(-1, id="d64m1"),
+ pytest.param(1, id="d64p1"),
+ ],
+)
+def test_transaction64_rule(
+ state_test: StateTestFiller,
+ pre: Alloc,
+ fork: Fork,
+ residue: int,
+) -> None:
+ """A subcall asking above the tx budget receives `base - base // 64`."""
+ # Callee returns the gas it observed on entry back to the caller.
+ gas_return_contract = pre.deploy_contract(
+ code=Op.MSTORE(0, Op.GAS, new_memory_size=0x20) + Op.RETURN(0, 0x20),
+ )
+
+ call_code = Op.CALL(
+ gas=OVERSIZED_GAS_ASK,
+ address=gas_return_contract,
+ ret_size=0x20,
+ address_warm=False,
+ account_new=False,
+ new_memory_size=0x20,
+ )
+ # The observed-gas store is the only op after the call; the callee's
+ # returned surplus always covers it.
+ store_code = Op.SSTORE(
+ key=GAS_SLOT,
+ value=Op.MLOAD(0),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ caller = pre.deploy_contract(code=call_code + store_code + Op.STOP)
+
+ # Choose the 63/64 rounding base: large enough that the frame can
+ # afford the trailing store from what the callee hands back, shaped to
+ # the parametrized residue mod 64. The +1024 margin absorbs the ops
+ # around the store.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ min_base = store_code.gas_cost(fork) + 1024
+ base = -(-min_base // 64) * 64 + residue
+ assert base < OVERSIZED_GAS_ASK, "the 63/64 clamp must apply"
+ gas_limit = intrinsic + call_code.gas_cost(fork) + base
+
+ tx = Transaction(
+ sender=pre.fund_eoa(),
+ to=caller,
+ gas_limit=gas_limit,
+ )
+
+ # The EVM floors the forwarded gas as `base - base // 64`; the callee
+ # observes it minus its own GAS opcode. An implementation using
+ # `base * 63 // 64` is exactly one gas short on the m1/p1 residues.
+ forwarded = base - base // 64
+ expected_gas = forwarded - Op.GAS.gas_cost(fork)
+
+ post = {caller: Account(storage={GAS_SLOT: expected_gas})}
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64e0.py b/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64e0.py
deleted file mode 100644
index 256cf7ea0bb..00000000000
--- a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64e0.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""
-Test_transaction64_rule_d64e0.
-
-Ported from:
-state_tests/stEIP150Specific/Transaction64Rule_d64e0Filler.json
-"""
-
-import pytest
-from execution_testing import (
- Account,
- Address,
- Alloc,
- Bytes,
- Environment,
- StateTestFiller,
- Transaction,
-)
-from execution_testing.vm import Op
-
-REFERENCE_SPEC_GIT_PATH = "N/A"
-REFERENCE_SPEC_VERSION = "N/A"
-
-
-@pytest.mark.ported_from(
- ["state_tests/stEIP150Specific/Transaction64Rule_d64e0Filler.json"],
-)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
-def test_transaction64_rule_d64e0(
- state_test: StateTestFiller,
- pre: Alloc,
-) -> None:
- """Test_transaction64_rule_d64e0."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
-
- # Source: lll
- # { [[1]] 12 }
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP,
- nonce=0,
- )
- # Source: lll
- # { [0] (GAS) (CALL 160000 0 0 0 0 0) [[2]] (SUB @0 (GAS)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.POP(
- Op.CALL(
- gas=0x27100,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- )
- + Op.SSTORE(key=0x2, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.STOP,
- nonce=0,
- )
-
- tx = Transaction(
- sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=160062,
- )
-
- post = {
- addr: Account(storage={1: 12}),
- target: Account(storage={2: 24740}),
- }
-
- state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64m1.py b/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64m1.py
deleted file mode 100644
index dd89bd167ec..00000000000
--- a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64m1.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""
-Test_transaction64_rule_d64m1.
-
-Ported from:
-state_tests/stEIP150Specific/Transaction64Rule_d64m1Filler.json
-"""
-
-import pytest
-from execution_testing import (
- Account,
- Address,
- Alloc,
- Bytes,
- Environment,
- StateTestFiller,
- Transaction,
-)
-from execution_testing.vm import Op
-
-REFERENCE_SPEC_GIT_PATH = "N/A"
-REFERENCE_SPEC_VERSION = "N/A"
-
-
-@pytest.mark.ported_from(
- ["state_tests/stEIP150Specific/Transaction64Rule_d64m1Filler.json"],
-)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
-def test_transaction64_rule_d64m1(
- state_test: StateTestFiller,
- pre: Alloc,
-) -> None:
- """Test_transaction64_rule_d64m1."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
-
- # Source: lll
- # { [[1]] 12 }
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP,
- nonce=0,
- )
- # Source: lll
- # { [0] (GAS) (CALL 160000 0 0 0 0 0) [[2]] (SUB @0 (GAS)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.POP(
- Op.CALL(
- gas=0x27100,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- )
- + Op.SSTORE(key=0x2, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.STOP,
- nonce=0,
- )
-
- tx = Transaction(
- sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=160061,
- )
-
- post = {
- addr: Account(storage={1: 12}),
- target: Account(storage={2: 24740}),
- }
-
- state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64p1.py b/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64p1.py
deleted file mode 100644
index 2dead5d9e1a..00000000000
--- a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64p1.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""
-Test_transaction64_rule_d64p1.
-
-Ported from:
-state_tests/stEIP150Specific/Transaction64Rule_d64p1Filler.json
-"""
-
-import pytest
-from execution_testing import (
- Account,
- Address,
- Alloc,
- Bytes,
- Environment,
- StateTestFiller,
- Transaction,
-)
-from execution_testing.vm import Op
-
-REFERENCE_SPEC_GIT_PATH = "N/A"
-REFERENCE_SPEC_VERSION = "N/A"
-
-
-@pytest.mark.ported_from(
- ["state_tests/stEIP150Specific/Transaction64Rule_d64p1Filler.json"],
-)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
-def test_transaction64_rule_d64p1(
- state_test: StateTestFiller,
- pre: Alloc,
-) -> None:
- """Test_transaction64_rule_d64p1."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
-
- # Source: lll
- # { [[1]] 12 }
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP,
- nonce=0,
- )
- # Source: lll
- # { [0] (GAS) (CALL 160000 0 0 0 0 0) [[2]] (SUB @0 (GAS)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.POP(
- Op.CALL(
- gas=0x27100,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- )
- + Op.SSTORE(key=0x2, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.STOP,
- nonce=0,
- )
-
- tx = Transaction(
- sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=160063,
- )
-
- post = {
- addr: Account(storage={1: 12}),
- target: Account(storage={2: 24740}),
- }
-
- state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py
index d84fd092041..f1d08b2f849 100644
--- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py
+++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py
@@ -1,11 +1,15 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Measure the gas cost of each opcode via a crafted one-opcode contract
+(by Ori Pomerantz qbzzt1@gmail.com).
Ported from:
state_tests/stEIP150singleCodeGasPrices/gasCostFiller.yml
@manually-enhanced: Do not overwrite. This crafts a one-opcode
contract, CALLs it, and stores the opcode's measured gas via `Op.GAS`.
+The SSTORE case (d40) derives its cost from the fork — EIP-8037 moves
+the bulk into state gas — and the crafted CALL forwards effectively
+all gas (0xFFFFFF, same PUSH3 width) so that case cannot OOG.
EIP-8038 reprices state access, so four opcodes shift: `BALANCE` and
`SELFDESTRUCT` (cold account, `COLD_ACCOUNT_ACCESS` 2600 -> 3000, +400),
`EXTCODESIZE` (cold account plus the extra `WARM_ACCESS` charged for
@@ -734,6 +738,12 @@ def test_gas_cost(
code_read_delta = cold_account_delta + (
gas_costs.WARM_ACCESS if fork.is_eip_enabled(8037) else 0
)
+ # EIP-8037 moves the bulk of a zero->nonzero SSTORE into state gas;
+ # the explicit gas limit equals the cap (zero reservoir), so the
+ # crafted contract's GAS delta observes the full cost.
+ sstore_new_slot_cost = Op.SSTORE(
+ key_warm=False, original_value=0, new_value=1
+ ).gas_cost(fork)
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = EOA(
key=0x40AC0FC28C27E961EE46EC43355A094DE205856EDBD4654CF2577C2608D4EC1E
@@ -837,7 +847,12 @@ def test_gas_cost(
+ Op.MSTORE(offset=0x300, value=Op.GAS)
+ Op.POP(
Op.CALL(
- gas=0x10000,
+ # Effectively "all gas": EIP-8037 repriced the SSTORE case
+ # (d40) past the ported 0x10000 budget, OOGing the callee.
+ # 0xFFFFFF keeps the same PUSH3 width, so the hand-coded
+ # JUMP targets and every measurement stay unchanged
+ # (unused gas returns to the caller).
+ gas=0xFFFFFF,
address=Op.MLOAD(offset=0x280),
value=0x0,
args_offset=0x0,
@@ -1102,10 +1117,18 @@ def test_gas_cost(
},
},
{
+ # SSTORE zero->nonzero to a fresh cold slot. Stored value =
+ # actual cost minus the 0x4E20 expected-cost operand in the
+ # data word minus the file-wide 0x258 baseline; 1500 before
+ # EIP-8037, dominated by state gas after.
"indexes": {"data": [40], "gas": -1, "value": -1},
"network": [">=Cancun"],
"result": {
- addr: Account(storage=_storage_with_any({0: 1500}, [1]))
+ addr: Account(
+ storage=_storage_with_any(
+ {0: sstore_new_slot_cost - 0x4E20 - 0x258}, [1]
+ )
+ )
},
},
{
diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py
index 5a92e62ca28..b5f361384c2 100644
--- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py
+++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py
@@ -1,5 +1,6 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Measure the gas cost of each opcode via a crafted one-opcode contract
+(by Ori Pomerantz qbzzt1@gmail.com).
Ported from:
state_tests/stEIP150singleCodeGasPrices/gasCostBerlinFiller.yml
@@ -7,6 +8,9 @@
@manually-enhanced: Do not overwrite. This crafts a one-opcode
contract, CALLs it, and stores the opcode's measured gas minus the
data's hardcoded Cancun-era expected cost (so the net is normally 0).
+The SSTORE case (d40) derives its net from the fork — EIP-8037 moves
+the bulk into state gas — and the crafted CALL forwards effectively
+all gas (0xFFFFFF, same PUSH3 width) so that case cannot OOG.
EIP-8038 reprices state access, so four opcodes now exceed their old
expected cost by a fork-derived delta: `BALANCE` and `SELFDESTRUCT`
(cold account, `COLD_ACCOUNT_ACCESS` 2600 -> 3000, +400), `EXTCODESIZE`
@@ -733,10 +737,18 @@ def test_gas_cost_berlin(
# Each measured opcode subtracts its Cancun-era expected cost, so the
# net is the (Amsterdam - Cancun) repricing of the one state access
# it performs (cold address 0 / cold fresh slot), keyed by data index.
+ # EIP-8037 moves the bulk of a zero->nonzero SSTORE into state gas;
+ # the explicit gas limit equals the cap (zero reservoir), so the
+ # crafted contract's GAS delta observes the full cost. The data word
+ # encodes the 0x5654 (22100) pre-8037 cost, so the net is 0 there.
+ sstore_new_slot_cost = Op.SSTORE(
+ key_warm=False, original_value=0, new_value=1
+ ).gas_cost(fork)
measured_delta = {
23: cold_account_delta, # BALANCE
31: code_read_delta, # EXTCODESIZE
39: cold_storage_delta, # SLOAD
+ 40: sstore_new_slot_cost - 0x5654, # SSTORE zero->nonzero
45: cold_account_delta, # SELFDESTRUCT
}.get(d, 0)
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
@@ -839,7 +851,12 @@ def test_gas_cost_berlin(
+ Op.MSTORE(offset=0x300, value=Op.GAS)
+ Op.POP(
Op.CALL(
- gas=0x10000,
+ # Effectively "all gas": EIP-8037 repriced the SSTORE case
+ # (d40) past the ported 0x10000 budget, OOGing the callee.
+ # 0xFFFFFF keeps the same PUSH3 width, so the hand-coded
+ # JUMP targets and every measurement stay unchanged
+ # (unused gas returns to the caller).
+ gas=0xFFFFFF,
address=Op.MLOAD(offset=0x280),
value=0x0,
args_offset=0x0,
diff --git a/tests/ported_static/stEIP158Specific/test_exp_empty.py b/tests/ported_static/stEIP158Specific/test_exp_empty.py
index 9b7b323744c..558f1b6d071 100644
--- a/tests/ported_static/stEIP158Specific/test_exp_empty.py
+++ b/tests/ported_static/stEIP158Specific/test_exp_empty.py
@@ -1,17 +1,23 @@
"""
-Test_exp_empty.
+Measure the gas cost of EXP with a zero base or a zero exponent across
+exponent widths (the per-byte exponent charge applies only to the
+exponent operand).
Ported from:
state_tests/stEIP158Specific/EXP_EmptyFiller.json
+
+@manually-enhanced: Do not overwrite. The eight measurement windows are
+generated from one case list and every stored delta is derived from
+opcode metadata (`exponent=` drives the per-byte charge); the transaction
+budget is fork-derived.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Bytecode,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,100 +26,78 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# (base, exponent) pairs: a zero on either side, exponent widths 1-32.
+EXP_CASES = [
+ (0x0, 0xC),
+ (0xC, 0x0),
+ (0x0, 2**64 - 1),
+ (0x0, 2**128 - 1),
+ (0x0, 2**256 - 1),
+ (2**64 - 1, 0x0),
+ (2**128 - 1, 0x0),
+ (2**256 - 1, 0x0),
+]
+
@pytest.mark.ported_from(
["state_tests/stEIP158Specific/EXP_EmptyFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_exp_empty(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_exp_empty."""
- 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 EXP's cost for zero-base and zero-exponent operands."""
+ code = Bytecode()
+ storage: dict = {}
+ budget = 0
+ for i, (base, exponent) in enumerate(EXP_CASES):
+ result = 1 if exponent == 0 else 0
+ result_slot = 1 + 2 * i
+ # The last window stores its delta at slot 100, as ported.
+ delta_slot = 0x64 if i == len(EXP_CASES) - 1 else result_slot + 1
- # Source: lll
- # { [0](GAS) [[1]](EXP 0 12) [[2]](SUB @0 (GAS)) [0](GAS) [[3]](EXP 12 0) [[4]](SUB @0 (GAS)) [0](GAS) [[5]](EXP 0 0xffffffffffffffff) [[6]](SUB @0 (GAS)) [0](GAS) [[7]](EXP 0 0xffffffffffffffffffffffffffffffff) [[8]](SUB @0 (GAS)) [0](GAS) [[9]](EXP 0 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) [[10]](SUB @0 (GAS)) [0](GAS) [[11]](EXP 0xffffffffffffffff 0) [[12]](SUB @0 (GAS)) [0](GAS) [[13]](EXP 0xffffffffffffffffffffffffffffffff 0) [[14]](SUB @0 (GAS)) [0] (GAS) [[15]](EXP 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0) [[100]] (SUB @0 (GAS)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(key=0x1, value=Op.EXP(0x0, 0xC))
- + Op.SSTORE(key=0x2, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(key=0x3, value=Op.EXP(0xC, 0x0))
- + Op.SSTORE(key=0x4, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(key=0x5, value=Op.EXP(0x0, 0xFFFFFFFFFFFFFFFF))
- + Op.SSTORE(key=0x6, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(
- key=0x7, value=Op.EXP(0x0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
+ lead = Op.MSTORE(
+ offset=0x0,
+ value=Op.GAS,
+ new_memory_size=0x20,
+ old_memory_size=0x0 if i == 0 else 0x20,
)
- + Op.SSTORE(key=0x8, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(
- key=0x9,
- value=Op.EXP(
- 0x0,
- 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, # noqa: E501
- ),
+ exp_store = Op.SSTORE(
+ key=result_slot,
+ value=Op.EXP(base, exponent, exponent=exponent),
+ key_warm=False,
+ original_value=0,
+ new_value=result,
)
- + Op.SSTORE(key=0xA, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(key=0xB, value=Op.EXP(0xFFFFFFFFFFFFFFFF, 0x0))
- + Op.SSTORE(key=0xC, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(
- key=0xD, value=Op.EXP(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0x0)
+ # The window's measured delta: both GAS reads cancel out of the
+ # two composites' sum.
+ measured = lead.gas_cost(fork) + exp_store.gas_cost(fork)
+ delta_store = Op.SSTORE(
+ key=delta_slot,
+ value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS),
+ key_warm=False,
+ original_value=0,
+ new_value=measured,
)
- + Op.SSTORE(key=0xE, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(
- key=0xF,
- value=Op.EXP(
- 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, # noqa: E501
- 0x0,
- ),
- )
- + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.STOP,
- nonce=0,
- )
+ code += lead + exp_store + delta_store
+ storage[result_slot] = result
+ storage[delta_slot] = measured
+ budget += measured + delta_store.gas_cost(fork) + 9
+
+ target = pre.deploy_contract(code=code + Op.STOP)
+
+ # Fork-derived budget with an EIP-2200 stipend margin for the final
+ # store.
+ gas_limit = fork.transaction_intrinsic_cost_calculator()() + budget + 5_000
tx = Transaction(
- sender=sender,
+ sender=pre.fund_eoa(),
to=target,
- data=Bytes(""),
- gas_limit=600000,
+ gas_limit=gas_limit,
)
- post = {
- target: Account(
- storage={
- 2: 2280,
- 3: 1,
- 4: 22127,
- 6: 2627,
- 8: 3027,
- 10: 3827,
- 11: 1,
- 12: 22127,
- 13: 1,
- 14: 22127,
- 15: 1,
- 100: 22127,
- },
- ),
- }
+ post = {target: Account(storage=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/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py b/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py
index 82d92551fc3..b59d7c67858 100644
--- a/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py
+++ b/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py
@@ -1,17 +1,23 @@
"""
-Test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.
+Verify an out-of-gas contract creation leaves no account behind (the
+Homestead-era bug left empty shells), while a sufficient budget creates a
+codeless account whose init code called out to a storage writer.
Ported from:
state_tests/stHomesteadSpecific/contractCreationOOGdontLeaveEmptyContractViaTransactionFiller.json
+
+@manually-enhanced: Do not overwrite. The ported single case had silently
+become success-only (its OOG arm was gone); both arms are restored with
+fork-derived budgets, the init code's call budget is derived (the writer's
+store is state-priced under EIP-8037), and the writer's slot plus the
+created account's fields are asserted.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -27,72 +33,72 @@
"state_tests/stHomesteadSpecific/contractCreationOOGdontLeaveEmptyContractViaTransactionFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
+@pytest.mark.parametrize(
+ "enough_gas",
+ [
+ pytest.param(True, id="created"),
+ pytest.param(False, id="oog_no_account"),
+ ],
+)
def test_contract_creation_oo_gdont_leave_empty_contract_via_transaction(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
+ enough_gas: bool,
) -> None:
- """Test_contract_creation_oo_gdont_leave_empty_contract_via_transaction."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- contract_1 = Address(0x1000000000000000000000000000000000000001)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """An OOG creation must not leave an account behind."""
+ writer_store = Op.SSTORE(
+ key=0x1, value=0x1, key_warm=False, original_value=0, new_value=1
)
+ 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=1000000,
+ # The init code calls the writer and deposits nothing. The forwarded
+ # budget is derived: with a zero reservoir the writer's state-priced
+ # store must fit inside its grant.
+ writer_needed = writer_store.gas_cost(fork)
+ call_code = Op.CALL(
+ gas=writer_needed + 1_000,
+ address=writer,
+ args_size=0x40,
+ ret_size=0x40,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
+ new_memory_size=0x40,
)
+ initcode = call_code + Op.STOP
- pre[sender] = Account(balance=0x10C8E0)
- # Source: lll
- # {(SSTORE 1 1)}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP,
- nonce=0,
- address=Address(0x1000000000000000000000000000000000000001), # noqa: E501
- )
- # Source: lll
- # {(CALL 50000 0x1000000000000000000000000000000000000001 0 0 64 0 64)}
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.CALL(
- gas=0xC350,
- address=0x1000000000000000000000000000000000000001,
- value=0x0,
- args_offset=0x0,
- args_size=0x40,
- ret_offset=0x0,
- ret_size=0x40,
- )
- + Op.STOP,
- balance=0x186A0,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
- )
+ overhead = fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ ) + fork.transaction_top_frame_state_gas(contract_creation=True)
+ execution = call_code.gas_cost(fork) + writer_needed
+ # The OOG arm dies charging the init code's own CALL (a failed inner
+ # call alone would not fail the creation): a bare 100-gas allowance
+ # over the fixed charges cannot cover the call's access cost even
+ # with the intrinsic estimate's slack.
+ gas_limit = overhead + (execution + 2_000 if enough_gas else 100)
+ sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
to=None,
- data=Op.CALL(
- gas=0xC350,
- address=contract_1,
- value=0x0,
- args_offset=0x0,
- args_size=0x40,
- ret_offset=0x0,
- ret_size=0x40,
- ),
- gas_limit=96000,
+ data=initcode,
+ gas_limit=gas_limit,
)
+ created = compute_create_address(address=sender, nonce=0)
+ if enough_gas:
+ created_account: Account | None = Account(nonce=1, code=b"", balance=0)
+ writer_storage = {1: 1}
+ else:
+ created_account = Account.NONEXISTENT
+ writer_storage = {1: 0}
post = {
- compute_create_address(address=sender, nonce=0): Account(balance=0)
+ sender: Account(nonce=1),
+ created: created_account,
+ writer: Account(storage=writer_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/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_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py
index e181c5c7f03..f080fc4a5db 100644
--- a/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py
+++ b/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py
@@ -1,17 +1,24 @@
"""
-Test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding...
+Verify the EIP-150 63/64 clamp at call depth 2 when the calls also expand
+memory: a first-level call receives its exact (affordable) ask, and its own
+oversized ask is clamped to 63/64 of what remains after the memory
+expansion.
Ported from:
state_tests/stMemExpandingEIP150Calls/CallAskMoreGasOnDepth2ThenTransactionHasWithMemExpandingCallsFiller.json
+
+@manually-enhanced: Do not overwrite. The lower frames return their
+observed GAS up the stack instead of SSTORE-ing it (the ported lower-frame
+gas snapshots are EIP-8037 state-gas traps); every expectation is derived
+from the fork, including the top frame's entry snapshot, which pins the
+transaction intrinsic cost.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,86 +27,111 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+FLAG_SLOT = 0x0
+DEPTH2_GAS_SLOT = 0x1
+DEPTH1_GAS_SLOT = 0x2
+ENTRY_GAS_SLOT = 0x3
+
+# The ported depth-1 budget: affordable, so it is forwarded exactly.
+CALLER_GAS = 0x30D40
+# The ported depth-2 ask: above anything the depth-1 frame can hold, so
+# the 63/64 clamp decides what the depth-2 frame receives.
+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/CallAskMoreGasOnDepth2ThenTransactionHasWithMemExpandingCallsFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls( # noqa: E501
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expa...""" # noqa: E501
- 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,
+ """A depth-2 memory-expanding call is clamped to 63/64 of its frame."""
+ # Depth 2: returns the gas it observed on entry.
+ gas_return_contract = pre.deploy_contract(
+ code=Op.MSTORE(0, Op.GAS, new_memory_size=0x20) + Op.RETURN(0, 0x20),
)
- # Source: hex
- # 0x5a600855
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x8, value=Op.GAS),
- nonce=0,
+ # Depth 1: records its own entry gas, then asks depth 2 for more gas
+ # than this frame holds, expanding memory through the args window;
+ # both observations return to the top frame.
+ entry_snapshot = Op.MSTORE(0x20, Op.GAS, new_memory_size=0x40)
+ depth2_call = Op.CALL(
+ gas=ASK_GAS,
+ address=gas_return_contract,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_size=0x20,
+ address_warm=False,
+ account_new=False,
+ new_memory_size=MEM_OFFSET + MEM_SIZE,
+ old_memory_size=0x40,
)
- # Source: hex
- # 0x5a60085560ff60ff60ff60ff600073620927c0f1600955 # noqa: E501
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x8, value=Op.GAS)
- + Op.SSTORE(
- key=0x9,
- value=Op.CALL(
- gas=0x927C0,
- address=addr,
- value=0x0,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
- ),
- ),
- nonce=0,
+ caller = pre.deploy_contract(
+ code=entry_snapshot + depth2_call + Op.RETURN(0, 0x40),
)
- # Source: hex
- # 0x5a60085560ff60ff60ff60ff60007362030d40f1600955 # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x8, value=Op.GAS)
+
+ # Top frame: snapshots its entry gas (pinning the tx intrinsic), then
+ # forwards the exact depth-1 budget and stores the success flag plus
+ # both returned observations.
+ entry_code = (
+ Op.SSTORE(key=ENTRY_GAS_SLOT, value=Op.GAS)
+ Op.SSTORE(
- key=0x9,
+ key=FLAG_SLOT,
value=Op.CALL(
- gas=0x30D40,
- address=addr_2,
- value=0x0,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ gas=CALLER_GAS,
+ address=caller,
+ ret_size=0x40,
+ address_warm=False,
+ account_new=False,
+ new_memory_size=0x40,
),
- ),
- nonce=0,
+ )
+ + Op.SSTORE(key=DEPTH2_GAS_SLOT, value=Op.MLOAD(0))
+ + Op.SSTORE(key=DEPTH1_GAS_SLOT, value=Op.MLOAD(0x20))
)
+ entry = pre.deploy_contract(code=entry_code + Op.STOP)
+
+ # Conservative fork-derived budget: the entry's own costs (incl. the
+ # trailing state-priced stores) plus the full depth-1 grant.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = intrinsic + entry_code.gas_cost(fork) + CALLER_GAS
tx = Transaction(
- sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=600000,
+ sender=pre.fund_eoa(),
+ to=entry,
+ gas_limit=gas_limit,
)
+ # The entry snapshot observes everything after the intrinsic; depth 1
+ # received exactly CALLER_GAS; the depth-2 base is what remains after
+ # the snapshot and the call's own costs (incl. memory expansion),
+ # clamped by EIP-150.
+ entry_observed = gas_limit - intrinsic - Op.GAS.gas_cost(fork)
+ depth1_observed = CALLER_GAS - Op.GAS.gas_cost(fork)
+ base = (
+ CALLER_GAS - entry_snapshot.gas_cost(fork) - depth2_call.gas_cost(fork)
+ )
+ assert 0 < base < ASK_GAS, "the 63/64 clamp must apply at depth 2"
+ forwarded = base - base // 64
+ depth2_observed = forwarded - Op.GAS.gas_cost(fork)
+
post = {
- sender: Account(nonce=1),
- target: Account(storage={8: 0x8D5B6, 9: 1}),
- addr: Account(storage={8: 0x2A1C7}),
- addr_2: Account(storage={8: 0x30D3E, 9: 1}),
+ entry: Account(
+ storage={
+ ENTRY_GAS_SLOT: entry_observed,
+ FLAG_SLOT: 1,
+ DEPTH2_GAS_SLOT: depth2_observed,
+ DEPTH1_GAS_SLOT: depth1_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/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/stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py
index 47aed815167..3a6aabd3bc2 100644
--- a/tests/ported_static/stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py
+++ b/tests/ported_static/stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py
@@ -1,18 +1,26 @@
"""
-Test_new_gas_price_for_codes_with_mem_expanding_calls.
+Verify the EIP-150 repriced code/account operations in one frame whose
+calls also expand memory: EXTCODESIZE, EXTCODECOPY, failing value
+CALL/CALLCODE (insufficient balance), DELEGATECALL that writes the
+caller's storage, a call to a nonexistent account, BALANCE, and a final
+raw gas reading that pins the whole execution.
Ported from:
state_tests/stMemExpandingEIP150Calls/NewGasPriceForCodesWithMemExpandingCallsFiller.json
+
+@manually-enhanced: Do not overwrite. The ported bytecode shape is kept,
+but the final gas reading, the mid-execution sender balance, and the
+copied code word are derived (opcode metadata, fee formula, the deployed
+bytes); the delegate's budget is derived so its store — state-priced
+under EIP-8037 — fits inside the grant; each failed value call returns
+its stipend.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -21,135 +29,216 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+EXTCODE_BYTES = bytes.fromhex(
+ "1122334455667788991011121314151617181920212223242526272829303132"
+)
+COPY_SIZE = 0x14
+DELEGATE_VALUE = 0x11
+# Budget for the calls whose outcome does not depend on it.
+FORWARDED_GAS = 0x7530
+# The ported calls' argument window, driving the memory expansion.
+MEM_OFFSET = 0xFF
+MEM_SIZE = 0xFF
+GAS_PRICE = 10
+INITIAL_BALANCE = 10**15
+
@pytest.mark.ported_from(
[
"state_tests/stMemExpandingEIP150Calls/NewGasPriceForCodesWithMemExpandingCallsFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_new_gas_price_for_codes_with_mem_expanding_calls(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_new_gas_price_for_codes_with_mem_expanding_calls."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = EOA(
- key=0x3956FC06BD55836ACDB92DA0E38A15F2E568C088022CF2278180477F3F7702A
+ """Measure repriced operations with memory-expanding call windows."""
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
+ code_target = pre.deploy_contract(code=EXTCODE_BYTES, balance=111)
+ delegate_store = Op.SSTORE(
+ key=0x64,
+ value=DELEGATE_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=DELEGATE_VALUE,
)
+ storage_writer = pre.deploy_contract(code=delegate_store)
+ absent = pre.nonexistent_account()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
+ # The delegate must succeed: with a zero reservoir its state-priced
+ # store is paid from the regular grant, so the budget is derived.
+ delegate_budget = delegate_store.gas_cost(fork) + 2_000
- pre[sender] = Account(balance=0xE8D4A5100000)
- # Source: hex
- # 0x1122334455667788991011121314151617181920212223242526272829303132
- addr = pre.deploy_contract( # noqa: F841
- code=bytes.fromhex(
- "1122334455667788991011121314151617181920212223242526272829303132"
- ),
- balance=111,
- nonce=0,
- address=Address(0x6B6AF3C6E1714081C8C3085ACBAC8C2B21FADF0B), # noqa: E501
- )
- # Source: hex
- # 0x6011606455
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x64, value=0x11),
- nonce=0,
- address=Address(0x7B8C83E74CC8DFADB03138C2743C70588ACE4222), # noqa: E501
- )
- # Source: hex
- # 0x733b600155601460006000733c60005160025560005460045560ff60ff60ff60ff600173617530f160055560ff60ff60ff60ff600173617530f260065560ff60ff60ff60ff73617530f460075560ff60ff60ff60ff6000731000000000000000000000000000000000000013617530f160085573316003555a600a55 # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=Op.EXTCODESIZE(address=addr))
- + Op.EXTCODECOPY(address=addr, dest_offset=0x0, offset=0x0, size=0x14)
- + Op.SSTORE(key=0x2, value=Op.MLOAD(offset=0x0))
- + Op.SSTORE(key=0x4, value=Op.SLOAD(key=0x0))
+ call_window = MEM_OFFSET + MEM_SIZE
+ body = (
+ Op.SSTORE(
+ key=0x1,
+ value=Op.EXTCODESIZE(address=code_target, address_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.EXTCODECOPY(
+ address=code_target,
+ dest_offset=0x0,
+ offset=0x0,
+ size=COPY_SIZE,
+ address_warm=True,
+ data_size=COPY_SIZE,
+ new_memory_size=0x20,
+ )
+ + Op.SSTORE(
+ key=0x2,
+ value=Op.MLOAD(offset=0x0),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.SSTORE(
+ key=0x4,
+ value=Op.SLOAD(key=0x0, key_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ Op.SSTORE(
key=0x5,
value=Op.CALL(
- gas=0x7530,
- address=addr_2,
+ gas=FORWARDED_GAS,
+ address=storage_writer,
value=0x1,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=False,
+ value_transfer=True,
+ account_new=False,
+ new_memory_size=call_window,
+ old_memory_size=0x20,
),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
)
+ Op.SSTORE(
key=0x6,
value=Op.CALLCODE(
- gas=0x7530,
- address=addr_2,
+ gas=FORWARDED_GAS,
+ address=storage_writer,
value=0x1,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=True,
+ value_transfer=True,
+ account_new=False,
+ new_memory_size=call_window,
+ old_memory_size=call_window,
),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
)
+ Op.SSTORE(
key=0x7,
value=Op.DELEGATECALL(
- gas=0x7530,
- address=addr_2,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ gas=delegate_budget,
+ address=storage_writer,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=True,
+ new_memory_size=call_window,
+ old_memory_size=call_window,
),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
+ Op.SSTORE(
key=0x8,
value=Op.CALL(
- gas=0x7530,
- address=0x1000000000000000000000000000000000000013,
+ gas=FORWARDED_GAS,
+ address=absent,
value=0x0,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
+ new_memory_size=call_window,
+ old_memory_size=call_window,
),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- + Op.SSTORE(key=0x3, value=Op.BALANCE(address=sender))
- + Op.SSTORE(key=0xA, value=Op.GAS),
+ + Op.SSTORE(
+ key=0x3,
+ value=Op.BALANCE(address=sender, address_warm=True),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ )
+ final_store = Op.SSTORE(
+ key=0xA,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ target = pre.deploy_contract(
+ code=body + final_store + Op.STOP,
storage={0: 18},
- nonce=0,
- address=Address(0x23A2EC54F5F8589778DA7C2199CAF3B179A24CB9), # noqa: E501
)
+ # Consumption before the final GAS read: the body's composite plus
+ # the delegate's work, minus the two returned stipends.
+ consumed = (
+ body.gas_cost(fork)
+ + delegate_store.gas_cost(fork)
+ - 2 * fork.gas_costs().CALL_STIPEND
+ )
+
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = intrinsic + consumed + final_store.gas_cost(fork) + 5_000
+
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=600000,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
+ copied_word = int.from_bytes(
+ EXTCODE_BYTES[:COPY_SIZE].ljust(0x20, b"\x00"), "big"
+ )
post = {
- addr: Account(balance=111),
target: Account(
storage={
- 0: 18,
- 1: 32,
- 2: 0x1122334455667788991011121314151617181920000000000000000000000000, # noqa: E501
- 3: 0xE8D4A4B47280,
- 4: 18,
- 7: 1,
- 8: 1,
- 10: 0x60AE9,
- 100: 17,
+ 0x0: 18,
+ 0x1: len(EXTCODE_BYTES),
+ 0x2: copied_word,
+ # Mid-execution balance: the full fee is charged upfront.
+ 0x3: INITIAL_BALANCE - gas_limit * GAS_PRICE,
+ 0x4: 18,
+ # Slots 5 and 6 stay zero: the value calls failed.
+ 0x7: 1,
+ 0x8: 1,
+ # Raw reading: everything left after the body's work and
+ # the GAS opcode itself.
+ 0xA: gas_limit - intrinsic - consumed - Op.GAS.gas_cost(fork),
+ 0x64: DELEGATE_VALUE,
},
),
- sender: Account(nonce=1),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stMemoryTest/test_oog.py b/tests/ported_static/stMemoryTest/test_oog.py
index 5efece6b89d..2a94bc58e56 100644
--- a/tests/ported_static/stMemoryTest/test_oog.py
+++ b/tests/ported_static/stMemoryTest/test_oog.py
@@ -312,6 +312,29 @@ def test_oog(
# nested CALL to a cold contract plus the copy; the reprice eats the
# slack, so add it back to that one budget.
cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600
+ # The CREATE/CREATE2 success budgets are derived: EIP-8037 adds the
+ # new-account state gas (~183k), far past the ported 0xFFFF budget.
+ create_budget = (
+ Op.CREATE(
+ value=0x0,
+ offset=0x10000,
+ size=0x20,
+ new_memory_size=0x10020,
+ init_code_size=0x20,
+ ).gas_cost(fork)
+ + 1_000
+ )
+ create2_budget = (
+ Op.CREATE2(
+ value=0x0,
+ offset=0x10000,
+ size=0x20,
+ salt=0x5A17,
+ new_memory_size=0x10020,
+ init_code_size=0x20,
+ ).gas_cost(fork)
+ + 1_000
+ )
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
contract_0 = Address(0x0000000000000000000000000000000000010020)
contract_1 = Address(0x0000000000000000000000000000000000010037)
@@ -787,9 +810,9 @@ def test_oog(
Bytes("1a8451e6") + Hash(0xA3) + Hash(0x39D0),
Bytes("1a8451e6") + Hash(0xA4) + Hash(0xFFFF),
Bytes("1a8451e6") + Hash(0xA4) + Hash(0x39D0),
- Bytes("1a8451e6") + Hash(0xF0) + Hash(0xFFFF),
+ Bytes("1a8451e6") + Hash(0xF0) + Hash(create_budget),
Bytes("1a8451e6") + Hash(0xF0) + Hash(0x7D00),
- Bytes("1a8451e6") + Hash(0xF5) + Hash(0xFFFF),
+ Bytes("1a8451e6") + Hash(0xF5) + Hash(create2_budget),
Bytes("1a8451e6") + Hash(0xF5) + Hash(0x7D00),
Bytes("1a8451e6") + Hash(0xF3) + Hash(0xFFFF),
Bytes("1a8451e6") + Hash(0xF3) + Hash(0x36B0),
diff --git a/tests/ported_static/stRefundTest/test_refund50_2.py b/tests/ported_static/stRefundTest/test_refund50_2.py
index 4d471f24074..94ed5341671 100644
--- a/tests/ported_static/stRefundTest/test_refund50_2.py
+++ b/tests/ported_static/stRefundTest/test_refund50_2.py
@@ -1,17 +1,21 @@
"""
-Test_refund50_2.
+Verify the EIP-3529 refund cap over five storage clears: the sender's
+final balance reflects the executed gas minus the capped refund.
Ported from:
state_tests/stRefundTest/refund50_2Filler.json
+
+@manually-enhanced: Do not overwrite. The sender's balance, the refund cap
+and the transaction budget all derive from the fork (`code.gas_cost` /
+`code.refund` composites), so EIP-8037's repriced stores and any future
+refund change are tracked instead of pinned.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,57 +24,67 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+INITIAL_BALANCE = 10**18
+GAS_PRICE = 10
+
@pytest.mark.ported_from(
["state_tests/stRefundTest/refund50_2Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("London")
def test_refund50_2(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_refund50_2."""
- coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE)
- 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=1000000,
+ """Five storage clears refund gas up to the EIP-3529 cap."""
+ code = (
+ Op.SSTORE(
+ key=0xA, value=0x1, key_warm=False, original_value=0, new_value=1
+ )
+ + Op.SSTORE(
+ key=0xB, value=0x1, key_warm=False, original_value=0, new_value=1
+ )
+ + Op.SSTORE(
+ key=0x1, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x2, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x3, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x4, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x5, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
)
-
- pre[coinbase] = Account(balance=0, nonce=1)
- # Source: lll
- # { [[ 10 ]] 1 [[ 11 ]] 1 [[ 1 ]] 0 [[ 2 ]] 0 [[ 3 ]] 0 [[ 4 ]] 0 [[ 5 ]] 0 } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0xA, value=0x1)
- + Op.SSTORE(key=0xB, value=0x1)
- + Op.SSTORE(key=0x1, value=0x0)
- + Op.SSTORE(key=0x2, value=0x0)
- + Op.SSTORE(key=0x3, value=0x0)
- + Op.SSTORE(key=0x4, value=0x0)
- + Op.SSTORE(key=0x5, value=0x0)
- + Op.STOP,
+ target = pre.deploy_contract(
+ code=code + Op.STOP,
storage={1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
- balance=0xDE0B6B3A7640000,
- nonce=0,
)
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ executed = intrinsic + code.gas_cost(fork)
+ gas_limit = executed + 5_000
+
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=100000,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
+ # EIP-3529 caps the refund at a fifth of the executed gas.
+ refund = min(code.refund(fork), executed // 5)
+ gas_used = executed - refund
+
post = {
- target: Account(storage={10: 1, 11: 1}),
- coinbase: Account(balance=0),
- sender: Account(balance=0x8D926C, nonce=1),
+ target: Account(storage={0xA: 1, 0xB: 1}),
+ sender: Account(balance=INITIAL_BALANCE - gas_used * GAS_PRICE),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRefundTest/test_refund50percent_cap.py b/tests/ported_static/stRefundTest/test_refund50percent_cap.py
index d92a991123c..ece04d36914 100644
--- a/tests/ported_static/stRefundTest/test_refund50percent_cap.py
+++ b/tests/ported_static/stRefundTest/test_refund50percent_cap.py
@@ -1,18 +1,21 @@
"""
-Test_refund50percent_cap.
+Verify the EIP-3529 refund cap over six storage clears: the sender's final
+balance reflects the executed gas minus the capped refund.
Ported from:
state_tests/stRefundTest/refund50percentCapFiller.json
+
+@manually-enhanced: Do not overwrite. The sender's balance, the refund cap
+and the transaction budget all derive from the fork (`code.gas_cost` /
+`code.refund` composites), so EIP-8037's repriced stores and any future
+refund change are tracked instead of pinned.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -21,69 +24,84 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CONTRACT_BALANCE = 0xDE0B6B3A7640000
+INITIAL_BALANCE = 10**18
+GAS_PRICE = 10
+
@pytest.mark.ported_from(
["state_tests/stRefundTest/refund50percentCapFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("London")
def test_refund50percent_cap(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_refund50percent_cap."""
- coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE)
- sender = EOA(
- key=0xDC4EFA209AECDD4C2D5201A419EA27506151B4EC687F14A613229E310932491B
- )
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ """Six storage clears refund gas up to the EIP-3529 cap."""
+ code = (
+ Op.POP(Op.SLOAD(key=0x1, key_warm=False))
+ + Op.POP(Op.SLOAD(key=0x2, key_warm=False))
+ + Op.SSTORE(
+ key=0xA,
+ value=Op.EXP(0x2, 0xFF, exponent=0xFF),
+ key_warm=False,
+ original_value=0,
+ new_value=2**255,
+ )
+ + Op.SSTORE(
+ key=0xB,
+ value=Op.BALANCE(address=Op.ADDRESS, address_warm=True),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.SSTORE(
+ key=0x1, value=0x0, key_warm=True, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x2, value=0x0, key_warm=True, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x3, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x4, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x5, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x6, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
)
-
- pre[coinbase] = Account(balance=0, nonce=1)
- pre[sender] = Account(balance=0x989680)
- # Source: lll
- # { @@1 @@2 [[ 10 ]] (EXP 2 0xff) [[ 11 ]] (BALANCE (ADDRESS)) [[ 1 ]] 0 [[ 2 ]] 0 [[ 3 ]] 0 [[ 4 ]] 0 [[ 5 ]] 0 [[ 6 ]] 0 } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.POP(Op.SLOAD(key=0x1))
- + Op.POP(Op.SLOAD(key=0x2))
- + Op.SSTORE(key=0xA, value=Op.EXP(0x2, 0xFF))
- + Op.SSTORE(key=0xB, value=Op.BALANCE(address=Op.ADDRESS))
- + Op.SSTORE(key=0x1, value=0x0)
- + Op.SSTORE(key=0x2, value=0x0)
- + Op.SSTORE(key=0x3, value=0x0)
- + Op.SSTORE(key=0x4, value=0x0)
- + Op.SSTORE(key=0x5, value=0x0)
- + Op.SSTORE(key=0x6, value=0x0)
- + Op.STOP,
+ target = pre.deploy_contract(
+ code=code + Op.STOP,
storage={1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1},
- balance=0xDE0B6B3A7640000,
- nonce=0,
- address=Address(0xEF67F354C8505E1056889970C3D9B5E0FE65D1E2), # noqa: E501
+ balance=CONTRACT_BALANCE,
)
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ executed = intrinsic + code.gas_cost(fork)
+ gas_limit = executed + 5_000
+
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=100000,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
+ # EIP-3529 caps the refund at a fifth of the executed gas.
+ refund = min(code.refund(fork), executed // 5)
+ gas_used = executed - refund
+
post = {
target: Account(
- storage={
- 10: 0x8000000000000000000000000000000000000000000000000000000000000000, # noqa: E501
- 11: 0xDE0B6B3A7640000,
- },
+ storage={0xA: 2**255, 0xB: CONTRACT_BALANCE},
),
- coinbase: Account(balance=0),
- sender: Account(balance=0x8CF0A0),
+ sender: Account(balance=INITIAL_BALANCE - gas_used * GAS_PRICE),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRefundTest/test_refund600.py b/tests/ported_static/stRefundTest/test_refund600.py
index cce448a08c4..3f5def95619 100644
--- a/tests/ported_static/stRefundTest/test_refund600.py
+++ b/tests/ported_static/stRefundTest/test_refund600.py
@@ -1,18 +1,21 @@
"""
-Test_refund600.
+Verify the EIP-3529 refund cap over six storage clears: the sender's final
+balance reflects the executed gas minus the capped refund.
Ported from:
state_tests/stRefundTest/refund600Filler.json
+
+@manually-enhanced: Do not overwrite. The sender's balance, the refund cap
+and the transaction budget all derive from the fork (`code.gas_cost` /
+`code.refund` composites), so EIP-8037's repriced stores and any future
+refund change are tracked instead of pinned.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -21,64 +24,85 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CONTRACT_BALANCE = 0xDE0B6B3A7640000
+INITIAL_BALANCE = 10**18
+GAS_PRICE = 10
+
@pytest.mark.ported_from(
["state_tests/stRefundTest/refund600Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("London")
def test_refund600(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_refund600."""
- coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE)
- sender = EOA(
- key=0xDC4EFA209AECDD4C2D5201A419EA27506151B4EC687F14A613229E310932491B
- )
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ """Six storage clears refund gas up to the EIP-3529 cap."""
+ code = (
+ Op.POP(Op.SLOAD(key=0x1, key_warm=False))
+ + Op.POP(Op.SLOAD(key=0x2, key_warm=False))
+ # EXP(2, 0xFFFF) wraps to 0 mod 2^256, so this store is a no-op.
+ + Op.SSTORE(
+ key=0xA,
+ value=Op.EXP(0x2, 0xFFFF, exponent=0xFFFF),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
+ )
+ + Op.SSTORE(
+ key=0xB,
+ value=Op.BALANCE(address=Op.ADDRESS, address_warm=True),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.SSTORE(
+ key=0x1, value=0x0, key_warm=True, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x2, value=0x0, key_warm=True, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x3, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x4, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x5, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x6, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
)
-
- pre[coinbase] = Account(balance=0, nonce=1)
- pre[sender] = Account(balance=0x989680)
- # Source: lll
- # { @@1 @@2 [[ 10 ]] (EXP 2 0xffff) [[ 11 ]] (BALANCE (ADDRESS)) [[ 1 ]] 0 [[ 2 ]] 0 [[ 3 ]] 0 [[ 4 ]] 0 [[ 5 ]] 0 [[ 6 ]] 0 } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.POP(Op.SLOAD(key=0x1))
- + Op.POP(Op.SLOAD(key=0x2))
- + Op.SSTORE(key=0xA, value=Op.EXP(0x2, 0xFFFF))
- + Op.SSTORE(key=0xB, value=Op.BALANCE(address=Op.ADDRESS))
- + Op.SSTORE(key=0x1, value=0x0)
- + Op.SSTORE(key=0x2, value=0x0)
- + Op.SSTORE(key=0x3, value=0x0)
- + Op.SSTORE(key=0x4, value=0x0)
- + Op.SSTORE(key=0x5, value=0x0)
- + Op.SSTORE(key=0x6, value=0x0)
- + Op.STOP,
+ target = pre.deploy_contract(
+ code=code + Op.STOP,
storage={1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1},
- balance=0xDE0B6B3A7640000,
- nonce=0,
- address=Address(0xC09923E2275E4EE7822A1FEB5EEE1C18143575C7), # noqa: E501
+ balance=CONTRACT_BALANCE,
)
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ executed = intrinsic + code.gas_cost(fork)
+ gas_limit = executed + 5_000
+
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=100000,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
+ # EIP-3529 caps the refund at a fifth of the executed gas.
+ refund = min(code.refund(fork), executed // 5)
+ gas_used = executed - refund
+
post = {
- target: Account(storage={11: 0xDE0B6B3A7640000}),
- coinbase: Account(balance=0),
- sender: Account(balance=0x8F5CF0),
+ target: Account(
+ storage={0xA: 0, 0xB: CONTRACT_BALANCE},
+ ),
+ sender: Account(balance=INITIAL_BALANCE - gas_used * GAS_PRICE),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRefundTest/test_refund_call_a.py b/tests/ported_static/stRefundTest/test_refund_call_a.py
index bc745f449be..c42de668a97 100644
--- a/tests/ported_static/stRefundTest/test_refund_call_a.py
+++ b/tests/ported_static/stRefundTest/test_refund_call_a.py
@@ -1,17 +1,21 @@
"""
-Test_refund_call_a.
+Verify a storage-clear refund earned inside a sub-call: the sender's final
+balance reflects the executed gas minus the capped refund.
Ported from:
state_tests/stRefundTest/refund_CallAFiller.json
+
+@manually-enhanced: Do not overwrite. The sub-call forwards all gas
+instead of a schedule-sized constant, and the sender's balance, refund cap
+and budget derive from the fork (`code.gas_cost` / `code.refund`
+composites), so EIP-8037's repriced stores are tracked instead of pinned.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,72 +24,65 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+INITIAL_BALANCE = 10**18
+GAS_PRICE = 10
+
@pytest.mark.ported_from(
["state_tests/stRefundTest/refund_CallAFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("London")
def test_refund_call_a(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_refund_call_a."""
- coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE)
- sender = pre.fund_eoa(amount=0x1312D00)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ """A callee's storage clear refunds gas up to the EIP-3529 cap."""
+ callee_code = Op.SSTORE(
+ key=0x1, value=0x0, key_warm=False, original_value=1, new_value=0
)
-
- pre[coinbase] = Account(balance=0, nonce=1)
- # Source: lll
- # { [[ 1 ]] 0 }
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x0) + Op.STOP,
+ callee = pre.deploy_contract(
+ code=callee_code + Op.STOP,
storage={1: 1},
- balance=0xDE0B6B3A7640000,
- nonce=0,
)
- # Source: lll
- # { [[ 0 ]] (CALL 5500 0 0 0 0 0 )} # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(
- key=0x0,
- value=Op.CALL(
- gas=0x157C,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.STOP,
+
+ # The caller's own slot 1 stays set: the callee clears its own storage.
+ caller_code = Op.SSTORE(
+ key=0x0,
+ value=Op.CALL(address=callee, address_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ caller = pre.deploy_contract(
+ code=caller_code + Op.STOP,
storage={1: 1},
- balance=0xDE0B6B3A7640000,
- nonce=0,
)
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ executed = (
+ intrinsic + caller_code.gas_cost(fork) + callee_code.gas_cost(fork)
+ )
+ gas_limit = executed + 5_000
+
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
tx = Transaction(
sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=200000,
- value=10,
+ to=caller,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
+ )
+
+ # EIP-3529 caps the refund at a fifth of the executed gas.
+ refund = min(
+ caller_code.refund(fork) + callee_code.refund(fork), executed // 5
)
+ gas_used = executed - refund
post = {
- target: Account(storage={0: 1, 1: 1}, balance=0xDE0B6B3A764000A),
- coinbase: Account(balance=0),
- sender: Account(balance=0x12A2AD2, nonce=1),
- addr: Account(storage={}),
+ caller: Account(storage={0: 1, 1: 1}),
+ callee: Account(storage={}),
+ sender: Account(balance=INITIAL_BALANCE - gas_used * GAS_PRICE),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRefundTest/test_refund_suicide50procent_cap.py b/tests/ported_static/stRefundTest/test_refund_suicide50procent_cap.py
index 4083b826958..812a0cb1ed4 100644
--- a/tests/ported_static/stRefundTest/test_refund_suicide50procent_cap.py
+++ b/tests/ported_static/stRefundTest/test_refund_suicide50procent_cap.py
@@ -1,158 +1,181 @@
"""
-Test_refund_suicide50procent_cap.
+Verify the EIP-3529 refund cap when eight storage clears surround a
+gas-limited call to a self-destructing contract: the stored gas delta and
+the sender's final balance track the executed gas minus the capped refund,
+for both a starved and a fully funded sub-call.
Ported from:
state_tests/stRefundTest/refundSuicide50procentCapFiller.json
+
+@manually-enhanced: Do not overwrite. The sub-call grant, the stored gas
+delta, the refund cap and the budget all derive from fork composites; the
+destructor self-destructs to CALLER so every address is dynamic; the post
+branches on EIP-6780 for the destructor's survival.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Environment,
+ Fork,
Hash,
StateTestFiller,
Transaction,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+TARGET_BALANCE = 0xDE0B6B3A7640000
+DESTRUCTOR_BALANCE = 0xDE0B6B3A7640000
+INITIAL_BALANCE = 10**18
+GAS_PRICE = 10
+FLAG_SLOT = 0xA
+RESULT_SLOT = 0xB
+GAS_SLOT = 0x17
+SNAPSHOT_OFFSET = 0x16
+MEMORY_SIZE = SNAPSHOT_OFFSET + 32
+GRANT_MARGIN = 1_000
+
@pytest.mark.ported_from(
["state_tests/stRefundTest/refundSuicide50procentCapFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("London")
@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="d0",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1",
- ),
- ],
+ "call_succeeds",
+ [False, True],
+ ids=["starved_grant", "full_grant"],
)
-@pytest.mark.pre_alloc_mutable
def test_refund_suicide50procent_cap(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ call_succeeds: bool,
) -> None:
- """Test_refund_suicide50procent_cap."""
- coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE)
- sender = pre.fund_eoa(amount=0x3B9ACA00)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=100000000,
+ """Storage clears around a self-destruct call refund up to the cap."""
+ destructor_code = Op.SELFDESTRUCT(
+ address=Op.CALLER, address_warm=True, account_new=False
+ )
+ destructor = pre.deploy_contract(
+ code=destructor_code,
+ balance=DESTRUCTOR_BALANCE,
)
- pre[coinbase] = Account(balance=0, nonce=1)
- # Source: lll
- # { [22] (GAS) [[ 10 ]] 1 [[ 11 ]] (CALL (CALLDATALOAD 0) 0 0 0 0 0 ) [[ 1 ]] 0 [[ 2 ]] 0 [[ 3 ]] 0 [[ 4 ]] 0 [[ 5 ]] 0 [[ 6 ]] 0 [[ 7 ]] 0 [[ 8 ]] 0 [[ 23 ]] (SUB @22 (GAS)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x16, value=Op.GAS)
- + Op.SSTORE(key=0xA, value=0x1)
- + Op.SSTORE(
- key=0xB,
- value=Op.CALL(
- gas=Op.CALLDATALOAD(offset=0x0),
- address=0x4FF65047CE9C85F968689E4369C10003026A41A9,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x1, value=0x0)
- + Op.SSTORE(key=0x2, value=0x0)
- + Op.SSTORE(key=0x3, value=0x0)
- + Op.SSTORE(key=0x4, value=0x0)
- + Op.SSTORE(key=0x5, value=0x0)
- + Op.SSTORE(key=0x6, value=0x0)
- + Op.SSTORE(key=0x7, value=0x0)
- + Op.SSTORE(key=0x8, value=0x0)
- + Op.SSTORE(key=0x17, value=Op.SUB(Op.MLOAD(offset=0x16), Op.GAS))
- + Op.STOP,
- storage={1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1},
- balance=0xDE0B6B3A7640000,
- nonce=0,
- address=Address(0xA6CC2CA5611255D50118601AA8ECE6F124FC4C45), # noqa: E501
+ # The grant either covers the destructor completely or falls one gas
+ # short, so the sub-call forfeits its whole grant.
+ destructor_cost = destructor_code.gas_cost(fork)
+ if call_succeeds:
+ grant = destructor_cost + GRANT_MARGIN
+ inner_consumed = destructor_cost
+ else:
+ grant = destructor_cost - 1
+ inner_consumed = grant
+ call_result = 1 if call_succeeds else 0
+
+ # First GAS read: the delta window opens after the GAS opcode itself.
+ head = Op.MSTORE(
+ offset=SNAPSHOT_OFFSET, value=Op.GAS, new_memory_size=MEMORY_SIZE
+ )
+ body = Op.SSTORE(
+ key=FLAG_SLOT,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ ) + Op.SSTORE(
+ key=RESULT_SLOT,
+ value=Op.CALL(
+ gas=Op.CALLDATALOAD(offset=0x0),
+ address=destructor,
+ address_warm=False,
+ ),
+ key_warm=False,
+ original_value=0,
+ new_value=call_result,
)
- # Source: lll
- # { (SELFDESTRUCT ) } # noqa: E501
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SELFDESTRUCT(
- address=0xA6CC2CA5611255D50118601AA8ECE6F124FC4C45
+ for slot in range(1, 9):
+ body += Op.SSTORE(
+ key=slot,
+ value=0x0,
+ key_warm=False,
+ original_value=1,
+ new_value=0,
)
- + Op.STOP,
- balance=0xDE0B6B3A7640000,
- nonce=0,
- address=Address(0x4FF65047CE9C85F968689E4369C10003026A41A9), # noqa: E501
+ # Second GAS read closes the window; the head's own GAS cost stands in
+ # for it in the derived delta (both GAS reads cost the same).
+ # new_value is a placeholder: an SSTORE's cost depends only on the
+ # zero/non-zero transition, not the stored magnitude.
+ tail = Op.SSTORE(
+ key=GAS_SLOT,
+ value=Op.SUB(
+ Op.MLOAD(
+ offset=SNAPSHOT_OFFSET,
+ new_memory_size=MEMORY_SIZE,
+ old_memory_size=MEMORY_SIZE,
+ ),
+ Op.GAS,
+ ),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ target = pre.deploy_contract(
+ code=head + body + tail + Op.STOP,
+ storage=dict.fromkeys(range(1, 9), 1),
+ balance=TARGET_BALANCE,
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 0, "gas": -1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- target: Account(
- storage={10: 1, 11: 0, 23: 0x107A7},
- balance=0xDE0B6B3A7640000,
- ),
- sender: Account(nonce=1),
- },
- },
- {
- "indexes": {"data": 1, "gas": -1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- target: Account(
- storage={10: 1, 11: 1, 23: 0x166FA},
- balance=0x1BC16D674EC80000,
- ),
- sender: Account(nonce=1),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ gas_delta = head.gas_cost(fork) + body.gas_cost(fork) + inner_consumed
- tx_data = [
- Hash(0x1F4),
- Hash(0x10000),
- ]
- tx_gas = [10000000]
+ data = Hash(grant)
+ # The refund cap is a fifth of the gas actually deducted before
+ # execution, which excludes the EIP-7623 calldata floor.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()(
+ calldata=data, return_cost_deducted_prior_execution=True
+ )
+ executed = intrinsic + gas_delta + tail.gas_cost(fork)
+ gas_limit = executed + 5_000
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
tx = Transaction(
sender=sender,
to=target,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- error=_exc,
+ data=data,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ # EIP-3529 caps the refund at a fifth of the executed gas.
+ total_refund = body.refund(fork) + (
+ destructor_code.refund(fork) if call_succeeds else 0
+ )
+ refund = min(total_refund, executed // 5)
+ gas_used = executed - refund
+
+ post = {
+ target: Account(
+ storage={
+ FLAG_SLOT: 1,
+ RESULT_SLOT: call_result,
+ GAS_SLOT: gas_delta,
+ },
+ balance=TARGET_BALANCE
+ + (DESTRUCTOR_BALANCE if call_succeeds else 0),
+ ),
+ # EIP-6780: a pre-existing contract is no longer deleted, only
+ # its balance is transferred.
+ destructor: (
+ (
+ Account(balance=0)
+ if fork.is_eip_enabled(6780)
+ else Account.NONEXISTENT
+ )
+ if call_succeeds
+ else Account(balance=DESTRUCTOR_BALANCE, storage={})
+ ),
+ sender: Account(balance=INITIAL_BALANCE - gas_used * GAS_PRICE),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRefundTest/test_refund_tx_to_suicide.py b/tests/ported_static/stRefundTest/test_refund_tx_to_suicide.py
index ab754657c75..0d9d5be2f6b 100644
--- a/tests/ported_static/stRefundTest/test_refund_tx_to_suicide.py
+++ b/tests/ported_static/stRefundTest/test_refund_tx_to_suicide.py
@@ -1,18 +1,21 @@
"""
-Test_refund_tx_to_suicide.
+Verify a transaction into a self-destructing contract: the balance
+(including the transaction value) moves to the beneficiary and, post
+EIP-3529, no self-destruct refund is granted.
Ported from:
state_tests/stRefundTest/refund_TxToSuicideFiller.json
+
+@manually-enhanced: Do not overwrite. Beneficiary and budget are derived
+(nonexistent account, `code.gas_cost` composite) and the post branches on
+EIP-6780 (pre-Cancun the contract is deleted, after it persists).
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -21,59 +24,61 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CONTRACT_BALANCE = 0xDE0B6B3A7640000
+INITIAL_BALANCE = 10**18
+GAS_PRICE = 10
+TX_VALUE = 10
+
@pytest.mark.ported_from(
["state_tests/stRefundTest/refund_TxToSuicideFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("London")
def test_refund_tx_to_suicide(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_refund_tx_to_suicide."""
- coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE)
- sender = EOA(
- key=0xA2333EEF5630066B928DEA5FD85A239F511B5B067D1441EE7AC290D0122B917B
- )
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ """Self-destruct moves the balance and grants no refund."""
+ beneficiary = pre.nonexistent_account()
+ code = Op.SELFDESTRUCT(
+ address=beneficiary, address_warm=False, account_new=True
)
-
- pre[coinbase] = Account(balance=0, nonce=1)
- pre[sender] = Account(balance=0x5F5E100)
- # Source: lll
- # { (SELFDESTRUCT 0x095e7baea6a6c7c4c2dfeb977efac326af552d87) }
- target = pre.deploy_contract( # noqa: F841
- code=Op.SELFDESTRUCT(address=0x95E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87)
- + Op.STOP,
+ target = pre.deploy_contract(
+ code=code,
storage={1: 1},
- balance=0xDE0B6B3A7640000,
- nonce=0,
- address=Address(0x2BC33A472F0FBA1E30BF2317D07910367908C7F6), # noqa: E501
+ balance=CONTRACT_BALANCE,
)
+ intrinsic = fork.transaction_intrinsic_cost_calculator()(sends_value=True)
+ executed = intrinsic + code.gas_cost(fork)
+ gas_limit = executed + 5_000
+
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=61003,
- value=10,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
+ value=TX_VALUE,
)
+ # EIP-3529 removed the self-destruct refund entirely.
+ refund = min(code.refund(fork), executed // 5)
+ gas_used = executed - refund
+
post = {
- Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87): Account(
- storage={}, balance=0xDE0B6B3A764000A
+ beneficiary: Account(balance=CONTRACT_BALANCE + TX_VALUE),
+ # EIP-6780: a pre-existing contract is no longer deleted, only
+ # its balance is transferred.
+ target: (
+ Account(storage={1: 1}, balance=0)
+ if fork.is_eip_enabled(6780)
+ else Account.NONEXISTENT
+ ),
+ sender: Account(
+ balance=INITIAL_BALANCE - TX_VALUE - gas_used * GAS_PRICE
),
- coinbase: Account(balance=0),
- sender: Account(balance=0x5EDB318, nonce=1),
- target: Account(storage={1: 1}, balance=0, nonce=0),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRevertTest/test_loop_calls_depth_then_revert.py b/tests/ported_static/stRevertTest/test_loop_calls_depth_then_revert.py
index fc4b0089a75..f94a727fd09 100644
--- a/tests/ported_static/stRevertTest/test_loop_calls_depth_then_revert.py
+++ b/tests/ported_static/stRevertTest/test_loop_calls_depth_then_revert.py
@@ -1,8 +1,16 @@
"""
-Test_loop_calls_depth_then_revert.
+Verify a mutual CALL recursion that terminates by gas exhaustion: two
+contracts increment their own counters and call each other until the
+EIP-150 63/64 attenuation starves the deepest frame, whose failed store
+reverts alone while every ancestor's increment persists.
Ported from:
state_tests/stRevertTest/LoopCallsDepthThenRevertFiller.json
+
+@manually-enhanced: Do not overwrite. The reached depth is bounded by the
+fixed gas budget (not the 1024 depth limit), so the frame counts are
+pinned per gas-schedule era: EIP-8037/EIP-2780 shift the attenuation on
+Amsterdam. One address literal remains to break the reference cycle.
"""
import pytest
@@ -10,16 +18,31 @@
Account,
Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
-from execution_testing.vm import Op
+from execution_testing.vm import Bytecode, Op
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# The recursion depth is a function of this budget via EIP-150's 63/64
+# forwarding rule; changing it changes the pinned frame counts.
+GAS_BUDGET = 10_000_000
+# Fixed address for the second contract: it must be known before the
+# first contract's code (which calls it) can be built.
+PONG_ADDRESS = Address(0x80D46FA47B41AB46A227915AE4F63559C0D4DFE2)
+
+
+def loop_code(partner: Address) -> Bytecode:
+ """Increment the own counter, then recurse into the partner."""
+ return (
+ Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
+ + Op.CALL(address=partner)
+ + Op.STOP
+ )
+
@pytest.mark.ported_from(
["state_tests/stRevertTest/LoopCallsDepthThenRevertFiller.json"],
@@ -29,65 +52,30 @@
def test_loop_calls_depth_then_revert(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_loop_calls_depth_then_revert."""
- 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=100000000,
- )
-
- # Source: lll
- # { [[0]] (+ (SLOAD 0) 1) (CALL (GAS) 0 0 0 0 0) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.CALL(
- gas=Op.GAS,
- address=0x80D46FA47B41AB46A227915AE4F63559C0D4DFE2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP,
- nonce=0,
- address=Address(0xF59FD1C021541704A4A52C067454304566717666), # noqa: E501
- )
- # Source: lll
- # { [[0]] (+ (SLOAD 0) 1) (CALL (GAS) 0 0 0 0 0) } # noqa: E501
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.CALL(
- gas=Op.GAS,
- address=0xF59FD1C021541704A4A52C067454304566717666,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP,
- nonce=0,
- address=Address(0x80D46FA47B41AB46A227915AE4F63559C0D4DFE2), # noqa: E501
- )
+ """Only the gas-starved deepest frame of a call loop reverts."""
+ ping = pre.deploy_contract(code=loop_code(PONG_ADDRESS))
+ pong = pre.deploy_contract(code=loop_code(ping), address=PONG_ADDRESS)
+ sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=10000000,
+ to=ping,
+ gas_limit=GAS_BUDGET,
)
+ # Completed frames under GAS_BUDGET, pinned per gas-schedule era:
+ # EIP-8037's state gas for the two first stores trims one frame off
+ # the depth the 63/64 attenuation allows.
+ if fork.is_eip_enabled(8037):
+ ping_frames, pong_frames = 192, 192
+ else:
+ ping_frames, pong_frames = 193, 192
+
post = {
- target: Account(storage={0: 193}),
- addr: Account(storage={0: 192}),
+ ping: Account(storage={0: ping_frames}),
+ pong: Account(storage={0: pong_frames}),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRevertTest/test_loop_delegate_calls_depth_then_revert.py b/tests/ported_static/stRevertTest/test_loop_delegate_calls_depth_then_revert.py
index 1be3fb70d90..9267e1e2355 100644
--- a/tests/ported_static/stRevertTest/test_loop_delegate_calls_depth_then_revert.py
+++ b/tests/ported_static/stRevertTest/test_loop_delegate_calls_depth_then_revert.py
@@ -1,8 +1,17 @@
"""
-Test_loop_delegate_calls_depth_then_revert.
+Verify a mutual DELEGATECALL recursion that terminates by gas
+exhaustion: both contracts' code increments the entry contract's counter
+(the storage context never changes) until the EIP-150 63/64 attenuation
+starves the deepest frame, whose failed store reverts alone while every
+ancestor's increment persists.
Ported from:
state_tests/stRevertTest/LoopDelegateCallsDepthThenRevertFiller.json
+
+@manually-enhanced: Do not overwrite. The reached depth is bounded by the
+fixed gas budget (not the 1024 depth limit), so the frame count is
+pinned per gas-schedule era: EIP-8037/EIP-2780 shift the attenuation on
+Amsterdam. One address literal remains to break the reference cycle.
"""
import pytest
@@ -10,16 +19,31 @@
Account,
Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
-from execution_testing.vm import Op
+from execution_testing.vm import Bytecode, Op
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# The recursion depth is a function of this budget via EIP-150's 63/64
+# forwarding rule; changing it changes the pinned frame count.
+GAS_BUDGET = 10_000_000
+# Fixed address for the second contract: it must be known before the
+# first contract's code (which delegate-calls it) can be built.
+PONG_ADDRESS = Address(0xF798CB78490DA31DFACDCD1F2B3FB1948BB2B228)
+
+
+def loop_code(partner: Address) -> Bytecode:
+ """Increment the context counter, then recurse into the partner."""
+ return (
+ Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
+ + Op.DELEGATECALL(address=partner)
+ + Op.STOP
+ )
+
@pytest.mark.ported_from(
["state_tests/stRevertTest/LoopDelegateCallsDepthThenRevertFiller.json"],
@@ -29,63 +53,29 @@
def test_loop_delegate_calls_depth_then_revert(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_loop_delegate_calls_depth_then_revert."""
- 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=100000000,
- )
-
- # Source: lll
- # { [[0]] (+ (SLOAD 0) 1) (DELEGATECALL (GAS) 0 0 0 0) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.DELEGATECALL(
- gas=Op.GAS,
- address=0xF798CB78490DA31DFACDCD1F2B3FB1948BB2B228,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP,
- nonce=0,
- address=Address(0xB0923C4A632DE291FCDAC653E6C6CC2B4E4CDFA8), # noqa: E501
- )
- # Source: lll
- # { [[0]] (+ (SLOAD 0) 1) (DELEGATECALL (GAS) 0 0 0 0) } # noqa: E501
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.DELEGATECALL(
- gas=Op.GAS,
- address=0xB0923C4A632DE291FCDAC653E6C6CC2B4E4CDFA8,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP,
- nonce=0,
- address=Address(0xF798CB78490DA31DFACDCD1F2B3FB1948BB2B228), # noqa: E501
- )
+ """Only the gas-starved deepest frame of a delegate loop reverts."""
+ ping = pre.deploy_contract(code=loop_code(PONG_ADDRESS))
+ pong = pre.deploy_contract(code=loop_code(ping), address=PONG_ADDRESS)
+ sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=10000000,
+ to=ping,
+ gas_limit=GAS_BUDGET,
)
+ # Completed frames under GAS_BUDGET, pinned per gas-schedule era:
+ # every frame increments the entry contract's counter because
+ # DELEGATECALL keeps the storage context; the partner's own storage
+ # is never touched. EIP-8037's state gas for the first store shifts
+ # the depth the 63/64 attenuation allows.
+ frames = 385 if fork.is_eip_enabled(8037) else 386
+
post = {
- target: Account(storage={0: 386}),
- addr: Account(storage={}),
+ ping: Account(storage={0: frames}),
+ pong: 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/stRevertTest/test_revert_depth_create_address_collision.py b/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py
index 11c3aaa568c..b123500878c 100644
--- a/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py
+++ b/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py
@@ -1,222 +1,183 @@
"""
-Test_revert_depth_create_address_collision.
+Verify revert propagation around a nested CREATE whose target address
+collides with an existing contract - its own caller: the creating frame
+always fails and the collided account survives untouched, while the
+caller completes only when its budget covers the forfeited grant.
Ported from:
state_tests/stRevertTest/RevertDepthCreateAddressCollisionFiller.json
+
+@manually-enhanced: Do not overwrite. Restores the collision the machine
+port lost: the caller is deployed at the creator's CREATE address (as in
+the original filler). Grants and budgets derive from fork composites and
+the collided account's code, nonce and storage are pinned in every arm.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
Hash,
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"
+# Gas left in the creator frame when its CREATE executes: the collision
+# consumes it, so the following store can never be paid.
+BURN_MARGIN = 5_000
+# Head room on top of a derived budget.
+BUDGET_MARGIN = 5_000
+# Gas left at the caller's call site in the starved arm: too little for
+# any frame to complete.
+STARVE_MARGIN = 1_000
+
@pytest.mark.ported_from(
["state_tests/stRevertTest/RevertDepthCreateAddressCollisionFiller.json"],
)
@pytest.mark.valid_from("Cancun")
@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="d0-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="d0-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="d0-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="d0-g1-v1",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1-g0-v0",
- ),
- pytest.param(
- 1,
- 0,
- 1,
- id="d1-g0-v1",
- ),
- pytest.param(
- 1,
- 1,
- 0,
- id="d1-g1-v0",
- ),
- pytest.param(
- 1,
- 1,
- 1,
- id="d1-g1-v1",
- ),
- ],
+ "oversized_ask",
+ [False, True],
+ ids=["modest_ask", "oversized_ask"],
)
+@pytest.mark.parametrize(
+ "ample_budget",
+ [False, True],
+ ids=["starved", "ample"],
+)
+@pytest.mark.parametrize("tx_value", [1, 0], ids=["v1", "v0"])
@pytest.mark.pre_alloc_mutable
def test_revert_depth_create_address_collision(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ oversized_ask: bool,
+ ample_budget: bool,
+ tx_value: int,
) -> None:
- """Test_revert_depth_create_address_collision."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = EOA(
- key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52
+ """A CREATE address collision leaves the collided account intact."""
+ creator_store = Op.SSTORE(
+ key=0x2, value=0x8, key_warm=False, original_value=0, new_value=8
)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
+ create_code = Op.POP(
+ Op.CREATE(
+ value=0x0,
+ offset=0x0,
+ size=0x0,
+ init_code_size=0,
+ new_memory_size=0,
+ )
)
-
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[2]] 8 (CREATE 0 0 0) [[3]] 12}
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x8)
- + Op.POP(Op.CREATE(value=0x0, offset=0x0, size=0x0))
- + Op.SSTORE(key=0x3, value=0xC)
- + Op.STOP,
- nonce=0,
- address=Address(0xB1B49241A4ECF7860872E686090781C906B1B437), # noqa: E501
+ creator_tail = Op.SSTORE(
+ key=0x3, value=0xC, key_warm=False, original_value=0, new_value=0xC
)
- # Source: lll
- # { [[0]] 1 [[1]] (CALL (CALLDATALOAD 0) 0 0 0 0 0) [[4]] 12 } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=0x1)
- + Op.SSTORE(
- key=0x1,
- value=Op.CALL(
- gas=Op.CALLDATALOAD(offset=0x0),
- address=0xB1B49241A4ECF7860872E686090781C906B1B437,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x4, value=0xC)
- + Op.STOP,
- balance=5,
- nonce=54,
- address=Address(0x97E33A176B7C8D61B356D1C170AC2119D28867DF), # noqa: E501
+ creator = pre.deploy_contract(
+ code=creator_store + create_code + creator_tail + Op.STOP
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- target: Account(
- storage={},
- code=bytes.fromhex(
- "60016000556000600060006000600073b1b49241a4ecf7860872e686090781c906b1b437600035f1600155600c60045500" # noqa: E501
- ),
- nonce=54,
- ),
- addr: Account(storage={}),
- },
- },
- {
- "indexes": {"data": 0, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- target: Account(
- storage={0: 1, 4: 12},
- code=bytes.fromhex(
- "60016000556000600060006000600073b1b49241a4ecf7860872e686090781c906b1b437600035f1600155600c60045500" # noqa: E501
- ),
- nonce=54,
- ),
- addr: Account(storage={}),
- },
- },
- {
- "indexes": {"data": 1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- target: Account(
- storage={},
- code=bytes.fromhex(
- "60016000556000600060006000600073b1b49241a4ecf7860872e686090781c906b1b437600035f1600155600c60045500" # noqa: E501
- ),
- balance=5,
- nonce=54,
- ),
- addr: Account(storage={}),
- },
- },
- {
- "indexes": {"data": 0, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- target: Account(
- storage={},
- code=bytes.fromhex(
- "60016000556000600060006000600073b1b49241a4ecf7860872e686090781c906b1b437600035f1600155600c60045500" # noqa: E501
- ),
- nonce=54,
- ),
- addr: Account(storage={}),
- },
- },
- ]
-
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ head_store = Op.SSTORE(
+ key=0x0, value=0x1, key_warm=False, original_value=0, new_value=1
+ )
+ call_code = Op.CALL(
+ gas=Op.CALLDATALOAD(offset=0x0), address=creator, address_warm=False
+ )
+ # The creator's frame always fails, so the call result is always 0.
+ call_store = Op.SSTORE(
+ key=0x1,
+ value=call_code,
+ key_warm=False,
+ original_value=0,
+ new_value=0,
+ )
+ tail_store = Op.SSTORE(
+ key=0x4, value=0xC, key_warm=False, original_value=0, new_value=0xC
+ )
+ caller_code = head_store + call_store + tail_store + Op.STOP
+ # The caller sits exactly at the creator's CREATE address: the
+ # nested creation collides with the very contract that called it.
+ caller = pre.deploy_contract(
+ code=caller_code,
+ address=compute_create_address(address=creator, nonce=1),
+ )
- tx_data = [
- Hash(0xEA60),
- Hash(0x1EA60),
- ]
- tx_gas = [110000, 160000]
- tx_value = [1, 0]
+ intrinsic_calculator = fork.transaction_intrinsic_cost_calculator()
+ modest_grant = (creator_store + create_code).gas_cost(fork) + BURN_MARGIN
+ gas_limit = (
+ intrinsic_calculator(
+ calldata=Hash(modest_grant),
+ sends_value=tx_value > 0,
+ return_cost_deducted_prior_execution=True,
+ )
+ + head_store.gas_cost(fork)
+ + call_store.gas_cost(fork)
+ + modest_grant
+ + tail_store.gas_cost(fork)
+ + BUDGET_MARGIN
+ )
+ # An oversized ask is clamped to the EIP-150 63/64 cap, leaving the
+ # caller only 1/64 of its remaining gas: it can never complete.
+ grant = gas_limit if oversized_ask else modest_grant
+ data = Hash(grant)
+ intrinsic = intrinsic_calculator(
+ calldata=data,
+ sends_value=tx_value > 0,
+ return_cost_deducted_prior_execution=True,
+ )
+ if ample_budget:
+ available = (
+ gas_limit
+ - intrinsic
+ - head_store.gas_cost(fork)
+ - call_code.gas_cost(fork)
+ )
+ if oversized_ask:
+ store_costs = (
+ call_store.gas_cost(fork)
+ - call_code.gas_cost(fork)
+ + tail_store.gas_cost(fork)
+ )
+ assert available // 64 < store_costs, "caller must fail"
+ else:
+ assert grant <= available - available // 64, "grant is granted"
+ else:
+ # The caller reaches its call with only STARVE_MARGIN left: the
+ # creator halts at its first store and the retained 1/64 cannot
+ # pass the EIP-2200 stipend check, so everything reverts.
+ gas_limit = (
+ intrinsic
+ + head_store.gas_cost(fork)
+ + call_code.gas_cost(fork)
+ + STARVE_MARGIN
+ )
+ assert STARVE_MARGIN - STARVE_MARGIN // 64 <= 2300, "creator halts"
+ assert STARVE_MARGIN // 64 <= 2300, "caller store must halt"
+ sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
- to=target,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ to=caller,
+ data=data,
+ gas_limit=gas_limit,
+ value=tx_value,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ caller_completes = ample_budget and not oversized_ask
+ post = {
+ # The collided account survives with its code and nonce intact.
+ caller: Account(
+ code=caller_code,
+ nonce=1,
+ storage={0: 1, 4: 0xC} if caller_completes else {},
+ balance=tx_value if caller_completes else 0,
+ ),
+ creator: Account(storage={}),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py b/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py
index 3cd7ee838c3..9eca41eee77 100644
--- a/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py
+++ b/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py
@@ -1,195 +1,180 @@
"""
-Test_revert_depth_create_oog.
+Verify revert propagation around a nested CREATE that runs out of gas:
+a sub-call either funds its CREATE-then-store sequence completely, or
+runs out of gas after the CREATE, reverting the created account but not
+the caller; a starved outer budget reverts everything.
Ported from:
state_tests/stRevertTest/RevertDepthCreateOOGFiller.json
+
+@manually-enhanced: Do not overwrite. The sub-call grants and both
+transaction budgets derive from fork composites (EIP-8037 state gas is
+tracked instead of pinned), all addresses are dynamic, and every account
+including the created one is pinned in each arm.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Environment,
+ Fork,
Hash,
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"
+# Gas left in the creator frame after its CREATE: enough to reach the
+# following store, never enough to pay for it.
+PARTIAL_MARGIN = 5_000
+# Head room on top of a derived budget.
+BUDGET_MARGIN = 5_000
+# Gas left at the caller's call site in the starved arm: too little for
+# any frame to complete.
+STARVE_MARGIN = 1_000
+
@pytest.mark.ported_from(
["state_tests/stRevertTest/RevertDepthCreateOOGFiller.json"],
)
@pytest.mark.valid_from("Cancun")
@pytest.mark.parametrize(
- "d, g, v",
- [
- pytest.param(
- 0,
- 0,
- 0,
- id="d0-g0-v0",
- ),
- pytest.param(
- 0,
- 0,
- 1,
- id="d0-g0-v1",
- ),
- pytest.param(
- 0,
- 1,
- 0,
- id="d0-g1-v0",
- ),
- pytest.param(
- 0,
- 1,
- 1,
- id="d0-g1-v1",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1-g0-v0",
- ),
- pytest.param(
- 1,
- 0,
- 1,
- id="d1-g0-v1",
- ),
- pytest.param(
- 1,
- 1,
- 0,
- id="d1-g1-v0",
- ),
- pytest.param(
- 1,
- 1,
- 1,
- id="d1-g1-v1",
- ),
- ],
+ "full_grant",
+ [False, True],
+ ids=["partial_grant", "full_grant"],
+)
+@pytest.mark.parametrize(
+ "ample_budget",
+ [False, True],
+ ids=["starved", "ample"],
)
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.parametrize("tx_value", [1, 0], ids=["v1", "v0"])
def test_revert_depth_create_oog(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ full_grant: bool,
+ ample_budget: bool,
+ tx_value: int,
) -> None:
- """Test_revert_depth_create_oog."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xA000000000000000000000000000000000000000)
- contract_1 = Address(0xB000000000000000000000000000000000000000)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
+ """An out-of-gas CREATE frame reverts alone; a starved caller fully."""
+ creator_store = Op.SSTORE(
+ key=0x2, value=0x8, key_warm=False, original_value=0, new_value=8
)
-
- # Source: lll
- # { [[2]] 8 (CREATE 0 0 0) [[3]] 12}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x2, value=0x8)
- + Op.POP(Op.CREATE(value=0x0, offset=0x0, size=0x0))
- + Op.SSTORE(key=0x3, value=0xC)
- + Op.STOP,
- nonce=0,
- )
- # Source: lll
- # { [[0]] 1 [[1]] (CALL (CALLDATALOAD 0) 0xb000000000000000000000000000000000000000 0 0 0 0 0) [[4]] 12 } # noqa: E501
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=0x1)
- + Op.SSTORE(
- key=0x1,
- value=Op.CALL(
- gas=Op.CALLDATALOAD(offset=0x0),
- address=contract_1,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
+ create_code = Op.POP(
+ Op.CREATE(
+ value=0x0,
+ offset=0x0,
+ size=0x0,
+ init_code_size=0,
+ new_memory_size=0,
)
- + Op.SSTORE(key=0x4, value=0xC)
- + Op.STOP,
- balance=5,
- nonce=54,
)
+ creator_tail = Op.SSTORE(
+ key=0x3, value=0xC, key_warm=False, original_value=0, new_value=0xC
+ )
+ creator_code = creator_store + create_code + creator_tail + Op.STOP
+ creator = pre.deploy_contract(code=creator_code)
+ created = compute_create_address(address=creator, nonce=1)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- compute_create_address(address=contract_1, nonce=0): Account(
- nonce=1
- ),
- contract_0: Account(storage={0: 1, 1: 1, 4: 12}),
- contract_1: Account(storage={2: 8, 3: 12}),
- },
- },
- {
- "indexes": {"data": 0, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- compute_create_address(
- address=contract_1, nonce=0
- ): Account.NONEXISTENT,
- contract_0: Account(storage={0: 1, 4: 12}),
- contract_1: Account(storage={}),
- },
- },
- {
- "indexes": {"data": [0, 1], "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- compute_create_address(
- address=contract_1, nonce=0
- ): Account.NONEXISTENT,
- contract_0: Account(storage={}),
- contract_1: Account(storage={}),
- },
- },
- ]
+ # The grant covers the creator completely, or only up to and
+ # including its CREATE, leaving too little for the following store.
+ if full_grant:
+ grant = creator_code.gas_cost(fork) + BUDGET_MARGIN
+ inner_consumed = creator_code.gas_cost(fork)
+ else:
+ grant = (creator_store + create_code).gas_cost(fork) + PARTIAL_MARGIN
+ inner_consumed = grant
+ inner_succeeds = ample_budget and full_grant
+ inner_fails_reaching_create = ample_budget and not full_grant
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ head_store = Op.SSTORE(
+ key=0x0, value=0x1, key_warm=False, original_value=0, new_value=1
+ )
+ call_code = Op.CALL(
+ gas=Op.CALLDATALOAD(offset=0x0), address=creator, address_warm=False
+ )
+ call_store = Op.SSTORE(
+ key=0x1,
+ value=call_code,
+ key_warm=False,
+ original_value=0,
+ new_value=1 if inner_succeeds else 0,
+ )
+ tail_store = Op.SSTORE(
+ key=0x4, value=0xC, key_warm=False, original_value=0, new_value=0xC
+ )
+ caller = pre.deploy_contract(
+ code=head_store + call_store + tail_store + Op.STOP
+ )
- tx_data = [
- Hash(0xEA60),
- Hash(0x1EA60),
- ]
- tx_gas = [110000, 180000]
- tx_value = [1, 0]
+ data = Hash(grant)
+ intrinsic = fork.transaction_intrinsic_cost_calculator()(
+ calldata=data,
+ sends_value=tx_value > 0,
+ return_cost_deducted_prior_execution=True,
+ )
+ if ample_budget:
+ gas_limit = (
+ intrinsic
+ + head_store.gas_cost(fork)
+ + call_store.gas_cost(fork)
+ + inner_consumed
+ + tail_store.gas_cost(fork)
+ + BUDGET_MARGIN
+ )
+ # The requested grant must fit under the EIP-150 63/64 cap.
+ available = (
+ gas_limit
+ - intrinsic
+ - head_store.gas_cost(fork)
+ - call_code.gas_cost(fork)
+ )
+ assert grant <= available - available // 64, "grant must be granted"
+ else:
+ # The caller reaches its call with only STARVE_MARGIN left: the
+ # creator halts at its first store and the retained 1/64 cannot
+ # pass the EIP-2200 stipend check, so everything reverts.
+ gas_limit = (
+ intrinsic
+ + head_store.gas_cost(fork)
+ + call_code.gas_cost(fork)
+ + STARVE_MARGIN
+ )
+ assert STARVE_MARGIN - STARVE_MARGIN // 64 <= 2300, "creator halts"
+ assert STARVE_MARGIN // 64 <= 2300, "caller store must halt"
+ sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
- to=contract_0,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ to=caller,
+ data=data,
+ gas_limit=gas_limit,
+ value=tx_value,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ post: dict
+ if inner_succeeds:
+ post = {
+ created: Account(nonce=1),
+ caller: Account(storage={0: 1, 1: 1, 4: 0xC}, balance=tx_value),
+ creator: Account(storage={2: 8, 3: 0xC}),
+ }
+ elif inner_fails_reaching_create:
+ post = {
+ created: Account.NONEXISTENT,
+ caller: Account(storage={0: 1, 4: 0xC}, balance=tx_value),
+ creator: Account(storage={}),
+ }
+ else:
+ post = {
+ created: Account.NONEXISTENT,
+ caller: Account(storage={}, balance=0),
+ creator: Account(storage={}),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py b/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py
index a49cc7b38bc..2f6ab5355af 100644
--- a/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py
+++ b/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py
@@ -1,35 +1,46 @@
"""
-Test: this test checks that the returndata buffer is changed when a...
+Verify that a reverting callee's return data replaces the empty return
+data left by a previously failed call: each prober records the failed
+call's result and RETURNDATASIZE, for CALL, CALLCODE, DELEGATECALL and a
+nested CALL chain, with an ample and a starved transaction budget.
Ported from:
state_tests/stRevertTest/RevertOpcodeInCallsOnNonEmptyReturnDataFiller.json
-@manually-enhanced: Do not overwrite. Inner-CALL/DELEGATECALL gas
-bumped on Amsterdam to cover EIP-8037 state-gas spill into regular gas;
-pre-EIP-8037 unchanged.
+@manually-enhanced: Do not overwrite. Sub-calls forward all gas and the
+ample arm omits the gas limit (maxing the EIP-8037 reservoir), replacing
+per-fork gas bumps; the starved budget derives from fork composites; all
+addresses are dynamic and every contract is pinned in the post.
"""
import pytest
from execution_testing import (
- EOA,
Account,
Address,
Alloc,
- Environment,
+ Fork,
Hash,
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,
-)
+from execution_testing.vm import Bytecode, Op
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+RESULT_SLOT = 0x0
+RETURN_DATA_SIZE_SLOT = 0x2
+NESTED_RESULT_SLOT = 0x4
+NESTED_RETURN_DATA_SIZE_SLOT = 0x5
+ENTRY_SLOT = 0xA
+ENTRY_SLOT_INITIAL = 255
+# A zero-gas grant: the prelude call must fail without touching the
+# return data buffer.
+FAILING_CALL_GAS = 0
+# Gas left at the entry frame's call site in the starved arm: enough to
+# start the call, too little for any frame to complete.
+STARVE_MARGIN = 1_000
+
@pytest.mark.ported_from(
[
@@ -38,396 +49,146 @@
)
@pytest.mark.valid_from("Cancun")
@pytest.mark.parametrize(
- "d, g, v",
- [
- 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(
- 2,
- 0,
- 0,
- id="d2-g0",
- ),
- pytest.param(
- 2,
- 1,
- 0,
- id="d2-g1",
- ),
- pytest.param(
- 3,
- 0,
- 0,
- id="d3-g0",
- ),
- pytest.param(
- 3,
- 1,
- 0,
- id="d3-g1",
- ),
- ],
+ "call_op",
+ [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, None],
+ ids=["call", "callcode", "delegatecall", "nested_call"],
+)
+@pytest.mark.parametrize(
+ "ample_gas",
+ [True, False],
+ ids=["ample", "starved"],
)
-@pytest.mark.pre_alloc_mutable
def test_revert_opcode_in_calls_on_non_empty_return_data(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ call_op: Op | None,
+ ample_gas: bool,
) -> None:
- """Test: tis test checks that the returndata buffer is changed when a..."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = EOA(
- key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52
- )
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
-
- pre[sender] = Account(balance=0xE8D4A51000)
- # EIP-8037 inner-CALL/DELEGATECALL gas bumps: original values
- # restored for pre-EIP-8037 forks; bumped for state-gas spill on
- # Amsterdam.
- inner_call_gas = 50000
- deeper_call_gas = 100000
- deepest_call_gas = 260000
- if fork.is_eip_enabled(8037):
- inner_call_gas = 100000
- deeper_call_gas = 1000000
- deepest_call_gas = 1000000
- # Source: lll
- # { [[1]] 12 (REVERT 0 1) [[3]] 13 }
- addr_6 = pre.deploy_contract( # noqa: F841
+ """A revert's return data is observed after a failed call."""
+ # Reverts one byte of return data; the store before the REVERT is
+ # undone and the code after it must never run.
+ reverter = pre.deploy_contract(
code=Op.SSTORE(key=0x1, value=0xC)
+ Op.REVERT(offset=0x0, size=0x1)
+ Op.SSTORE(key=0x3, value=0xD)
+ Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B), # noqa: E501
)
- # Source: lll
- # { [1] 12 (RETURN 0 64) }
- addr_7 = pre.deploy_contract( # noqa: F841
+ # Would return 64 bytes, but is only ever called with zero gas.
+ returner = pre.deploy_contract(
code=Op.MSTORE(offset=0x1, value=0xC)
- + Op.RETURN(offset=0x0, size=0x40)
- + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0x127EAF7E31D691A8393B7A2F84A6E94372190C01), # noqa: E501
+ + Op.RETURN(offset=0x0, size=0x40),
)
- # Source: lll
- # { (CALL 0 0 0 0 0 0) [[0]] (DELEGATECALL 50000 0 0 0 0) [[2]] (RETURNDATASIZE) } # noqa: E501
- addr_3 = pre.deploy_contract( # noqa: F841
- code=Op.POP(
- Op.CALL(
- gas=0x0,
- address=0x127EAF7E31D691A8393B7A2F84A6E94372190C01,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- )
- + Op.SSTORE(
- key=0x0,
- value=Op.DELEGATECALL(
- gas=inner_call_gas,
- address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x2, value=Op.RETURNDATASIZE)
- + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0xF20CCAF271BEAA36E7CF4C9CED2867FAC9558F14), # noqa: E501
+
+ prelude = Op.POP(
+ Op.CALL(gas=FAILING_CALL_GAS, address=returner, address_warm=False)
)
- # Source: lll
- # { (CALL 0 0 0 0 0 0) [[0]] (CALLCODE 50000 0 0 0 0 0) [[2]] (RETURNDATASIZE) } # noqa: E501
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.POP(
- Op.CALL(
- gas=0x0,
- address=0x127EAF7E31D691A8393B7A2F84A6E94372190C01,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- )
- + Op.SSTORE(
- key=0x0,
- value=Op.CALLCODE(
- gas=inner_call_gas,
- address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
+
+ def prober_code(
+ op: Op, callee: Address, result_slot: int, rds_slot: int
+ ) -> Bytecode:
+ """Call the callee and record its result and RETURNDATASIZE."""
+ return (
+ prelude
+ + Op.SSTORE(key=result_slot, value=op(address=callee))
+ + Op.SSTORE(key=rds_slot, value=Op.RETURNDATASIZE)
+ + Op.STOP
)
- + Op.SSTORE(key=0x2, value=Op.RETURNDATASIZE)
- + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0xC9DA6CD8413F64323F12CD44C99671F280F15E1C), # noqa: E501
+
+ prober_call = pre.deploy_contract(
+ code=prober_code(Op.CALL, reverter, RESULT_SLOT, RETURN_DATA_SIZE_SLOT)
)
- # Source: lll
- # { (CALL 0 0 0 0 0 0) [[4]] (CALL 50000 0 0 0 0 0) [[5]] (RETURNDATASIZE) } # noqa: E501
- addr_5 = pre.deploy_contract( # noqa: F841
- code=Op.POP(
- Op.CALL(
- gas=0x0,
- address=0x127EAF7E31D691A8393B7A2F84A6E94372190C01,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
+ prober_callcode = pre.deploy_contract(
+ code=prober_code(
+ Op.CALLCODE, reverter, RESULT_SLOT, RETURN_DATA_SIZE_SLOT
)
- + Op.SSTORE(
- key=0x4,
- value=Op.CALL(
- gas=inner_call_gas,
- address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x5, value=Op.RETURNDATASIZE)
- + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0xEA519C47889074E6378B0D83747F2C3EA0B9CBC9), # noqa: E501
)
- # Source: lll
- # { (CALL 0 0 0 0 0 0) [[0]] (CALL 50000 0 0 0 0 0) [[2]] (RETURNDATASIZE) } # noqa: E501
- addr = pre.deploy_contract( # noqa: F841
- code=Op.POP(
- Op.CALL(
- gas=0x0,
- address=0x127EAF7E31D691A8393B7A2F84A6E94372190C01,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
+ prober_delegatecall = pre.deploy_contract(
+ code=prober_code(
+ Op.DELEGATECALL, reverter, RESULT_SLOT, RETURN_DATA_SIZE_SLOT
)
- + Op.SSTORE(
- key=0x0,
- value=Op.CALL(
- gas=inner_call_gas,
- address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x2, value=Op.RETURNDATASIZE)
- + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0xE73611B5B479B30C93AC377AEB3BFB199764F3C3), # noqa: E501
)
- # Source: lll
- # { (CALL 0 0 0 0 0 0) [[10]] (CALL 260000 (CALLDATALOAD 0) 0 0 0 0 0)} # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.POP(
- Op.CALL(
- gas=0x0,
- address=0x127EAF7E31D691A8393B7A2F84A6E94372190C01,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
+ inner_prober = pre.deploy_contract(
+ code=prober_code(
+ Op.CALL,
+ reverter,
+ NESTED_RESULT_SLOT,
+ NESTED_RETURN_DATA_SIZE_SLOT,
)
- + Op.SSTORE(
- key=0xA,
- value=Op.CALL(
- gas=deepest_call_gas,
- address=Op.CALLDATALOAD(offset=0x0),
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.STOP,
- storage={10: 255},
- balance=1,
- nonce=0,
- address=Address(0x172A8F572404293AA810685DFDC6F740C300CC4B), # noqa: E501
)
- # Source: lll
- # { (CALL 0 0 0 0 0 0) [[0]] (CALL 100000 0 0 0 0 0) [[2]] (RETURNDATASIZE) } # noqa: E501
- addr_4 = pre.deploy_contract( # noqa: F841
- code=Op.POP(
- Op.CALL(
- gas=0x0,
- address=0x127EAF7E31D691A8393B7A2F84A6E94372190C01,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
+ prober_nested = pre.deploy_contract(
+ code=prober_code(
+ Op.CALL, inner_prober, RESULT_SLOT, RETURN_DATA_SIZE_SLOT
)
- + Op.SSTORE(
- key=0x0,
- value=Op.CALL(
- gas=deeper_call_gas,
- address=0xEA519C47889074E6378B0D83747F2C3EA0B9CBC9,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.SSTORE(key=0x2, value=Op.RETURNDATASIZE)
- + Op.STOP,
- balance=1,
- nonce=0,
- address=Address(0x6BACDFA8216DBB2A09819F8739E57AE3574C9FFF), # noqa: E501
)
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": 0, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- addr_6: Account(storage={}),
- target: Account(storage={10: 1}),
- addr: Account(storage={2: 1}, nonce=0),
- },
- },
- {
- "indexes": {"data": 0, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- addr_6: Account(storage={}),
- addr: Account(storage={}),
- },
- },
- {
- "indexes": {"data": 1, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- addr_6: Account(storage={}),
- target: Account(storage={10: 1}),
- addr_2: Account(storage={2: 1}, nonce=0),
- },
- },
- {
- "indexes": {"data": 1, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- addr_6: Account(storage={}),
- addr_2: Account(storage={}),
- },
- },
- {
- "indexes": {"data": 2, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- addr_6: Account(storage={}),
- target: Account(storage={10: 1}),
- addr_3: Account(storage={2: 1}, nonce=0),
- },
- },
- {
- "indexes": {"data": 2, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- addr_6: Account(storage={}),
- addr_3: Account(storage={}),
- },
- },
- {
- "indexes": {"data": 3, "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {
- addr_6: Account(storage={}),
- target: Account(storage={10: 1}),
- addr_4: Account(storage={0: 1}, nonce=0),
- addr_5: Account(storage={5: 1}, nonce=0),
- },
- },
- {
- "indexes": {"data": 3, "gas": 1, "value": -1},
- "network": [">=Cancun"],
- "result": {
- addr_6: Account(storage={}),
- target: Account(storage={10: 255}),
- addr_4: Account(storage={0: 0}, nonce=0),
- addr_5: Account(storage={5: 0}, nonce=0),
- },
- },
- ]
+ big_call = Op.CALL(address=Op.CALLDATALOAD(offset=0x0), address_warm=False)
+ entry = pre.deploy_contract(
+ code=prelude + Op.SSTORE(key=ENTRY_SLOT, value=big_call) + Op.STOP,
+ storage={ENTRY_SLOT: ENTRY_SLOT_INITIAL},
+ )
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ probers = {
+ Op.CALL: prober_call,
+ Op.CALLCODE: prober_callcode,
+ Op.DELEGATECALL: prober_delegatecall,
+ None: prober_nested,
+ }
+ prober = probers[call_op]
+ data = Hash(prober, left_padding=True)
- tx_data = [
- Hash(addr, left_padding=True),
- Hash(addr_2, left_padding=True),
- Hash(addr_3, left_padding=True),
- Hash(addr_4, left_padding=True),
- ]
- tx_gas = [860000, 28000]
+ sender = pre.fund_eoa()
+ if ample_gas:
+ tx = Transaction(sender=sender, to=entry, data=data)
+ else:
+ # The entry frame reaches its call with only STARVE_MARGIN left:
+ # the prober cannot even pay its prelude, and the retained 1/64
+ # cannot pass the EIP-2200 stipend check, so everything reverts.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()(
+ calldata=data, return_cost_deducted_prior_execution=True
+ )
+ starved = (
+ intrinsic
+ + prelude.gas_cost(fork)
+ + big_call.gas_cost(fork)
+ + STARVE_MARGIN
+ )
+ forwarded = STARVE_MARGIN - STARVE_MARGIN // 64
+ assert forwarded < prelude.gas_cost(fork), "prober must starve"
+ assert STARVE_MARGIN // 64 <= 2300, "entry store must halt"
+ tx = Transaction(sender=sender, to=entry, data=data, gas_limit=starved)
- tx = Transaction(
- sender=sender,
- to=target,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- error=_exc,
- )
+ untouched = {
+ reverter: Account(storage={}),
+ prober_call: Account(storage={}),
+ prober_callcode: Account(storage={}),
+ prober_delegatecall: Account(storage={}),
+ inner_prober: Account(storage={}),
+ prober_nested: Account(storage={}),
+ }
+ if not ample_gas:
+ # The transaction runs out of gas at the entry frame: everything
+ # reverts.
+ post = {
+ **untouched,
+ entry: Account(storage={ENTRY_SLOT: ENTRY_SLOT_INITIAL}),
+ }
+ elif call_op is None:
+ # The nested prober's callee completes (its own probe fails), so
+ # the outer call succeeds and returns no data.
+ post = {
+ **untouched,
+ entry: Account(storage={ENTRY_SLOT: 1}),
+ prober_nested: Account(storage={RESULT_SLOT: 1}),
+ inner_prober: Account(storage={NESTED_RETURN_DATA_SIZE_SLOT: 1}),
+ }
+ else:
+ # The probed call reverts with one byte of return data: result 0,
+ # RETURNDATASIZE 1.
+ post = {
+ **untouched,
+ entry: Account(storage={ENTRY_SLOT: 1}),
+ prober: Account(storage={RETURN_DATA_SIZE_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/stSStoreTest/test_sstore_gas.py b/tests/ported_static/stSStoreTest/test_sstore_gas.py
index 975d3765c56..06e840689ad 100644
--- a/tests/ported_static/stSStoreTest/test_sstore_gas.py
+++ b/tests/ported_static/stSStoreTest/test_sstore_gas.py
@@ -1,8 +1,16 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Measure the gas cost of every SSTORE transition class (cold/warm x
+original/current/new value combinations) via inline GAS deltas (by Ori
+Pomerantz qbzzt1@gmail.com).
Ported from:
state_tests/stSStoreTest/sstoreGasFiller.yml
+
+@manually-enhanced: Do not overwrite. The nine measured transition costs
+are derived from SSTORE opcode metadata instead of pinned numbers, so
+EIP-8037's state-gas repricing (and any future one) is tracked
+automatically; the explicit gas limit equals the EIP-7825 cap, so the
+state gas spills into the measured deltas.
"""
import pytest
@@ -12,6 +20,7 @@
Alloc,
Bytes,
Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -29,8 +38,9 @@
def test_sstore_gas(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Ori Pomerantz qbzzt1@gmail."""
+ """Measure each SSTORE transition's gas against opcode metadata."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0xBA1A9CE0BA1A9CE, nonce=1)
@@ -171,18 +181,51 @@ def test_sstore_gas(
nonce=1,
)
+ # The measured transitions, in bytecode order (slots 0 and 1 start at
+ # 24743; slots 2 and 3 start empty). Each stored delta is the pure
+ # SSTORE cost (the contract subtracts its own 8-gas overhead).
+ transitions = [
+ # slot 0: cold, original nonzero -> different nonzero
+ dict(key_warm=False, original_value=24743, new_value=0xBEEF),
+ # slot 0: warm dirty, nonzero -> nonzero
+ dict(
+ key_warm=True,
+ original_value=24743,
+ current_value=0xBEEF,
+ new_value=0xDEADBEEF,
+ ),
+ # slot 0: warm dirty, nonzero -> zero
+ dict(
+ key_warm=True,
+ original_value=24743,
+ current_value=0xDEADBEEF,
+ new_value=0,
+ ),
+ # slot 0: warm dirty, zero -> zero
+ dict(
+ key_warm=True, original_value=24743, current_value=0, new_value=0
+ ),
+ # slot 0: warm dirty, zero -> nonzero
+ dict(
+ key_warm=True,
+ original_value=24743,
+ current_value=0,
+ new_value=0x1234,
+ ),
+ # slot 1: cold, original nonzero -> zero
+ dict(key_warm=False, original_value=24743, new_value=0),
+ # slot 2: cold fresh, zero -> nonzero
+ dict(key_warm=False, original_value=0, new_value=0x60A7),
+ # slot 3: cold fresh, zero -> zero
+ dict(key_warm=False, original_value=0, new_value=0),
+ # slot 3: warm fresh, zero -> nonzero
+ dict(key_warm=True, original_value=0, new_value=0x60A7),
+ ]
post = {
target: Account(
storage={
- 4096: 5000,
- 4097: 100,
- 4098: 100,
- 4099: 100,
- 4100: 100,
- 4101: 5000,
- 4102: 22100,
- 4103: 2200,
- 4104: 20000,
+ 0x1000 + i: Op.SSTORE.with_metadata(**md).gas_cost(fork)
+ for i, md in enumerate(transitions)
},
),
}
diff --git a/tests/ported_static/stSStoreTest/test_sstore_gas_left.py b/tests/ported_static/stSStoreTest/test_sstore_gas_left.py
index 30686397044..34fefa79a0c 100644
--- a/tests/ported_static/stSStoreTest/test_sstore_gas_left.py
+++ b/tests/ported_static/stSStoreTest/test_sstore_gas_left.py
@@ -1,440 +1,116 @@
"""
-Checks EIP-1706/EIP-2200 out of gas requirement for non-mutating SSTOREs.
+Verify the EIP-2200 (EIP-1706) minimum-gas rule for SSTORE: a non-mutating
+store fails unless the gas left exceeds the call stipend, across CALL,
+CALLCODE and DELEGATECALL entry into the storing frame.
Ported from:
state_tests/stSStoreTest/sstore_gasLeftFiller.json
-@manually-enhanced: Do not overwrite. Gas budget refactored to be
-fork-aware (`tx_gas = [intrinsic + tx_data[d].gas_cost(fork)]`), and
-each `Op.CALL` annotated with `inner_call_cost=` metadata so
-`Bytecode.gas_cost(fork)` covers the forwarded inner-frame gas.
-Required for the test to fill correctly under EIP-8037's two-
-dimensional gas model. Hex `gas=` literals also converted to
-human-readable decimals.
+@manually-enhanced: Do not overwrite. The stored-to slot is warmed before
+the boundary call so the stipend check (not the cold-access charge, which
+EIP-8037/8038 reprice) is the binding constraint on every fork; the
+boundary gas is derived as stipend + push cost +/- 1; the success
+indicator forwards gas via `flag * INDICATOR_GAS` instead of the ported
+hardcoded-pc JUMPI; the tx gas is maxed so the indicator's storage write
+is not budget-bound.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ 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"
+# Gas forwarded to the success indicator; only needs to cover its regular
+# costs (state gas rides on the transaction's implicit reservoir).
+INDICATOR_GAS = 30_000
+
@pytest.mark.ported_from(
["state_tests/stSStoreTest/sstore_gasLeftFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Istanbul")
+@pytest.mark.parametrize(
+ "opcode",
+ [
+ pytest.param(Op.CALL, id="call"),
+ pytest.param(Op.CALLCODE, id="callcode"),
+ pytest.param(Op.DELEGATECALL, id="delegatecall"),
+ ],
+)
@pytest.mark.parametrize(
- "d, g, v",
+ "gas_offset, store_succeeds",
[
- 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(-1, False, id="below_boundary"),
+ pytest.param(0, False, id="at_boundary"),
+ pytest.param(1, True, id="above_boundary"),
],
)
-@pytest.mark.pre_alloc_mutable
def test_sstore_gas_left(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ opcode: Op,
+ gas_offset: int,
+ store_succeeds: bool,
) -> None:
- """Checks EIP-1706/EIP-2200 out of gas requirement for non-mutating..."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = EOA(
- key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52
- )
+ """A non-mutating SSTORE needs gas left above the call stipend."""
+ gas_costs = fork.gas_costs()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
-
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[1]] 1 }
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP,
- storage={1: 1},
- nonce=0,
- address=Address(0xB0409D84AB61455CB8BEC14B94F635146AB55613), # noqa: E501
- )
- # Source: lll
- # { [[1]] 1 }
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP,
- nonce=0,
- address=Address(0x4092B3905CFEA2485EA53222F41EB26E67587802), # noqa: E501
- )
+ # The storing contract: a no-op SSTORE (slot 1 already holds 1 for the
+ # CALL arm; the CALLCODE/DELEGATECALL arms pre-set the caller's own
+ # slot 1). At the SSTORE, gas left = forwarded - two pushes; EIP-2200
+ # requires it to exceed the stipend.
+ store_code = Op.SSTORE(key=0x1, value=0x1)
+ storer = pre.deploy_contract(code=store_code + Op.STOP, storage={1: 1})
+ push_cost = 2 * gas_costs.VERY_LOW
+ boundary_gas = gas_costs.CALL_STIPEND + push_cost + gas_offset
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": [0, 1, 3, 4, 6, 7], "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {addr_2: Account(storage={1: 0})},
- },
- {
- "indexes": {"data": [8, 2, 5], "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {addr_2: Account(storage={1: 1})},
- },
- ]
+ # Written by the success indicator call.
+ indicator = pre.deploy_contract(code=Op.SSTORE(key=0x1, value=0x1))
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ if opcode == Op.CALL:
+ # Warm the storer's slot (and pre-write it back to 1) with an
+ # unbounded call, so the boundary call's SSTORE is a warm no-op
+ # and only the stipend check can fail it.
+ prelude = Op.POP(Op.CALL(address=storer))
+ boundary_call = opcode(gas=boundary_gas, address=storer)
+ else:
+ # CALLCODE/DELEGATECALL store into the caller's own slot 1: the
+ # pre-write makes the boundary store a warm no-op.
+ prelude = Op.SSTORE(key=0x1, value=0x1)
+ if opcode == Op.CALLCODE:
+ boundary_call = opcode(gas=boundary_gas, address=storer, value=0)
+ else:
+ boundary_call = opcode(gas=boundary_gas, address=storer)
- tx_data = [
- Op.JUMPI(
- pc=0x4B,
- condition=Op.ISZERO(
- Op.CALL(
- gas=2305,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2305,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.JUMPI(
- pc=0x4B,
- condition=Op.ISZERO(
- Op.CALL(
- gas=2306,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2306,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.JUMPI(
- pc=0x4B,
- condition=Op.ISZERO(
- Op.CALL(
- gas=2307,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2307,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x50,
- condition=Op.ISZERO(
- Op.CALLCODE(
- gas=2305,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2305,
- )
- ),
- )
+ # The indicator receives gas only if the boundary call succeeded
+ # (flag * INDICATOR_GAS), so no jump destinations are needed.
+ caller = pre.deploy_contract(
+ code=prelude
+ Op.POP(
Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
+ gas=Op.MUL(INDICATOR_GAS, boundary_call),
+ address=indicator,
)
)
- + Op.JUMPDEST
+ Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x50,
- condition=Op.ISZERO(
- Op.CALLCODE(
- gas=2306,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2306,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x50,
- condition=Op.ISZERO(
- Op.CALLCODE(
- gas=2307,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2307,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x4E,
- condition=Op.ISZERO(
- Op.DELEGATECALL(
- gas=2305,
- address=addr,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x4E,
- condition=Op.ISZERO(
- Op.DELEGATECALL(
- gas=2306,
- address=addr,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x4E,
- condition=Op.ISZERO(
- Op.DELEGATECALL(
- gas=2307,
- address=addr,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- ]
- # Fork-aware gas budget: contract-creation intrinsic from the
- # fork's calculator, plus the bytecode's own gas cost (which
- # already includes the gas forwarded to inner CALLs via opcode
- # metadata). Any future fork-cost change is automatically
- # respected.
- intrinsic = fork.transaction_intrinsic_cost_calculator()(
- calldata=tx_data[d],
- contract_creation=True,
)
- tx_gas = [intrinsic + tx_data[d].gas_cost(fork)]
- tx_value = [1]
tx = Transaction(
- sender=sender,
- to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ sender=pre.fund_eoa(),
+ to=caller,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ post = {
+ indicator: Account(storage={1: 1 if store_succeeds else 0}),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stSolidityTest/test_recursive_create_contracts.py b/tests/ported_static/stSolidityTest/test_recursive_create_contracts.py
index 836f16024d1..8f23e6ec41e 100644
--- a/tests/ported_static/stSolidityTest/test_recursive_create_contracts.py
+++ b/tests/ported_static/stSolidityTest/test_recursive_create_contracts.py
@@ -1,8 +1,14 @@
"""
-Test_recursive_create_contracts.
+Verify recursively self-creating Solidity contracts stop when the
+transaction budget runs dry, leaving exactly one child.
Ported from:
state_tests/stSolidityTest/RecursiveCreateContractsFiller.json
+
+@manually-enhanced: Do not overwrite. The EIP-8037 state gas of the
+in-test creations is added to the ported budget as a fork-derived
+surcharge (exactly 0 before EIP-8037), preserving the ported
+behavior on every fork.
"""
import pytest
@@ -17,6 +23,7 @@
Transaction,
compute_create_address,
)
+from execution_testing.forks import Fork
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
@@ -31,8 +38,9 @@
def test_recursive_create_contracts(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_recursive_create_contracts."""
+ """Recursive contract creation runs dry at the expected depth."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87)
sender = pre.fund_eoa(amount=0x1DCD6500)
@@ -250,7 +258,12 @@ def test_recursive_create_contracts(
sender=sender,
to=contract_0,
data=Bytes("a444f5e9") + Hash(0x304),
- gas_limit=300000,
+ # EIP-8037 surcharge (0 before): the first child creation's
+ # new-account and code-deposit state gas spill into this
+ # budget; the recursion still runs dry at the ported depth.
+ gas_limit=300000
+ + fork.create_state_gas()
+ + fork.code_deposit_state_gas(code_size=0xC8),
value=1,
)
diff --git a/tests/ported_static/stSolidityTest/test_test_contract_interaction.py b/tests/ported_static/stSolidityTest/test_test_contract_interaction.py
index 0bcc63132a1..594a260899a 100644
--- a/tests/ported_static/stSolidityTest/test_test_contract_interaction.py
+++ b/tests/ported_static/stSolidityTest/test_test_contract_interaction.py
@@ -1,8 +1,14 @@
"""
-Test_test_contract_interaction.
+Verify a Solidity contract creating a child and interacting with it
+through its dispatcher within the same transaction.
Ported from:
state_tests/stSolidityTest/TestContractInteractionFiller.json
+
+@manually-enhanced: Do not overwrite. The EIP-8037 state gas of the
+in-test creations is added to the ported budget as a fork-derived
+surcharge (exactly 0 before EIP-8037), preserving the ported
+behavior on every fork.
"""
import pytest
@@ -15,6 +21,7 @@
StateTestFiller,
Transaction,
)
+from execution_testing.forks import Fork
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
@@ -29,8 +36,9 @@
def test_test_contract_interaction(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_test_contract_interaction."""
+ """Create a child contract and interact with it in one transaction."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0x5F5E100)
@@ -165,7 +173,11 @@ def test_test_contract_interaction(
sender=sender,
to=target,
data=Bytes("c0406226"),
- gas_limit=350000,
+ # EIP-8037 surcharge (0 before): the created child's new-account
+ # and code-deposit state gas spill into this budget.
+ gas_limit=350000
+ + fork.create_state_gas()
+ + fork.code_deposit_state_gas(code_size=0x81),
value=1,
)
diff --git a/tests/ported_static/stSolidityTest/test_test_contract_suicide.py b/tests/ported_static/stSolidityTest/test_test_contract_suicide.py
index 1dd26ffea76..fc12dd3e319 100644
--- a/tests/ported_static/stSolidityTest/test_test_contract_suicide.py
+++ b/tests/ported_static/stSolidityTest/test_test_contract_suicide.py
@@ -1,8 +1,14 @@
"""
-Test_test_contract_suicide.
+Verify a Solidity contract that creates a child, tells it to
+self-destruct, and re-calls it within the same transaction.
Ported from:
state_tests/stSolidityTest/TestContractSuicideFiller.json
+
+@manually-enhanced: Do not overwrite. The EIP-8037 state gas of the
+in-test creations is added to the ported budget as a fork-derived
+surcharge (exactly 0 before EIP-8037), preserving the ported
+behavior on every fork.
"""
import pytest
@@ -15,6 +21,7 @@
StateTestFiller,
Transaction,
)
+from execution_testing.forks import Fork
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
@@ -29,8 +36,9 @@
def test_test_contract_suicide(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_test_contract_suicide."""
+ """Create a child, destroy it, and call it again in one transaction."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0x5F5E100)
@@ -187,7 +195,11 @@ def test_test_contract_suicide(
sender=sender,
to=target,
data=Bytes("c0406226"),
- gas_limit=350000,
+ # EIP-8037 surcharge (0 before): the created child's new-account
+ # and code-deposit state gas spill into this budget.
+ gas_limit=350000
+ + fork.create_state_gas()
+ + fork.code_deposit_state_gas(code_size=0x81),
value=1,
)
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)
diff --git a/tests/ported_static/stSystemOperationsTest/test_ab_acalls0.py b/tests/ported_static/stSystemOperationsTest/test_ab_acalls0.py
index 863e937e7bb..c87ef000bf1 100644
--- a/tests/ported_static/stSystemOperationsTest/test_ab_acalls0.py
+++ b/tests/ported_static/stSystemOperationsTest/test_ab_acalls0.py
@@ -1,8 +1,24 @@
"""
-Test_ab_acalls0.
+Verify mutual A<->B recursion with value transfers and fixed gas asks.
+
+Contract A calls B forwarding a fixed 100,000-gas ask with 24 wei; B
+calls its caller back with a 50,000 ask and 23 wei, storing one plus
+the result. Both store into a PC-derived slot only after their call
+returns, so every level's store competes with what the descent left
+behind: levels too deep to afford it halt and forfeit, rolling back
+their stores and transfers, and the surviving storage and balances pin
+exactly how far the budget reaches.
Ported from:
state_tests/stSystemOperationsTest/ABAcalls0Filler.json
+
+@manually-enhanced: Do not overwrite. The post state (stores and
+balances) is predicted by an exact fork-derived replay of the gas flow
+(EIP-150 grants, stipend gifting and return, warm/cold and SSTORE
+pricing via opcode metadata, EIP-8037 state-gas spill), validated
+against the ported Cancun stores. B reaches A as its CALLER instead of
+a hardcoded address, which shifts B's PC-derived slot; both slots are
+computed from the assembled code.
"""
import pytest
@@ -10,8 +26,7 @@
Account,
Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,84 +35,198 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+A_CALL_GAS = 100_000
+A_CALL_VALUE = 0x18
+B_CALL_GAS = 50_000
+B_CALL_VALUE = 0x17
+# One transfer per level up to the call-depth limit can never run dry.
+A_INITIAL_BALANCE = A_CALL_VALUE * 1024
+# Exactly one return payment before any income (the ported balance).
+B_INITIAL_BALANCE = B_CALL_VALUE
+# Ported budget; pins how deep the mutual recursion reaches.
+TX_GAS_LIMIT = 1_000_000
+
+
+def predict_final_state(
+ fork: Fork, tx_gas_limit: int, b_address: Address
+) -> tuple[int, int, int, int]:
+ """
+ Replay the mutual recursion's gas flow.
+
+ Return A's stored value, B's stored value, and the committed
+ balance deltas of A and B. Descend the alternating call chain
+ computing each level's EIP-150 grant (both asks are pushed
+ constants; a value-bearing call gifts the callee the stipend and
+ gets any unused part back), then unwind: a level that cannot afford
+ its post-call store (EIP-2200's stipend rule included) halts and
+ forfeits its grant, reverting its own store and the transfer that
+ funded it. Every cost is derived from the fork via opcode metadata,
+ including EIP-8037 state gas: with a sub-cap gas limit the state
+ reservoir is zero, so state charges spill from the charging frame's
+ own gas.
+ """
+ stipend = fork.gas_costs().CALL_STIPEND
+ pc_cost = Op.PC.gas_cost(fork)
+
+ def raw_store_cost(key_warm: bool, current: int, new: int) -> int:
+ """Cost of a bare SSTORE; original value is always zero here."""
+ return Op.SSTORE(
+ key_warm=key_warm,
+ original_value=0,
+ current_value=current,
+ new_value=new,
+ ).gas_cost(fork)
+
+ # A's charges before forwarding: argument pushes plus the call's
+ # upfront costs (B is cold only in the top level). The ask is a
+ # pushed constant, so the whole call expression charges up front.
+ def a_charges(b_warm: bool) -> int:
+ return Op.CALL(
+ gas=A_CALL_GAS,
+ address=b_address,
+ value=A_CALL_VALUE,
+ address_warm=b_warm,
+ value_transfer=True,
+ ).gas_cost(fork)
+
+ b_value_expr = Op.ADD(
+ 1,
+ Op.CALL(
+ gas=B_CALL_GAS,
+ address=Op.CALLER,
+ value=B_CALL_VALUE,
+ # A is the transaction target: always warm.
+ address_warm=True,
+ value_transfer=True,
+ ),
+ )
+ # B's ADD and its constant push run only after the call returns.
+ b_post_call = Op.PUSH1[0].gas_cost(fork) + Op.ADD.gas_cost(fork)
+ b_charges = b_value_expr.gas_cost(fork) - b_post_call
+
+ # Descend: alternate A and B levels until one dies mid-charges.
+ gas = (
+ tx_gas_limit
+ - fork.transaction_intrinsic_cost_calculator()()
+ - fork.transaction_top_frame_state_gas()
+ )
+ levels: list[tuple[int, int]] = []
+ level = 0
+ balance = {"A": A_INITIAL_BALANCE, "B": B_INITIAL_BALANCE}
+ while True:
+ level += 1
+ is_a = level % 2 == 1
+ if is_a:
+ gas -= a_charges(b_warm=level > 1)
+ ask, value = A_CALL_GAS, A_CALL_VALUE
+ else:
+ gas -= b_charges
+ ask, value = B_CALL_GAS, B_CALL_VALUE
+ if gas < 0:
+ break
+ assert level < 1024, "recursion must die of gas, not depth"
+ payer = "A" if is_a else "B"
+ assert balance[payer] >= value, "value transfer must be funded"
+ balance[payer] -= value
+ balance["B" if is_a else "A"] += value
+ forwarded = min(ask, gas - gas // 64)
+ levels.append((gas, forwarded))
+ gas = forwarded + stipend
+
+ # Unwind: a failed level forfeits its grant and reverts the whole
+ # committed state below it (stores, warmth, and transfers).
+ child_ok = False
+ leftover = 0
+ a_val, a_warm, b_val, b_warm = 0, False, 0, False
+ a_delta, b_delta = 0, 0
+ for lvl in range(len(levels), 0, -1):
+ available, forwarded = levels[lvl - 1]
+ is_a = lvl % 2 == 1
+ gas = available - forwarded + (leftover if child_ok else 0)
+ result = 1 if child_ok else 0
+ if is_a:
+ gas -= pc_cost
+ store_value, current, warm = result, a_val, a_warm
+ else:
+ gas -= b_post_call + pc_cost
+ store_value, current, warm = 1 + result, b_val, b_warm
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ gas -= raw_store_cost(warm, current, store_value)
+ ok = gas >= 0
+ if ok:
+ # Commit this level: its store and the transfer into it.
+ if is_a:
+ a_val, a_warm = store_value, True
+ if lvl > 1:
+ a_delta += B_CALL_VALUE
+ b_delta -= B_CALL_VALUE
+ else:
+ b_val, b_warm = store_value, True
+ a_delta -= A_CALL_VALUE
+ b_delta += A_CALL_VALUE
+ leftover = gas
+ child_ok = True
+ else:
+ child_ok = False
+ leftover = 0
+ a_val, a_warm, b_val, b_warm = 0, False, 0, False
+ a_delta, b_delta = 0, 0
+ assert child_ok, "the top level must complete"
+ return a_val, b_val, a_delta, b_delta
+
@pytest.mark.ported_from(
["state_tests/stSystemOperationsTest/ABAcalls0Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_ab_acalls0(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_ab_acalls0."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
+ """Pin how deep a value-bearing A<->B recursion reaches."""
+ # B calls whoever called it, so it needs no embedded address.
+ b_value_expr = Op.ADD(
+ 1,
+ Op.CALL(gas=B_CALL_GAS, address=Op.CALLER, value=B_CALL_VALUE),
+ )
+ contract_b = pre.deploy_contract(
+ code=Op.SSTORE(key=Op.PC, value=b_value_expr) + Op.STOP,
+ balance=B_INITIAL_BALANCE,
)
- # Source: lll
- # { [[ (PC) ]] (CALL 100000 24 0 0 0 0) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(
- key=Op.PC,
- value=Op.CALL(
- gas=0x186A0,
- address=0x44EB1162303B6A60F2F8882D43D661787B3011E6,
- value=0x18,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.STOP,
- balance=0xDE0B6B3A7640000,
- nonce=0,
- address=Address(0xD6CD6EC9ADCA299F2BBFD754FF8BCF6A4B9AAE40), # noqa: E501
+ a_value_expr = Op.CALL(
+ gas=A_CALL_GAS, address=contract_b, value=A_CALL_VALUE
)
- # Source: lll
- # { [[ (PC) ]] (ADD 1 (CALL 50000 23 0 0 0 0)) } # noqa: E501
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(
- key=Op.PC,
- value=Op.ADD(
- 0x1,
- Op.CALL(
- gas=0xC350,
- address=0xD6CD6EC9ADCA299F2BBFD754FF8BCF6A4B9AAE40,
- value=0x17,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- ),
- )
- + Op.STOP,
- balance=23,
- nonce=0,
- address=Address(0x44EB1162303B6A60F2F8882D43D661787B3011E6), # noqa: E501
+ contract_a = pre.deploy_contract(
+ code=Op.SSTORE(key=Op.PC, value=a_value_expr) + Op.STOP,
+ balance=A_INITIAL_BALANCE,
)
+ # PC keys: each store's key is the code offset of its PC opcode,
+ # which sits right after the assembled value expression.
+ a_key = len(bytes(a_value_expr))
+ b_key = len(bytes(b_value_expr))
+
tx = Transaction(
- sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=1000000,
- value=0x186A0,
+ sender=pre.fund_eoa(),
+ to=contract_a,
+ gas_limit=TX_GAS_LIMIT,
)
+ a_val, b_val, a_delta, b_delta = predict_final_state(
+ fork, TX_GAS_LIMIT, contract_b
+ )
post = {
- target: Account(storage={36: 1}),
- addr: Account(storage={38: 1}),
+ contract_a: Account(
+ storage={a_key: a_val},
+ balance=A_INITIAL_BALANCE + a_delta,
+ ),
+ contract_b: Account(
+ storage={b_key: b_val},
+ balance=B_INITIAL_BALANCE + b_delta,
+ ),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stSystemOperationsTest/test_ab_acalls3.py b/tests/ported_static/stSystemOperationsTest/test_ab_acalls3.py
index 8f2b77d0966..5647ebd2c95 100644
--- a/tests/ported_static/stSystemOperationsTest/test_ab_acalls3.py
+++ b/tests/ported_static/stSystemOperationsTest/test_ab_acalls3.py
@@ -1,8 +1,22 @@
"""
-Test_ab_acalls3.
+Verify mutual A<->B recursion where each side reserves 100,000 gas.
+
+Both contracts bump their own depth counter during descent, then call
+the other side forwarding everything but a 100,000-gas reserve (A sends
+one wei each level; B sends nothing back). Nothing runs after the call,
+so only the single deepest level dies of gas and every completed
+level's counter bump and transfer persist: the counters and balances
+pin exactly how many rounds the budget sustains.
Ported from:
state_tests/stSystemOperationsTest/ABAcalls3Filler.json
+
+@manually-enhanced: Do not overwrite. The post state (counters and
+balances) is predicted by an exact fork-derived replay of the gas flow
+(EIP-150 grants, stipend gifting, warm/cold and SSTORE pricing via
+opcode metadata, EIP-8037 state-gas spill), validated against the
+ported Cancun counters. B reaches A as its CALLER instead of a
+hardcoded address.
"""
import pytest
@@ -10,8 +24,8 @@
Account,
Address,
Alloc,
- Bytes,
- Environment,
+ Bytecode,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,76 +34,184 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+COUNTER_SLOT = 0
+# Gas each level keeps back for itself before forwarding the rest.
+GAS_RESERVE = 100_000
+A_CALL_VALUE = 1
+# One transfer per level up to the call-depth limit can never run dry.
+A_INITIAL_BALANCE = A_CALL_VALUE * 1024
+# Ported budget; pins how many rounds the recursion sustains.
+TX_GAS_LIMIT = 10_000_000
+
+
+def predict_depths(
+ fork: Fork, tx_gas_limit: int, b_address: Address
+) -> tuple[int, int]:
+ """
+ Replay the mutual recursion's gas flow.
+
+ Return how many A and B levels complete. Descend the alternating
+ call chain: each level bumps its own counter (one cold set per
+ contract, then dirty rewrites), pays its call charges, and forwards
+ everything but the reserve under the EIP-150 63/64 rule; once the
+ reserve underflows, the wrapped ask forwards the 63/64 maximum.
+ Nothing runs after a call, so only the single deepest level dies
+ and its bump and incoming transfer revert. Every cost is derived
+ from the fork via opcode metadata, including EIP-8037 state gas:
+ with a sub-cap gas limit the state reservoir is zero, so state
+ charges spill from the charging frame's own gas.
+ """
+ push_cost = Op.PUSH1[0].gas_cost(fork)
+ # The ask expression's SUB runs after GAS reads gas_left.
+ post_gas_read = Op.SUB.gas_cost(fork)
+ # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally.
+ stipend = fork.gas_costs().CALL_STIPEND
+
+ def raw_store_cost(key_warm: bool, current: int, new: int) -> int:
+ """Cost of a bare SSTORE; original value is always zero here."""
+ return Op.SSTORE(
+ key_warm=key_warm,
+ original_value=0,
+ current_value=current,
+ new_value=new,
+ ).gas_cost(fork)
+
+ sstore_warm_set = raw_store_cost(True, 0, 1)
+ sstore_warm_dirty = raw_store_cost(True, 1, 2)
+
+ def bump_statics(key_warm: bool) -> int:
+ """Counter-bump costs before its SSTORE (value expr plus key)."""
+ return (
+ Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost(
+ fork
+ )
+ + push_cost
+ )
+
+ def call_split(
+ address: Address | Op, warm: bool, value: int
+ ) -> tuple[int, int]:
+ """Pre-GAS-read and upfront charges of one side's call."""
+ upfront = Op.CALL(
+ address_warm=warm, value_transfer=value > 0
+ ).gas_cost(fork)
+ composite = Op.CALL(
+ gas=Op.SUB(Op.GAS, GAS_RESERVE),
+ address=address,
+ value=value,
+ address_warm=warm,
+ value_transfer=value > 0,
+ ).gas_cost(fork)
+ return composite - upfront - post_gas_read, upfront
+
+ a_pre, a_upfront_cold = call_split(b_address, False, A_CALL_VALUE)
+ _, a_upfront_warm = call_split(b_address, True, A_CALL_VALUE)
+ # A is the transaction target: always warm for B's call back.
+ b_pre, b_upfront = call_split(Op.CALLER, True, 0)
+
+ gas = (
+ tx_gas_limit
+ - fork.transaction_intrinsic_cost_calculator()()
+ - fork.transaction_top_frame_state_gas()
+ )
+ level = 0
+ a_balance = A_INITIAL_BALANCE
+ while True:
+ level += 1
+ is_a = level % 2 == 1
+ # Each contract's first level pays the cold counter set.
+ first = level <= 2
+ gas -= bump_statics(key_warm=not first)
+ if gas < 0 or gas <= stipend:
+ break
+ gas -= sstore_warm_set if first else sstore_warm_dirty
+ if gas < 0:
+ break
+ gas -= a_pre if is_a else b_pre
+ if gas < 0:
+ break
+ gas_read = gas
+ if is_a:
+ gas -= post_gas_read + (
+ a_upfront_cold if level == 1 else a_upfront_warm
+ )
+ else:
+ gas -= post_gas_read + b_upfront
+ if gas < 0:
+ break
+ assert level < 1024, "recursion must die of gas, not depth"
+ if is_a:
+ assert a_balance >= A_CALL_VALUE, "transfer must be funded"
+ a_balance -= A_CALL_VALUE
+ # A reserve underflow wraps mod 2**256: an effectively infinite
+ # ask, clamped to the 63/64 forwardable maximum.
+ ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256
+ forwarded = min(ask, gas - gas // 64)
+ gas = forwarded + (stipend if is_a else 0)
+
+ completed = level - 1
+ assert completed >= 2, "both sides must run at least once"
+ a_count = (completed + 1) // 2
+ b_count = completed // 2
+ return a_count, b_count
+
@pytest.mark.ported_from(
["state_tests/stSystemOperationsTest/ABAcalls3Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_ab_acalls3(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_ab_acalls3."""
- 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=100000000,
- )
+ """Pin how many rounds a reserve-throttled A<->B recursion runs."""
- # Source: lll
- # { [[ 0 ]] (ADD (SLOAD 0) 1) (CALL (- (GAS) 100000) 1 0 0 0 0) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.CALL(
- gas=Op.SUB(Op.GAS, 0x186A0),
- address=0xA890CEB693666313E0A5A1BE4F59F06C1E33F5C9,
- value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ def bounce_code(call: Bytecode) -> Bytecode:
+ """Bump the own-depth counter, then call the other side."""
+ return (
+ Op.SSTORE(
+ key=COUNTER_SLOT,
+ value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1),
+ )
+ + call
+ + Op.STOP
)
- + Op.STOP,
- balance=0xFA3E8,
- nonce=0,
- address=Address(0x4776B53DEB22F16581088F679DBA75E205B65D34), # noqa: E501
+
+ # B calls whoever called it, so it needs no embedded address.
+ contract_b = pre.deploy_contract(
+ code=bounce_code(
+ Op.CALL(gas=Op.SUB(Op.GAS, GAS_RESERVE), address=Op.CALLER)
+ ),
)
- # Source: lll
- # { [[ 0 ]] (ADD (SLOAD 0) 1) (CALL (- (GAS) 100000) 0 0 0 0 0) } # noqa: E501
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.CALL(
- gas=Op.SUB(Op.GAS, 0x186A0),
- address=0x4776B53DEB22F16581088F679DBA75E205B65D34,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- + Op.STOP,
- nonce=0,
- address=Address(0xA890CEB693666313E0A5A1BE4F59F06C1E33F5C9), # noqa: E501
+ contract_a = pre.deploy_contract(
+ code=bounce_code(
+ Op.CALL(
+ gas=Op.SUB(Op.GAS, GAS_RESERVE),
+ address=contract_b,
+ value=A_CALL_VALUE,
+ )
+ ),
+ balance=A_INITIAL_BALANCE,
)
tx = Transaction(
- sender=sender,
- to=target,
- data=Bytes(""),
- gas_limit=10000000,
- value=0x186A0,
+ sender=pre.fund_eoa(),
+ to=contract_a,
+ gas_limit=TX_GAS_LIMIT,
)
+ a_count, b_count = predict_depths(fork, TX_GAS_LIMIT, contract_b)
+ # Each completed B level keeps the wei its calling A level sent.
post = {
- target: Account(storage={0: 52}),
- addr: Account(storage={0: 52}),
+ contract_a: Account(
+ storage={COUNTER_SLOT: a_count},
+ balance=A_INITIAL_BALANCE - b_count * A_CALL_VALUE,
+ ),
+ contract_b: Account(
+ storage={COUNTER_SLOT: b_count},
+ balance=b_count * A_CALL_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/stSystemOperationsTest/test_call_recursive_bomb3.py b/tests/ported_static/stSystemOperationsTest/test_call_recursive_bomb3.py
index ee666d8bf61..eaac392a28b 100644
--- a/tests/ported_static/stSystemOperationsTest/test_call_recursive_bomb3.py
+++ b/tests/ported_static/stSystemOperationsTest/test_call_recursive_bomb3.py
@@ -1,17 +1,29 @@
"""
-Test_call_recursive_bomb3.
+Verify a self-recursive CALL bomb that keeps only a 224-gas reserve.
+
+Each level bumps a shared depth counter and forwards everything but a
+tiny reserve to a call to itself, so descent is throttled only by the
+EIP-150 63/64 withhold. On the way back up a level must afford its
+success-flag store from its 1/64 retention plus whatever its child
+returned; levels that cannot (EIP-2200's stipend rule included) halt
+and forfeit, so the surviving storage pins the exact depth the budget
+sustains.
Ported from:
state_tests/stSystemOperationsTest/CallRecursiveBomb3Filler.json
+
+@manually-enhanced: Do not overwrite. The post state is predicted by an
+exact fork-derived replay of the recursion's gas flow (EIP-150 grants,
+returned-leftover propagation, warm/cold and SSTORE pricing via opcode
+metadata, EIP-8037 state-gas spill), validated against the ported
+Cancun depth.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,58 +32,179 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+COUNTER_SLOT = 0
+RESULT_SLOT = 1
+# Gas each level keeps back; far below a cold store, so completing the
+# post-call flag store depends on the 1/64 retention and the child's
+# returned leftover.
+GAS_RESERVE = 224
+# Ported budget; pins the OOG-terminated depth.
+TX_GAS_LIMIT = 1_000_000
+
+RECURSION_CODE = (
+ Op.SSTORE(
+ key=COUNTER_SLOT,
+ value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1),
+ )
+ + Op.SSTORE(
+ key=RESULT_SLOT,
+ value=Op.CALL(
+ gas=Op.SUB(Op.GAS, GAS_RESERVE),
+ address=Op.ADDRESS,
+ ),
+ )
+ + Op.STOP
+)
+
+
+def predict_recursion_storage(fork: Fork, tx_gas_limit: int) -> dict[int, int]:
+ """
+ Replay the recursion's gas flow and return the surviving storage.
+
+ Descend the self-call chain computing each level's EIP-150 grant,
+ then unwind: a level that cannot afford its flag store halts and
+ forfeits its entire grant to its parent, so the deepest level that
+ completes fixes the surviving depth counter (deeper levels' writes
+ and warmth all revert). The level above the deepest survivor funds
+ its more expensive zero-to-one flag set partly from the survivor's
+ returned leftover. Every cost is derived from the fork via opcode
+ metadata, including EIP-8037 state gas: with a sub-cap gas limit
+ the state reservoir is zero, so state charges spill from the
+ charging frame's own gas.
+ """
+ push_cost = Op.PUSH1[0].gas_cost(fork)
+ # The ask expression's SUB runs after GAS reads gas_left.
+ post_gas_read = Op.SUB.gas_cost(fork)
+ # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally.
+ stipend = fork.gas_costs().CALL_STIPEND
+
+ def raw_store_cost(key_warm: bool, current: int, new: int) -> int:
+ """Cost of a bare SSTORE; original value is always zero here."""
+ return Op.SSTORE(
+ key_warm=key_warm,
+ original_value=0,
+ current_value=current,
+ new_value=new,
+ ).gas_cost(fork)
+
+ sstore_warm_set = raw_store_cost(True, 0, 1)
+ sstore_warm_dirty = raw_store_cost(True, 1, 2)
+ sstore_warm_noop = raw_store_cost(True, 1, 1)
+ sstore_cold_noop = raw_store_cost(False, 0, 0)
+
+ def bump_statics(key_warm: bool) -> int:
+ """Counter-bump costs before its SSTORE (value expr plus key)."""
+ return (
+ Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost(
+ fork
+ )
+ + push_cost
+ )
+
+ bump_statics_cold = bump_statics(False)
+ bump_statics_warm = bump_statics(True)
+
+ ask_expr = Op.SUB(Op.GAS, GAS_RESERVE)
+ call_upfront = Op.CALL(address_warm=True).gas_cost(fork)
+ # Everything charged before GAS reads gas_left: the call's argument
+ # pushes, ADDRESS, and the reserve push plus the GAS opcode itself.
+ pre_gas_read = (
+ Op.CALL(gas=ask_expr, address=Op.ADDRESS, address_warm=True).gas_cost(
+ fork
+ )
+ - call_upfront
+ - post_gas_read
+ )
+
+ # Descend: compute each level's grant until a level dies mid-frame.
+ gas = (
+ tx_gas_limit
+ - fork.transaction_intrinsic_cost_calculator()()
+ - fork.transaction_top_frame_state_gas()
+ )
+ levels: list[tuple[int, int]] = []
+ level = 0
+ while True:
+ level += 1
+ first = level == 1
+ gas -= bump_statics_cold if first else bump_statics_warm
+ if gas < 0 or gas <= stipend:
+ break
+ gas -= sstore_warm_set if first else sstore_warm_dirty
+ if gas < 0:
+ break
+ gas -= pre_gas_read
+ if gas < 0:
+ break
+ gas_read = gas
+ gas -= post_gas_read + call_upfront
+ if gas < 0:
+ break
+ assert level < 1024, "recursion must die of gas, not depth"
+ # A reserve underflow wraps mod 2**256: an effectively infinite
+ # ask, clamped to the 63/64 forwardable maximum.
+ ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256
+ forwarded = min(ask, gas - gas // 64)
+ levels.append((gas, forwarded))
+ gas = forwarded
+
+ # Unwind: a failed level forfeits its whole grant to its parent.
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ for lvl in range(len(levels), 0, -1):
+ available, forwarded = levels[lvl - 1]
+ gas = available - forwarded + (leftover if child_ok else 0)
+ # Flag store: push the slot key, then store the success flag.
+ # Below the deepest completing level everything reverts, so its
+ # own store finds a cold slot and a zero current value.
+ gas -= push_cost
+ ok = gas >= 0 and gas > stipend
+ if ok:
+ if not child_ok:
+ result_store = sstore_cold_noop
+ elif result_below == 0:
+ result_store = sstore_warm_set
+ else:
+ result_store = sstore_warm_noop
+ gas -= result_store
+ ok = gas >= 0
+ if ok:
+ if not child_ok:
+ survivor = lvl
+ result_below = 1 if child_ok else 0
+ leftover = gas
+ child_ok = True
+ else:
+ child_ok = False
+ result_below = 0
+ leftover = 0
+ survivor = 0
+ assert child_ok and survivor > 0, "the top level must complete"
+ return {COUNTER_SLOT: survivor, RESULT_SLOT: result_below}
+
@pytest.mark.ported_from(
["state_tests/stSystemOperationsTest/CallRecursiveBomb3Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_call_recursive_bomb3(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_call_recursive_bomb3."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
-
- # Source: lll
- # { [[ 0 ]] (+ (SLOAD 0) 1) [[ 1 ]] (CALL (- (GAS) 224) (ADDRESS) 0 0 0 0 0) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1))
- + Op.SSTORE(
- key=0x1,
- value=Op.CALL(
- gas=Op.SUB(Op.GAS, 0xE0),
- address=Op.ADDRESS,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- ),
- )
- + Op.STOP,
- balance=0x1312D00,
- nonce=0,
- )
+ """Pin the depth a thin-reserve CALL self-recursion sustains."""
+ target = pre.deploy_contract(code=RECURSION_CODE)
tx = Transaction(
- sender=sender,
+ sender=pre.fund_eoa(),
to=target,
- data=Bytes(""),
- gas_limit=1000000,
- value=0x186A0,
+ gas_limit=TX_GAS_LIMIT,
)
- post = {target: Account(storage={0: 18, 1: 1})}
+ post = {
+ target: Account(storage=predict_recursion_storage(fork, TX_GAS_LIMIT)),
+ }
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py
index b20492dea03..fcb7f0e4d7a 100644
--- a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py
+++ b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py
@@ -1,11 +1,13 @@
"""
-A single contract can execute SELFDESTRUCT multiple times using by...
-
-multiple times. The second and later SELFDESTRUCTs have little effect but can
-touch some new beneficiary addresses.
+Verify a contract executing SELFDESTRUCT twice in one transaction: the
+second has little effect but touches a new beneficiary address.
Ported from:
state_tests/stSystemOperationsTest/doubleSelfdestructTouch_ParisFiller.yml
+
+@manually-enhanced: Do not overwrite. Both forwarded call budgets derive
+from the fork (the callee's cold first-set store is state-priced under
+EIP-8037 and must fit the grant).
"""
import pytest
@@ -85,6 +87,16 @@ def test_double_selfdestruct_touch_paris(
gas_limit=30000000,
)
+ # Derived budget for each selfdestruct call: the callee's cold
+ # first-set store is state-priced under EIP-8037 and must fit the
+ # grant; the margin covers its SLOAD, SELFDESTRUCT, and accesses.
+ sd_call_gas = (
+ Op.SSTORE(
+ key=0x0, value=0x1, key_warm=False, original_value=0, new_value=1
+ ).gas_cost(fork)
+ + 20_000
+ )
+
pre[sender] = Account(balance=0x5F5E102)
pre[empty_account_1] = Account(balance=10)
pre[empty_account_2] = Account(balance=10)
@@ -120,7 +132,7 @@ def test_double_selfdestruct_touch_paris(
+ Op.SWAP1
+ Op.POP(
Op.CALL(
- gas=0x11170,
+ gas=sd_call_gas,
address=0x29E4504A3D2A0E0AE0EBBBEFEDD4570639B3EBEE,
value=Op.DUP6,
args_offset=Op.DUP1,
@@ -131,7 +143,7 @@ def test_double_selfdestruct_touch_paris(
)
+ Op.SUB
+ Op.PUSH20[0x29E4504A3D2A0E0AE0EBBBEFEDD4570639B3EBEE]
- + Op.PUSH3[0x11170]
+ + Op.PUSH3[sd_call_gas]
+ Op.CALL
+ Op.STOP,
nonce=0,
diff --git a/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py b/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py
index e14591f4675..44ea5d78185 100644
--- a/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py
+++ b/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py
@@ -1,5 +1,6 @@
"""
-Test_opcodes_transaction_init.
+Verify each opcode family executes inside a creation transaction's init
+code, including invalid-code and side-effect cases.
Ported from:
state_tests/stTransactionTest/Opcodes_TransactionInitFiller.json
@@ -838,7 +839,7 @@ def test_opcodes_transaction_init(
g: int,
v: int,
) -> None:
- """Test_opcodes_transaction_init."""
+ """Run each opcode inside a creation transaction's init code."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
contract_1 = Address(0x0F572E5295C57F15886F9B263E2F6D2D6C7B5EC6)
@@ -1468,7 +1469,16 @@ def test_opcodes_transaction_init(
Op.SELFDESTRUCT(address=Op.ORIGIN),
Bytes("ef"),
Op.CALL(
- gas=0xC350,
+ # Derived: the callee's cold first-set store is state-priced
+ # under EIP-8037 and must fit the grant.
+ gas=Op.SSTORE(
+ key=0x0,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ ).gas_cost(fork)
+ + 5_000,
address=contract_0,
value=Op.DUP1,
args_offset=Op.DUP1,
@@ -1503,7 +1513,9 @@ def test_opcodes_transaction_init(
+ Op.MSTORE8(offset=0x0, value=0xEF)
+ Op.RETURN(offset=0x0, size=0x1),
]
- tx_gas = [400000]
+ # The d120 arm's nested CREATE adds a new-account state charge under
+ # EIP-8037 (0 before); every other arm keeps the ported budget.
+ tx_gas = [400000 + (fork.create_state_gas() if d == 120 else 0)]
tx_value = [100000]
tx = Transaction(
diff --git a/tests/ported_static/stTransactionTest/test_store_gas_on_create.py b/tests/ported_static/stTransactionTest/test_store_gas_on_create.py
index 2d97f61d885..0cad55501a9 100644
--- a/tests/ported_static/stTransactionTest/test_store_gas_on_create.py
+++ b/tests/ported_static/stTransactionTest/test_store_gas_on_create.py
@@ -1,17 +1,21 @@
"""
-Test_store_gas_on_create.
+Verify the gas a CREATE's init code observes when the creating contract is
+entered directly by the transaction: the child receives all but one 64th
+of what remains in the creating frame.
Ported from:
state_tests/stTransactionTest/StoreGasOnCreateFiller.json
+
+@manually-enhanced: Do not overwrite. The ported bytecode is kept, but the
+transaction budget and the child's stored GAS observation are derived from
+the fork (the ported absolute pin moved with every schedule change).
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -21,51 +25,79 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CHILD_GAS_SLOT = 0xFD
+
@pytest.mark.ported_from(
["state_tests/stTransactionTest/StoreGasOnCreateFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_store_gas_on_create(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_store_gas_on_create."""
- coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0x17D78400)
+ """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)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ 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),
+ )
+ creator = pre.deploy_contract(
+ code=setup + Op.POP(create_code) + Op.STOP,
)
- # Source: lll
- # { (MSTORE 0 0x5a60fd55) (CREATE 0 28 4)}
- coinbase = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=0x5A60FD55)
- + Op.CREATE(value=0x0, offset=0x1C, size=0x4)
- + Op.STOP,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ # Fork-derived budget with margin left after the child's work.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = (
+ intrinsic
+ + setup.gas_cost(fork)
+ + create_code.gas_cost(fork)
+ + child_code.gas_cost(fork)
+ + 15_000
)
tx = Transaction(
- sender=sender,
- to=coinbase,
- data=Bytes(""),
- gas_limit=131882,
- value=100,
+ sender=pre.fund_eoa(),
+ to=creator,
+ gas_limit=gas_limit,
+ )
+
+ # The child receives all but one 64th of what remains after the
+ # setup and the CREATE's own charges; its GAS read costs 2.
+ base = (
+ gas_limit
+ - intrinsic
+ - 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)
post = {
- compute_create_address(address=coinbase, nonce=0): Account(
- storage={253: 0x12F39}
+ compute_create_address(address=creator, nonce=1): 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/stTransactionTest/test_suicides_and_internal_call_suicides_success.py b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py
index 03537745a43..91b32041a05 100644
--- a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py
+++ b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py
@@ -1,8 +1,14 @@
"""
-Test_suicides_and_internal_call_suicides_success.
+Verify SELFDESTRUCT inside an internal call: the callee self-destructs to
+a previously nonexistent beneficiary, which materializes only when the
+forwarded gas covers the new-account charge.
Ported from:
state_tests/stTransactionTest/SuicidesAndInternalCallSuicidesSuccessFiller.json
+
+@manually-enhanced: Do not overwrite. The two forwarded-gas calldata words
+derive from the fork's SELFDESTRUCT new-account cost (state-priced under
+EIP-8037), keeping one arm starved and one funded on every fork.
"""
import pytest
@@ -59,7 +65,7 @@ def test_suicides_and_internal_call_suicides_success(
g: int,
v: int,
) -> None:
- """Test_suicides_and_internal_call_suicides_success."""
+ """A funded SELFDESTRUCT materializes its beneficiary."""
coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
contract_0 = Address(0x0000000000000000000000000000000000000000)
contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
@@ -127,11 +133,25 @@ def test_suicides_and_internal_call_suicides_success(
post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ # The calldata word is the gas forwarded to the self-destructing
+ # callee. Its SELFDESTRUCT pays a new-account charge for the funded
+ # beneficiary (state-priced under EIP-8037, spilling from the
+ # callee's grant), so both budgets derive from that cost: one starves
+ # it, one funds it with margin.
+ sd_cost = Op.SELFDESTRUCT.with_metadata(
+ address_warm=True, account_new=True
+ ).gas_cost(fork)
tx_data = [
- Hash(0x55F0),
- Hash(0xAAF0),
+ Hash(sd_cost // 2),
+ Hash(sd_cost + 5_000),
+ ]
+ tx_gas = [
+ fork.transaction_intrinsic_cost_calculator()(
+ calldata=Hash(0), sends_value=True
+ )
+ + sd_cost
+ + 40_000
]
- tx_gas = [150000]
tx_value = [10]
tx = Transaction(