Skip to content

feat: add EIP-8037 / TIP-1016 state gas support#3406

Merged
rakita merged 46 commits into
mainfrom
rakita/state-gas
Apr 10, 2026
Merged

feat: add EIP-8037 / TIP-1016 state gas support#3406
rakita merged 46 commits into
mainfrom
rakita/state-gas

Conversation

@rakita

@rakita rakita commented Feb 6, 2026

Copy link
Copy Markdown
Member

Summary

Implements EIP-8037 (TIP-1016) dual-limit gas accounting, separating execution gas (CPU work) from state gas (storage/account creation, code deposit). This introduces a reservoir model where state gas draws from a separate pool before spilling into regular gas.

Key Changes

  • Gas struct (interpreter/gas.rs): Added reservoir, regular_gas_budget, state_gas_spent fields with record_state_cost() that draws from reservoir first, then spills to regular gas. gas_left() excludes reservoir per EIP-8037.
  • GasParams (context/interface/cfg/gas_params.rs): New state gas cost IDs — sstore_set_state_gas, new_account_state_gas, code_deposit_state_gas, create_state_gas.
  • Charging sites (5 locations):
    • SSTORE — new slot creation (host.rs)
    • CREATE/CREATE2 — contract creation (contract.rs)
    • Code deposit — per-byte code storage (frame.rs)
    • CALL — new account with value transfer (call_helpers.rs)
    • SELFDESTRUCT — new beneficiary account (host.rs)
  • Frame propagation (handler/frame.rs): Child frame state gas accumulates to parent on success; reservoir refill on halt/revert prevents state gas from permanently consuming regular gas budget.
  • Precompiles: All precompile gas charges use record_regular_cost() — no state gas for precompiles.
  • GAS opcode (system.rs): Returns remaining - reservoir per spec.
  • op-revm: Updated to handle new gas propagation in last_frame_result.
  • Inspector/EIP-3155: Logs state gas fields for tracing.

Architecture

Component Details
Gas struct remaining = gas_left + reservoir; regular_gas_budget is implicit cap for regular gas
Regular gas Checked against gas_left only via record_cost(), cannot draw from reservoir
State gas Drawn from reservoir first via record_state_cost(), spills to gas_left when exhausted
GAS opcode Returns gas_left() only, excluding the state gas reservoir
Child frames Inherit parent's regular_gas_budget and reservoir directly
EIP-8037 Higher gas costs for state-creating ops, auto-enabled for Amsterdam+ specs
TIP-1016 Dual-limit model with separate state gas reservoir, opt-in via CfgEnv

Cfg Integration

  • Cfg::state_gas_ids() returns Option<GasParams>None disables state gas (backward compatible).
  • CfgEnv stores state gas config behind #[cfg(feature = "optional_state_gas_params")].

Test Suite

35+ test vectors in crates/ee-tests/src/eip8037.rs covering 7 categories:

  1. SSTORE state gas — new slot, overwrite, zero-to-zero, multiple slots
  2. CREATE/CREATE2 state gas — empty code, with code, with SSTORE, large code, OOG
  3. CALL state gas — new/existing account, with/without value, SELFDESTRUCT
  4. Regular gas cap enforcement — OOG, sufficient, reservoir interaction, block gas limit
  5. Cross-frame propagation — child success/revert/halt, nested 3+ levels
  6. Interactions — refund, GAS opcode, spend_all, precompiles, state_gas_spent tracking
  7. Reservoir refill — revert vs halt behavior, boundary cases

Test Plan

  • cargo build --workspace passes
  • cargo nextest run --workspace — all tests pass
  • cargo nextest run -p ee-tests — all 35+ EIP-8037 tests pass
  • cargo clippy --workspace --all-targets --all-features — clean
  • cargo fmt --all — clean
  • no_std compatibility check
  • Ethereum state tests via revme statetest

@codspeed-hq

codspeed-hq Bot commented Feb 6, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 5.58%

⚠️ 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

⚡ 3 improved benchmarks
❌ 17 regressed benchmarks
✅ 157 untouched benchmarks

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

Performance Changes

Benchmark BASE HEAD Efficiency
JUMP_50 17.9 µs 18.6 µs -3.34%
GASLIMIT_50 18.4 µs 19 µs -3.11%
DELEGATECALL_50 86.6 µs 90 µs -3.79%
GAS_50 18.3 µs 18.9 µs -3.12%
JUMPDEST_50 15.6 µs 16.3 µs -4.33%
MCOPY_50 20.8 µs 21.5 µs -3.01%
MSIZE_50 18.4 µs 19 µs -3.11%
MSTORE_50 19.7 µs 20.4 µs -3.31%
PUSH0_50 18.3 µs 18.9 µs -3.27%
PUSH1_50 18.4 µs 19 µs -3.26%
PUSH2_50 18.5 µs 19.1 µs -3.24%
PUSH8_50 18.6 µs 19.2 µs -3.07%
PUSH7_50 18.8 µs 19.4 µs -3.05%
STATICCALL_50 89.8 µs 92.9 µs -3.33%
subcall_1000_nested 2.1 ms 2.3 ms -5.58%
subcall_1000_transfer_1wei 1.2 ms 1.3 ms -4.15%
subcall_1000_same_account 1.1 ms 1.2 ms -5.34%
blake2/12_rounds 4.7 µs 4.5 µs +4.06%
blake2/2_rounds 3.2 µs 3.1 µs +4.12%
blake2/4_rounds 3.6 µs 3.4 µs +5.49%

Comparing rakita/state-gas (f410840) with main (d060498)

Open in CodSpeed

@rakita
rakita changed the base branch from main to expose-gas February 10, 2026 16:42
Base automatically changed from expose-gas to main February 12, 2026 13:20
Comment thread crates/context/interface/src/cfg/gas_params.rs Outdated
Comment thread crates/context/interface/src/cfg/gas.rs
Comment thread crates/context/interface/src/cfg/gas.rs Outdated
@rakita rakita changed the title feat: add TIP-1016 state gas support feat: add EIP-8037 / TIP-1016 state gas support Mar 4, 2026
@rakita
rakita changed the base branch from main to devnet3 March 4, 2026 09:05
Base automatically changed from devnet3 to main March 13, 2026 10:45
Add state gas accounting with reservoir model across interpreter, handler,
context, and precompile crates. Includes comprehensive EIP-8037 test suite
with 35+ test vectors covering SSTORE, CALL, CREATE, SELFDESTRUCT, reservoir
refill, and gas opcode behavior.
@rakita
rakita force-pushed the rakita/state-gas branch from f904eac to ee5f210 Compare March 13, 2026 10:50
Comment thread crates/context/interface/src/cfg/gas_params.rs
Comment thread crates/context/interface/src/cfg/gas_params.rs
rakita added 11 commits March 16, 2026 14:00
… refill

- Rename ResultGas::spent to regular_gas_spent to distinguish from state gas after EIP-8037
- Add total_gas_spent() method and deprecate old spent()/set_spent()/with_spent() accessors
- Simplify reservoir refill logic: replace handler_reservoir_refill() with inline max()
- Remove unnecessary reservoir manipulation in precompile path
- Update op-revm handler to use simplified reservoir refill
- Update Cargo.lock dependencies
Consolidate the EIP-8037 auth list refund splitting logic from
run_without_catch_error into apply_eip7702_auth_list, avoiding
duplication between handler and inspector. pre_execution now takes
init_and_floor_gas as a mutable reference and returns only the
regular refund portion.
Introduce PrecompileFailure wrapper that carries both the error and
an optional GasTracker, allowing precompiles that charge state gas
(EIP-8037) to propagate gas info back to the caller on failure for
proper reservoir refill.
Update all precompile Err returns to use .into() for converting
PrecompileError to PrecompileFailure, enabling state gas tracking
on precompile failures.
Commit 257d261 introduced a regression in gas accounting by incorrectly
erasing both remaining and reservoir gas from total_gas_spent. The reservoir
should not be erased since it's tracked separately via state_gas_spent.

This fix reverts the erase_cost line from erase_cost(remaining + reservoir)
to erase_cost(remaining), which correctly accounts for:
- Regular gas: deducted from remaining calculation
- State gas: tracked separately in state_gas_spent field

Results:
- State test failures reduced from 20 to 4 (pre-regression count)
- All 408 workspace tests now pass
- Updated EIP-8037 test data and assertions to match correct values
Refactor to improve code organization by moving the state gas deduction
logic from the execution method into the first_frame_input method, where
frame initialization concerns are naturally grouped together.

This keeps frame setup logic cohesive and simplifies the execution method
to focus on the execution loop rather than frame setup details.

No functional change - same gas accounting and execution behavior.
Four fixes for EIP-8037 state gas interactions:

1. post_execution: Fix EIP-7623 floor gas check to account for EIP-8037
   reservoir. Per EIP-8037, gas_used = tx.gas - gas_left - reservoir.
   The floor check now subtracts reservoir when computing gas used.

2. handler: Simplify reservoir handling in last_frame_result to always
   use the frame's final reservoir value directly. It already reflects
   child frame restorations via handle_reservoir_remaining_gas.

3. handler: Add EIP-8037 floor gas validation when gas_limit exceeds
   TX_MAX_GAS_LIMIT. The maximum gas_used is capped at TX_MAX_GAS_LIMIT
   since excess goes to reservoir, so floor_gas must not exceed that cap.

4. inspector: Remove double deduction of initial_state_gas in
   inspect_execution. The first_frame_input method already handles the
   deduction; the explicit deduction in the inspector was redundant.
… and EIP-7702

- tx_gas_used now subtracts unused reservoir gas (tx.gas - gas_left - state_gas_left)
- Child revert/halt returns all state gas to parent reservoir (matching Python spec)
- EIP-7702 state gas refund goes to reservoir instead of reducing initial_state_gas
- Refund cap uses gas_used excluding reservoir
- Precompile provider preserves reservoir across gas tracker replacement
- Updated EIP-8037 test expectations and test data
…recovery

Move require_non_staticcall! after gas charging in the CREATE/CREATE2
instruction handler. In the execution-specs, the static check is inside
generic_create() after all gas (including state gas) has been charged.
The previous ordering prevented state gas from being recorded on the
frame, so on child error the parent could not recover it via
handle_reservoir_remaining_gas.
@jenpaff
jenpaff requested a review from klkvr March 23, 2026 18:01
rakita and others added 11 commits March 25, 2026 10:33
Compute total_gas_spent as limit - remaining - reservoir in
build_result_gas to correctly exclude unused state gas. Use
saturating_sub in Gas::spent/total_gas_spent to prevent underflow.
Add std feature to ee-tests revm dependency.
Replace manual ResultGas construction with the shared build_result_gas
function for consistency with the main execution path.
Gas had its own `limit` field duplicating `GasTracker::gas_limit`.
Remove it and delegate `Gas::limit()` to `self.tracker.limit()`.

Also fixes `Gas::new_spent` which previously set tracker gas_limit
to 0 instead of the actual limit.
…reservoir support (#3541)

* refactor(precompile): split PrecompileOutput and PrecompileError for state gas support

Rename PrecompileOutput to PrecompileOutputEth (simple gas_used + bytes) for
individual precompile functions. Introduce new PrecompileOutput with halt,
gas_used, state_gas_used, reservoir, and bytes fields for provider-level results.

Split PrecompileError: non-fatal errors become PrecompileHaltReason (expressed
via PrecompileOutput::halt), while PrecompileError now only holds fatal errors
(Fatal/FatalAny) that abort EVM execution.

New type aliases:
- PrecompileEthResult = Result<PrecompileOutputEth, PrecompileHaltReason>
- PrecompileResult = Result<PrecompileOutput, PrecompileError>

* refactor(precompile): replace Option<PrecompileHalt> with PrecompileStatus enum

Introduce PrecompileStatus enum (Success, Revert, Halt) to replace the
Option<PrecompileHalt> field in PrecompileOutput. Add PrecompileEthFn type
alias for legacy precompile function signatures, with Precompile::execute()
now returning PrecompileOutput via a From<PrecompileEthResult> conversion.
Add helper methods: is_success, is_revert, halt_reason, into_halt_reason.

* refactor(handler): extract precompile_output_to_interpreter_result helper

Add a standalone function that converts PrecompileOutput into
InterpreterResult, and use it in EthPrecompiles::run. Fmt and clippy fixes.

* refactor(precompile): thread reservoir through PrecompileOutput and revert execute signature

- Add `reservoir` parameter to PrecompileOutput::new, halt, revert constructors
- Add `from_eth_result` constructor that takes reservoir
- Revert Precompile::execute to return PrecompileEthResult
- Move PrecompileOutput conversion to call site in EthPrecompiles
- Remove unused into_halt_reason and From<PrecompileEthResult> impl

* refactor(precompile): change Precompile fn_ from PrecompileEthFn to PrecompileFn (#3546)

Change the Precompile struct's fn_ field from PrecompileEthFn (fn(&[u8], u64) -> PrecompileEthResult)
to PrecompileFn (fn(&[u8], u64, u64) -> PrecompileOutput) so precompiles receive reservoir
directly and return PrecompileOutput.

Add call_eth_precompile helper to wrap existing PrecompileEthFn implementations into
the new PrecompileFn signature. Each precompile module gets a thin wrapper using this helper.

* refactor(precompile): add eth_precompile_fn! macro to eliminate wrapper boilerplate

Replace 17 hand-written PrecompileEthFn-to-PrecompileFn wrapper functions
with the new eth_precompile_fn! macro that generates them uniformly.

* refactor(op-revm): use eth_precompile_fn! macro for precompile wrappers

Replace 8 hand-written wrapper functions in op-revm with the
eth_precompile_fn! macro.

* style: apply cargo fmt

* Rename Eth precompile structs

* fix(op-revm): add missing reservoir argument to execute calls in tests
…ecompileStatus helpers

PrecompileOutput now carries reservoir and state_gas_used, so
precompile_output_to_interpreter_result no longer needs a separate
reservoir parameter. Added convenience methods on PrecompileStatus
and fixed unused import warning.
Update eip2537 benchmarks to use Precompile::execute() instead of
removed precompile() method, and fix manual div_ceil clippy warning.
@rakita rakita added the cyclops label Apr 9, 2026
rakita added 4 commits April 9, 2026 14:15
Remove references to removed getters (limit, spent, intrinsic_gas,
remaining) and update table and derived values to reflect the actual
methods (total_gas_spent, tx_gas_used, block_regular_gas_used, etc.).
- Fix unresolved links to non-existent `regular_gas_spent` and
  `with_regular_gas_spent` methods in deprecation notes
- Fix typos: "substract" → "subtract", "enought" → "enough"
- Fix stale comments referencing `regular_gas_spent` field

@tempoxyz-bot tempoxyz-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ Cyclops Review

EIP-8037 dual gas model implementation. Core reservoir accounting matches the spec, but several downstream integration gaps exist — particularly in op-revm fee settlement, Amsterdam validation coverage, inspector gas reporting, and the precompile API boundary.

7 findings (2 high, 4 medium, 1 low)

Reviewer Callouts
  • Downstream reth/alloy-evm gas accounting: ExecutionResult::gas_used() is now receipt-domain only. Block builders using alloy-evm and reth still collapse receipt and block gas into a single accumulator. Once Amsterdam is enabled, valid blocks with state-gas transactions will produce incorrect header.gas_used. The in-tree harness was updated, but downstream integration needs block_regular_gas_used() / block_state_gas_used().
  • reth precompile cache: PrecompileOutput now carries halt status and live reservoir. reth's unchanged cache treats every Ok(output) as context-free success keyed by calldata/spec. Cached OOG halts (which set gas_used = 0) will always hit on later calls with sufficient gas, and cached success outputs replay stale reservoirs.
  • Gas accounting complexity: The saturating_sub0 pattern in first_frame_input() silently allows impossible negative regular-gas budgets instead of failing validation.

Comment thread crates/op-revm/src/handler.rs
Comment thread crates/handler/src/handler.rs
Comment thread crates/inspector/src/gas.rs
Comment thread crates/precompile/src/interface.rs
Comment thread crates/context/src/cfg.rs
Comment thread crates/handler/src/precompile_provider.rs Outdated
Comment thread examples/custom_precompile_journal/src/precompile_provider.rs Outdated
rakita added 6 commits April 9, 2026 18:28
…utput API

Use `PrecompileOutput::from_eth_result` and `precompile_output_to_interpreter_result`
instead of manually constructing InterpreterResult, aligning with the new gas handling.
…ecompileError>

Distinguish fatal/unrecoverable precompile errors from non-fatal halts
by returning a Result. PrecompileFn signature updated accordingly, and
all call sites now propagate or unwrap the error.
The core revm handler already excludes reservoir gas when calculating
beneficiary rewards and caller reimbursement, but op-revm's fee
settlement did not account for it. Once Osaka/EIP-8037 is enabled,
transactions with leftover reservoir gas would over-credit
BASE_FEE_RECIPIENT and OPERATOR_FEE_RECIPIENT, and under-refund
operator fees to the caller.

- reward_beneficiary: use effective_used (gas.used() - reservoir) for
  base_fee_amount and operator_fee_cost calculations
- operator_fee_refund: include gas.reservoir() when computing gas_used
  so the refund correctly covers unused reservoir gas
- handler validation: use initial_regular_gas (excluding state gas)
  when checking floor_gas against TX_MAX_GAS_LIMIT cap
@rakita
rakita merged commit f158464 into main Apr 10, 2026
29 of 31 checks passed
@github-actions github-actions Bot mentioned this pull request Apr 9, 2026
@rakita
rakita deleted the rakita/state-gas branch April 14, 2026 18:07
@rakita
rakita restored the rakita/state-gas branch April 14, 2026 18:07
theochap pushed a commit to ethereum-optimism/optimism that referenced this pull request Apr 15, 2026
* feat: implement EIP-8037 state gas (reservoir model)

Add state gas accounting with reservoir model across interpreter, handler,
context, and precompile crates. Includes comprehensive EIP-8037 test suite
with 35+ test vectors covering SSTORE, CALL, CREATE, SELFDESTRUCT, reservoir
refill, and gas opcode behavior.

* rm statetest jsons

* Rename spent to regular_gas_spent in ResultGas and simplify reservoir refill

- Rename ResultGas::spent to regular_gas_spent to distinguish from state gas after EIP-8037
- Add total_gas_spent() method and deprecate old spent()/set_spent()/with_spent() accessors
- Simplify reservoir refill logic: replace handler_reservoir_refill() with inline max()
- Remove unnecessary reservoir manipulation in precompile path
- Update op-revm handler to use simplified reservoir refill
- Update Cargo.lock dependencies

* Simplify EIP-8037 state gas: remove redundant fields from ResultGas and update test data

* Move EIP-7702 state gas refund split into pre_execution

Consolidate the EIP-8037 auth list refund splitting logic from
run_without_catch_error into apply_eip7702_auth_list, avoiding
duplication between handler and inspector. pre_execution now takes
init_and_floor_gas as a mutable reference and returns only the
regular refund portion.

* Add PrecompileFailure type for state gas propagation on error

Introduce PrecompileFailure wrapper that carries both the error and
an optional GasTracker, allowing precompiles that charge state gas
(EIP-8037) to propagate gas info back to the caller on failure for
proper reservoir refill.

* Convert precompile errors to PrecompileFailure via .into()

Update all precompile Err returns to use .into() for converting
PrecompileError to PrecompileFailure, enabling state gas tracking
on precompile failures.

* Fix EIP-8037 state gas accounting regression

Commit 14bd5a61 introduced a regression in gas accounting by incorrectly
erasing both remaining and reservoir gas from total_gas_spent. The reservoir
should not be erased since it's tracked separately via state_gas_spent.

This fix reverts the erase_cost line from erase_cost(remaining + reservoir)
to erase_cost(remaining), which correctly accounts for:
- Regular gas: deducted from remaining calculation
- State gas: tracked separately in state_gas_spent field

Results:
- State test failures reduced from 20 to 4 (pre-regression count)
- All 408 workspace tests now pass
- Updated EIP-8037 test data and assertions to match correct values

* Move state gas deduction into first_frame_input

Refactor to improve code organization by moving the state gas deduction
logic from the execution method into the first_frame_input method, where
frame initialization concerns are naturally grouped together.

This keeps frame setup logic cohesive and simplifies the execution method
to focus on the execution loop rather than frame setup details.

No functional change - same gas accounting and execution behavior.

* Fix EIP-8037 gas accounting: floor gas, reservoir, inspector, validation

Four fixes for EIP-8037 state gas interactions:

1. post_execution: Fix EIP-7623 floor gas check to account for EIP-8037
   reservoir. Per EIP-8037, gas_used = tx.gas - gas_left - reservoir.
   The floor check now subtracts reservoir when computing gas used.

2. handler: Simplify reservoir handling in last_frame_result to always
   use the frame's final reservoir value directly. It already reflects
   child frame restorations via handle_reservoir_remaining_gas.

3. handler: Add EIP-8037 floor gas validation when gas_limit exceeds
   TX_MAX_GAS_LIMIT. The maximum gas_used is capped at TX_MAX_GAS_LIMIT
   since excess goes to reservoir, so floor_gas must not exceed that cap.

4. inspector: Remove double deduction of initial_state_gas in
   inspect_execution. The first_frame_input method already handles the
   deduction; the explicit deduction in the inspector was redundant.

* Fix EIP-8037 reservoir accounting: tx_gas_used, child revert refunds, and EIP-7702

- tx_gas_used now subtracts unused reservoir gas (tx.gas - gas_left - state_gas_left)
- Child revert/halt returns all state gas to parent reservoir (matching Python spec)
- EIP-7702 state gas refund goes to reservoir instead of reducing initial_state_gas
- Refund cap uses gas_used excluding reservoir
- Precompile provider preserves reservoir across gas tracker replacement
- Updated EIP-8037 test expectations and test data

* Fix CREATE/CREATE2 static call check ordering for EIP-8037 state gas recovery

Move require_non_staticcall! after gas charging in the CREATE/CREATE2
instruction handler. In the execution-specs, the static check is inside
generic_create() after all gas (including state gas) has been charged.
The previous ordering prevented state gas from being recorded on the
frame, so on child error the parent could not recover it via
handle_reservoir_remaining_gas.

* Simplify last_frame_result: remove initial_reservoir parameter and fix reservoir handling

- Remove unused `initial_reservoir` parameter from `last_frame_result` across all handlers
- Fix precompile provider to save reservoir before overwriting gas tracker
- Add deprecated `gas_used()` method on ExecutionResult pointing to `tx_gas_used()`

* Revert CREATE/CREATE2 static call check to before gas charging

Static call check doesn't need to be after gas charging - CREATE in a
static context is always an error regardless of gas accounting.

* Fix reservoir handling: restore state gas on revert/halt

On revert/halt, state changes are rolled back so state gas should be
refunded back to the reservoir. Only on success should the frame's
final reservoir be used as-is.

* Fix EIP-8037 state_gas_spent accounting for EIP-7702 auth list

Two bugs in state_gas_spent reporting:

1. build_result_gas added full initial_state_gas without subtracting
   eip7702_reservoir_refund, overcounting state gas for existing
   authority accounts. This caused block_state_gas_used to be too
   high and block_regular_gas_used to be too low.

2. On revert/halt, last_frame_result set state_gas_spent before
   restoring it to the reservoir, leaving a stale execution value.
   Now correctly zeroed since state changes are rolled back.

* Improve precompile reservoir handling and apply formatting fixes

Refactor precompile provider to properly handle reservoir refill logic
for both success and error/halt cases, ensuring state gas accounting
is correct when precompiles don't track reservoir. Also apply rustfmt
formatting across multiple files.

* Fix precompile_provider compilation errors against current precompile API

PrecompileOutput has gas_used (not gas: GasTracker) and PrecompileError
is a flat enum (no .error/.gas subfields). Use record_regular_cost()
to avoid deprecation warning.

* Simplify precompile interface: remove gas_limit from PrecompileOutput, flatten PrecompileError

Remove gas_limit field from PrecompileOutput (only gas_used needed) and
eliminate the PrecompileError wrapper layer so errors are returned directly
as the enum variant without .into() conversion.

* Fix EIP-8037 reservoir refill tests and complete precompile interface cleanup

Update 4 EIP-8037 tests to match current reservoir refill behavior: on
revert/halt at the top level, state_gas_spent is zeroed and state gas is
restored to the reservoir (state changes rolled back). Regenerate testdata.

Also complete the PrecompileOutput/PrecompileError simplification from the
previous commit: remove reverted field, flatten error matching in tests.

* temporary remove all ee-tests

* Fix state gas accounting: include reservoir in total_gas_spent and prevent underflow

- Remove saturating_sub of reservoir from total_gas_spent in build_result_gas
- Use saturating_sub in block_regular_gas_used to prevent underflow

* Apply formatting fixes and use saturating arithmetic for state gas spent

* Revert "temporary remove all ee-tests"

This reverts commit 03986fcd15d861b32ce2e56186ffd9ebdbfa3871.

* Fix total_gas_spent to exclude reservoir and use saturating arithmetic

Compute total_gas_spent as limit - remaining - reservoir in
build_result_gas to correctly exclude unused state gas. Use
saturating_sub in Gas::spent/total_gas_spent to prevent underflow.
Add std feature to ee-tests revm dependency.

* Use build_result_gas helper in system call gas handling

Replace manual ResultGas construction with the shared build_result_gas
function for consistency with the main execution path.

* bump rust to 1.91

* Remove redundant gas_limit from Gas, delegate to GasTracker

Gas had its own `limit` field duplicating `GasTracker::gas_limit`.
Remove it and delegate `Gas::limit()` to `self.tracker.limit()`.

Also fixes `Gas::new_spent` which previously set tracker gas_limit
to 0 instead of the actual limit.

* refactor(precompile): restructure PrecompileOutput for state gas and reservoir support (bluealloy/revm#3541)

* refactor(precompile): split PrecompileOutput and PrecompileError for state gas support

Rename PrecompileOutput to PrecompileOutputEth (simple gas_used + bytes) for
individual precompile functions. Introduce new PrecompileOutput with halt,
gas_used, state_gas_used, reservoir, and bytes fields for provider-level results.

Split PrecompileError: non-fatal errors become PrecompileHaltReason (expressed
via PrecompileOutput::halt), while PrecompileError now only holds fatal errors
(Fatal/FatalAny) that abort EVM execution.

New type aliases:
- PrecompileEthResult = Result<PrecompileOutputEth, PrecompileHaltReason>
- PrecompileResult = Result<PrecompileOutput, PrecompileError>

* refactor(precompile): replace Option<PrecompileHalt> with PrecompileStatus enum

Introduce PrecompileStatus enum (Success, Revert, Halt) to replace the
Option<PrecompileHalt> field in PrecompileOutput. Add PrecompileEthFn type
alias for legacy precompile function signatures, with Precompile::execute()
now returning PrecompileOutput via a From<PrecompileEthResult> conversion.
Add helper methods: is_success, is_revert, halt_reason, into_halt_reason.

* refactor(handler): extract precompile_output_to_interpreter_result helper

Add a standalone function that converts PrecompileOutput into
InterpreterResult, and use it in EthPrecompiles::run. Fmt and clippy fixes.

* refactor(precompile): thread reservoir through PrecompileOutput and revert execute signature

- Add `reservoir` parameter to PrecompileOutput::new, halt, revert constructors
- Add `from_eth_result` constructor that takes reservoir
- Revert Precompile::execute to return PrecompileEthResult
- Move PrecompileOutput conversion to call site in EthPrecompiles
- Remove unused into_halt_reason and From<PrecompileEthResult> impl

* refactor(precompile): change Precompile fn_ from PrecompileEthFn to PrecompileFn (bluealloy/revm#3546)

Change the Precompile struct's fn_ field from PrecompileEthFn (fn(&[u8], u64) -> PrecompileEthResult)
to PrecompileFn (fn(&[u8], u64, u64) -> PrecompileOutput) so precompiles receive reservoir
directly and return PrecompileOutput.

Add call_eth_precompile helper to wrap existing PrecompileEthFn implementations into
the new PrecompileFn signature. Each precompile module gets a thin wrapper using this helper.

* refactor(precompile): add eth_precompile_fn! macro to eliminate wrapper boilerplate

Replace 17 hand-written PrecompileEthFn-to-PrecompileFn wrapper functions
with the new eth_precompile_fn! macro that generates them uniformly.

* refactor(op-revm): use eth_precompile_fn! macro for precompile wrappers

Replace 8 hand-written wrapper functions in op-revm with the
eth_precompile_fn! macro.

* style: apply cargo fmt

* Rename Eth precompile structs

* fix(op-revm): add missing reservoir argument to execute calls in tests

* refactor(precompile): move reservoir into PrecompileOutput and add PrecompileStatus helpers

PrecompileOutput now carries reservoir and state_gas_used, so
precompile_output_to_interpreter_result no longer needs a separate
reservoir parameter. Added convenience methods on PrecompileStatus
and fixed unused import warning.

* fix: update bench and test code for new Precompile API

Update eip2537 benchmarks to use Precompile::execute() instead of
removed precompile() method, and fix manual div_ceil clippy warning.

* docs: fix ResultGas doc comments to match current API

Remove references to removed getters (limit, spent, intrinsic_gas,
remaining) and update table and derived values to reflect the actual
methods (total_gas_spent, tx_gas_used, block_regular_gas_used, etc.).

* docs: fix broken doc links and typos in ResultGas

- Fix unresolved links to non-existent `regular_gas_spent` and
  `with_regular_gas_spent` methods in deprecation notes
- Fix typos: "substract" → "subtract", "enought" → "enough"
- Fix stale comments referencing `regular_gas_spent` field

* feat: track state_gas_spent and reservoir in GasInspector

* fix: validate max(intrinsic_gas, floor_gas) against TX_MAX_GAS_LIMIT cap

* chore: add must_use annotations to gas recording methods and minor cleanups

* refactor: update custom_precompile_journal example to use PrecompileOutput API

Use `PrecompileOutput::from_eth_result` and `precompile_output_to_interpreter_result`
instead of manually constructing InterpreterResult, aligning with the new gas handling.

* refactor: make Precompile::execute return Result<PrecompileOutput, PrecompileError>

Distinguish fatal/unrecoverable precompile errors from non-fatal halts
by returning a Result. PrecompileFn signature updated accordingly, and
all call sites now propagate or unwrap the error.

* chore: bump MSRV to 1.91 in CI

* fix: exclude EIP-8037 reservoir gas from op-revm fee settlement

The core revm handler already excludes reservoir gas when calculating
beneficiary rewards and caller reimbursement, but op-revm's fee
settlement did not account for it. Once Osaka/EIP-8037 is enabled,
transactions with leftover reservoir gas would over-credit
BASE_FEE_RECIPIENT and OPERATOR_FEE_RECIPIENT, and under-refund
operator fees to the caller.

- reward_beneficiary: use effective_used (gas.used() - reservoir) for
  base_fee_amount and operator_fee_cost calculations
- operator_fee_refund: include gas.reservoir() when computing gas_used
  so the refund correctly covers unused reservoir gas
- handler validation: use initial_regular_gas (excluding state gas)
  when checking floor_gas against TX_MAX_GAS_LIMIT cap
pull Bot pushed a commit to Poya0073/optimism that referenced this pull request Apr 24, 2026
* feat: add EIP-8037 / TIP-1016 state gas support (bluealloy/revm#3406)

* feat: implement EIP-8037 state gas (reservoir model)

Add state gas accounting with reservoir model across interpreter, handler,
context, and precompile crates. Includes comprehensive EIP-8037 test suite
with 35+ test vectors covering SSTORE, CALL, CREATE, SELFDESTRUCT, reservoir
refill, and gas opcode behavior.

* rm statetest jsons

* Rename spent to regular_gas_spent in ResultGas and simplify reservoir refill

- Rename ResultGas::spent to regular_gas_spent to distinguish from state gas after EIP-8037
- Add total_gas_spent() method and deprecate old spent()/set_spent()/with_spent() accessors
- Simplify reservoir refill logic: replace handler_reservoir_refill() with inline max()
- Remove unnecessary reservoir manipulation in precompile path
- Update op-revm handler to use simplified reservoir refill
- Update Cargo.lock dependencies

* Simplify EIP-8037 state gas: remove redundant fields from ResultGas and update test data

* Move EIP-7702 state gas refund split into pre_execution

Consolidate the EIP-8037 auth list refund splitting logic from
run_without_catch_error into apply_eip7702_auth_list, avoiding
duplication between handler and inspector. pre_execution now takes
init_and_floor_gas as a mutable reference and returns only the
regular refund portion.

* Add PrecompileFailure type for state gas propagation on error

Introduce PrecompileFailure wrapper that carries both the error and
an optional GasTracker, allowing precompiles that charge state gas
(EIP-8037) to propagate gas info back to the caller on failure for
proper reservoir refill.

* Convert precompile errors to PrecompileFailure via .into()

Update all precompile Err returns to use .into() for converting
PrecompileError to PrecompileFailure, enabling state gas tracking
on precompile failures.

* Fix EIP-8037 state gas accounting regression

Commit 14bd5a61 introduced a regression in gas accounting by incorrectly
erasing both remaining and reservoir gas from total_gas_spent. The reservoir
should not be erased since it's tracked separately via state_gas_spent.

This fix reverts the erase_cost line from erase_cost(remaining + reservoir)
to erase_cost(remaining), which correctly accounts for:
- Regular gas: deducted from remaining calculation
- State gas: tracked separately in state_gas_spent field

Results:
- State test failures reduced from 20 to 4 (pre-regression count)
- All 408 workspace tests now pass
- Updated EIP-8037 test data and assertions to match correct values

* Move state gas deduction into first_frame_input

Refactor to improve code organization by moving the state gas deduction
logic from the execution method into the first_frame_input method, where
frame initialization concerns are naturally grouped together.

This keeps frame setup logic cohesive and simplifies the execution method
to focus on the execution loop rather than frame setup details.

No functional change - same gas accounting and execution behavior.

* Fix EIP-8037 gas accounting: floor gas, reservoir, inspector, validation

Four fixes for EIP-8037 state gas interactions:

1. post_execution: Fix EIP-7623 floor gas check to account for EIP-8037
   reservoir. Per EIP-8037, gas_used = tx.gas - gas_left - reservoir.
   The floor check now subtracts reservoir when computing gas used.

2. handler: Simplify reservoir handling in last_frame_result to always
   use the frame's final reservoir value directly. It already reflects
   child frame restorations via handle_reservoir_remaining_gas.

3. handler: Add EIP-8037 floor gas validation when gas_limit exceeds
   TX_MAX_GAS_LIMIT. The maximum gas_used is capped at TX_MAX_GAS_LIMIT
   since excess goes to reservoir, so floor_gas must not exceed that cap.

4. inspector: Remove double deduction of initial_state_gas in
   inspect_execution. The first_frame_input method already handles the
   deduction; the explicit deduction in the inspector was redundant.

* Fix EIP-8037 reservoir accounting: tx_gas_used, child revert refunds, and EIP-7702

- tx_gas_used now subtracts unused reservoir gas (tx.gas - gas_left - state_gas_left)
- Child revert/halt returns all state gas to parent reservoir (matching Python spec)
- EIP-7702 state gas refund goes to reservoir instead of reducing initial_state_gas
- Refund cap uses gas_used excluding reservoir
- Precompile provider preserves reservoir across gas tracker replacement
- Updated EIP-8037 test expectations and test data

* Fix CREATE/CREATE2 static call check ordering for EIP-8037 state gas recovery

Move require_non_staticcall! after gas charging in the CREATE/CREATE2
instruction handler. In the execution-specs, the static check is inside
generic_create() after all gas (including state gas) has been charged.
The previous ordering prevented state gas from being recorded on the
frame, so on child error the parent could not recover it via
handle_reservoir_remaining_gas.

* Simplify last_frame_result: remove initial_reservoir parameter and fix reservoir handling

- Remove unused `initial_reservoir` parameter from `last_frame_result` across all handlers
- Fix precompile provider to save reservoir before overwriting gas tracker
- Add deprecated `gas_used()` method on ExecutionResult pointing to `tx_gas_used()`

* Revert CREATE/CREATE2 static call check to before gas charging

Static call check doesn't need to be after gas charging - CREATE in a
static context is always an error regardless of gas accounting.

* Fix reservoir handling: restore state gas on revert/halt

On revert/halt, state changes are rolled back so state gas should be
refunded back to the reservoir. Only on success should the frame's
final reservoir be used as-is.

* Fix EIP-8037 state_gas_spent accounting for EIP-7702 auth list

Two bugs in state_gas_spent reporting:

1. build_result_gas added full initial_state_gas without subtracting
   eip7702_reservoir_refund, overcounting state gas for existing
   authority accounts. This caused block_state_gas_used to be too
   high and block_regular_gas_used to be too low.

2. On revert/halt, last_frame_result set state_gas_spent before
   restoring it to the reservoir, leaving a stale execution value.
   Now correctly zeroed since state changes are rolled back.

* Improve precompile reservoir handling and apply formatting fixes

Refactor precompile provider to properly handle reservoir refill logic
for both success and error/halt cases, ensuring state gas accounting
is correct when precompiles don't track reservoir. Also apply rustfmt
formatting across multiple files.

* Fix precompile_provider compilation errors against current precompile API

PrecompileOutput has gas_used (not gas: GasTracker) and PrecompileError
is a flat enum (no .error/.gas subfields). Use record_regular_cost()
to avoid deprecation warning.

* Simplify precompile interface: remove gas_limit from PrecompileOutput, flatten PrecompileError

Remove gas_limit field from PrecompileOutput (only gas_used needed) and
eliminate the PrecompileError wrapper layer so errors are returned directly
as the enum variant without .into() conversion.

* Fix EIP-8037 reservoir refill tests and complete precompile interface cleanup

Update 4 EIP-8037 tests to match current reservoir refill behavior: on
revert/halt at the top level, state_gas_spent is zeroed and state gas is
restored to the reservoir (state changes rolled back). Regenerate testdata.

Also complete the PrecompileOutput/PrecompileError simplification from the
previous commit: remove reverted field, flatten error matching in tests.

* temporary remove all ee-tests

* Fix state gas accounting: include reservoir in total_gas_spent and prevent underflow

- Remove saturating_sub of reservoir from total_gas_spent in build_result_gas
- Use saturating_sub in block_regular_gas_used to prevent underflow

* Apply formatting fixes and use saturating arithmetic for state gas spent

* Revert "temporary remove all ee-tests"

This reverts commit 03986fcd15d861b32ce2e56186ffd9ebdbfa3871.

* Fix total_gas_spent to exclude reservoir and use saturating arithmetic

Compute total_gas_spent as limit - remaining - reservoir in
build_result_gas to correctly exclude unused state gas. Use
saturating_sub in Gas::spent/total_gas_spent to prevent underflow.
Add std feature to ee-tests revm dependency.

* Use build_result_gas helper in system call gas handling

Replace manual ResultGas construction with the shared build_result_gas
function for consistency with the main execution path.

* bump rust to 1.91

* Remove redundant gas_limit from Gas, delegate to GasTracker

Gas had its own `limit` field duplicating `GasTracker::gas_limit`.
Remove it and delegate `Gas::limit()` to `self.tracker.limit()`.

Also fixes `Gas::new_spent` which previously set tracker gas_limit
to 0 instead of the actual limit.

* refactor(precompile): restructure PrecompileOutput for state gas and reservoir support (bluealloy/revm#3541)

* refactor(precompile): split PrecompileOutput and PrecompileError for state gas support

Rename PrecompileOutput to PrecompileOutputEth (simple gas_used + bytes) for
individual precompile functions. Introduce new PrecompileOutput with halt,
gas_used, state_gas_used, reservoir, and bytes fields for provider-level results.

Split PrecompileError: non-fatal errors become PrecompileHaltReason (expressed
via PrecompileOutput::halt), while PrecompileError now only holds fatal errors
(Fatal/FatalAny) that abort EVM execution.

New type aliases:
- PrecompileEthResult = Result<PrecompileOutputEth, PrecompileHaltReason>
- PrecompileResult = Result<PrecompileOutput, PrecompileError>

* refactor(precompile): replace Option<PrecompileHalt> with PrecompileStatus enum

Introduce PrecompileStatus enum (Success, Revert, Halt) to replace the
Option<PrecompileHalt> field in PrecompileOutput. Add PrecompileEthFn type
alias for legacy precompile function signatures, with Precompile::execute()
now returning PrecompileOutput via a From<PrecompileEthResult> conversion.
Add helper methods: is_success, is_revert, halt_reason, into_halt_reason.

* refactor(handler): extract precompile_output_to_interpreter_result helper

Add a standalone function that converts PrecompileOutput into
InterpreterResult, and use it in EthPrecompiles::run. Fmt and clippy fixes.

* refactor(precompile): thread reservoir through PrecompileOutput and revert execute signature

- Add `reservoir` parameter to PrecompileOutput::new, halt, revert constructors
- Add `from_eth_result` constructor that takes reservoir
- Revert Precompile::execute to return PrecompileEthResult
- Move PrecompileOutput conversion to call site in EthPrecompiles
- Remove unused into_halt_reason and From<PrecompileEthResult> impl

* refactor(precompile): change Precompile fn_ from PrecompileEthFn to PrecompileFn (bluealloy/revm#3546)

Change the Precompile struct's fn_ field from PrecompileEthFn (fn(&[u8], u64) -> PrecompileEthResult)
to PrecompileFn (fn(&[u8], u64, u64) -> PrecompileOutput) so precompiles receive reservoir
directly and return PrecompileOutput.

Add call_eth_precompile helper to wrap existing PrecompileEthFn implementations into
the new PrecompileFn signature. Each precompile module gets a thin wrapper using this helper.

* refactor(precompile): add eth_precompile_fn! macro to eliminate wrapper boilerplate

Replace 17 hand-written PrecompileEthFn-to-PrecompileFn wrapper functions
with the new eth_precompile_fn! macro that generates them uniformly.

* refactor(op-revm): use eth_precompile_fn! macro for precompile wrappers

Replace 8 hand-written wrapper functions in op-revm with the
eth_precompile_fn! macro.

* style: apply cargo fmt

* Rename Eth precompile structs

* fix(op-revm): add missing reservoir argument to execute calls in tests

* refactor(precompile): move reservoir into PrecompileOutput and add PrecompileStatus helpers

PrecompileOutput now carries reservoir and state_gas_used, so
precompile_output_to_interpreter_result no longer needs a separate
reservoir parameter. Added convenience methods on PrecompileStatus
and fixed unused import warning.

* fix: update bench and test code for new Precompile API

Update eip2537 benchmarks to use Precompile::execute() instead of
removed precompile() method, and fix manual div_ceil clippy warning.

* docs: fix ResultGas doc comments to match current API

Remove references to removed getters (limit, spent, intrinsic_gas,
remaining) and update table and derived values to reflect the actual
methods (total_gas_spent, tx_gas_used, block_regular_gas_used, etc.).

* docs: fix broken doc links and typos in ResultGas

- Fix unresolved links to non-existent `regular_gas_spent` and
  `with_regular_gas_spent` methods in deprecation notes
- Fix typos: "substract" → "subtract", "enought" → "enough"
- Fix stale comments referencing `regular_gas_spent` field

* feat: track state_gas_spent and reservoir in GasInspector

* fix: validate max(intrinsic_gas, floor_gas) against TX_MAX_GAS_LIMIT cap

* chore: add must_use annotations to gas recording methods and minor cleanups

* refactor: update custom_precompile_journal example to use PrecompileOutput API

Use `PrecompileOutput::from_eth_result` and `precompile_output_to_interpreter_result`
instead of manually constructing InterpreterResult, aligning with the new gas handling.

* refactor: make Precompile::execute return Result<PrecompileOutput, PrecompileError>

Distinguish fatal/unrecoverable precompile errors from non-fatal halts
by returning a Result. PrecompileFn signature updated accordingly, and
all call sites now propagate or unwrap the error.

* chore: bump MSRV to 1.91 in CI

* fix: exclude EIP-8037 reservoir gas from op-revm fee settlement

The core revm handler already excludes reservoir gas when calculating
beneficiary rewards and caller reimbursement, but op-revm's fee
settlement did not account for it. Once Osaka/EIP-8037 is enabled,
transactions with leftover reservoir gas would over-credit
BASE_FEE_RECIPIENT and OPERATOR_FEE_RECIPIENT, and under-refund
operator fees to the caller.

- reward_beneficiary: use effective_used (gas.used() - reservoir) for
  base_fee_amount and operator_fee_cost calculations
- operator_fee_refund: include gas.reservoir() when computing gas_used
  so the refund correctly covers unused reservoir gas
- handler validation: use initial_regular_gas (excluding state gas)
  when checking floor_gas against TX_MAX_GAS_LIMIT cap

* refactor: move EIP-8037 gas cap validation into validate_initial_tx_gas (bluealloy/revm#3552)

Extract `initial_regular_gas()` helper on InitialAndFloorGas and move
the EIP-8037 TX_MAX_GAS_LIMIT validation from the handler into
`validate_initial_tx_gas`, consolidating validation logic. Also clean
up formatting across precompile and op-revm crates.

* chore: release (bluealloy/revm#3472)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: v106 release prep (bluealloy/revm#3554)

* docs: add v106 migration guide entry

* chore: v106 release prep — version bumps and changelogs

* add revm version

* fix(op-revm): update validate_against_state_and_deduct_caller signature

Adds the new `&mut InitialAndFloorGas` parameter introduced in ethereum-optimism#3577
to the OpHandler trait impl, and passes `&mut Default::default()` at
all test call sites.

* chore: v107 release prep

* chore(op-revm): fmt

---------

Co-authored-by: rakita <rakita@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rakita <dragan0rakita@gmail.com>
ClaytonNorthey92 pushed a commit to hemilabs/optimism that referenced this pull request Jun 25, 2026
* feat: add EIP-8037 / TIP-1016 state gas support (bluealloy/revm#3406)

* feat: implement EIP-8037 state gas (reservoir model)

Add state gas accounting with reservoir model across interpreter, handler,
context, and precompile crates. Includes comprehensive EIP-8037 test suite
with 35+ test vectors covering SSTORE, CALL, CREATE, SELFDESTRUCT, reservoir
refill, and gas opcode behavior.

* rm statetest jsons

* Rename spent to regular_gas_spent in ResultGas and simplify reservoir refill

- Rename ResultGas::spent to regular_gas_spent to distinguish from state gas after EIP-8037
- Add total_gas_spent() method and deprecate old spent()/set_spent()/with_spent() accessors
- Simplify reservoir refill logic: replace handler_reservoir_refill() with inline max()
- Remove unnecessary reservoir manipulation in precompile path
- Update op-revm handler to use simplified reservoir refill
- Update Cargo.lock dependencies

* Simplify EIP-8037 state gas: remove redundant fields from ResultGas and update test data

* Move EIP-7702 state gas refund split into pre_execution

Consolidate the EIP-8037 auth list refund splitting logic from
run_without_catch_error into apply_eip7702_auth_list, avoiding
duplication between handler and inspector. pre_execution now takes
init_and_floor_gas as a mutable reference and returns only the
regular refund portion.

* Add PrecompileFailure type for state gas propagation on error

Introduce PrecompileFailure wrapper that carries both the error and
an optional GasTracker, allowing precompiles that charge state gas
(EIP-8037) to propagate gas info back to the caller on failure for
proper reservoir refill.

* Convert precompile errors to PrecompileFailure via .into()

Update all precompile Err returns to use .into() for converting
PrecompileError to PrecompileFailure, enabling state gas tracking
on precompile failures.

* Fix EIP-8037 state gas accounting regression

Commit 14bd5a61 introduced a regression in gas accounting by incorrectly
erasing both remaining and reservoir gas from total_gas_spent. The reservoir
should not be erased since it's tracked separately via state_gas_spent.

This fix reverts the erase_cost line from erase_cost(remaining + reservoir)
to erase_cost(remaining), which correctly accounts for:
- Regular gas: deducted from remaining calculation
- State gas: tracked separately in state_gas_spent field

Results:
- State test failures reduced from 20 to 4 (pre-regression count)
- All 408 workspace tests now pass
- Updated EIP-8037 test data and assertions to match correct values

* Move state gas deduction into first_frame_input

Refactor to improve code organization by moving the state gas deduction
logic from the execution method into the first_frame_input method, where
frame initialization concerns are naturally grouped together.

This keeps frame setup logic cohesive and simplifies the execution method
to focus on the execution loop rather than frame setup details.

No functional change - same gas accounting and execution behavior.

* Fix EIP-8037 gas accounting: floor gas, reservoir, inspector, validation

Four fixes for EIP-8037 state gas interactions:

1. post_execution: Fix EIP-7623 floor gas check to account for EIP-8037
   reservoir. Per EIP-8037, gas_used = tx.gas - gas_left - reservoir.
   The floor check now subtracts reservoir when computing gas used.

2. handler: Simplify reservoir handling in last_frame_result to always
   use the frame's final reservoir value directly. It already reflects
   child frame restorations via handle_reservoir_remaining_gas.

3. handler: Add EIP-8037 floor gas validation when gas_limit exceeds
   TX_MAX_GAS_LIMIT. The maximum gas_used is capped at TX_MAX_GAS_LIMIT
   since excess goes to reservoir, so floor_gas must not exceed that cap.

4. inspector: Remove double deduction of initial_state_gas in
   inspect_execution. The first_frame_input method already handles the
   deduction; the explicit deduction in the inspector was redundant.

* Fix EIP-8037 reservoir accounting: tx_gas_used, child revert refunds, and EIP-7702

- tx_gas_used now subtracts unused reservoir gas (tx.gas - gas_left - state_gas_left)
- Child revert/halt returns all state gas to parent reservoir (matching Python spec)
- EIP-7702 state gas refund goes to reservoir instead of reducing initial_state_gas
- Refund cap uses gas_used excluding reservoir
- Precompile provider preserves reservoir across gas tracker replacement
- Updated EIP-8037 test expectations and test data

* Fix CREATE/CREATE2 static call check ordering for EIP-8037 state gas recovery

Move require_non_staticcall! after gas charging in the CREATE/CREATE2
instruction handler. In the execution-specs, the static check is inside
generic_create() after all gas (including state gas) has been charged.
The previous ordering prevented state gas from being recorded on the
frame, so on child error the parent could not recover it via
handle_reservoir_remaining_gas.

* Simplify last_frame_result: remove initial_reservoir parameter and fix reservoir handling

- Remove unused `initial_reservoir` parameter from `last_frame_result` across all handlers
- Fix precompile provider to save reservoir before overwriting gas tracker
- Add deprecated `gas_used()` method on ExecutionResult pointing to `tx_gas_used()`

* Revert CREATE/CREATE2 static call check to before gas charging

Static call check doesn't need to be after gas charging - CREATE in a
static context is always an error regardless of gas accounting.

* Fix reservoir handling: restore state gas on revert/halt

On revert/halt, state changes are rolled back so state gas should be
refunded back to the reservoir. Only on success should the frame's
final reservoir be used as-is.

* Fix EIP-8037 state_gas_spent accounting for EIP-7702 auth list

Two bugs in state_gas_spent reporting:

1. build_result_gas added full initial_state_gas without subtracting
   eip7702_reservoir_refund, overcounting state gas for existing
   authority accounts. This caused block_state_gas_used to be too
   high and block_regular_gas_used to be too low.

2. On revert/halt, last_frame_result set state_gas_spent before
   restoring it to the reservoir, leaving a stale execution value.
   Now correctly zeroed since state changes are rolled back.

* Improve precompile reservoir handling and apply formatting fixes

Refactor precompile provider to properly handle reservoir refill logic
for both success and error/halt cases, ensuring state gas accounting
is correct when precompiles don't track reservoir. Also apply rustfmt
formatting across multiple files.

* Fix precompile_provider compilation errors against current precompile API

PrecompileOutput has gas_used (not gas: GasTracker) and PrecompileError
is a flat enum (no .error/.gas subfields). Use record_regular_cost()
to avoid deprecation warning.

* Simplify precompile interface: remove gas_limit from PrecompileOutput, flatten PrecompileError

Remove gas_limit field from PrecompileOutput (only gas_used needed) and
eliminate the PrecompileError wrapper layer so errors are returned directly
as the enum variant without .into() conversion.

* Fix EIP-8037 reservoir refill tests and complete precompile interface cleanup

Update 4 EIP-8037 tests to match current reservoir refill behavior: on
revert/halt at the top level, state_gas_spent is zeroed and state gas is
restored to the reservoir (state changes rolled back). Regenerate testdata.

Also complete the PrecompileOutput/PrecompileError simplification from the
previous commit: remove reverted field, flatten error matching in tests.

* temporary remove all ee-tests

* Fix state gas accounting: include reservoir in total_gas_spent and prevent underflow

- Remove saturating_sub of reservoir from total_gas_spent in build_result_gas
- Use saturating_sub in block_regular_gas_used to prevent underflow

* Apply formatting fixes and use saturating arithmetic for state gas spent

* Revert "temporary remove all ee-tests"

This reverts commit 03986fcd15d861b32ce2e56186ffd9ebdbfa3871.

* Fix total_gas_spent to exclude reservoir and use saturating arithmetic

Compute total_gas_spent as limit - remaining - reservoir in
build_result_gas to correctly exclude unused state gas. Use
saturating_sub in Gas::spent/total_gas_spent to prevent underflow.
Add std feature to ee-tests revm dependency.

* Use build_result_gas helper in system call gas handling

Replace manual ResultGas construction with the shared build_result_gas
function for consistency with the main execution path.

* bump rust to 1.91

* Remove redundant gas_limit from Gas, delegate to GasTracker

Gas had its own `limit` field duplicating `GasTracker::gas_limit`.
Remove it and delegate `Gas::limit()` to `self.tracker.limit()`.

Also fixes `Gas::new_spent` which previously set tracker gas_limit
to 0 instead of the actual limit.

* refactor(precompile): restructure PrecompileOutput for state gas and reservoir support (bluealloy/revm#3541)

* refactor(precompile): split PrecompileOutput and PrecompileError for state gas support

Rename PrecompileOutput to PrecompileOutputEth (simple gas_used + bytes) for
individual precompile functions. Introduce new PrecompileOutput with halt,
gas_used, state_gas_used, reservoir, and bytes fields for provider-level results.

Split PrecompileError: non-fatal errors become PrecompileHaltReason (expressed
via PrecompileOutput::halt), while PrecompileError now only holds fatal errors
(Fatal/FatalAny) that abort EVM execution.

New type aliases:
- PrecompileEthResult = Result<PrecompileOutputEth, PrecompileHaltReason>
- PrecompileResult = Result<PrecompileOutput, PrecompileError>

* refactor(precompile): replace Option<PrecompileHalt> with PrecompileStatus enum

Introduce PrecompileStatus enum (Success, Revert, Halt) to replace the
Option<PrecompileHalt> field in PrecompileOutput. Add PrecompileEthFn type
alias for legacy precompile function signatures, with Precompile::execute()
now returning PrecompileOutput via a From<PrecompileEthResult> conversion.
Add helper methods: is_success, is_revert, halt_reason, into_halt_reason.

* refactor(handler): extract precompile_output_to_interpreter_result helper

Add a standalone function that converts PrecompileOutput into
InterpreterResult, and use it in EthPrecompiles::run. Fmt and clippy fixes.

* refactor(precompile): thread reservoir through PrecompileOutput and revert execute signature

- Add `reservoir` parameter to PrecompileOutput::new, halt, revert constructors
- Add `from_eth_result` constructor that takes reservoir
- Revert Precompile::execute to return PrecompileEthResult
- Move PrecompileOutput conversion to call site in EthPrecompiles
- Remove unused into_halt_reason and From<PrecompileEthResult> impl

* refactor(precompile): change Precompile fn_ from PrecompileEthFn to PrecompileFn (bluealloy/revm#3546)

Change the Precompile struct's fn_ field from PrecompileEthFn (fn(&[u8], u64) -> PrecompileEthResult)
to PrecompileFn (fn(&[u8], u64, u64) -> PrecompileOutput) so precompiles receive reservoir
directly and return PrecompileOutput.

Add call_eth_precompile helper to wrap existing PrecompileEthFn implementations into
the new PrecompileFn signature. Each precompile module gets a thin wrapper using this helper.

* refactor(precompile): add eth_precompile_fn! macro to eliminate wrapper boilerplate

Replace 17 hand-written PrecompileEthFn-to-PrecompileFn wrapper functions
with the new eth_precompile_fn! macro that generates them uniformly.

* refactor(op-revm): use eth_precompile_fn! macro for precompile wrappers

Replace 8 hand-written wrapper functions in op-revm with the
eth_precompile_fn! macro.

* style: apply cargo fmt

* Rename Eth precompile structs

* fix(op-revm): add missing reservoir argument to execute calls in tests

* refactor(precompile): move reservoir into PrecompileOutput and add PrecompileStatus helpers

PrecompileOutput now carries reservoir and state_gas_used, so
precompile_output_to_interpreter_result no longer needs a separate
reservoir parameter. Added convenience methods on PrecompileStatus
and fixed unused import warning.

* fix: update bench and test code for new Precompile API

Update eip2537 benchmarks to use Precompile::execute() instead of
removed precompile() method, and fix manual div_ceil clippy warning.

* docs: fix ResultGas doc comments to match current API

Remove references to removed getters (limit, spent, intrinsic_gas,
remaining) and update table and derived values to reflect the actual
methods (total_gas_spent, tx_gas_used, block_regular_gas_used, etc.).

* docs: fix broken doc links and typos in ResultGas

- Fix unresolved links to non-existent `regular_gas_spent` and
  `with_regular_gas_spent` methods in deprecation notes
- Fix typos: "substract" → "subtract", "enought" → "enough"
- Fix stale comments referencing `regular_gas_spent` field

* feat: track state_gas_spent and reservoir in GasInspector

* fix: validate max(intrinsic_gas, floor_gas) against TX_MAX_GAS_LIMIT cap

* chore: add must_use annotations to gas recording methods and minor cleanups

* refactor: update custom_precompile_journal example to use PrecompileOutput API

Use `PrecompileOutput::from_eth_result` and `precompile_output_to_interpreter_result`
instead of manually constructing InterpreterResult, aligning with the new gas handling.

* refactor: make Precompile::execute return Result<PrecompileOutput, PrecompileError>

Distinguish fatal/unrecoverable precompile errors from non-fatal halts
by returning a Result. PrecompileFn signature updated accordingly, and
all call sites now propagate or unwrap the error.

* chore: bump MSRV to 1.91 in CI

* fix: exclude EIP-8037 reservoir gas from op-revm fee settlement

The core revm handler already excludes reservoir gas when calculating
beneficiary rewards and caller reimbursement, but op-revm's fee
settlement did not account for it. Once Osaka/EIP-8037 is enabled,
transactions with leftover reservoir gas would over-credit
BASE_FEE_RECIPIENT and OPERATOR_FEE_RECIPIENT, and under-refund
operator fees to the caller.

- reward_beneficiary: use effective_used (gas.used() - reservoir) for
  base_fee_amount and operator_fee_cost calculations
- operator_fee_refund: include gas.reservoir() when computing gas_used
  so the refund correctly covers unused reservoir gas
- handler validation: use initial_regular_gas (excluding state gas)
  when checking floor_gas against TX_MAX_GAS_LIMIT cap

* refactor: move EIP-8037 gas cap validation into validate_initial_tx_gas (bluealloy/revm#3552)

Extract `initial_regular_gas()` helper on InitialAndFloorGas and move
the EIP-8037 TX_MAX_GAS_LIMIT validation from the handler into
`validate_initial_tx_gas`, consolidating validation logic. Also clean
up formatting across precompile and op-revm crates.

* chore: release (bluealloy/revm#3472)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: v106 release prep (bluealloy/revm#3554)

* docs: add v106 migration guide entry

* chore: v106 release prep — version bumps and changelogs

* add revm version

* fix(op-revm): update validate_against_state_and_deduct_caller signature

Adds the new `&mut InitialAndFloorGas` parameter introduced in ethereum-optimism#3577
to the OpHandler trait impl, and passes `&mut Default::default()` at
all test call sites.

* chore: v107 release prep

* chore(op-revm): fmt

---------

Co-authored-by: rakita <rakita@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rakita <dragan0rakita@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants