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(
82 output: PrecompileOutput,
83 gas_limit: u64,
84) -> InterpreterResult {
85 let bytes = if output.status.is_success_or_revert() {
87 output.bytes
88 } else {
89 Bytes::new()
90 };
91
92 let mut result = InterpreterResult {
93 result: InstructionResult::Return,
94 gas: Gas::new_with_regular_gas_and_reservoir(gas_limit, output.reservoir),
95 output: bytes,
96 };
97
98 result.gas.set_state_gas_spent(output.state_gas_used as i64);
100 result.gas.set_refill_amount(output.refill_amount);
101 result.gas.record_refund(output.gas_refunded);
102
103 if output.status.is_success_or_revert() {
105 if !result.gas.record_regular_cost(output.gas_used) {
106 result.gas.spend_all();
107 result.output = Bytes::new();
108 result.result = InstructionResult::PrecompileOOG;
109 return result;
110 }
111 } else {
112 result.gas.spend_all();
113 }
114
115 result.result = match output.status {
117 PrecompileStatus::Success => InstructionResult::Return,
118 PrecompileStatus::Revert => InstructionResult::Revert,
119 PrecompileStatus::Halt(halt_reason) => {
120 if halt_reason.is_oog() {
121 InstructionResult::PrecompileOOG
122 } else {
123 InstructionResult::PrecompileError
124 }
125 }
126 };
127
128 result
129}
130
131impl<CTX: ContextTr> PrecompileProvider<CTX> for EthPrecompiles {
132 type Output = InterpreterResult;
133
134 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
135 let spec = spec.into();
136 if spec == self.spec {
138 return false;
139 }
140 self.precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec));
141 self.spec = spec;
142 true
143 }
144
145 fn run(
146 &mut self,
147 context: &mut CTX,
148 inputs: &CallInputs,
149 ) -> Result<Option<InterpreterResult>, String> {
150 let Some(precompile) = self.precompiles.get(&inputs.bytecode_address) else {
151 return Ok(None);
152 };
153
154 let output = precompile
155 .execute(
156 &inputs.input.as_bytes(context),
157 inputs.gas_limit,
158 inputs.reservoir,
159 )
160 .map_err(|e| e.to_string())?;
161
162 if let Some(halt_reason) = output.halt_reason() {
166 if !halt_reason.is_oog() && context.journal().depth() == 1 {
167 context
168 .local_mut()
169 .set_precompile_error_context(halt_reason.to_string());
170 }
171 }
172
173 let result = precompile_output_to_interpreter_result(output, inputs.gas_limit);
174 Ok(Some(result))
175 }
176
177 fn warm_addresses(&self) -> &AddressSet {
178 Self::warm_addresses(self)
179 }
180
181 fn contains(&self, address: &Address) -> bool {
182 Self::contains(self, address)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crate::{instructions::EthInstructions, ExecuteEvm, MainContext};
190 use context::{Context, Evm, FrameStack, TxEnv};
191 use context_interface::result::{ExecutionResult, HaltReason, OutOfGasError};
192 use database::InMemoryDB;
193 use interpreter::interpreter::EthInterpreter;
194 use primitives::{address, hardfork::SpecId, TxKind, U256};
195 use state::AccountInfo;
196
197 const OVERSPEND_PRECOMPILE: Address = address!("0000000000000000000000000000000000000100");
199
200 #[derive(Debug)]
207 struct OverspendingPrecompiles {
208 inner: EthPrecompiles,
209 warm: AddressSet,
210 }
211
212 impl OverspendingPrecompiles {
213 fn new(spec: SpecId) -> Self {
214 let inner = EthPrecompiles::new(spec);
215 let mut warm = AddressSet::default();
216 warm.clone_from(inner.warm_addresses());
217 warm.insert(OVERSPEND_PRECOMPILE);
218 Self { inner, warm }
219 }
220 }
221
222 impl<CTX> PrecompileProvider<CTX> for OverspendingPrecompiles
223 where
224 CTX: ContextTr<Cfg: Cfg<Spec = SpecId>>,
225 {
226 type Output = InterpreterResult;
227
228 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
229 let changed =
230 <EthPrecompiles as PrecompileProvider<CTX>>::set_spec(&mut self.inner, spec);
231 self.warm.clone_from(self.inner.warm_addresses());
232 self.warm.insert(OVERSPEND_PRECOMPILE);
233 changed
234 }
235
236 fn run(
237 &mut self,
238 context: &mut CTX,
239 inputs: &CallInputs,
240 ) -> Result<Option<Self::Output>, String> {
241 if inputs.bytecode_address == OVERSPEND_PRECOMPILE {
242 let output = PrecompileOutput {
243 status: PrecompileStatus::Success,
244 gas_used: u64::MAX,
245 gas_refunded: 0,
246 state_gas_used: 0,
247 reservoir: inputs.reservoir,
248 refill_amount: 0,
249 bytes: Bytes::from_static(b"unreliable"),
250 };
251 return Ok(Some(precompile_output_to_interpreter_result(
252 output,
253 inputs.gas_limit,
254 )));
255 }
256 <EthPrecompiles as PrecompileProvider<CTX>>::run(&mut self.inner, context, inputs)
257 }
258
259 fn warm_addresses(&self) -> &AddressSet {
260 &self.warm
261 }
262 }
263
264 #[test]
268 fn overspending_precompile_halts_tx_with_precompile_oog() {
269 let caller = address!("0000000000000000000000000000000000000001");
270 let mut db = InMemoryDB::default();
271 db.insert_account_info(
272 caller,
273 AccountInfo {
274 balance: U256::from(10).pow(U256::from(18)),
275 ..Default::default()
276 },
277 );
278
279 let spec = SpecId::default();
280 let ctx = Context::mainnet().with_db(db);
281 let mut evm = Evm {
282 ctx,
283 inspector: (),
284 instruction: EthInstructions::<EthInterpreter, _>::new_mainnet_with_spec(spec),
285 precompiles: OverspendingPrecompiles::new(spec),
286 frame_stack: FrameStack::new_prealloc(8),
287 };
288
289 let tx = TxEnv::builder()
290 .caller(caller)
291 .kind(TxKind::Call(OVERSPEND_PRECOMPILE))
292 .gas_limit(100_000)
293 .build()
294 .unwrap();
295
296 let exec = evm.transact_one(tx).expect("handler returned an error");
297
298 match exec {
299 ExecutionResult::Halt { reason, .. } => {
300 assert_eq!(
301 reason,
302 HaltReason::OutOfGas(OutOfGasError::Precompile),
303 "expected precompile OOG halt for over-spending precompile",
304 );
305 }
306 ExecutionResult::Success { .. } => panic!(
307 "before-fix behavior leaked: over-spending precompile reported Success \
308 instead of halting with PrecompileOOG"
309 ),
310 ExecutionResult::Revert { .. } => panic!("expected Halt(PrecompileOOG), got Revert"),
311 }
312 }
313}