diff --git a/.claude/commands/enhance-ported-test.md b/.claude/commands/enhance-ported-test.md
index f6d3f6852e..0d0404b2a0 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 7aba6f38bb..0000000000
--- 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 c5d9aff2a9..0000000000
--- 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/stCreate2/test_create2_oo_gafter_init_code_revert2.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert2.py
index 2b48b4cb0b..8ca16e80b5 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 9efd489bbb..9aef43337a 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 4def59bad8..70ae08055a 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 587c64889c..c61a5f6fed 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 4e4ef237ca..e75adcb8de 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 00ddb3595a..1778241727 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 c15b8760d4..0000000000
--- 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 38873c202b..ac9cbaf99d 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 907358ab62..0000000000
--- 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/stRefundTest/test_refund50_2.py b/tests/ported_static/stRefundTest/test_refund50_2.py
index 4d471f2407..94ed534167 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 d92a991123..ece04d3691 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_refund_call_a.py b/tests/ported_static/stRefundTest/test_refund_call_a.py
index bc745f449b..c42de668a9 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 4083b82695..812a0cb1ed 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 ab754657c7..0d9d5be2f6 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 fc4b0089a7..f94a727fd0 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 1be3fb70d9..9267e1e235 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 11c3aaa568..b123500878 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 3cd7ee838c..9eca41eee7 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 a49cc7b38b..2f6ab5355a 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)