refactor!: add ResultGas struct to ExecutionResult#3413
Conversation
Replace loose `gas_used` and `gas_refunded` fields in all three `ExecutionResult` variants with a single `gas: ResultGas` struct that exposes richer gas accounting: `gas_used`, `gas_refunded`, `gas_spent` (pre-refund), and `floor_gas` (EIP-7623).
Merging this PR will improve performance by 3.1%
Performance Changes
Comparing |
Remove the redundant `gas_used` field from `ResultGas` since it can always be derived as `gas_spent - gas_refunded`. The struct now stores only three independent values (gas_spent, gas_refunded, floor_gas) and exposes `gas_used()` as a method.
- Rename fields to align with Gas struct methods: gas_spent → spent, gas_refunded → refunded, gas_used() → used() Eliminates redundant gas.gas_spent pattern (now gas.spent) - Add #[serde(rename)] to preserve JSON key stability - Add Display impl for ResultGas (conditionally shows refunded/floor) - Simplify ExecutionResult Display to delegate to ResultGas Display - Improve documentation with source mapping table - Add tests for ResultGas Display and used() method
Add `limit` field to `ResultGas` so it contains all gas information without requiring external context. Also add `remaining()` derived method (limit - spent) to complement `used()` (spent - refunded). ResultGas now mirrors the full Gas struct snapshot: - limit: transaction gas limit - spent: gas consumed before refund - refunded: gas refund amount - floor_gas: EIP-7623 floor gas - used(): spent - refunded (derived) - remaining(): limit - spent (derived)
…cution` Add intrinsic_gas field to ResultGas so it carries the initial tx overhead gas. Change post_execution to return ResultGas directly, and have execution_result receive a pre-built ResultGas instead of InitialAndFloorGas.
- Make all ResultGas fields private - Add getter methods: limit(), spent(), refunded(), floor_gas(), intrinsic_gas() - Add builder methods: with_limit(), with_spent(), with_refunded(), with_floor_gas(), with_intrinsic_gas() - Add setter methods: set_limit(), set_spent(), set_refunded(), set_floor_gas(), set_intrinsic_gas() - Add derived methods: final_used(), inner_refunded(), final_refunded()
- Rewrite struct doc table to reference getter methods instead of fields - Fix used() doc formula to include floor_gas: max(spent - refunded, floor_gas) - Fix incomplete refunded field doc, point to final_refunded() - Remove stale final_used() reference, remove refunded() getter (replaced by inner_refunded)
There was a problem hiding this comment.
Pull request overview
This PR refactors gas accounting exposure by introducing a ResultGas struct and embedding it into ExecutionResult variants, threading the required InitialAndFloorGas information through the handler pipeline and updating examples + JSON test vectors accordingly.
Changes:
- Added
ResultGas(limit/spent/refunded/floor/intrinsic + derived helpers) and replaced per-variantgas_used/gas_refundedfields with a singlegas: ResultGas. - Updated handler/inspector/op-handler execution result plumbing to pass
ResultGasintopost_execution::output. - Regenerated ee-test JSON fixtures and updated examples/tests to use
gas.used()/gas: ResultGas.
Reviewed changes
Copilot reviewed 37 out of 37 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/custom_precompile_journal/src/main.rs | Update example pattern matches/printing to use gas.used() |
| examples/bal_example/src/main.rs | Update example printing to use gas.used() |
| crates/op-revm/src/handler.rs | Thread ResultGas into execution_result, update FailedDeposit halt to include ResultGas |
| crates/inspector/src/handler.rs | Thread ResultGas through inspector run paths; build ResultGas for system calls |
| crates/handler/src/system_call.rs | Update test expected ExecutionResult to include gas: ResultGas |
| crates/handler/src/post_execution.rs | Add build_result_gas helper and update output() to accept ResultGas |
| crates/handler/src/handler.rs | Change post_execution() to return ResultGas and pass into execution_result() |
| crates/ee-tests/tests/revm_testdata/test_selfdestruct_multi_tx.json | Update serialized result format to nested gas object |
| crates/ee-tests/tests/revm_testdata/test_multi_tx_create.json | Update serialized result format to nested gas object |
| crates/ee-tests/tests/revm_testdata/test_frame_stack_index.json | Update serialized result format to nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_tx_call_p256verify.json | Update serialized result format to nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_log_inspector.json | Update serialized result format to nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_l1block_load_for_pre_regolith.json | Update serialized result format to nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_p256verify.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bn254_pair_granite.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bn254_pair_fjord.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_halted_deposit_tx.json | Update serialized halt format to include nested gas object |
| crates/ee-tests/tests/op_revm_testdata/test_deposit_tx.json | Update serialized result format to nested gas object |
| crates/ee-tests/src/op_revm_tests.rs | Update Rust assertions to construct ResultGas in expected results |
| crates/context/interface/src/result.rs | Introduce ResultGas, refactor ExecutionResult gas fields, add gas() accessor and update Display |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Build ResultGas from the final gas state | ||
| // This include all necessary fields and gas values. | ||
| let result_gas = post_execution::build_result_gas(exec_result.gas(), init_and_floor_gas); | ||
|
|
||
| // Ensure gas floor is met and minimum floor gas is spent. | ||
| // if `cfg.is_eip7623_disabled` is true, floor gas will be set to zero | ||
| self.eip7623_check_gas_floor(evm, exec_result, init_and_floor_gas); | ||
| // Return unused gas to caller | ||
| self.reimburse_caller(evm, exec_result)?; | ||
| // Pay transaction fees to beneficiary | ||
| self.reward_beneficiary(evm, exec_result)?; | ||
| Ok(()) | ||
| // Build ResultGas from the final gas state | ||
| Ok(result_gas) |
There was a problem hiding this comment.
result_gas is built before eip7623_check_gas_floor mutates the underlying Gas (it can set spent=floor and clear refunds). This makes ResultGas inconsistent with the actual charged/reimbursed gas (e.g., spent, remaining, and refunded values can be wrong when the floor triggers). Build ResultGas after the floor check (and after any other gas-mutating steps) so the snapshot matches final gas state.
There was a problem hiding this comment.
This is correct and expected, we want gas and refund that we got from loop if we get gas after eip7623_check_gas_floor we will lose this information.
This info was needed for EIP-7778
| /// Returns the effective refund after EIP-7623 floor gas adjustment: `spent - used()`. | ||
| /// | ||
| /// When `floor_gas` kicks in, this may be less than [`inner_refunded()`](ResultGas::inner_refunded). | ||
| /// Always satisfies: `spent == used() + final_refunded()`. | ||
| #[inline] | ||
| pub const fn final_refunded(&self) -> u64 { | ||
| self.spent.saturating_sub(self.used()) | ||
| } |
There was a problem hiding this comment.
final_refunded() docs claim it “Always satisfies: spent == used() + final_refunded()”, but used() can exceed spent when floor_gas > spent (EIP-7623 can enforce a minimum charge higher than actual spent). In that case final_refunded() saturates to 0 and the equality no longer holds. Please relax/qualify this invariant (or adjust the representation so spent >= used() is guaranteed).
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Initial plan * fix: correct grammar in handler.rs comment
Summary
ResultGasstruct withgas_used,gas_refunded,gas_spent, andfloor_gasfields to expose richer gas accounting to callersgas_used/gas_refundedfields in all threeExecutionResultvariants (Success,Revert,Halt) with a singlegas: ResultGasfieldgas()accessor onExecutionResultreturning&ResultGas; existinggas_used()method still worksInitialAndFloorGasthroughexecution_result()→post_execution::output()to populategas_spentandfloor_gasBreaking changes
ExecutionResultvariants now usegas: ResultGasinstead ofgas_used: u64/gas_refunded: u64Handler::execution_result()now takes an additionalInitialAndFloorGasparameterTest plan
cargo build --workspacecargo clippy --workspace --all-targets --all-featurescargo nextest run --workspace— 361 tests passcargo check --target riscv32imac-unknown-none-elf --no-default-features— no_std OK