Skip to content

alloy-op-evm: don't rebate inner CREATE warming - #22098

Open
nonsense wants to merge 3 commits into
developfrom
ae/sdm/fm17-inner-create-warming-rebate
Open

alloy-op-evm: don't rebate inner CREATE warming#22098
nonsense wants to merge 3 commits into
developfrom
ae/sdm/fm17-inner-create-warming-rebate

Conversation

@nonsense

@nonsense nonsense commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Inner CREATE/CREATE2 operations could claim a 2,500-gas SDM warm-account rebate for the created address. These opcodes warm that address but do not pay EIP-2929's cold-account surcharge, so there is no cold-to-warm delta to refund.

The excess refund over-debited fee vaults. Verifiers apply the producer's refund rather than recomputing it, so affected blocks remained valid.

Fix

Mark created addresses as non-refundable at every depth while still recording them in the block-warm set. A later transaction that genuinely cold-accesses the address can therefore still claim its rebate.

The same hook seeds revm's cached created address before frame initialization. Its comment now reflects that ordering, missing creator state fails closed, and CreateScheme variants are matched explicitly.

Tests

  • Reproduces the inner CREATE2 over-refund.
  • Verifies inner-created addresses remain warm for later transactions.
  • Retains top-level CREATE coverage.
  • Checks settlement arithmetic and conservation at maximum-width inputs.

cargo nextest run -p alloy-op-evm: 82 passed
cargo clippy -p alloy-op-evm --all-features --all-targets -- -D warnings: clean

Known limitation

Per-transaction warming sets are not journal-revert-aware. A reverted inner create can therefore suppress a later valid rebate in the same transaction. This is an under-refund and is left for a separate change.

Companion PR

ethereum-optimism/optimism-premium#98 applies the same fix to BlockWarmingPolicy. The premium repository's monorepo pin is unchanged; this PR lands first.

@nonsense
nonsense force-pushed the ae/sdm/fm17-inner-create-warming-rebate branch from 1dabc40 to 2b62eb5 Compare July 29, 2026 10:30
nonsense and others added 2 commits July 29, 2026 13:31
An inner (depth > 0) CREATE/CREATE2 claimed the 2500-gas SDM warm-account
rebate for the address it created, even though the creating transaction never
paid an EIP-2929 cold-account access for it: SDMWarmingInspector::create
suppressed the rebate only `if top_level`.

CREATE and CREATE2 are not EIP-2929-metered opcodes. The create path in
revm-interpreter-41.0.0 (src/instructions/contract.rs:23-110) charges
initcode_cost + create_cost/create2_cost + memory (+ EIP-8037 state gas) and
never a cold-account-access surcharge, so there is no cold->warm delta to
return at any depth. The rebate refunded a cost the EVM never levied,
over-debiting the fee vaults on legitimate CREATE2 factory traffic.

Insert the created address into intrinsic_warm_accounts unconditionally and
keep the observe_account_touch call, so the address is still recorded in the
block-warm set and a later tx that genuinely cold-accesses it still earns its
rebate. The change only ever adds to the per-tx suppression set, so it cannot
introduce an over-refund.

Also correct the created_address memoization comment, which had the revm
ordering backwards: inspect_frame_init dispatches Inspector::create through
frame_start before calling frame_init, so this hook seeds the OnceCell and revm
reuses our value rather than the reverse. The journal nonce read is
load-bearing, not a fallback. The CreateScheme match arms are enumerated so a
future nonce-dependent scheme cannot silently seed the cell with nonce 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…remes

Closes the max-input saturation gap the SDM FMA flags as NON-BLOCKING under
FM2. Nothing pinned that the saturating arithmetic in post_exec_settlement_deltas
cannot silently truncate a settlement leg and break value conservation under
the widest inputs a hostile producer can reach.

Sweeps base_fee in {0, 1, 1e9, u64::MAX} x refund in {1, 21k, u64::MAX/2,
u64::MAX} x effective_gas_price in {basefee, basefee+1, u128::MAX}, asserting
each leg against its exact closed form as well as the conservation identity
sender == beneficiary + base_fee + operator_fee. Checking the legs individually
matters: the identity alone still holds if two legs clamp by offsetting
amounts. All of them hold exactly, so no saturation engages — the widest
product is ~2^192, well inside U256.

Only effective_gas_price >= basefee is exercised: below-basefee txs never reach
settlement (revm rejects them with GasPriceLessThanBasefee upstream), which is
the precondition the beneficiary_gas_price saturating_sub relies on.

This is coverage only; no behavior change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nonsense
nonsense force-pushed the ae/sdm/fm17-inner-create-warming-rebate branch from 2b62eb5 to 5390fbe Compare July 29, 2026 10:31
Comment thread rust/alloy-op-evm/src/block/tests.rs
Comment thread rust/alloy-op-evm/src/post_exec/inspector.rs Outdated
Comment thread rust/alloy-op-evm/src/block/tests.rs Outdated
@nonsense
nonsense marked this pull request as ready for review July 29, 2026 14:09
@nonsense
nonsense requested a review from a team as a code owner July 29, 2026 14:09
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