Skip to content

refactor(eip8037): defer state-gas to frame return via NewStateTracker#3634

Open
rakita wants to merge 7 commits intosnobal-devnet-4from
dev5-new-way
Open

refactor(eip8037): defer state-gas to frame return via NewStateTracker#3634
rakita wants to merge 7 commits intosnobal-devnet-4from
dev5-new-way

Conversation

@rakita
Copy link
Copy Markdown
Member

@rakita rakita commented Apr 29, 2026

Summary

Switches EIP-8037 state-gas from immediate charging to a deferred model. State-creating opcodes bump a simple per-frame counter on the Interpreter (new_state); the totals are reconciled with the gas tracker at frame return inside EthFrame::process_next_action.

  • on ok: state_gas_refunded added to the reservoir first, then state_gas charged (may OOG → result flipped to OutOfGas before journal commit)
  • on revert/halt: counters dropped — state work didn't happen, no reservoir change

Notes

  • NewStateTracker (crates/context/interface/src/cfg/new_state_tracker.rs) is intentionally minimal: two u64 counters and a small set of add_* / remove_* methods. Uses hardcoded Glamsterdam EIP-8037 values (bytes_per_unit × CPSB_GLAMSTERDAM) for now — does not read GasParams at runtime.
  • Migrates the immediate-charge call sites (SSTORE in host.rs, SELFDESTRUCT in host.rs, CREATE/CREATE2 in contract.rs, CALL-with-value in call_helpers.rs) to bump interpreter.new_state.add_*(). Removes the state_gas! macro.
  • return_create no longer commits/reverts the journal; it validates code, charges regular code-deposit + hash gas, bumps add_code_deposit_bytes, and lets process_next_action apply state-gas and finalize (set_code on success).
  • handle_reservoir_remaining_gas is simplified — the deferred model removes the spill-on-revert distortion, so the old logical_refund / baseline / excess math is no longer needed.
  • Parent's return_result Create-branch: on child failure → interpreter.new_state.remove_create_account() (replaces refill_reservoir(create_state_gas)).

Test plan

  • cargo build --workspace clean
  • 23 EIP-8037 tests in revm-ee-tests currently fail because the test suite overrides sstore_set_state_gas etc. via GasParams (e.g. 200_000) but the new NewStateTracker ignores those — the numbers it produces are EIP-8037 Glamsterdam defaults (e.g. SSTORE = 32 × 1174 = 37_568). Need to either update test expectations, switch NewStateTracker to read GasParams/cpsb, or both.
  • Spot-check tracing/inspector output (state_gas no longer goes negative since charges are deferred)

State-creating opcodes (SSTORE 0→x, CREATE/CREATE2, CALL/SELFDESTRUCT
to an empty account, code deposit) no longer charge the reservoir
inline. Instead they bump a simple per-frame counter on the
Interpreter (`new_state`). At frame return — in
`EthFrame::process_next_action` — the totals are reconciled with the
gas tracker:

* on ok: `state_gas_refunded` is added to the reservoir first, then
  `state_gas` is charged from it (may OOG, in which case the result is
  flipped to OutOfGas before journal commit)
* on revert/halt: the counters are dropped; state work didn't happen,
  so no reservoir change

`NewStateTracker` is intentionally minimal — two `u64` counters and
`add_storage` / `remove_storage` / `add_create_account` /
`remove_create_account` / `add_call_account` / `add_code_deposit_bytes`
/ `merge` / `clear` — and uses hardcoded Glamsterdam EIP-8037 values
(`bytes_per_unit × CPSB_GLAMSTERDAM`) for now.

`return_create` no longer commits/reverts the journal; it validates
the deployed code, charges the regular code-deposit + hash gas, bumps
`add_code_deposit_bytes`, and lets `process_next_action` apply
state-gas and finalize the journal (set_code on success).

`handle_reservoir_remaining_gas` is simplified: with the deferred
model, the child's reservoir on revert/halt is just the parent
pre-call value plus successful-grandchild contributions — the old
`logical_refund / baseline / excess` math compensating for
spill-on-revert is no longer necessary.

Removes the `state_gas!` macro.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 29, 2026

Merging this PR will degrade performance by 4.84%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 8 regressed benchmarks
✅ 168 untouched benchmarks
⏩ 1 skipped benchmark1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation REVERT_50 109 µs 113.1 µs -3.59%
Simulation SLOAD_50 24.3 µs 25.4 µs -4.3%
Simulation SSTORE_50 30.2 µs 31.5 µs -4.08%
Simulation subcall_1000_transfer_1wei 1.2 ms 1.3 ms -4.84%
Simulation subcall_1000_same_account 1.1 ms 1.2 ms -3.3%
Simulation transact_1000tx_commit_inner_every_40 2.2 ms 2.4 ms -4.37%
Simulation subcall_1000_nested 2.2 ms 2.2 ms -3.43%
Simulation transact_commit_1000txs 2.8 ms 2.9 ms -3.97%

Comparing dev5-new-way (05c0d7a) with main (bd89862)2

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

  2. No successful run was found on snobal-devnet-4 (d9b5182) during the generation of this report, so main (bd89862) was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

rakita added 6 commits April 29, 2026 14:13
Following dev4-frame-oog: return_create now owns all create-specific
work (validation, code-deposit gas, state-gas counter bump, and
set_code on success) and returns a `bool commit`. process_next_action
becomes create-agnostic — it only forwards to return_create or treats
a Call as committed when the interpreter returned ok, then runs the
generic state-gas reconciliation and journal commit/revert that's
shared by both paths.
…ccount

Gating purely on is_amsterdam_eip8037_enabled meant every CALL bumped
the per-frame state-gas counter, so a frame making N non-empty-target
CALLs would charge N × NEW_ACCOUNT_STATE_GAS at frame return.
Restoring the state_gas_cost > 0 guard — non-zero only when EIP-8037
is enabled AND the CALL actually materializes a new empty account.
…t/halt

When a frame reverts or halts, the per-frame state-gas counters are
dropped (state work didn't happen). Add the reverted state_gas back to
the frame's reservoir so the parent's revert/halt branch in
handle_reservoir_remaining_gas accounts for it correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant