revm_handler/
precompile_provider.rs

1use auto_impl::auto_impl;
2use context::{Cfg, LocalContextTr};
3use context_interface::{ContextTr, JournalTr};
4use interpreter::{CallInput, CallInputs, Gas, InstructionResult, InterpreterResult};
5use precompile::{PrecompileError, PrecompileSpecId, Precompiles};
6use primitives::{hardfork::SpecId, Address, Bytes};
7use std::{
8    boxed::Box,
9    string::{String, ToString},
10};
11
12/// Provider for precompiled contracts in the EVM.
13#[auto_impl(&mut, Box)]
14pub trait PrecompileProvider<CTX: ContextTr> {
15    /// The output type returned by precompile execution.
16    type Output;
17
18    /// Sets the spec id and returns true if the spec id was changed. Initial call to set_spec will always return true.
19    ///
20    /// Returns `true` if precompile addresses should be injected into the journal.
21    fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool;
22
23    /// Run the precompile.
24    fn run(
25        &mut self,
26        context: &mut CTX,
27        inputs: &CallInputs,
28    ) -> Result<Option<Self::Output>, String>;
29
30    /// Get the warm addresses.
31    fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>>;
32
33    /// Check if the address is a precompile.
34    fn contains(&self, address: &Address) -> bool;
35}
36
37/// The [`PrecompileProvider`] for ethereum precompiles.
38#[derive(Debug)]
39pub struct EthPrecompiles {
40    /// Contains precompiles for the current spec.
41    pub precompiles: &'static Precompiles,
42    /// Current spec. None means that spec was not set yet.
43    pub spec: SpecId,
44}
45
46impl EthPrecompiles {
47    /// Returns addresses of the precompiles.
48    pub fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
49        Box::new(self.precompiles.addresses().cloned())
50    }
51
52    /// Returns whether the address is a precompile.
53    pub fn contains(&self, address: &Address) -> bool {
54        self.precompiles.contains(address)
55    }
56}
57
58impl Clone for EthPrecompiles {
59    fn clone(&self) -> Self {
60        Self {
61            precompiles: self.precompiles,
62            spec: self.spec,
63        }
64    }
65}
66
67impl Default for EthPrecompiles {
68    fn default() -> Self {
69        let spec = SpecId::default();
70        Self {
71            precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
72            spec,
73        }
74    }
75}
76
77impl<CTX: ContextTr> PrecompileProvider<CTX> for EthPrecompiles {
78    type Output = InterpreterResult;
79
80    fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
81        let spec = spec.into();
82        // generate new precompiles only on new spec
83        if spec == self.spec {
84            return false;
85        }
86        self.precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec));
87        self.spec = spec;
88        true
89    }
90
91    fn run(
92        &mut self,
93        context: &mut CTX,
94        inputs: &CallInputs,
95    ) -> Result<Option<InterpreterResult>, String> {
96        let Some(precompile) = self.precompiles.get(&inputs.bytecode_address) else {
97            return Ok(None);
98        };
99
100        let mut result = InterpreterResult {
101            result: InstructionResult::Return,
102            gas: Gas::new(inputs.gas_limit),
103            output: Bytes::new(),
104        };
105
106        let exec_result = {
107            let r;
108            let input_bytes = match &inputs.input {
109                CallInput::SharedBuffer(range) => {
110                    if let Some(slice) = context.local().shared_memory_buffer_slice(range.clone()) {
111                        r = slice;
112                        r.as_ref()
113                    } else {
114                        &[]
115                    }
116                }
117                CallInput::Bytes(bytes) => bytes.0.iter().as_slice(),
118            };
119            precompile.execute(input_bytes, inputs.gas_limit)
120        };
121
122        match exec_result {
123            Ok(output) => {
124                result.gas.record_refund(output.gas_refunded);
125                let underflow = result.gas.record_cost(output.gas_used);
126                assert!(underflow, "Gas underflow is not possible");
127                result.result = if output.reverted {
128                    InstructionResult::Revert
129                } else {
130                    InstructionResult::Return
131                };
132                result.output = output.bytes;
133            }
134            Err(PrecompileError::Fatal(e)) => return Err(e),
135            Err(e) => {
136                result.result = if e.is_oog() {
137                    InstructionResult::PrecompileOOG
138                } else {
139                    InstructionResult::PrecompileError
140                };
141                // If this is a top-level precompile call (depth == 1), persist the error message
142                // into the local context so it can be returned as output in the final result.
143                // Only do this for non-OOG errors (OOG is a distinct halt reason without output).
144                if !e.is_oog() && context.journal().depth() == 1 {
145                    context
146                        .local_mut()
147                        .set_precompile_error_context(e.to_string());
148                }
149            }
150        }
151        Ok(Some(result))
152    }
153
154    fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
155        Self::warm_addresses(self)
156    }
157
158    fn contains(&self, address: &Address) -> bool {
159        Self::contains(self, address)
160    }
161}