diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/provider.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/provider.rs index cdc13fd6e01..bf652d810fe 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/provider.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/provider.rs @@ -143,11 +143,17 @@ where } else { InstructionResult::PrecompileError }; - } else { - let underflow = result.gas.record_regular_cost(output.gas_used); - assert!(underflow, "Gas underflow is not possible"); + } else if result.gas.record_regular_cost(output.gas_used) { result.result = InstructionResult::Return; result.output = output.bytes; + } else { + // Mirror revm's `EthPrecompiles::run`: a precompile reporting more gas than the + // call limit gracefully out-of-gases instead of panicking. Unreachable for the + // registered precompile set (each checks cost <= gas_limit before returning `Ok`), + // but matching upstream keeps the FPVM and op-reth aligned by construction and + // avoids halting the fault-proof program on a future precompile that omits the check. + result.gas.spend_all(); + result.result = InstructionResult::PrecompileOOG; } Ok(Some(result)) @@ -396,6 +402,51 @@ mod test { assert_eq!(interpreter_result.output.as_ref(), b"mock"); } + /// A mock accelerated precompile that reports spending more gas than the call limit. + fn overspending_accelerated_precompile( + _input: &[u8], + gas_limit: u64, + _hint_writer: &H, + _oracle_reader: &O, + ) -> EthPrecompileResult + where + H: HintWriterClient + Send + Sync, + O: PreimageOracleClient + Send + Sync, + { + Ok(revm::precompile::EthPrecompileOutput::new( + gas_limit.saturating_add(1), + Bytes::from_static(b"overspend"), + )) + } + + #[test] + fn test_run_overspending_precompile_oogs_without_panicking() { + let (hint_chan, preimage_chan) = ( + kona_preimage::BidirectionalChannel::new().unwrap(), + kona_preimage::BidirectionalChannel::new().unwrap(), + ); + let hint_writer = kona_preimage::HintWriter::new(hint_chan.client); + let oracle_reader = kona_preimage::OracleReader::new(preimage_chan.client); + + let mut ctx = create_test_context(); + + let mut precompiles = + OpFpvmPrecompiles::new_with_spec(OpSpecId::BEDROCK, hint_writer, oracle_reader); + + // A precompile that reports gas_used > gas_limit must gracefully OOG, matching + // revm's `EthPrecompiles::run`, rather than panicking the fault-proof program. + precompiles + .accelerated_precompiles + .insert(ECRECOVER_ADDR, overspending_accelerated_precompile); + + let call_inputs = create_call_inputs(ECRECOVER_ADDR, Bytes::from_static(b"test"), 1000); + + let result = precompiles.run(&mut ctx, &call_inputs).unwrap().unwrap(); + assert_eq!(result.result, InstructionResult::PrecompileOOG); + assert!(result.output.is_empty()); + assert_eq!(result.gas.remaining(), 0); + } + #[test] fn test_run_default_precompile_sha256() { let (hint_chan, preimage_chan) = (