1use auto_impl::auto_impl;
2use context::{Cfg, LocalContextTr};
3use context_interface::{ContextTr, JournalTr};
4use interpreter::{CallInputs, Gas, InstructionResult, InterpreterResult};
5use precompile::{PrecompileOutput, PrecompileSpecId, PrecompileStatus, Precompiles};
6use primitives::{hardfork::SpecId, Address, AddressSet, Bytes};
7use std::string::{String, ToString};
8
9#[auto_impl(&mut, Box)]
11pub trait PrecompileProvider<CTX: ContextTr> {
12 type Output;
14
15 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool;
19
20 fn run(
31 &mut self,
32 context: &mut CTX,
33 inputs: &CallInputs,
34 ) -> Result<Option<Self::Output>, String>;
35
36 fn warm_addresses(&self) -> &AddressSet;
38
39 fn contains(&self, address: &Address) -> bool {
41 self.warm_addresses().contains(address)
42 }
43}
44
45#[derive(Debug)]
47pub struct EthPrecompiles {
48 pub precompiles: &'static Precompiles,
50 pub spec: SpecId,
52}
53
54impl EthPrecompiles {
55 pub fn new(spec: SpecId) -> Self {
57 Self {
58 precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
59 spec,
60 }
61 }
62
63 pub const fn warm_addresses(&self) -> &AddressSet {
65 self.precompiles.addresses_set()
66 }
67
68 pub fn contains(&self, address: &Address) -> bool {
70 self.precompiles.contains(address)
71 }
72}
73
74impl Clone for EthPrecompiles {
75 fn clone(&self) -> Self {
76 Self {
77 precompiles: self.precompiles,
78 spec: self.spec,
79 }
80 }
81}
82
83pub fn precompile_output_to_interpreter_result(
96 output: PrecompileOutput,
97 gas_limit: u64,
98) -> InterpreterResult {
99 let result = if output.gas_used > gas_limit {
102 InstructionResult::PrecompileOOG
103 } else {
104 match &output.status {
105 PrecompileStatus::Success => InstructionResult::Return,
106 PrecompileStatus::Revert => InstructionResult::Revert,
107 PrecompileStatus::Halt(reason) if reason.is_oog() => InstructionResult::PrecompileOOG,
108 PrecompileStatus::Halt(_) => InstructionResult::PrecompileError,
109 }
110 };
111
112 let mut gas = Gas::new(gas_limit);
116 *gas.tracker_mut() = output.to_gas_tracker(gas_limit);
117
118 if result.is_halt() {
120 gas.spend_all();
121 return InterpreterResult::new(result, Bytes::new(), gas);
122 }
123
124 InterpreterResult::new(result, output.bytes, gas)
125}
126
127impl<CTX: ContextTr> PrecompileProvider<CTX> for EthPrecompiles {
128 type Output = InterpreterResult;
129
130 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
131 let spec = spec.into();
132 if spec == self.spec {
134 return false;
135 }
136 self.precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec));
137 self.spec = spec;
138 true
139 }
140
141 fn run(
142 &mut self,
143 context: &mut CTX,
144 inputs: &CallInputs,
145 ) -> Result<Option<InterpreterResult>, String> {
146 let Some(precompile) = self.precompiles.get(&inputs.bytecode_address) else {
147 return Ok(None);
148 };
149
150 let output = precompile
151 .execute(
152 &inputs.input.as_bytes(context),
153 inputs.gas_limit,
154 inputs.reservoir,
155 )
156 .map_err(|e| e.to_string())?;
157
158 if let Some(halt_reason) = output.halt_reason() {
162 if !halt_reason.is_oog() && context.journal().depth() == 1 {
163 context
164 .local_mut()
165 .set_precompile_error_context(halt_reason.to_string());
166 }
167 }
168
169 let result = precompile_output_to_interpreter_result(output, inputs.gas_limit);
170 Ok(Some(result))
171 }
172
173 fn warm_addresses(&self) -> &AddressSet {
174 Self::warm_addresses(self)
175 }
176
177 fn contains(&self, address: &Address) -> bool {
178 Self::contains(self, address)
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::{instructions::EthInstructions, ExecuteEvm, MainContext};
186 use context::{Context, Evm, FrameStack, TxEnv};
187 use context_interface::result::{ExecutionResult, HaltReason, OutOfGasError};
188 use database::InMemoryDB;
189 use interpreter::interpreter::EthInterpreter;
190 use primitives::{address, hardfork::SpecId, TxKind, U256};
191 use state::AccountInfo;
192
193 const OVERSPEND_PRECOMPILE: Address = address!("0000000000000000000000000000000000000100");
195
196 #[derive(Debug)]
203 struct OverspendingPrecompiles {
204 inner: EthPrecompiles,
205 warm: AddressSet,
206 }
207
208 impl OverspendingPrecompiles {
209 fn new(spec: SpecId) -> Self {
210 let inner = EthPrecompiles::new(spec);
211 let mut warm = AddressSet::default();
212 warm.clone_from(inner.warm_addresses());
213 warm.insert(OVERSPEND_PRECOMPILE);
214 Self { inner, warm }
215 }
216 }
217
218 impl<CTX> PrecompileProvider<CTX> for OverspendingPrecompiles
219 where
220 CTX: ContextTr<Cfg: Cfg<Spec = SpecId>>,
221 {
222 type Output = InterpreterResult;
223
224 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
225 let changed =
226 <EthPrecompiles as PrecompileProvider<CTX>>::set_spec(&mut self.inner, spec);
227 self.warm.clone_from(self.inner.warm_addresses());
228 self.warm.insert(OVERSPEND_PRECOMPILE);
229 changed
230 }
231
232 fn run(
233 &mut self,
234 context: &mut CTX,
235 inputs: &CallInputs,
236 ) -> Result<Option<Self::Output>, String> {
237 if inputs.bytecode_address == OVERSPEND_PRECOMPILE {
238 let output = PrecompileOutput {
239 status: PrecompileStatus::Success,
240 gas_used: u64::MAX,
241 gas_refunded: 0,
242 state_gas_used: 0,
243 state_gas_spilled: 0,
244 reservoir: inputs.reservoir,
245 bytes: Bytes::from_static(b"unreliable"),
246 };
247 return Ok(Some(precompile_output_to_interpreter_result(
248 output,
249 inputs.gas_limit,
250 )));
251 }
252 <EthPrecompiles as PrecompileProvider<CTX>>::run(&mut self.inner, context, inputs)
253 }
254
255 fn warm_addresses(&self) -> &AddressSet {
256 &self.warm
257 }
258 }
259
260 #[test]
264 fn precompile_output_propagates_spilled_state_gas() {
265 let output = PrecompileOutput {
266 status: PrecompileStatus::Success,
267 gas_used: 40,
269 gas_refunded: 0,
270 state_gas_used: 30,
271 state_gas_spilled: 20,
272 reservoir: 0,
273 bytes: Bytes::new(),
274 };
275 let mut result = precompile_output_to_interpreter_result(output, 100);
276
277 assert_eq!(result.result, InstructionResult::Return);
278 assert_eq!(result.gas.state_gas_spent(), 30);
279 assert_eq!(result.gas.state_gas_spilled(), 20);
280 assert_eq!(result.gas.remaining(), 60);
281
282 result.gas.rollback_state_gas();
284 assert_eq!(result.gas.remaining(), 80);
285 assert_eq!(result.gas.reservoir(), 10);
286 assert_eq!(result.gas.state_gas_spent(), 0);
287 assert_eq!(result.gas.state_gas_spilled(), 0);
288 }
289
290 #[test]
293 fn precompile_output_overspend_is_oog() {
294 let output = PrecompileOutput::new(u64::MAX, Bytes::from_static(b"out"), 0);
295 let result = precompile_output_to_interpreter_result(output, 100);
296 assert_eq!(result.result, InstructionResult::PrecompileOOG);
297 assert_eq!(result.gas.remaining(), 0);
298 assert!(result.output.is_empty());
299 }
300
301 #[test]
305 fn overspending_precompile_halts_tx_with_precompile_oog() {
306 let caller = address!("0000000000000000000000000000000000000001");
307 let mut db = InMemoryDB::default();
308 db.insert_account_info(
309 caller,
310 AccountInfo {
311 balance: U256::from(10).pow(U256::from(18)),
312 ..Default::default()
313 },
314 );
315
316 let spec = SpecId::default();
317 let ctx = Context::mainnet().with_db(db);
318 let mut evm = Evm {
319 ctx,
320 inspector: (),
321 instruction: EthInstructions::<EthInterpreter, _>::new_mainnet_with_spec(spec),
322 precompiles: OverspendingPrecompiles::new(spec),
323 frame_stack: FrameStack::new_prealloc(8),
324 #[cfg(feature = "asyncdb")]
325 async_stack: database_interface::async_db::FiberStack::default(),
326 };
327
328 let tx = TxEnv::builder()
329 .caller(caller)
330 .kind(TxKind::Call(OVERSPEND_PRECOMPILE))
331 .gas_limit(100_000)
332 .build()
333 .unwrap();
334
335 let exec = evm.transact_one(tx).expect("handler returned an error");
336
337 match exec {
338 ExecutionResult::Halt { reason, .. } => {
339 assert_eq!(
340 reason,
341 HaltReason::OutOfGas(OutOfGasError::Precompile),
342 "expected precompile OOG halt for over-spending precompile",
343 );
344 }
345 ExecutionResult::Success { .. } => panic!(
346 "before-fix behavior leaked: over-spending precompile reported Success \
347 instead of halting with PrecompileOOG"
348 ),
349 ExecutionResult::Revert { .. } => panic!("expected Halt(PrecompileOOG), got Revert"),
350 }
351 }
352}