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(
22 &mut self,
23 context: &mut CTX,
24 inputs: &CallInputs,
25 ) -> Result<Option<Self::Output>, String>;
26
27 fn warm_addresses(&self) -> &AddressSet;
29
30 fn contains(&self, address: &Address) -> bool {
32 self.warm_addresses().contains(address)
33 }
34}
35
36#[derive(Debug)]
38pub struct EthPrecompiles {
39 pub precompiles: &'static Precompiles,
41 pub spec: SpecId,
43}
44
45impl EthPrecompiles {
46 pub fn new(spec: SpecId) -> Self {
48 Self {
49 precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
50 spec,
51 }
52 }
53
54 pub const fn warm_addresses(&self) -> &AddressSet {
56 self.precompiles.addresses_set()
57 }
58
59 pub fn contains(&self, address: &Address) -> bool {
61 self.precompiles.contains(address)
62 }
63}
64
65impl Clone for EthPrecompiles {
66 fn clone(&self) -> Self {
67 Self {
68 precompiles: self.precompiles,
69 spec: self.spec,
70 }
71 }
72}
73
74pub fn precompile_output_to_interpreter_result(
87 output: PrecompileOutput,
88 gas_limit: u64,
89) -> InterpreterResult {
90 let result = if output.gas_used > gas_limit {
93 InstructionResult::PrecompileOOG
94 } else {
95 match &output.status {
96 PrecompileStatus::Success => InstructionResult::Return,
97 PrecompileStatus::Revert => InstructionResult::Revert,
98 PrecompileStatus::Halt(reason) if reason.is_oog() => InstructionResult::PrecompileOOG,
99 PrecompileStatus::Halt(_) => InstructionResult::PrecompileError,
100 }
101 };
102
103 let mut gas = Gas::new(gas_limit);
107 *gas.tracker_mut() = output.to_gas_tracker(gas_limit);
108
109 if result.is_halt() {
111 gas.spend_all();
112 return InterpreterResult::new(result, Bytes::new(), gas);
113 }
114
115 InterpreterResult::new(result, output.bytes, gas)
116}
117
118impl<CTX: ContextTr> PrecompileProvider<CTX> for EthPrecompiles {
119 type Output = InterpreterResult;
120
121 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
122 let spec = spec.into();
123 if spec == self.spec {
125 return false;
126 }
127 self.precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec));
128 self.spec = spec;
129 true
130 }
131
132 fn run(
133 &mut self,
134 context: &mut CTX,
135 inputs: &CallInputs,
136 ) -> Result<Option<InterpreterResult>, String> {
137 let Some(precompile) = self.precompiles.get(&inputs.bytecode_address) else {
138 return Ok(None);
139 };
140
141 let output = precompile
142 .execute(
143 &inputs.input.as_bytes(context),
144 inputs.gas_limit,
145 inputs.reservoir,
146 )
147 .map_err(|e| e.to_string())?;
148
149 if let Some(halt_reason) = output.halt_reason() {
153 if !halt_reason.is_oog() && context.journal().depth() == 1 {
154 context
155 .local_mut()
156 .set_precompile_error_context(halt_reason.to_string());
157 }
158 }
159
160 let result = precompile_output_to_interpreter_result(output, inputs.gas_limit);
161 Ok(Some(result))
162 }
163
164 fn warm_addresses(&self) -> &AddressSet {
165 Self::warm_addresses(self)
166 }
167
168 fn contains(&self, address: &Address) -> bool {
169 Self::contains(self, address)
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::{instructions::EthInstructions, ExecuteEvm, MainContext};
177 use context::{Context, Evm, FrameStack, TxEnv};
178 use context_interface::result::{ExecutionResult, HaltReason, OutOfGasError};
179 use database::InMemoryDB;
180 use interpreter::interpreter::EthInterpreter;
181 use primitives::{address, hardfork::SpecId, TxKind, U256};
182 use state::AccountInfo;
183
184 const OVERSPEND_PRECOMPILE: Address = address!("0000000000000000000000000000000000000100");
186
187 #[derive(Debug)]
194 struct OverspendingPrecompiles {
195 inner: EthPrecompiles,
196 warm: AddressSet,
197 }
198
199 impl OverspendingPrecompiles {
200 fn new(spec: SpecId) -> Self {
201 let inner = EthPrecompiles::new(spec);
202 let mut warm = AddressSet::default();
203 warm.clone_from(inner.warm_addresses());
204 warm.insert(OVERSPEND_PRECOMPILE);
205 Self { inner, warm }
206 }
207 }
208
209 impl<CTX> PrecompileProvider<CTX> for OverspendingPrecompiles
210 where
211 CTX: ContextTr<Cfg: Cfg<Spec = SpecId>>,
212 {
213 type Output = InterpreterResult;
214
215 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
216 let changed =
217 <EthPrecompiles as PrecompileProvider<CTX>>::set_spec(&mut self.inner, spec);
218 self.warm.clone_from(self.inner.warm_addresses());
219 self.warm.insert(OVERSPEND_PRECOMPILE);
220 changed
221 }
222
223 fn run(
224 &mut self,
225 context: &mut CTX,
226 inputs: &CallInputs,
227 ) -> Result<Option<Self::Output>, String> {
228 if inputs.bytecode_address == OVERSPEND_PRECOMPILE {
229 let output = PrecompileOutput {
230 status: PrecompileStatus::Success,
231 gas_used: u64::MAX,
232 gas_refunded: 0,
233 state_gas_used: 0,
234 state_gas_spilled: 0,
235 reservoir: inputs.reservoir,
236 bytes: Bytes::from_static(b"unreliable"),
237 };
238 return Ok(Some(precompile_output_to_interpreter_result(
239 output,
240 inputs.gas_limit,
241 )));
242 }
243 <EthPrecompiles as PrecompileProvider<CTX>>::run(&mut self.inner, context, inputs)
244 }
245
246 fn warm_addresses(&self) -> &AddressSet {
247 &self.warm
248 }
249 }
250
251 #[test]
255 fn precompile_output_propagates_spilled_state_gas() {
256 let output = PrecompileOutput {
257 status: PrecompileStatus::Success,
258 gas_used: 40,
260 gas_refunded: 0,
261 state_gas_used: 30,
262 state_gas_spilled: 20,
263 reservoir: 0,
264 bytes: Bytes::new(),
265 };
266 let mut result = precompile_output_to_interpreter_result(output, 100);
267
268 assert_eq!(result.result, InstructionResult::Return);
269 assert_eq!(result.gas.state_gas_spent(), 30);
270 assert_eq!(result.gas.state_gas_spilled(), 20);
271 assert_eq!(result.gas.remaining(), 60);
272
273 result.gas.rollback_state_gas();
275 assert_eq!(result.gas.remaining(), 80);
276 assert_eq!(result.gas.reservoir(), 10);
277 assert_eq!(result.gas.state_gas_spent(), 0);
278 assert_eq!(result.gas.state_gas_spilled(), 0);
279 }
280
281 #[test]
284 fn precompile_output_overspend_is_oog() {
285 let output = PrecompileOutput::new(u64::MAX, Bytes::from_static(b"out"), 0);
286 let result = precompile_output_to_interpreter_result(output, 100);
287 assert_eq!(result.result, InstructionResult::PrecompileOOG);
288 assert_eq!(result.gas.remaining(), 0);
289 assert!(result.output.is_empty());
290 }
291
292 #[test]
296 fn overspending_precompile_halts_tx_with_precompile_oog() {
297 let caller = address!("0000000000000000000000000000000000000001");
298 let mut db = InMemoryDB::default();
299 db.insert_account_info(
300 caller,
301 AccountInfo {
302 balance: U256::from(10).pow(U256::from(18)),
303 ..Default::default()
304 },
305 );
306
307 let spec = SpecId::default();
308 let ctx = Context::mainnet().with_db(db);
309 let mut evm = Evm {
310 ctx,
311 inspector: (),
312 instruction: EthInstructions::<EthInterpreter, _>::new_mainnet_with_spec(spec),
313 precompiles: OverspendingPrecompiles::new(spec),
314 frame_stack: FrameStack::new_prealloc(8),
315 };
316
317 let tx = TxEnv::builder()
318 .caller(caller)
319 .kind(TxKind::Call(OVERSPEND_PRECOMPILE))
320 .gas_limit(100_000)
321 .build()
322 .unwrap();
323
324 let exec = evm.transact_one(tx).expect("handler returned an error");
325
326 match exec {
327 ExecutionResult::Halt { reason, .. } => {
328 assert_eq!(
329 reason,
330 HaltReason::OutOfGas(OutOfGasError::Precompile),
331 "expected precompile OOG halt for over-spending precompile",
332 );
333 }
334 ExecutionResult::Success { .. } => panic!(
335 "before-fix behavior leaked: over-spending precompile reported Success \
336 instead of halting with PrecompileOOG"
337 ),
338 ExecutionResult::Revert { .. } => panic!("expected Halt(PrecompileOOG), got Revert"),
339 }
340 }
341}