diff --git a/.claude/commands/enhance-ported-test.md b/.claude/commands/enhance-ported-test.md
index f6d3f6852e5..0d0404b2a04 100644
--- a/.claude/commands/enhance-ported-test.md
+++ b/.claude/commands/enhance-ported-test.md
@@ -42,11 +42,12 @@ so a failure is attributable.
- **Checkpoint / done:** fill the whole `valid_from` range (omit `--fork`) so all
deployed forks are exercised.
- **Probe the future fork:** explicitly `--fork Amsterdam` (or the latest fork
- that enables new EIPs). A ported test listed in `amsterdam_skip_list.txt` will
- always show `sss` there — to see its *real* behavior, temporarily remove its
- entry from that file, fill, then restore (or, once fixed, remove it for good —
- see Finishing). A gas/state-cost change there is the most likely future
- breakage.
+ that enables new EIPs). A gas/state-cost change there is the most likely
+ future breakage. (Historical note: broken tests used to be parked in a
+ `tests/ported_static/amsterdam_skip_list.txt` consumed by a local conftest;
+ the list was emptied and both were removed. If a future fork's repricing
+ breaks tests en masse, the same parking pattern — a substring-matched skip
+ list plus a `pytest_collection_modifyitems` hook — is in git history.)
- **`fill` output:** writes to `./fixtures` (`--clean` resets it), or pass
`--output
` for a scratch location. Do **not** use `-o` — that is
pytest's `--override-ini`, not the output dir.
@@ -77,8 +78,8 @@ gas the tx receives, so the body executes fully. See `write-test.md` "Transactio
- **Gas-snapshot tests are gas-sensitive.** If the post asserts a stored `GAS`
reading or a `SUB(@gas_before, GAS)` delta (legacy slots `0` / `0x64`), the
test *measures gas* — handle it under step 10 (preserve via `CodeGasMeasure`),
- do not just strip `gas_limit`. This is the dominant `amsterdam_skip_list.txt`
- shape: the stored gas value is exactly what EIP-8037 re-prices and breaks.
+ do not just strip `gas_limit`. This was the dominant skip-list shape:
+ the stored gas value is exactly what EIP-8037 re-prices and breaks.
- **EIP-8037 caveat:** when you omit `gas_limit` on a test that *measures* an
operation incurring **state gas** (account creation, storage writes), add
`state_gas_reservoir=0` to the tx, or that state gas is silently dropped from
@@ -294,8 +295,8 @@ ties a `CREATE`'s `size` operand to the memory/gas math that depends on it.
on `test_make_money`.
### 10. (Gas-subject / gas-snapshot tests) Replace hardcoded gas with dynamic calculation
-Covers both tests that *assert* a gas amount and the dominant
-`amsterdam_skip_list.txt` shape: a legacy `GAS` snapshot / `SUB(@gas_before,
+Covers both tests that *assert* a gas amount and the dominant broken-port
+shape: a legacy `GAS` snapshot / `SUB(@gas_before,
GAS)` delta stored to slot `0`/`0x64`. That stored value is *why* EIP-8037
breaks the test, but it is real coverage — **preserve and fork-robustify it, do
not drop it.**
@@ -386,6 +387,20 @@ previous_bytes=)`; EIP-3860 init-code words → `fork.gas_costs().CODE_INIT_PER_
* ceil(size/32)`. You can also call `.gas_cost` / `.regular_cost` / `.state_cost`
on exactly the measured bytecode.
+**Reservoir-less sub-calls pay state gas from their regular grant.** With
+the tx reservoir at 0, a sub-frame's state charges spill from its own
+`gas_left` — a delegate that does one first-set SSTORE needs its *whole*
+~111k inside the forwarded grant on Amsterdam, not just the ~13k regular
+part. Size derived sub-call budgets from the callee composite's full
+`gas_cost(fork)`. Corollaries: (a) a *failed* sub-frame contributes its
+entire forfeited grant to the parent's measured window, not its "cost";
+(b) `SSTORE(flag, )` silently degrades to a ~3k no-op store when
+the call fails — the flag reads 0 and no state gas is charged, which can
+mask a broken callee behind a plausible-looking measurement. Validated on
+`test_new_gas_price_for_codes` (delegate budget derived; failed value
+calls return their stipends: subtract one `CALL_STIPEND` per failed
+value-bearing call from window measurements).
+
**Nested / callee-side measurements.** When the measured op is a `CALL` whose
callee does real work, the measured cost = `call_code.gas_cost(fork) +
callee_code.gas_cost(fork)` (the CALL's own cost plus what the callee consumed).
@@ -402,6 +417,88 @@ transition, not the magnitude — which also breaks the `forward_gas`/`new_value
circularity.) Set `state_gas_reservoir=0` so the state gas is captured.
Validated on `test_raw_call_gas`.
+**An expensive store after a callee that eats all forwarded gas — pre-write
+the slot.** When a frame must SSTORE a result *after* a subcall that
+deliberately consumes its whole 63/64 grant (an OOG-probe callee), the frame
+retains only 1/64 — under EIP-8037 that cannot afford a cold zero→nonzero
+store (~111k), and pre-8037 it often couldn't afford the cold 2.2k either
+(making the ported `{slot: 0}` expectation vacuous: caller-OOG and
+callee-failure were indistinguishable). Fix: write a sentinel to the slot
+*before* the call (paying cold + state with the full budget), then store
+`BASE + result` after it — now a dirty-warm write (100 gas) the retention
+always covers, and the three outcomes (success `BASE+1`, failure `BASE`,
+caller OOG `sentinel`) are all distinct. Validated on
+`test_static_execute_call_that_ask_fore_gas_then_trabsaction_has`.
+**Caveat — EIP-2200's stipend rule caps this trick.** Any SSTORE (even a
+100-gas dirty-warm one) exceptionally halts unless `gas_left > 2300`
+(Istanbul+), so the 1/64 retention must exceed ~2400, i.e. the pre-call
+budget must exceed ~154k. When the scenario *requires* a smaller budget
+(e.g. a starved arm whose forwarded gas must undercut the callee's cost),
+no post-call SSTORE is possible at all: write the sentinel *before* the
+call and put nothing but a `POP` after it — frame completion (the account
+persists with the sentinel) plus the callee-side observable already
+separate the outcomes. Validated on
+`test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided`.
+
+**Refund-cap derivations need the EIP-7623 kwarg.** The EIP-3529 cap's
+base is the gas deducted before execution, which excludes the calldata
+floor: pass `return_cost_deducted_prior_execution=True` to the intrinsic
+calculator whenever the tx has calldata, or the derived `executed` (and
+the cap) overstate. Validated on `test_refund_suicide50procent_cap`.
+
+**A CREATE address collision burns the child's gas allowance** (the
+EIP-684 path): the withheld child grant is consumed, nothing is created,
+and under EIP-8037 the new-account state charge is refunded. Useful to
+build always-failing creator frames with predictable consumption.
+Validated on `test_revert_depth_create_address_collision`.
+
+**Loop-to-depth-1024 cannot replace loop-to-OOG.** With 63/64
+attenuation, reaching depth 1024 needs ~e^16 × the terminal gas — no
+legal budget gets there. For call-loop depth tests the honest shape is a
+fixed named budget with per-gas-schedule-era pinned depth counts, each
+shift explained (±1 frame ≈ 64·ln(cost ratio)). Validated on
+`test_loop_calls_depth_then_revert`.
+
+**Framework wart: the SSTORE dirty-rewrite composite prices 100 on every
+fork**, but Constantinople/Petersburg charge 5,000 for a dirty re-store —
+a derived budget that must survive pre-Istanbul forks needs an explicit
+headroom constant for it (named, commented). Observed on
+`test_revert_depth_create_address_collision`'s ConstantinopleFix sweep.
+
+**EIP-8037 repriced the code deposit's regular part — boundaries beware.**
+On 8037 forks the deposit charges only the keccak word cost
+(`OPCODE_KECCAK256_PER_WORD * ceil32(len)/32`, ~6 gas) as regular gas plus
+`len * 1530` state; `fork.gas_costs().CODE_DEPOSIT_PER_BYTE` (200) is the
+*pre-8037* constant. Using 200/byte in a *sufficiency* budget merely
+overshoots (safe); using it in a one-gas-short *boundary* silently funds
+the deposit on Amsterdam. Branch on `fork.is_eip_enabled(8037)` for exact
+deposit boundaries. Validated on
+`test_create_oo_gafter_init_code_returndata_size`.
+
+**Match the intrinsic calculator's kwargs to the transaction's shape.**
+`fork.transaction_intrinsic_cost_calculator()()` defaults to
+`sends_value=False`; under EIP-2780 a value-bearing transaction's intrinsic
+includes the folded value-transfer cost (~5.9k), so a derived budget or
+GAS-observation formula silently skews by that amount on Amsterdam only.
+Pass `sends_value=True` when the tx carries value — or drop an incidental
+tx `value` entirely (step 5) so the default holds. Validated on
+`test_store_gas_on_create`.
+
+**A creation transaction's top frame pays new-account state gas
+(EIP-8037) — but only for a fresh target.** When deriving a create-tx
+budget, the intrinsic calculator does not include the created account's
+state gas — add
+`fork.transaction_top_frame_state_gas(contract_creation=True)` (183,600 on
+Amsterdam, 0 before) or the whole creation silently OOGs only on the
+future fork. Exception: `prepare_dispatch` charges it only when the
+target's *pre-state* account is `EMPTY_ACCOUNT` — a prefunded create
+address pays nothing (validated on
+`test_out_of_gas_prefunded_contract_creation`, whose budgets omit the
+term). A nested CREATE's new-account state is charged to the parent
+before the 63/64 withhold and refunded if the child fails, so a derived
+budget must cover its *peak* (use the composite `gas_cost(fork)`), even
+on paths where the net is zero.
+
**Measuring forwarded gas / the EIP-150 63/64 rule (the `*_gas_ask` shape).**
Ported fillers probe "how much gas does a subcall receive when it asks for more
than is available" by pinning an absolute forwarded amount — fork-fragile,
@@ -499,11 +596,10 @@ Not yet covered by a validated walkthrough; figure out and append when hit:
## Finishing
-**Remove the skip-list entry.** Once the test passes on the future fork, delete
-its line from `tests/ported_static/amsterdam_skip_list.txt` and decrement both
-its per-directory count header (`# stXxx (N)`) and the `# Total entries:` count.
-Confirm with a full-range fill (`--fork` omitted) with the entry gone — that is
-the definition of done.
+**Confirm with a full-range fill** (`--fork` omitted) — every deployed fork
+green is the definition of done. (If a skip list is ever reintroduced for a
+future fork, also delete the test's entry and keep its count headers
+accurate.)
**Final sweep checklist** — each of these has been missed in practice; check
them one by one before calling the test done:
diff --git a/tests/ported_static/amsterdam_skip_list.txt b/tests/ported_static/amsterdam_skip_list.txt
index 7aba6f38bb0..eb495840c56 100644
--- a/tests/ported_static/amsterdam_skip_list.txt
+++ b/tests/ported_static/amsterdam_skip_list.txt
@@ -8,10 +8,9 @@
# Entries are substring-matched against each pytest nodeid (after
# stripping the fixture-format suffix in conftest.py).
#
-# Total entries: 153
+# Total entries: 116
-# stAttackTest (1)
-stAttackTest/test_crashing_transaction.py::test_crashing_transaction[fork_Amsterdam]
+# stAttackTest (0)
# stBadOpcode (4)
stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE2]
@@ -37,8 +36,7 @@ stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py:
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]
+# stCallDelegateCodesCallCodeHomestead (0)
# stCreate2 (31)
stCreate2/test_create2_oo_gafter_init_code_revert2.py::test_create2_oo_gafter_init_code_revert2[fork_Amsterdam]
@@ -73,20 +71,7 @@ stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_dept
stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v0]
stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v1]
-# stCreateTest (36)
-stCreateTest/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 (23)
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]
@@ -117,24 +102,19 @@ stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterd
stDelegatecallTestHomestead/test_delegatecall1024_oog.py::test_delegatecall1024_oog[fork_Amsterdam]
stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py::test_delegatecall_in_initcode_to_existing_contract[fork_Amsterdam]
-# stEIP150Specific (7)
+# stEIP150Specific (6)
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]
+# stEIP150singleCodeGasPrices (0)
-# stEIP158Specific (1)
-stEIP158Specific/test_exp_empty.py::test_exp_empty[fork_Amsterdam]
+# stEIP158Specific (0)
-# 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]
+# stHomesteadSpecific (0)
# stInitCodeTest (7)
stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d0-g0]
@@ -145,20 +125,16 @@ stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_p
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 (3)
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]
+# stMemoryTest (0)
-# stRefundTest (7)
+# stRefundTest (6)
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]
@@ -178,31 +154,18 @@ stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_rever
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]
+# stSStoreTest (0)
-# 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]
+# stSolidityTest (0)
# 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 (3)
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]
+
+# stTransactionTest (0)
diff --git a/tests/ported_static/stAttackTest/test_crashing_transaction.py b/tests/ported_static/stAttackTest/test_crashing_transaction.py
index 8f8320dd1b8..d775e035754 100644
--- a/tests/ported_static/stAttackTest/test_crashing_transaction.py
+++ b/tests/ported_static/stAttackTest/test_crashing_transaction.py
@@ -1,8 +1,17 @@
"""
-Https://ropsten.etherscan.io/tx/0x8ec445380649f6c75a042a438ea9256c2fab2a...
+Verify the Ropsten "crashing transaction" attack replay: a creation
+transaction whose init code CREATEs children in a loop while more than
+50000 gas remains, then deposits its runtime code.
Ported from:
state_tests/stAttackTest/CrashingTransactionFiller.json
+
+@manually-enhanced: Do not overwrite. On pre-EIP-8037 forks the loop
+drains to the ported child count (created nonce 124); under EIP-8037
+with the revised EIP-8038 pricing an iteration is dearer (new-account
+plus code-deposit state gas spill from the frame) but still fits the
+loop's 50000-gas guard, so the loop drains earlier and deposits with
+fewer children — the split post pins both child counts.
"""
import pytest
@@ -11,6 +20,7 @@
Address,
Alloc,
Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -25,12 +35,14 @@
["state_tests/stAttackTest/CrashingTransactionFiller.json"],
)
@pytest.mark.valid_from("Cancun")
+# Required: the sender is funded at the attack's historical nonce 3270.
@pytest.mark.pre_alloc_mutable
def test_crashing_transaction(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Https://ropsten."""
+ """Replay the attack loop; EIP-8037 shrinks the child count."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000, nonce=3270)
@@ -95,13 +107,25 @@ def test_crashing_transaction(
gas_price=11,
)
- post = {
- sender: Account(nonce=3271),
- compute_create_address(address=sender, nonce=3270): Account(
+ created = compute_create_address(address=sender, nonce=3270)
+ if fork.is_eip_enabled(8037):
+ # An iteration's state gas spill makes each pass dearer while
+ # still fitting the loop's 50000-gas guard, so the loop drains
+ # after far fewer children than the ported count.
+ created_account = Account(
+ code=bytes.fromhex("60606040526008565b00"),
+ balance=1,
+ nonce=23,
+ )
+ else:
+ created_account = Account(
code=bytes.fromhex("60606040526008565b00"),
balance=1,
nonce=124,
- ),
+ )
+ post = {
+ sender: Account(nonce=3271),
+ created: created_account,
}
state_test(env=env, pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py
index 9d540998144..ae588b130d8 100644
--- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py
+++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py
@@ -1,12 +1,15 @@
"""
-Test_callcallcallcode_001_suicide_end.
+Verify a CALLCODE -> CALLCODE -> (DELEGATECALL + SELFDESTRUCT) chain:
+every store lands in the outermost target's storage and the SELFDESTRUCT
+(running in the target's context) destroys the target.
Ported from:
state_tests/stCallDelegateCodesCallCodeHomestead/callcallcallcode_001_SuicideEndFiller.json
-@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas
-values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget.
-
+@manually-enhanced: Do not overwrite. The three call budgets are derived
+bottom-up from the fork (each frame pays its stores' state gas from its
+own grant under EIP-8037), and the target's post code is coupled to the
+composed bytecode (the gas operand varies by fork).
"""
import pytest
@@ -38,17 +41,16 @@ def test_callcallcallcode_001_suicide_end(
pre: Alloc,
fork: Fork,
) -> None:
- """Test_callcallcallcode_001_suicide_end."""
- # EIP-8037 inner-CALL gas bumps: original values restored for
- # pre-EIP-8037 forks; bumped values cover the per-storage state-
- # gas spill into regular gas on Amsterdam.
- outer_call_gas = 150000
- middle_call_gas = 100000
- inner_call_gas = 50000
- if fork.is_eip_enabled(8037):
- outer_call_gas = 1000000
- middle_call_gas = 800000
- inner_call_gas = 100000
+ """Chained callcode stores land in the target; it self-destructs."""
+ # Derived bottom-up call budgets: with a zero state-gas reservoir
+ # each frame pays its stores' state gas from its own grant, so every
+ # level's budget covers its callee plus its own work with margin.
+ store_cost = Op.SSTORE(
+ key=0x3, value=0x1, key_warm=False, original_value=0, new_value=1
+ ).gas_cost(fork)
+ inner_call_gas = store_cost + 5_000
+ middle_call_gas = inner_call_gas + 2 * store_cost + 30_000
+ outer_call_gas = middle_call_gas + store_cost + 30_000
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
@@ -72,8 +74,8 @@ def test_callcallcallcode_001_suicide_end(
)
# Source: lll
# { [[ 0 ]] (CALLCODE 150000 0 0 64 0 64 ) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(
+ target_code = (
+ Op.SSTORE(
key=0x0,
value=Op.CALLCODE(
gas=outer_call_gas,
@@ -85,7 +87,10 @@ def test_callcallcallcode_001_suicide_end(
ret_size=0x40,
),
)
- + Op.STOP,
+ + Op.STOP
+ )
+ target = pre.deploy_contract( # noqa: F841
+ code=target_code,
balance=0xDE0B6B3A7640000,
nonce=0,
address=Address(0xA74CA10B765DCDA3B60687F73F2881E2A56EDA64), # noqa: E501
@@ -141,9 +146,10 @@ def test_callcallcallcode_001_suicide_end(
post = {
target: Account(
storage={0: 1, 1: 1, 2: 1, 3: 1},
- code=bytes.fromhex(
- "6040600060406000600073eaf8c2ae0d01a880cea4e1aa88def5edd153d57b620249f0f260005500" # noqa: E501
- ),
+ # Coupled to the deployed bytecode (the gas operand varies
+ # by fork), proving SELFDESTRUCT in a CALLCODE context kills
+ # nothing here.
+ code=target_code,
balance=0,
nonce=0,
),
diff --git a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py
index dd8381ad14c..a6f301f95bf 100644
--- a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py
+++ b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py
@@ -10,13 +10,13 @@
Ported from:
state_tests/stCreateTest/CreateAddressWarmAfterFailFiller.yml
-@manually-enhanced: Do not overwrite. The post-state records the
-measured cost of accessing the create address after a failed CREATE,
-which is a cold account access. EIP-8038 reprices a cold account
-access from 2 600 to 3 000, so each such measurement gains 400 at
-Amsterdam. Derive that delta from the fork's gas model so it is
-exactly 0 pre-EIP-8037 and tracks parameter changes; do not hardcode
-the Amsterdam value.
+@manually-enhanced: Do not overwrite. The post-state records measured
+probe-CALL costs; derive them from the fork's gas model (CALL regular
+cost with warm/cold, value-transfer, and new-account metadata, minus
+the returned stipend) plus the dispatcher's fixed framing gas, so
+EIP-2929/8037/8038 repricings track automatically. The transaction
+gas limit carries reservoir headroom for the state gas the dispatcher
+incurs on EIP-8037 forks, keeping state gas out of the measurements.
"""
import pytest
@@ -390,10 +390,43 @@ def test_create_address_warm_after_fail(
address=Address(0x00000000000000000000000000000000000C0DEC), # noqa: E501
)
- # The create address access after a failed CREATE is cold here;
- # EIP-8038 reprices a cold account access from 2 600 to 3 000.
- # Derive the delta from the fork so it is 0 pre-EIP-8037.
- cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600
+ # The dispatcher measures each probe CALL with a GAS-delta window.
+ # The window's framing (stack shuffling, the two dirty-warm SSTOREs
+ # bracketing the call, and the closing GAS read) is baked into the
+ # ported bytecode blob and fork-stable; the CALL's own cost is
+ # derived from the fork so warm/cold, value-transfer, and
+ # new-account repricings (EIP-2929, EIP-8037, EIP-8038) track
+ # automatically. On EIP-8037 forks the state-gas component is paid
+ # from the transaction's reservoir (see the gas limit below), so
+ # the windows observe only the regular cost.
+ first_call_frame = 228
+ repeat_call_frame = 216
+
+ def measured_call(frame: int, *, warm: bool, new: bool) -> int:
+ """Compute the gas one dispatcher probe-CALL window measures."""
+ call = Op.CALL(
+ address_warm=warm,
+ value_transfer=bool(v),
+ account_new=new and bool(v),
+ )
+ measured = frame + call.execution_cost(fork)
+ if v:
+ # The callee is empty (or STOP-only), so the stipend
+ # forwarded with the value returns unused.
+ measured -= fork.gas_costs().CALL_STIPEND
+ return measured
+
+ # Slot 12: first call to the CREATE target. Warm if a failed CREATE
+ # accessed it (the subject, EIP-2929), cold if the creating frame
+ # itself failed (or CREATE aborted on a nonce overflow), and an
+ # existing account when the CREATE succeeded.
+ warm_new_call = measured_call(first_call_frame, warm=True, new=True)
+ cold_new_call = measured_call(first_call_frame, warm=False, new=True)
+ warm_existing_call = measured_call(first_call_frame, warm=True, new=False)
+ # Slots 13/15: repeated calls, always warm to an existing account.
+ repeat_call = measured_call(repeat_call_frame, warm=True, new=False)
+ # Slot 14: first call to the never-created empty address.
+ empty_call = cold_new_call
expect_entries_: list[dict] = [
{
@@ -408,10 +441,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -432,10 +465,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -459,10 +492,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -483,10 +516,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -510,10 +543,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -534,10 +567,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -561,10 +594,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -585,10 +618,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -612,10 +645,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -636,10 +669,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 32028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -663,10 +696,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 2828 + cold_account_delta,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: cold_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=0,
),
@@ -690,10 +723,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 34528,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: cold_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=0,
),
@@ -717,10 +750,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 2828 + cold_account_delta,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: cold_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=0,
),
@@ -741,10 +774,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 34528,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: cold_new_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=0,
),
@@ -765,10 +798,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_existing_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -789,10 +822,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 7028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_existing_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -816,10 +849,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 328,
- 13: 316,
- 14: 2828 + cold_account_delta,
- 15: 316,
+ 12: warm_existing_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -840,10 +873,10 @@ def test_create_address_warm_after_fail(
3: 1,
4: 1,
5: 1,
- 12: 7028,
- 13: 7016,
- 14: 34528,
- 15: 7016,
+ 12: warm_existing_call,
+ 13: repeat_call,
+ 14: empty_call,
+ 15: repeat_call,
},
nonce=1,
),
@@ -876,12 +909,22 @@ def test_create_address_warm_after_fail(
Bytes("52c3fd24") + Hash(0x7),
Bytes("52c3fd24") + Hash(0x11),
]
- # The dispatcher writes to ~14 fresh storage slots; under EIP-8037
- # each slot's 32-byte cost is settled at frame end out of the
- # reservoir/`gas_left` (~37_500 gas/slot on Amsterdam). Add that
- # headroom — `sstore_state_gas` is 0 pre-EIP-8037, so the budget
- # is unchanged on older forks.
- tx_gas = [16777216 + 14 * Op.SSTORE(new_value=1).state_cost(fork)]
+ # Under EIP-8037 the gas above the execution cap becomes the
+ # state-gas reservoir. Size it to cover every state charge the
+ # dispatcher can incur — nine 0→non-zero SSTOREs, the CREATE's
+ # peak new-account charge plus up to two accounts created by the
+ # value-bearing probe calls (one spare), and the code deposit —
+ # so no state gas spills into the measured windows. The headroom
+ # is 0 pre-EIP-8037, leaving the budget unchanged on older forks.
+ state_gas_headroom = (
+ 9 * Op.SSTORE(new_value=1).state_cost(fork)
+ + 4
+ * Op.CALL(
+ address_warm=True, value_transfer=True, account_new=True
+ ).state_cost(fork)
+ + fork.code_deposit_state_gas(code_size=1)
+ )
+ tx_gas = [16777216 + state_gas_headroom]
tx_value = [0, 1]
tx = Transaction(
diff --git a/tests/ported_static/stEIP150Specific/test_new_gas_price_for_codes.py b/tests/ported_static/stEIP150Specific/test_new_gas_price_for_codes.py
index 0be45107816..6ed52393b2d 100644
--- a/tests/ported_static/stEIP150Specific/test_new_gas_price_for_codes.py
+++ b/tests/ported_static/stEIP150Specific/test_new_gas_price_for_codes.py
@@ -1,18 +1,25 @@
"""
-Test_new_gas_price_for_codes.
+Verify the EIP-150 repriced code/account operations in one frame:
+EXTCODESIZE, EXTCODECOPY, SLOAD, failing value CALL/CALLCODE (insufficient
+balance), DELEGATECALL that writes the caller's storage, a call to a
+nonexistent account, BALANCE, and the whole window's measured gas.
Ported from:
state_tests/stEIP150Specific/NewGasPriceForCodesFiller.json
+
+@manually-enhanced: Do not overwrite. The ported bytecode shape is kept,
+but the window delta, the mid-execution sender balance, and the copied
+code word are derived (opcode metadata, fee formula, the deployed bytes);
+the delegate's budget is derived so its store — state-priced under
+EIP-8037 — fits inside the grant (a reservoir-less sub-call pays state
+gas from its regular grant); each failed value call returns its stipend.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -21,133 +28,196 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+EXTCODE_BYTES = bytes.fromhex(
+ "1122334455667788991011121314151617181920212223242526272829303132"
+)
+COPY_SIZE = 0x14
+DELEGATE_VALUE = 0x11
+# Budget for the calls whose outcome does not depend on it (the value
+# calls fail on insufficient balance; the absent target runs nothing).
+FORWARDED_GAS = 0x7530
+GAS_PRICE = 10
+INITIAL_BALANCE = 10**15
+
@pytest.mark.ported_from(
["state_tests/stEIP150Specific/NewGasPriceForCodesFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_new_gas_price_for_codes(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_new_gas_price_for_codes."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = EOA(
- key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52
+ """Measure a frame exercising every repriced code/account operation."""
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
+ code_target = pre.deploy_contract(code=EXTCODE_BYTES, balance=111)
+ delegate_store = Op.SSTORE(
+ key=0x64,
+ value=DELEGATE_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=DELEGATE_VALUE,
)
+ storage_writer = pre.deploy_contract(code=delegate_store + Op.STOP)
+ absent = pre.nonexistent_account()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
+ # The delegate must succeed: with a zero reservoir its state-priced
+ # store is paid from the regular grant, so the budget is derived.
+ delegate_budget = delegate_store.gas_cost(fork) + 2_000
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: raw
- # 0x1122334455667788991011121314151617181920212223242526272829303132
- addr = pre.deploy_contract( # noqa: F841
- code=bytes.fromhex(
- "1122334455667788991011121314151617181920212223242526272829303132"
- ),
- balance=111,
- nonce=0,
- address=Address(0xC572A70AFAAB9D01D0A2AFB855BFBAFB47C8211B), # noqa: E501
- )
- # Source: lll
- # { (SSTORE 100 0x11) }
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x64, value=0x11) + Op.STOP,
- nonce=0,
- address=Address(0xAD9D325B811CB0701839C07C6F139F3799476798), # noqa: E501
- )
- # Source: lll
- # { [999] (GAS) (SSTORE 1 (EXTCODESIZE )) (EXTCODECOPY 0 0 20) (SSTORE 2 (MLOAD 0)) (SSTORE 4 (SLOAD 0)) (SSTORE 5 (CALL 30000 1 0 0 0 0)) (SSTORE 6 (CALLCODE 30000 1 0 0 0 0)) (SSTORE 7 (DELEGATECALL 30000 0 0 0 0)) (SSTORE 8 (CALL 30000 0x1000000000000000000000000000000000000013 0 0 0 0 0)) (SSTORE 3 (BALANCE )) (SSTORE 10 (SUB (MLOAD 999) (GAS))) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x3E7, value=Op.GAS)
- + Op.SSTORE(key=0x1, value=Op.EXTCODESIZE(address=addr))
- + Op.EXTCODECOPY(address=addr, dest_offset=0x0, offset=0x0, size=0x14)
- + Op.SSTORE(key=0x2, value=Op.MLOAD(offset=0x0))
- + Op.SSTORE(key=0x4, value=Op.SLOAD(key=0x0))
+ # The measured window: entry GAS snapshot through the closing GAS.
+ # The value-bearing CALL and CALLCODE fail on insufficient balance
+ # (this contract holds nothing), costing their access and transfer
+ # charges minus the returned stipend; the DELEGATECALL runs the
+ # writer against this contract's storage.
+ window = (
+ Op.MSTORE(offset=0x3E7, value=Op.GAS, new_memory_size=0x407)
+ + Op.SSTORE(
+ key=0x1,
+ value=Op.EXTCODESIZE(address=code_target, address_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.EXTCODECOPY(
+ address=code_target,
+ dest_offset=0x0,
+ offset=0x0,
+ size=COPY_SIZE,
+ address_warm=True,
+ data_size=COPY_SIZE,
+ new_memory_size=0x407,
+ old_memory_size=0x407,
+ )
+ + Op.SSTORE(
+ key=0x2,
+ value=Op.MLOAD(offset=0x0),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.SSTORE(
+ key=0x4,
+ value=Op.SLOAD(key=0x0, key_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ Op.SSTORE(
key=0x5,
value=Op.CALL(
- gas=0x7530,
- address=addr_2,
+ gas=FORWARDED_GAS,
+ address=storage_writer,
value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ address_warm=False,
+ value_transfer=True,
+ account_new=False,
),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
)
+ Op.SSTORE(
key=0x6,
value=Op.CALLCODE(
- gas=0x7530,
- address=addr_2,
+ gas=FORWARDED_GAS,
+ address=storage_writer,
value=0x1,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ address_warm=True,
+ value_transfer=True,
+ account_new=False,
),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
)
+ Op.SSTORE(
key=0x7,
value=Op.DELEGATECALL(
- gas=0x7530,
- address=addr_2,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ gas=delegate_budget,
+ address=storage_writer,
+ address_warm=True,
),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
+ Op.SSTORE(
key=0x8,
value=Op.CALL(
- gas=0x7530,
- address=0x1000000000000000000000000000000000000013,
+ gas=FORWARDED_GAS,
+ address=absent,
value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- + Op.SSTORE(key=0x3, value=Op.BALANCE(address=sender))
- + Op.SSTORE(key=0xA, value=Op.SUB(Op.MLOAD(offset=0x3E7), Op.GAS))
- + Op.STOP,
+ + Op.SSTORE(
+ key=0x3,
+ value=Op.BALANCE(address=sender, address_warm=True),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ )
+ delta_store = Op.SSTORE(
+ key=0xA,
+ value=Op.SUB(Op.MLOAD(offset=0x3E7), Op.GAS),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ target = pre.deploy_contract(
+ code=window + delta_store + Op.STOP,
storage={0: 18},
- nonce=0,
- address=Address(0xFD9AFC8315A88141164E2A753157EA3E0F72C707), # noqa: E501
)
+ # Window delta: everything from the entry GAS read to the closing
+ # one; the lead GAS and the closing GAS cancel out of the composite,
+ # the delegate's work is added on top, and each failed value call
+ # hands back its stipend along with the unused grant.
+ measured = (
+ window.gas_cost(fork)
+ + delegate_store.gas_cost(fork)
+ - 2 * fork.gas_costs().CALL_STIPEND
+ )
+
+ # Fork-derived budget with an EIP-2200 stipend margin for the
+ # trailing delta store.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = intrinsic + measured + delta_store.gas_cost(fork) + 5_000
+
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=600000,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
+ copied_word = int.from_bytes(
+ EXTCODE_BYTES[:COPY_SIZE].ljust(0x20, b"\x00"), "big"
+ )
post = {
target: Account(
storage={
- 0: 18,
- 1: 32,
- 2: 0x1122334455667788991011121314151617181920000000000000000000000000, # noqa: E501
- 3: 0xE8D4498280,
- 4: 18,
- 7: 1,
- 8: 1,
- 10: 0x2CB0A,
- 100: 17,
+ 0x0: 18,
+ 0x1: len(EXTCODE_BYTES),
+ 0x2: copied_word,
+ # Mid-execution balance: the full fee is charged upfront.
+ 0x3: INITIAL_BALANCE - gas_limit * GAS_PRICE,
+ 0x4: 18,
+ # Slots 5 and 6 stay zero: the value calls failed.
+ 0x7: 1,
+ 0x8: 1,
+ 0xA: measured,
+ 0x64: DELEGATE_VALUE,
},
),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py
index d84fd092041..f1d08b2f849 100644
--- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py
+++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py
@@ -1,11 +1,15 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Measure the gas cost of each opcode via a crafted one-opcode contract
+(by Ori Pomerantz qbzzt1@gmail.com).
Ported from:
state_tests/stEIP150singleCodeGasPrices/gasCostFiller.yml
@manually-enhanced: Do not overwrite. This crafts a one-opcode
contract, CALLs it, and stores the opcode's measured gas via `Op.GAS`.
+The SSTORE case (d40) derives its cost from the fork — EIP-8037 moves
+the bulk into state gas — and the crafted CALL forwards effectively
+all gas (0xFFFFFF, same PUSH3 width) so that case cannot OOG.
EIP-8038 reprices state access, so four opcodes shift: `BALANCE` and
`SELFDESTRUCT` (cold account, `COLD_ACCOUNT_ACCESS` 2600 -> 3000, +400),
`EXTCODESIZE` (cold account plus the extra `WARM_ACCESS` charged for
@@ -734,6 +738,12 @@ def test_gas_cost(
code_read_delta = cold_account_delta + (
gas_costs.WARM_ACCESS if fork.is_eip_enabled(8037) else 0
)
+ # EIP-8037 moves the bulk of a zero->nonzero SSTORE into state gas;
+ # the explicit gas limit equals the cap (zero reservoir), so the
+ # crafted contract's GAS delta observes the full cost.
+ sstore_new_slot_cost = Op.SSTORE(
+ key_warm=False, original_value=0, new_value=1
+ ).gas_cost(fork)
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = EOA(
key=0x40AC0FC28C27E961EE46EC43355A094DE205856EDBD4654CF2577C2608D4EC1E
@@ -837,7 +847,12 @@ def test_gas_cost(
+ Op.MSTORE(offset=0x300, value=Op.GAS)
+ Op.POP(
Op.CALL(
- gas=0x10000,
+ # Effectively "all gas": EIP-8037 repriced the SSTORE case
+ # (d40) past the ported 0x10000 budget, OOGing the callee.
+ # 0xFFFFFF keeps the same PUSH3 width, so the hand-coded
+ # JUMP targets and every measurement stay unchanged
+ # (unused gas returns to the caller).
+ gas=0xFFFFFF,
address=Op.MLOAD(offset=0x280),
value=0x0,
args_offset=0x0,
@@ -1102,10 +1117,18 @@ def test_gas_cost(
},
},
{
+ # SSTORE zero->nonzero to a fresh cold slot. Stored value =
+ # actual cost minus the 0x4E20 expected-cost operand in the
+ # data word minus the file-wide 0x258 baseline; 1500 before
+ # EIP-8037, dominated by state gas after.
"indexes": {"data": [40], "gas": -1, "value": -1},
"network": [">=Cancun"],
"result": {
- addr: Account(storage=_storage_with_any({0: 1500}, [1]))
+ addr: Account(
+ storage=_storage_with_any(
+ {0: sstore_new_slot_cost - 0x4E20 - 0x258}, [1]
+ )
+ )
},
},
{
diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py
index 5a92e62ca28..b5f361384c2 100644
--- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py
+++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py
@@ -1,5 +1,6 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Measure the gas cost of each opcode via a crafted one-opcode contract
+(by Ori Pomerantz qbzzt1@gmail.com).
Ported from:
state_tests/stEIP150singleCodeGasPrices/gasCostBerlinFiller.yml
@@ -7,6 +8,9 @@
@manually-enhanced: Do not overwrite. This crafts a one-opcode
contract, CALLs it, and stores the opcode's measured gas minus the
data's hardcoded Cancun-era expected cost (so the net is normally 0).
+The SSTORE case (d40) derives its net from the fork — EIP-8037 moves
+the bulk into state gas — and the crafted CALL forwards effectively
+all gas (0xFFFFFF, same PUSH3 width) so that case cannot OOG.
EIP-8038 reprices state access, so four opcodes now exceed their old
expected cost by a fork-derived delta: `BALANCE` and `SELFDESTRUCT`
(cold account, `COLD_ACCOUNT_ACCESS` 2600 -> 3000, +400), `EXTCODESIZE`
@@ -733,10 +737,18 @@ def test_gas_cost_berlin(
# Each measured opcode subtracts its Cancun-era expected cost, so the
# net is the (Amsterdam - Cancun) repricing of the one state access
# it performs (cold address 0 / cold fresh slot), keyed by data index.
+ # EIP-8037 moves the bulk of a zero->nonzero SSTORE into state gas;
+ # the explicit gas limit equals the cap (zero reservoir), so the
+ # crafted contract's GAS delta observes the full cost. The data word
+ # encodes the 0x5654 (22100) pre-8037 cost, so the net is 0 there.
+ sstore_new_slot_cost = Op.SSTORE(
+ key_warm=False, original_value=0, new_value=1
+ ).gas_cost(fork)
measured_delta = {
23: cold_account_delta, # BALANCE
31: code_read_delta, # EXTCODESIZE
39: cold_storage_delta, # SLOAD
+ 40: sstore_new_slot_cost - 0x5654, # SSTORE zero->nonzero
45: cold_account_delta, # SELFDESTRUCT
}.get(d, 0)
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
@@ -839,7 +851,12 @@ def test_gas_cost_berlin(
+ Op.MSTORE(offset=0x300, value=Op.GAS)
+ Op.POP(
Op.CALL(
- gas=0x10000,
+ # Effectively "all gas": EIP-8037 repriced the SSTORE case
+ # (d40) past the ported 0x10000 budget, OOGing the callee.
+ # 0xFFFFFF keeps the same PUSH3 width, so the hand-coded
+ # JUMP targets and every measurement stay unchanged
+ # (unused gas returns to the caller).
+ gas=0xFFFFFF,
address=Op.MLOAD(offset=0x280),
value=0x0,
args_offset=0x0,
diff --git a/tests/ported_static/stEIP158Specific/test_exp_empty.py b/tests/ported_static/stEIP158Specific/test_exp_empty.py
index 9b7b323744c..558f1b6d071 100644
--- a/tests/ported_static/stEIP158Specific/test_exp_empty.py
+++ b/tests/ported_static/stEIP158Specific/test_exp_empty.py
@@ -1,17 +1,23 @@
"""
-Test_exp_empty.
+Measure the gas cost of EXP with a zero base or a zero exponent across
+exponent widths (the per-byte exponent charge applies only to the
+exponent operand).
Ported from:
state_tests/stEIP158Specific/EXP_EmptyFiller.json
+
+@manually-enhanced: Do not overwrite. The eight measurement windows are
+generated from one case list and every stored delta is derived from
+opcode metadata (`exponent=` drives the per-byte charge); the transaction
+budget is fork-derived.
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Bytecode,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -20,100 +26,78 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# (base, exponent) pairs: a zero on either side, exponent widths 1-32.
+EXP_CASES = [
+ (0x0, 0xC),
+ (0xC, 0x0),
+ (0x0, 2**64 - 1),
+ (0x0, 2**128 - 1),
+ (0x0, 2**256 - 1),
+ (2**64 - 1, 0x0),
+ (2**128 - 1, 0x0),
+ (2**256 - 1, 0x0),
+]
+
@pytest.mark.ported_from(
["state_tests/stEIP158Specific/EXP_EmptyFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_exp_empty(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_exp_empty."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = pre.fund_eoa(amount=0xE8D4A51000)
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
+ """Measure EXP's cost for zero-base and zero-exponent operands."""
+ code = Bytecode()
+ storage: dict = {}
+ budget = 0
+ for i, (base, exponent) in enumerate(EXP_CASES):
+ result = 1 if exponent == 0 else 0
+ result_slot = 1 + 2 * i
+ # The last window stores its delta at slot 100, as ported.
+ delta_slot = 0x64 if i == len(EXP_CASES) - 1 else result_slot + 1
- # Source: lll
- # { [0](GAS) [[1]](EXP 0 12) [[2]](SUB @0 (GAS)) [0](GAS) [[3]](EXP 12 0) [[4]](SUB @0 (GAS)) [0](GAS) [[5]](EXP 0 0xffffffffffffffff) [[6]](SUB @0 (GAS)) [0](GAS) [[7]](EXP 0 0xffffffffffffffffffffffffffffffff) [[8]](SUB @0 (GAS)) [0](GAS) [[9]](EXP 0 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) [[10]](SUB @0 (GAS)) [0](GAS) [[11]](EXP 0xffffffffffffffff 0) [[12]](SUB @0 (GAS)) [0](GAS) [[13]](EXP 0xffffffffffffffffffffffffffffffff 0) [[14]](SUB @0 (GAS)) [0] (GAS) [[15]](EXP 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0) [[100]] (SUB @0 (GAS)) } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(key=0x1, value=Op.EXP(0x0, 0xC))
- + Op.SSTORE(key=0x2, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(key=0x3, value=Op.EXP(0xC, 0x0))
- + Op.SSTORE(key=0x4, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(key=0x5, value=Op.EXP(0x0, 0xFFFFFFFFFFFFFFFF))
- + Op.SSTORE(key=0x6, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(
- key=0x7, value=Op.EXP(0x0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
+ lead = Op.MSTORE(
+ offset=0x0,
+ value=Op.GAS,
+ new_memory_size=0x20,
+ old_memory_size=0x0 if i == 0 else 0x20,
)
- + Op.SSTORE(key=0x8, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(
- key=0x9,
- value=Op.EXP(
- 0x0,
- 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, # noqa: E501
- ),
+ exp_store = Op.SSTORE(
+ key=result_slot,
+ value=Op.EXP(base, exponent, exponent=exponent),
+ key_warm=False,
+ original_value=0,
+ new_value=result,
)
- + Op.SSTORE(key=0xA, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(key=0xB, value=Op.EXP(0xFFFFFFFFFFFFFFFF, 0x0))
- + Op.SSTORE(key=0xC, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(
- key=0xD, value=Op.EXP(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0x0)
+ # The window's measured delta: both GAS reads cancel out of the
+ # two composites' sum.
+ measured = lead.gas_cost(fork) + exp_store.gas_cost(fork)
+ delta_store = Op.SSTORE(
+ key=delta_slot,
+ value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS),
+ key_warm=False,
+ original_value=0,
+ new_value=measured,
)
- + Op.SSTORE(key=0xE, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.MSTORE(offset=0x0, value=Op.GAS)
- + Op.SSTORE(
- key=0xF,
- value=Op.EXP(
- 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, # noqa: E501
- 0x0,
- ),
- )
- + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS))
- + Op.STOP,
- nonce=0,
- )
+ code += lead + exp_store + delta_store
+ storage[result_slot] = result
+ storage[delta_slot] = measured
+ budget += measured + delta_store.gas_cost(fork) + 9
+
+ target = pre.deploy_contract(code=code + Op.STOP)
+
+ # Fork-derived budget with an EIP-2200 stipend margin for the final
+ # store.
+ gas_limit = fork.transaction_intrinsic_cost_calculator()() + budget + 5_000
tx = Transaction(
- sender=sender,
+ sender=pre.fund_eoa(),
to=target,
- data=Bytes(""),
- gas_limit=600000,
+ gas_limit=gas_limit,
)
- post = {
- target: Account(
- storage={
- 2: 2280,
- 3: 1,
- 4: 22127,
- 6: 2627,
- 8: 3027,
- 10: 3827,
- 11: 1,
- 12: 22127,
- 13: 1,
- 14: 22127,
- 15: 1,
- 100: 22127,
- },
- ),
- }
+ post = {target: Account(storage=storage)}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py b/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py
index 82d92551fc3..b59d7c67858 100644
--- a/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py
+++ b/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py
@@ -1,17 +1,23 @@
"""
-Test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.
+Verify an out-of-gas contract creation leaves no account behind (the
+Homestead-era bug left empty shells), while a sufficient budget creates a
+codeless account whose init code called out to a storage writer.
Ported from:
state_tests/stHomesteadSpecific/contractCreationOOGdontLeaveEmptyContractViaTransactionFiller.json
+
+@manually-enhanced: Do not overwrite. The ported single case had silently
+become success-only (its OOG arm was gone); both arms are restored with
+fork-derived budgets, the init code's call budget is derived (the writer's
+store is state-priced under EIP-8037), and the writer's slot plus the
+created account's fields are asserted.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -27,72 +33,72 @@
"state_tests/stHomesteadSpecific/contractCreationOOGdontLeaveEmptyContractViaTransactionFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
+@pytest.mark.parametrize(
+ "enough_gas",
+ [
+ pytest.param(True, id="created"),
+ pytest.param(False, id="oog_no_account"),
+ ],
+)
def test_contract_creation_oo_gdont_leave_empty_contract_via_transaction(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
+ enough_gas: bool,
) -> None:
- """Test_contract_creation_oo_gdont_leave_empty_contract_via_transaction."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- contract_1 = Address(0x1000000000000000000000000000000000000001)
- sender = EOA(
- key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8
+ """An OOG creation must not leave an account behind."""
+ writer_store = Op.SSTORE(
+ key=0x1, value=0x1, key_warm=False, original_value=0, new_value=1
)
+ writer = pre.deploy_contract(code=writer_store + Op.STOP)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ # The init code calls the writer and deposits nothing. The forwarded
+ # budget is derived: with a zero reservoir the writer's state-priced
+ # store must fit inside its grant.
+ writer_needed = writer_store.gas_cost(fork)
+ call_code = Op.CALL(
+ gas=writer_needed + 1_000,
+ address=writer,
+ args_size=0x40,
+ ret_size=0x40,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
+ new_memory_size=0x40,
)
+ initcode = call_code + Op.STOP
- pre[sender] = Account(balance=0x10C8E0)
- # Source: lll
- # {(SSTORE 1 1)}
- contract_1 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP,
- nonce=0,
- address=Address(0x1000000000000000000000000000000000000001), # noqa: E501
- )
- # Source: lll
- # {(CALL 50000 0x1000000000000000000000000000000000000001 0 0 64 0 64)}
- contract_0 = pre.deploy_contract( # noqa: F841
- code=Op.CALL(
- gas=0xC350,
- address=0x1000000000000000000000000000000000000001,
- value=0x0,
- args_offset=0x0,
- args_size=0x40,
- ret_offset=0x0,
- ret_size=0x40,
- )
- + Op.STOP,
- balance=0x186A0,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
- )
+ overhead = fork.transaction_intrinsic_cost_calculator()(
+ calldata=initcode,
+ contract_creation=True,
+ ) + fork.transaction_top_frame_state_gas(contract_creation=True)
+ execution = call_code.gas_cost(fork) + writer_needed
+ # The OOG arm dies charging the init code's own CALL (a failed inner
+ # call alone would not fail the creation): a bare 100-gas allowance
+ # over the fixed charges cannot cover the call's access cost even
+ # with the intrinsic estimate's slack.
+ gas_limit = overhead + (execution + 2_000 if enough_gas else 100)
+ sender = pre.fund_eoa()
tx = Transaction(
sender=sender,
to=None,
- data=Op.CALL(
- gas=0xC350,
- address=contract_1,
- value=0x0,
- args_offset=0x0,
- args_size=0x40,
- ret_offset=0x0,
- ret_size=0x40,
- ),
- gas_limit=96000,
+ data=initcode,
+ gas_limit=gas_limit,
)
+ created = compute_create_address(address=sender, nonce=0)
+ if enough_gas:
+ created_account: Account | None = Account(nonce=1, code=b"", balance=0)
+ writer_storage = {1: 1}
+ else:
+ created_account = Account.NONEXISTENT
+ writer_storage = {1: 0}
post = {
- compute_create_address(address=sender, nonce=0): Account(balance=0)
+ sender: Account(nonce=1),
+ created: created_account,
+ writer: Account(storage=writer_storage),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py
index 47aed815167..3a6aabd3bc2 100644
--- a/tests/ported_static/stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py
+++ b/tests/ported_static/stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py
@@ -1,18 +1,26 @@
"""
-Test_new_gas_price_for_codes_with_mem_expanding_calls.
+Verify the EIP-150 repriced code/account operations in one frame whose
+calls also expand memory: EXTCODESIZE, EXTCODECOPY, failing value
+CALL/CALLCODE (insufficient balance), DELEGATECALL that writes the
+caller's storage, a call to a nonexistent account, BALANCE, and a final
+raw gas reading that pins the whole execution.
Ported from:
state_tests/stMemExpandingEIP150Calls/NewGasPriceForCodesWithMemExpandingCallsFiller.json
+
+@manually-enhanced: Do not overwrite. The ported bytecode shape is kept,
+but the final gas reading, the mid-execution sender balance, and the
+copied code word are derived (opcode metadata, fee formula, the deployed
+bytes); the delegate's budget is derived so its store — state-priced
+under EIP-8037 — fits inside the grant; each failed value call returns
+its stipend.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -21,135 +29,216 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+EXTCODE_BYTES = bytes.fromhex(
+ "1122334455667788991011121314151617181920212223242526272829303132"
+)
+COPY_SIZE = 0x14
+DELEGATE_VALUE = 0x11
+# Budget for the calls whose outcome does not depend on it.
+FORWARDED_GAS = 0x7530
+# The ported calls' argument window, driving the memory expansion.
+MEM_OFFSET = 0xFF
+MEM_SIZE = 0xFF
+GAS_PRICE = 10
+INITIAL_BALANCE = 10**15
+
@pytest.mark.ported_from(
[
"state_tests/stMemExpandingEIP150Calls/NewGasPriceForCodesWithMemExpandingCallsFiller.json" # noqa: E501
],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_new_gas_price_for_codes_with_mem_expanding_calls(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_new_gas_price_for_codes_with_mem_expanding_calls."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = EOA(
- key=0x3956FC06BD55836ACDB92DA0E38A15F2E568C088022CF2278180477F3F7702A
+ """Measure repriced operations with memory-expanding call windows."""
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
+ code_target = pre.deploy_contract(code=EXTCODE_BYTES, balance=111)
+ delegate_store = Op.SSTORE(
+ key=0x64,
+ value=DELEGATE_VALUE,
+ key_warm=False,
+ original_value=0,
+ new_value=DELEGATE_VALUE,
)
+ storage_writer = pre.deploy_contract(code=delegate_store)
+ absent = pre.nonexistent_account()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
+ # The delegate must succeed: with a zero reservoir its state-priced
+ # store is paid from the regular grant, so the budget is derived.
+ delegate_budget = delegate_store.gas_cost(fork) + 2_000
- pre[sender] = Account(balance=0xE8D4A5100000)
- # Source: hex
- # 0x1122334455667788991011121314151617181920212223242526272829303132
- addr = pre.deploy_contract( # noqa: F841
- code=bytes.fromhex(
- "1122334455667788991011121314151617181920212223242526272829303132"
- ),
- balance=111,
- nonce=0,
- address=Address(0x6B6AF3C6E1714081C8C3085ACBAC8C2B21FADF0B), # noqa: E501
- )
- # Source: hex
- # 0x6011606455
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x64, value=0x11),
- nonce=0,
- address=Address(0x7B8C83E74CC8DFADB03138C2743C70588ACE4222), # noqa: E501
- )
- # Source: hex
- # 0x733b600155601460006000733c60005160025560005460045560ff60ff60ff60ff600173617530f160055560ff60ff60ff60ff600173617530f260065560ff60ff60ff60ff73617530f460075560ff60ff60ff60ff6000731000000000000000000000000000000000000013617530f160085573316003555a600a55 # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=Op.EXTCODESIZE(address=addr))
- + Op.EXTCODECOPY(address=addr, dest_offset=0x0, offset=0x0, size=0x14)
- + Op.SSTORE(key=0x2, value=Op.MLOAD(offset=0x0))
- + Op.SSTORE(key=0x4, value=Op.SLOAD(key=0x0))
+ call_window = MEM_OFFSET + MEM_SIZE
+ body = (
+ Op.SSTORE(
+ key=0x1,
+ value=Op.EXTCODESIZE(address=code_target, address_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.EXTCODECOPY(
+ address=code_target,
+ dest_offset=0x0,
+ offset=0x0,
+ size=COPY_SIZE,
+ address_warm=True,
+ data_size=COPY_SIZE,
+ new_memory_size=0x20,
+ )
+ + Op.SSTORE(
+ key=0x2,
+ value=Op.MLOAD(offset=0x0),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.SSTORE(
+ key=0x4,
+ value=Op.SLOAD(key=0x0, key_warm=False),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ Op.SSTORE(
key=0x5,
value=Op.CALL(
- gas=0x7530,
- address=addr_2,
+ gas=FORWARDED_GAS,
+ address=storage_writer,
value=0x1,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=False,
+ value_transfer=True,
+ account_new=False,
+ new_memory_size=call_window,
+ old_memory_size=0x20,
),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
)
+ Op.SSTORE(
key=0x6,
value=Op.CALLCODE(
- gas=0x7530,
- address=addr_2,
+ gas=FORWARDED_GAS,
+ address=storage_writer,
value=0x1,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=True,
+ value_transfer=True,
+ account_new=False,
+ new_memory_size=call_window,
+ old_memory_size=call_window,
),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
)
+ Op.SSTORE(
key=0x7,
value=Op.DELEGATECALL(
- gas=0x7530,
- address=addr_2,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ gas=delegate_budget,
+ address=storage_writer,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=True,
+ new_memory_size=call_window,
+ old_memory_size=call_window,
),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
+ Op.SSTORE(
key=0x8,
value=Op.CALL(
- gas=0x7530,
- address=0x1000000000000000000000000000000000000013,
+ gas=FORWARDED_GAS,
+ address=absent,
value=0x0,
- args_offset=0xFF,
- args_size=0xFF,
- ret_offset=0xFF,
- ret_size=0xFF,
+ args_offset=MEM_OFFSET,
+ args_size=MEM_SIZE,
+ ret_offset=MEM_OFFSET,
+ ret_size=MEM_SIZE,
+ address_warm=False,
+ value_transfer=False,
+ account_new=False,
+ new_memory_size=call_window,
+ old_memory_size=call_window,
),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
)
- + Op.SSTORE(key=0x3, value=Op.BALANCE(address=sender))
- + Op.SSTORE(key=0xA, value=Op.GAS),
+ + Op.SSTORE(
+ key=0x3,
+ value=Op.BALANCE(address=sender, address_warm=True),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ )
+ final_store = Op.SSTORE(
+ key=0xA,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ target = pre.deploy_contract(
+ code=body + final_store + Op.STOP,
storage={0: 18},
- nonce=0,
- address=Address(0x23A2EC54F5F8589778DA7C2199CAF3B179A24CB9), # noqa: E501
)
+ # Consumption before the final GAS read: the body's composite plus
+ # the delegate's work, minus the two returned stipends.
+ consumed = (
+ body.gas_cost(fork)
+ + delegate_store.gas_cost(fork)
+ - 2 * fork.gas_costs().CALL_STIPEND
+ )
+
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = intrinsic + consumed + final_store.gas_cost(fork) + 5_000
+
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=600000,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
+ copied_word = int.from_bytes(
+ EXTCODE_BYTES[:COPY_SIZE].ljust(0x20, b"\x00"), "big"
+ )
post = {
- addr: Account(balance=111),
target: Account(
storage={
- 0: 18,
- 1: 32,
- 2: 0x1122334455667788991011121314151617181920000000000000000000000000, # noqa: E501
- 3: 0xE8D4A4B47280,
- 4: 18,
- 7: 1,
- 8: 1,
- 10: 0x60AE9,
- 100: 17,
+ 0x0: 18,
+ 0x1: len(EXTCODE_BYTES),
+ 0x2: copied_word,
+ # Mid-execution balance: the full fee is charged upfront.
+ 0x3: INITIAL_BALANCE - gas_limit * GAS_PRICE,
+ 0x4: 18,
+ # Slots 5 and 6 stay zero: the value calls failed.
+ 0x7: 1,
+ 0x8: 1,
+ # Raw reading: everything left after the body's work and
+ # the GAS opcode itself.
+ 0xA: gas_limit - intrinsic - consumed - Op.GAS.gas_cost(fork),
+ 0x64: DELEGATE_VALUE,
},
),
- sender: Account(nonce=1),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stMemoryTest/test_oog.py b/tests/ported_static/stMemoryTest/test_oog.py
index 5efece6b89d..2a94bc58e56 100644
--- a/tests/ported_static/stMemoryTest/test_oog.py
+++ b/tests/ported_static/stMemoryTest/test_oog.py
@@ -312,6 +312,29 @@ def test_oog(
# nested CALL to a cold contract plus the copy; the reprice eats the
# slack, so add it back to that one budget.
cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600
+ # The CREATE/CREATE2 success budgets are derived: EIP-8037 adds the
+ # new-account state gas (~183k), far past the ported 0xFFFF budget.
+ create_budget = (
+ Op.CREATE(
+ value=0x0,
+ offset=0x10000,
+ size=0x20,
+ new_memory_size=0x10020,
+ init_code_size=0x20,
+ ).gas_cost(fork)
+ + 1_000
+ )
+ create2_budget = (
+ Op.CREATE2(
+ value=0x0,
+ offset=0x10000,
+ size=0x20,
+ salt=0x5A17,
+ new_memory_size=0x10020,
+ init_code_size=0x20,
+ ).gas_cost(fork)
+ + 1_000
+ )
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
contract_0 = Address(0x0000000000000000000000000000000000010020)
contract_1 = Address(0x0000000000000000000000000000000000010037)
@@ -787,9 +810,9 @@ def test_oog(
Bytes("1a8451e6") + Hash(0xA3) + Hash(0x39D0),
Bytes("1a8451e6") + Hash(0xA4) + Hash(0xFFFF),
Bytes("1a8451e6") + Hash(0xA4) + Hash(0x39D0),
- Bytes("1a8451e6") + Hash(0xF0) + Hash(0xFFFF),
+ Bytes("1a8451e6") + Hash(0xF0) + Hash(create_budget),
Bytes("1a8451e6") + Hash(0xF0) + Hash(0x7D00),
- Bytes("1a8451e6") + Hash(0xF5) + Hash(0xFFFF),
+ Bytes("1a8451e6") + Hash(0xF5) + Hash(create2_budget),
Bytes("1a8451e6") + Hash(0xF5) + Hash(0x7D00),
Bytes("1a8451e6") + Hash(0xF3) + Hash(0xFFFF),
Bytes("1a8451e6") + Hash(0xF3) + Hash(0x36B0),
diff --git a/tests/ported_static/stRefundTest/test_refund600.py b/tests/ported_static/stRefundTest/test_refund600.py
index cce448a08c4..3f5def95619 100644
--- a/tests/ported_static/stRefundTest/test_refund600.py
+++ b/tests/ported_static/stRefundTest/test_refund600.py
@@ -1,18 +1,21 @@
"""
-Test_refund600.
+Verify the EIP-3529 refund cap over six storage clears: the sender's final
+balance reflects the executed gas minus the capped refund.
Ported from:
state_tests/stRefundTest/refund600Filler.json
+
+@manually-enhanced: Do not overwrite. The sender's balance, the refund cap
+and the transaction budget all derive from the fork (`code.gas_cost` /
+`code.refund` composites), so EIP-8037's repriced stores and any future
+refund change are tracked instead of pinned.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -21,64 +24,85 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CONTRACT_BALANCE = 0xDE0B6B3A7640000
+INITIAL_BALANCE = 10**18
+GAS_PRICE = 10
+
@pytest.mark.ported_from(
["state_tests/stRefundTest/refund600Filler.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("London")
def test_refund600(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_refund600."""
- coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE)
- sender = EOA(
- key=0xDC4EFA209AECDD4C2D5201A419EA27506151B4EC687F14A613229E310932491B
- )
-
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ """Six storage clears refund gas up to the EIP-3529 cap."""
+ code = (
+ Op.POP(Op.SLOAD(key=0x1, key_warm=False))
+ + Op.POP(Op.SLOAD(key=0x2, key_warm=False))
+ # EXP(2, 0xFFFF) wraps to 0 mod 2^256, so this store is a no-op.
+ + Op.SSTORE(
+ key=0xA,
+ value=Op.EXP(0x2, 0xFFFF, exponent=0xFFFF),
+ key_warm=False,
+ original_value=0,
+ new_value=0,
+ )
+ + Op.SSTORE(
+ key=0xB,
+ value=Op.BALANCE(address=Op.ADDRESS, address_warm=True),
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ + Op.SSTORE(
+ key=0x1, value=0x0, key_warm=True, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x2, value=0x0, key_warm=True, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x3, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x4, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x5, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
+ + Op.SSTORE(
+ key=0x6, value=0x0, key_warm=False, original_value=1, new_value=0
+ )
)
-
- pre[coinbase] = Account(balance=0, nonce=1)
- pre[sender] = Account(balance=0x989680)
- # Source: lll
- # { @@1 @@2 [[ 10 ]] (EXP 2 0xffff) [[ 11 ]] (BALANCE (ADDRESS)) [[ 1 ]] 0 [[ 2 ]] 0 [[ 3 ]] 0 [[ 4 ]] 0 [[ 5 ]] 0 [[ 6 ]] 0 } # noqa: E501
- target = pre.deploy_contract( # noqa: F841
- code=Op.POP(Op.SLOAD(key=0x1))
- + Op.POP(Op.SLOAD(key=0x2))
- + Op.SSTORE(key=0xA, value=Op.EXP(0x2, 0xFFFF))
- + Op.SSTORE(key=0xB, value=Op.BALANCE(address=Op.ADDRESS))
- + Op.SSTORE(key=0x1, value=0x0)
- + Op.SSTORE(key=0x2, value=0x0)
- + Op.SSTORE(key=0x3, value=0x0)
- + Op.SSTORE(key=0x4, value=0x0)
- + Op.SSTORE(key=0x5, value=0x0)
- + Op.SSTORE(key=0x6, value=0x0)
- + Op.STOP,
+ target = pre.deploy_contract(
+ code=code + Op.STOP,
storage={1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1},
- balance=0xDE0B6B3A7640000,
- nonce=0,
- address=Address(0xC09923E2275E4EE7822A1FEB5EEE1C18143575C7), # noqa: E501
+ balance=CONTRACT_BALANCE,
)
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ executed = intrinsic + code.gas_cost(fork)
+ gas_limit = executed + 5_000
+
+ sender = pre.fund_eoa(amount=INITIAL_BALANCE)
tx = Transaction(
sender=sender,
to=target,
- data=Bytes(""),
- gas_limit=100000,
+ gas_limit=gas_limit,
+ gas_price=GAS_PRICE,
)
+ # EIP-3529 caps the refund at a fifth of the executed gas.
+ refund = min(code.refund(fork), executed // 5)
+ gas_used = executed - refund
+
post = {
- target: Account(storage={11: 0xDE0B6B3A7640000}),
- coinbase: Account(balance=0),
- sender: Account(balance=0x8F5CF0),
+ target: Account(
+ storage={0xA: 0, 0xB: CONTRACT_BALANCE},
+ ),
+ sender: Account(balance=INITIAL_BALANCE - gas_used * GAS_PRICE),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stSStoreTest/test_sstore_gas.py b/tests/ported_static/stSStoreTest/test_sstore_gas.py
index 975d3765c56..06e840689ad 100644
--- a/tests/ported_static/stSStoreTest/test_sstore_gas.py
+++ b/tests/ported_static/stSStoreTest/test_sstore_gas.py
@@ -1,8 +1,16 @@
"""
-Ori Pomerantz qbzzt1@gmail.com.
+Measure the gas cost of every SSTORE transition class (cold/warm x
+original/current/new value combinations) via inline GAS deltas (by Ori
+Pomerantz qbzzt1@gmail.com).
Ported from:
state_tests/stSStoreTest/sstoreGasFiller.yml
+
+@manually-enhanced: Do not overwrite. The nine measured transition costs
+are derived from SSTORE opcode metadata instead of pinned numbers, so
+EIP-8037's state-gas repricing (and any future one) is tracked
+automatically; the explicit gas limit equals the EIP-7825 cap, so the
+state gas spills into the measured deltas.
"""
import pytest
@@ -12,6 +20,7 @@
Alloc,
Bytes,
Environment,
+ Fork,
StateTestFiller,
Transaction,
)
@@ -29,8 +38,9 @@
def test_sstore_gas(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Ori Pomerantz qbzzt1@gmail."""
+ """Measure each SSTORE transition's gas against opcode metadata."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0xBA1A9CE0BA1A9CE, nonce=1)
@@ -171,18 +181,51 @@ def test_sstore_gas(
nonce=1,
)
+ # The measured transitions, in bytecode order (slots 0 and 1 start at
+ # 24743; slots 2 and 3 start empty). Each stored delta is the pure
+ # SSTORE cost (the contract subtracts its own 8-gas overhead).
+ transitions = [
+ # slot 0: cold, original nonzero -> different nonzero
+ dict(key_warm=False, original_value=24743, new_value=0xBEEF),
+ # slot 0: warm dirty, nonzero -> nonzero
+ dict(
+ key_warm=True,
+ original_value=24743,
+ current_value=0xBEEF,
+ new_value=0xDEADBEEF,
+ ),
+ # slot 0: warm dirty, nonzero -> zero
+ dict(
+ key_warm=True,
+ original_value=24743,
+ current_value=0xDEADBEEF,
+ new_value=0,
+ ),
+ # slot 0: warm dirty, zero -> zero
+ dict(
+ key_warm=True, original_value=24743, current_value=0, new_value=0
+ ),
+ # slot 0: warm dirty, zero -> nonzero
+ dict(
+ key_warm=True,
+ original_value=24743,
+ current_value=0,
+ new_value=0x1234,
+ ),
+ # slot 1: cold, original nonzero -> zero
+ dict(key_warm=False, original_value=24743, new_value=0),
+ # slot 2: cold fresh, zero -> nonzero
+ dict(key_warm=False, original_value=0, new_value=0x60A7),
+ # slot 3: cold fresh, zero -> zero
+ dict(key_warm=False, original_value=0, new_value=0),
+ # slot 3: warm fresh, zero -> nonzero
+ dict(key_warm=True, original_value=0, new_value=0x60A7),
+ ]
post = {
target: Account(
storage={
- 4096: 5000,
- 4097: 100,
- 4098: 100,
- 4099: 100,
- 4100: 100,
- 4101: 5000,
- 4102: 22100,
- 4103: 2200,
- 4104: 20000,
+ 0x1000 + i: Op.SSTORE.with_metadata(**md).gas_cost(fork)
+ for i, md in enumerate(transitions)
},
),
}
diff --git a/tests/ported_static/stSStoreTest/test_sstore_gas_left.py b/tests/ported_static/stSStoreTest/test_sstore_gas_left.py
index 30686397044..34fefa79a0c 100644
--- a/tests/ported_static/stSStoreTest/test_sstore_gas_left.py
+++ b/tests/ported_static/stSStoreTest/test_sstore_gas_left.py
@@ -1,440 +1,116 @@
"""
-Checks EIP-1706/EIP-2200 out of gas requirement for non-mutating SSTOREs.
+Verify the EIP-2200 (EIP-1706) minimum-gas rule for SSTORE: a non-mutating
+store fails unless the gas left exceeds the call stipend, across CALL,
+CALLCODE and DELEGATECALL entry into the storing frame.
Ported from:
state_tests/stSStoreTest/sstore_gasLeftFiller.json
-@manually-enhanced: Do not overwrite. Gas budget refactored to be
-fork-aware (`tx_gas = [intrinsic + tx_data[d].gas_cost(fork)]`), and
-each `Op.CALL` annotated with `inner_call_cost=` metadata so
-`Bytecode.gas_cost(fork)` covers the forwarded inner-frame gas.
-Required for the test to fill correctly under EIP-8037's two-
-dimensional gas model. Hex `gas=` literals also converted to
-human-readable decimals.
+@manually-enhanced: Do not overwrite. The stored-to slot is warmed before
+the boundary call so the stipend check (not the cold-access charge, which
+EIP-8037/8038 reprice) is the binding constraint on every fork; the
+boundary gas is derived as stipend + push cost +/- 1; the success
+indicator forwards gas via `flag * INDICATOR_GAS` instead of the ported
+hardcoded-pc JUMPI; the tx gas is maxed so the indicator's storage write
+is not budget-bound.
"""
import pytest
from execution_testing import (
- EOA,
Account,
- Address,
Alloc,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
)
-from execution_testing.forks import Fork
from execution_testing.vm import Op
-from tests.ported_static.post_state_resolution import (
- resolve_expect_post,
-)
-
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+# Gas forwarded to the success indicator; only needs to cover its regular
+# costs (state gas rides on the transaction's implicit reservoir).
+INDICATOR_GAS = 30_000
+
@pytest.mark.ported_from(
["state_tests/stSStoreTest/sstore_gasLeftFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
+@pytest.mark.valid_from("Istanbul")
+@pytest.mark.parametrize(
+ "opcode",
+ [
+ pytest.param(Op.CALL, id="call"),
+ pytest.param(Op.CALLCODE, id="callcode"),
+ pytest.param(Op.DELEGATECALL, id="delegatecall"),
+ ],
+)
@pytest.mark.parametrize(
- "d, g, v",
+ "gas_offset, store_succeeds",
[
- pytest.param(
- 0,
- 0,
- 0,
- id="d0",
- ),
- pytest.param(
- 1,
- 0,
- 0,
- id="d1",
- ),
- pytest.param(
- 2,
- 0,
- 0,
- id="d2",
- ),
- pytest.param(
- 3,
- 0,
- 0,
- id="d3",
- ),
- pytest.param(
- 4,
- 0,
- 0,
- id="d4",
- ),
- pytest.param(
- 5,
- 0,
- 0,
- id="d5",
- ),
- pytest.param(
- 6,
- 0,
- 0,
- id="d6",
- ),
- pytest.param(
- 7,
- 0,
- 0,
- id="d7",
- ),
- pytest.param(
- 8,
- 0,
- 0,
- id="d8",
- ),
+ pytest.param(-1, False, id="below_boundary"),
+ pytest.param(0, False, id="at_boundary"),
+ pytest.param(1, True, id="above_boundary"),
],
)
-@pytest.mark.pre_alloc_mutable
def test_sstore_gas_left(
state_test: StateTestFiller,
pre: Alloc,
fork: Fork,
- d: int,
- g: int,
- v: int,
+ opcode: Op,
+ gas_offset: int,
+ store_succeeds: bool,
) -> None:
- """Checks EIP-1706/EIP-2200 out of gas requirement for non-mutating..."""
- coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
- sender = EOA(
- key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52
- )
+ """A non-mutating SSTORE needs gas left above the call stipend."""
+ gas_costs = fork.gas_costs()
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=10000000,
- )
-
- pre[sender] = Account(balance=0xE8D4A51000)
- # Source: lll
- # { [[1]] 1 }
- addr = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP,
- storage={1: 1},
- nonce=0,
- address=Address(0xB0409D84AB61455CB8BEC14B94F635146AB55613), # noqa: E501
- )
- # Source: lll
- # { [[1]] 1 }
- addr_2 = pre.deploy_contract( # noqa: F841
- code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP,
- nonce=0,
- address=Address(0x4092B3905CFEA2485EA53222F41EB26E67587802), # noqa: E501
- )
+ # The storing contract: a no-op SSTORE (slot 1 already holds 1 for the
+ # CALL arm; the CALLCODE/DELEGATECALL arms pre-set the caller's own
+ # slot 1). At the SSTORE, gas left = forwarded - two pushes; EIP-2200
+ # requires it to exceed the stipend.
+ store_code = Op.SSTORE(key=0x1, value=0x1)
+ storer = pre.deploy_contract(code=store_code + Op.STOP, storage={1: 1})
+ push_cost = 2 * gas_costs.VERY_LOW
+ boundary_gas = gas_costs.CALL_STIPEND + push_cost + gas_offset
- expect_entries_: list[dict] = [
- {
- "indexes": {"data": [0, 1, 3, 4, 6, 7], "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {addr_2: Account(storage={1: 0})},
- },
- {
- "indexes": {"data": [8, 2, 5], "gas": 0, "value": -1},
- "network": [">=Cancun"],
- "result": {addr_2: Account(storage={1: 1})},
- },
- ]
+ # Written by the success indicator call.
+ indicator = pre.deploy_contract(code=Op.SSTORE(key=0x1, value=0x1))
- post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ if opcode == Op.CALL:
+ # Warm the storer's slot (and pre-write it back to 1) with an
+ # unbounded call, so the boundary call's SSTORE is a warm no-op
+ # and only the stipend check can fail it.
+ prelude = Op.POP(Op.CALL(address=storer))
+ boundary_call = opcode(gas=boundary_gas, address=storer)
+ else:
+ # CALLCODE/DELEGATECALL store into the caller's own slot 1: the
+ # pre-write makes the boundary store a warm no-op.
+ prelude = Op.SSTORE(key=0x1, value=0x1)
+ if opcode == Op.CALLCODE:
+ boundary_call = opcode(gas=boundary_gas, address=storer, value=0)
+ else:
+ boundary_call = opcode(gas=boundary_gas, address=storer)
- tx_data = [
- Op.JUMPI(
- pc=0x4B,
- condition=Op.ISZERO(
- Op.CALL(
- gas=2305,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2305,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.JUMPI(
- pc=0x4B,
- condition=Op.ISZERO(
- Op.CALL(
- gas=2306,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2306,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.JUMPI(
- pc=0x4B,
- condition=Op.ISZERO(
- Op.CALL(
- gas=2307,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2307,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x50,
- condition=Op.ISZERO(
- Op.CALLCODE(
- gas=2305,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2305,
- )
- ),
- )
+ # The indicator receives gas only if the boundary call succeeded
+ # (flag * INDICATOR_GAS), so no jump destinations are needed.
+ caller = pre.deploy_contract(
+ code=prelude
+ Op.POP(
Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
+ gas=Op.MUL(INDICATOR_GAS, boundary_call),
+ address=indicator,
)
)
- + Op.JUMPDEST
+ Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x50,
- condition=Op.ISZERO(
- Op.CALLCODE(
- gas=2306,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2306,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x50,
- condition=Op.ISZERO(
- Op.CALLCODE(
- gas=2307,
- address=addr,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=2307,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x4E,
- condition=Op.ISZERO(
- Op.DELEGATECALL(
- gas=2305,
- address=addr,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x4E,
- condition=Op.ISZERO(
- Op.DELEGATECALL(
- gas=2306,
- address=addr,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- Op.SSTORE(key=0x1, value=0x1)
- + Op.JUMPI(
- pc=0x4E,
- condition=Op.ISZERO(
- Op.DELEGATECALL(
- gas=2307,
- address=addr,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- )
- ),
- )
- + Op.POP(
- Op.CALL(
- gas=30_000,
- address=addr_2,
- value=0x0,
- args_offset=0x0,
- args_size=0x0,
- ret_offset=0x0,
- ret_size=0x0,
- inner_call_cost=30_000,
- )
- )
- + Op.JUMPDEST
- + Op.STOP,
- ]
- # Fork-aware gas budget: contract-creation intrinsic from the
- # fork's calculator, plus the bytecode's own gas cost (which
- # already includes the gas forwarded to inner CALLs via opcode
- # metadata). Any future fork-cost change is automatically
- # respected.
- intrinsic = fork.transaction_intrinsic_cost_calculator()(
- calldata=tx_data[d],
- contract_creation=True,
)
- tx_gas = [intrinsic + tx_data[d].gas_cost(fork)]
- tx_value = [1]
tx = Transaction(
- sender=sender,
- to=None,
- data=tx_data[d],
- gas_limit=tx_gas[g],
- value=tx_value[v],
- error=_exc,
+ sender=pre.fund_eoa(),
+ to=caller,
)
- state_test(env=env, pre=pre, post=post, tx=tx)
+ post = {
+ indicator: Account(storage={1: 1 if store_succeeds else 0}),
+ }
+
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stSolidityTest/test_recursive_create_contracts.py b/tests/ported_static/stSolidityTest/test_recursive_create_contracts.py
index 836f16024d1..8f23e6ec41e 100644
--- a/tests/ported_static/stSolidityTest/test_recursive_create_contracts.py
+++ b/tests/ported_static/stSolidityTest/test_recursive_create_contracts.py
@@ -1,8 +1,14 @@
"""
-Test_recursive_create_contracts.
+Verify recursively self-creating Solidity contracts stop when the
+transaction budget runs dry, leaving exactly one child.
Ported from:
state_tests/stSolidityTest/RecursiveCreateContractsFiller.json
+
+@manually-enhanced: Do not overwrite. The EIP-8037 state gas of the
+in-test creations is added to the ported budget as a fork-derived
+surcharge (exactly 0 before EIP-8037), preserving the ported
+behavior on every fork.
"""
import pytest
@@ -17,6 +23,7 @@
Transaction,
compute_create_address,
)
+from execution_testing.forks import Fork
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
@@ -31,8 +38,9 @@
def test_recursive_create_contracts(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_recursive_create_contracts."""
+ """Recursive contract creation runs dry at the expected depth."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87)
sender = pre.fund_eoa(amount=0x1DCD6500)
@@ -250,7 +258,12 @@ def test_recursive_create_contracts(
sender=sender,
to=contract_0,
data=Bytes("a444f5e9") + Hash(0x304),
- gas_limit=300000,
+ # EIP-8037 surcharge (0 before): the first child creation's
+ # new-account and code-deposit state gas spill into this
+ # budget; the recursion still runs dry at the ported depth.
+ gas_limit=300000
+ + fork.create_state_gas()
+ + fork.code_deposit_state_gas(code_size=0xC8),
value=1,
)
diff --git a/tests/ported_static/stSolidityTest/test_test_contract_interaction.py b/tests/ported_static/stSolidityTest/test_test_contract_interaction.py
index 0bcc63132a1..594a260899a 100644
--- a/tests/ported_static/stSolidityTest/test_test_contract_interaction.py
+++ b/tests/ported_static/stSolidityTest/test_test_contract_interaction.py
@@ -1,8 +1,14 @@
"""
-Test_test_contract_interaction.
+Verify a Solidity contract creating a child and interacting with it
+through its dispatcher within the same transaction.
Ported from:
state_tests/stSolidityTest/TestContractInteractionFiller.json
+
+@manually-enhanced: Do not overwrite. The EIP-8037 state gas of the
+in-test creations is added to the ported budget as a fork-derived
+surcharge (exactly 0 before EIP-8037), preserving the ported
+behavior on every fork.
"""
import pytest
@@ -15,6 +21,7 @@
StateTestFiller,
Transaction,
)
+from execution_testing.forks import Fork
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
@@ -29,8 +36,9 @@
def test_test_contract_interaction(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_test_contract_interaction."""
+ """Create a child contract and interact with it in one transaction."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0x5F5E100)
@@ -165,7 +173,11 @@ def test_test_contract_interaction(
sender=sender,
to=target,
data=Bytes("c0406226"),
- gas_limit=350000,
+ # EIP-8037 surcharge (0 before): the created child's new-account
+ # and code-deposit state gas spill into this budget.
+ gas_limit=350000
+ + fork.create_state_gas()
+ + fork.code_deposit_state_gas(code_size=0x81),
value=1,
)
diff --git a/tests/ported_static/stSolidityTest/test_test_contract_suicide.py b/tests/ported_static/stSolidityTest/test_test_contract_suicide.py
index 1dd26ffea76..fc12dd3e319 100644
--- a/tests/ported_static/stSolidityTest/test_test_contract_suicide.py
+++ b/tests/ported_static/stSolidityTest/test_test_contract_suicide.py
@@ -1,8 +1,14 @@
"""
-Test_test_contract_suicide.
+Verify a Solidity contract that creates a child, tells it to
+self-destruct, and re-calls it within the same transaction.
Ported from:
state_tests/stSolidityTest/TestContractSuicideFiller.json
+
+@manually-enhanced: Do not overwrite. The EIP-8037 state gas of the
+in-test creations is added to the ported budget as a fork-derived
+surcharge (exactly 0 before EIP-8037), preserving the ported
+behavior on every fork.
"""
import pytest
@@ -15,6 +21,7 @@
StateTestFiller,
Transaction,
)
+from execution_testing.forks import Fork
from execution_testing.vm import Op
REFERENCE_SPEC_GIT_PATH = "N/A"
@@ -29,8 +36,9 @@
def test_test_contract_suicide(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_test_contract_suicide."""
+ """Create a child, destroy it, and call it again in one transaction."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
sender = pre.fund_eoa(amount=0x5F5E100)
@@ -187,7 +195,11 @@ def test_test_contract_suicide(
sender=sender,
to=target,
data=Bytes("c0406226"),
- gas_limit=350000,
+ # EIP-8037 surcharge (0 before): the created child's new-account
+ # and code-deposit state gas spill into this budget.
+ gas_limit=350000
+ + fork.create_state_gas()
+ + fork.code_deposit_state_gas(code_size=0x81),
value=1,
)
diff --git a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py
index b20492dea03..fcb7f0e4d7a 100644
--- a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py
+++ b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py
@@ -1,11 +1,13 @@
"""
-A single contract can execute SELFDESTRUCT multiple times using by...
-
-multiple times. The second and later SELFDESTRUCTs have little effect but can
-touch some new beneficiary addresses.
+Verify a contract executing SELFDESTRUCT twice in one transaction: the
+second has little effect but touches a new beneficiary address.
Ported from:
state_tests/stSystemOperationsTest/doubleSelfdestructTouch_ParisFiller.yml
+
+@manually-enhanced: Do not overwrite. Both forwarded call budgets derive
+from the fork (the callee's cold first-set store is state-priced under
+EIP-8037 and must fit the grant).
"""
import pytest
@@ -85,6 +87,16 @@ def test_double_selfdestruct_touch_paris(
gas_limit=30000000,
)
+ # Derived budget for each selfdestruct call: the callee's cold
+ # first-set store is state-priced under EIP-8037 and must fit the
+ # grant; the margin covers its SLOAD, SELFDESTRUCT, and accesses.
+ sd_call_gas = (
+ Op.SSTORE(
+ key=0x0, value=0x1, key_warm=False, original_value=0, new_value=1
+ ).gas_cost(fork)
+ + 20_000
+ )
+
pre[sender] = Account(balance=0x5F5E102)
pre[empty_account_1] = Account(balance=10)
pre[empty_account_2] = Account(balance=10)
@@ -120,7 +132,7 @@ def test_double_selfdestruct_touch_paris(
+ Op.SWAP1
+ Op.POP(
Op.CALL(
- gas=0x11170,
+ gas=sd_call_gas,
address=0x29E4504A3D2A0E0AE0EBBBEFEDD4570639B3EBEE,
value=Op.DUP6,
args_offset=Op.DUP1,
@@ -131,7 +143,7 @@ def test_double_selfdestruct_touch_paris(
)
+ Op.SUB
+ Op.PUSH20[0x29E4504A3D2A0E0AE0EBBBEFEDD4570639B3EBEE]
- + Op.PUSH3[0x11170]
+ + Op.PUSH3[sd_call_gas]
+ Op.CALL
+ Op.STOP,
nonce=0,
diff --git a/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py b/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py
index e14591f4675..44ea5d78185 100644
--- a/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py
+++ b/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py
@@ -1,5 +1,6 @@
"""
-Test_opcodes_transaction_init.
+Verify each opcode family executes inside a creation transaction's init
+code, including invalid-code and side-effect cases.
Ported from:
state_tests/stTransactionTest/Opcodes_TransactionInitFiller.json
@@ -838,7 +839,7 @@ def test_opcodes_transaction_init(
g: int,
v: int,
) -> None:
- """Test_opcodes_transaction_init."""
+ """Run each opcode inside a creation transaction's init code."""
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
contract_1 = Address(0x0F572E5295C57F15886F9B263E2F6D2D6C7B5EC6)
@@ -1468,7 +1469,16 @@ def test_opcodes_transaction_init(
Op.SELFDESTRUCT(address=Op.ORIGIN),
Bytes("ef"),
Op.CALL(
- gas=0xC350,
+ # Derived: the callee's cold first-set store is state-priced
+ # under EIP-8037 and must fit the grant.
+ gas=Op.SSTORE(
+ key=0x0,
+ value=0x1,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ ).gas_cost(fork)
+ + 5_000,
address=contract_0,
value=Op.DUP1,
args_offset=Op.DUP1,
@@ -1503,7 +1513,9 @@ def test_opcodes_transaction_init(
+ Op.MSTORE8(offset=0x0, value=0xEF)
+ Op.RETURN(offset=0x0, size=0x1),
]
- tx_gas = [400000]
+ # The d120 arm's nested CREATE adds a new-account state charge under
+ # EIP-8037 (0 before); every other arm keeps the ported budget.
+ tx_gas = [400000 + (fork.create_state_gas() if d == 120 else 0)]
tx_value = [100000]
tx = Transaction(
diff --git a/tests/ported_static/stTransactionTest/test_store_gas_on_create.py b/tests/ported_static/stTransactionTest/test_store_gas_on_create.py
index 2d97f61d885..0cad55501a9 100644
--- a/tests/ported_static/stTransactionTest/test_store_gas_on_create.py
+++ b/tests/ported_static/stTransactionTest/test_store_gas_on_create.py
@@ -1,17 +1,21 @@
"""
-Test_store_gas_on_create.
+Verify the gas a CREATE's init code observes when the creating contract is
+entered directly by the transaction: the child receives all but one 64th
+of what remains in the creating frame.
Ported from:
state_tests/stTransactionTest/StoreGasOnCreateFiller.json
+
+@manually-enhanced: Do not overwrite. The ported bytecode is kept, but the
+transaction budget and the child's stored GAS observation are derived from
+the fork (the ported absolute pin moved with every schedule change).
"""
import pytest
from execution_testing import (
Account,
- Address,
Alloc,
- Bytes,
- Environment,
+ Fork,
StateTestFiller,
Transaction,
compute_create_address,
@@ -21,51 +25,79 @@
REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
+CHILD_GAS_SLOT = 0xFD
+
@pytest.mark.ported_from(
["state_tests/stTransactionTest/StoreGasOnCreateFiller.json"],
)
-@pytest.mark.valid_from("Cancun")
-@pytest.mark.pre_alloc_mutable
+@pytest.mark.valid_from("Berlin")
def test_store_gas_on_create(
state_test: StateTestFiller,
pre: Alloc,
+ fork: Fork,
) -> None:
- """Test_store_gas_on_create."""
- coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
- sender = pre.fund_eoa(amount=0x17D78400)
+ """A CREATE's init code observes 63/64 of the creating frame's gas."""
+ # Child init code: stores the gas it observes, deposits no code.
+ child_code = Op.SSTORE(
+ key=CHILD_GAS_SLOT,
+ value=Op.GAS,
+ key_warm=False,
+ original_value=0,
+ new_value=1,
+ )
+ child_bytes = bytes(child_code)
- env = Environment(
- fee_recipient=coinbase,
- number=1,
- timestamp=1000,
- prev_randao=0x20000,
- base_fee_per_gas=10,
- gas_limit=1000000,
+ setup = Op.MSTORE(
+ offset=0x0,
+ value=int.from_bytes(child_bytes, "big"),
+ new_memory_size=0x20,
+ )
+ create_code = Op.CREATE(
+ value=0x0,
+ offset=0x20 - len(child_bytes),
+ size=len(child_bytes),
+ new_memory_size=0x20,
+ old_memory_size=0x20,
+ init_code_size=len(child_bytes),
+ )
+ creator = pre.deploy_contract(
+ code=setup + Op.POP(create_code) + Op.STOP,
)
- # Source: lll
- # { (MSTORE 0 0x5a60fd55) (CREATE 0 28 4)}
- coinbase = pre.deploy_contract( # noqa: F841
- code=Op.MSTORE(offset=0x0, value=0x5A60FD55)
- + Op.CREATE(value=0x0, offset=0x1C, size=0x4)
- + Op.STOP,
- nonce=0,
- address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501
+ # Fork-derived budget with margin left after the child's work.
+ intrinsic = fork.transaction_intrinsic_cost_calculator()()
+ gas_limit = (
+ intrinsic
+ + setup.gas_cost(fork)
+ + create_code.gas_cost(fork)
+ + child_code.gas_cost(fork)
+ + 15_000
)
tx = Transaction(
- sender=sender,
- to=coinbase,
- data=Bytes(""),
- gas_limit=131882,
- value=100,
+ sender=pre.fund_eoa(),
+ to=creator,
+ gas_limit=gas_limit,
+ )
+
+ # The child receives all but one 64th of what remains after the
+ # setup and the CREATE's own charges; its GAS read costs 2.
+ base = (
+ gas_limit
+ - intrinsic
+ - setup.gas_cost(fork)
+ - create_code.gas_cost(fork)
)
+ assert base > 0, "the budget must cover the CREATE's charges"
+ child_observed = (base - base // 64) - Op.GAS.gas_cost(fork)
post = {
- compute_create_address(address=coinbase, nonce=0): Account(
- storage={253: 0x12F39}
+ compute_create_address(address=creator, nonce=1): Account(
+ nonce=1,
+ code=b"",
+ storage={CHILD_GAS_SLOT: child_observed},
),
}
- state_test(env=env, pre=pre, post=post, tx=tx)
+ state_test(pre=pre, post=post, tx=tx)
diff --git a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py
index 03537745a43..91b32041a05 100644
--- a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py
+++ b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py
@@ -1,8 +1,14 @@
"""
-Test_suicides_and_internal_call_suicides_success.
+Verify SELFDESTRUCT inside an internal call: the callee self-destructs to
+a previously nonexistent beneficiary, which materializes only when the
+forwarded gas covers the new-account charge.
Ported from:
state_tests/stTransactionTest/SuicidesAndInternalCallSuicidesSuccessFiller.json
+
+@manually-enhanced: Do not overwrite. The two forwarded-gas calldata words
+derive from the fork's SELFDESTRUCT new-account cost (state-priced under
+EIP-8037), keeping one arm starved and one funded on every fork.
"""
import pytest
@@ -59,7 +65,7 @@ def test_suicides_and_internal_call_suicides_success(
g: int,
v: int,
) -> None:
- """Test_suicides_and_internal_call_suicides_success."""
+ """A funded SELFDESTRUCT materializes its beneficiary."""
coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
contract_0 = Address(0x0000000000000000000000000000000000000000)
contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B)
@@ -127,11 +133,25 @@ def test_suicides_and_internal_call_suicides_success(
post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork)
+ # The calldata word is the gas forwarded to the self-destructing
+ # callee. Its SELFDESTRUCT pays a new-account charge for the funded
+ # beneficiary (state-priced under EIP-8037, spilling from the
+ # callee's grant), so both budgets derive from that cost: one starves
+ # it, one funds it with margin.
+ sd_cost = Op.SELFDESTRUCT.with_metadata(
+ address_warm=True, account_new=True
+ ).gas_cost(fork)
tx_data = [
- Hash(0x55F0),
- Hash(0xAAF0),
+ Hash(sd_cost // 2),
+ Hash(sd_cost + 5_000),
+ ]
+ tx_gas = [
+ fork.transaction_intrinsic_cost_calculator()(
+ calldata=Hash(0), sends_value=True
+ )
+ + sd_cost
+ + 40_000
]
- tx_gas = [150000]
tx_value = [10]
tx = Transaction(