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