Skip to content

Commit 17ce6d9

Browse files
committed
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.
1 parent 7a999fc commit 17ce6d9

2 files changed

Lines changed: 18 additions & 10 deletions

File tree

crates/handler/src/handler.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,19 @@ pub trait Handler {
470470
// Always track state gas spent regardless of outcome.
471471
gas.set_state_gas_spent(state_gas_spent);
472472

473-
// Reservoir handling at the top-level frame.
474-
// Use the frame's final reservoir directly — it already reflects
475-
// child frame restorations and any state gas consumed/spilled during execution.
476-
gas.set_reservoir(reservoir);
473+
// Reservoir handling at the top-level frame:
474+
// - On success: use the frame's final reservoir as-is, state gas was consumed.
475+
// - On revert/halt: restore state gas spent back to the reservoir,
476+
// because state changes are rolled back so state gas should be refunded.
477+
//
478+
// Note: eth devnet3 does NOT do this — it ignores state_gas_spent and
479+
// unconditionally sets gas.set_reservoir(reservoir) regardless of the
480+
// instruction_result kind. This is a bug in the devnet3 spec.
481+
if instruction_result.is_ok() {
482+
gas.set_reservoir(reservoir);
483+
} else {
484+
gas.set_reservoir(reservoir + state_gas_spent);
485+
}
477486

478487
Ok(())
479488
}

crates/interpreter/src/instructions/contract.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ use crate::InstructionContext;
2424
pub fn create<WIRE: InterpreterTypes, const IS_CREATE2: bool, H: Host + ?Sized>(
2525
context: InstructionContext<'_, H, WIRE>,
2626
) {
27+
// Static call check is before gas charging (unlike execution-specs where it's
28+
// inside generic_create). This is safe because CREATE in a static context is
29+
// always an error regardless of gas accounting.
30+
require_non_staticcall!(context.interpreter);
31+
2732
// EIP-1014: Skinny CREATE2
2833
if IS_CREATE2 {
2934
check!(context.interpreter, PETERSBURG);
@@ -93,12 +98,6 @@ pub fn create<WIRE: InterpreterTypes, const IS_CREATE2: bool, H: Host + ?Sized>(
9398
);
9499
}
95100

96-
// Static call check: must be AFTER gas charging to match the execution-specs,
97-
// where the static check is inside generic_create() after all gas has been charged.
98-
// This ensures state gas is recorded on the frame, so that on child error the parent
99-
// can recover it via handle_reservoir_remaining_gas (EIP-8037 state gas recovery).
100-
require_non_staticcall!(context.interpreter);
101-
102101
let mut gas_limit = context.interpreter.gas.remaining();
103102

104103
// EIP-150: Gas cost changes for IO-heavy operations

0 commit comments

Comments
 (0)