Skip to main content

revm_handler/
precompile_provider.rs

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, 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    /// Create a new precompile provider with the given spec.
48    pub fn new(spec: SpecId) -> Self {
49        Self {
50            precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
51            spec,
52        }
53    }
54
55    /// Returns addresses of the precompiles.
56    pub fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
57        Box::new(self.precompiles.addresses().cloned())
58    }
59
60    /// Returns whether the address is a precompile.
61    pub fn contains(&self, address: &Address) -> bool {
62        self.precompiles.contains(address)
63    }
64}
65
66impl Clone for EthPrecompiles {
67    fn clone(&self) -> Self {
68        Self {
69            precompiles: self.precompiles,
70            spec: self.spec,
71        }
72    }
73}
74
75/// Converts a [`PrecompileOutput`] into an [`InterpreterResult`].
76///
77/// Maps precompile status to the corresponding instruction result:
78/// - `Success` → `InstructionResult::Return`
79/// - `Revert` → `InstructionResult::Revert`
80/// - `Halt(OOG)` → `InstructionResult::PrecompileOOG`
81/// - `Halt(other)` → `InstructionResult::PrecompileError`
82pub fn precompile_output_to_interpreter_result(
83    output: PrecompileOutput,
84    gas_limit: u64,
85) -> InterpreterResult {
86    // set output bytes
87    let bytes = if output.status.is_success_or_revert() {
88        output.bytes
89    } else {
90        Bytes::new()
91    };
92
93    let mut result = InterpreterResult {
94        result: InstructionResult::Return,
95        gas: Gas::new_with_regular_gas_and_reservoir(gas_limit, output.reservoir),
96        output: bytes,
97    };
98
99    // set state gas, reservoir is already set in the Gas constructor
100    result.gas.set_state_gas_spent(output.state_gas_used);
101    result.gas.record_refund(output.gas_refunded);
102
103    // spend used gas.
104    if output.status.is_success_or_revert() {
105        let _ = result.gas.record_regular_cost(output.gas_used);
106    } else {
107        result.gas.spend_all();
108    }
109
110    // set result
111    result.result = match output.status {
112        PrecompileStatus::Success => InstructionResult::Return,
113        PrecompileStatus::Revert => InstructionResult::Revert,
114        PrecompileStatus::Halt(halt_reason) => {
115            if halt_reason.is_oog() {
116                InstructionResult::PrecompileOOG
117            } else {
118                InstructionResult::PrecompileError
119            }
120        }
121    };
122
123    result
124}
125
126impl<CTX: ContextTr> PrecompileProvider<CTX> for EthPrecompiles {
127    type Output = InterpreterResult;
128
129    fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
130        let spec = spec.into();
131        // generate new precompiles only on new spec
132        if spec == self.spec {
133            return false;
134        }
135        self.precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec));
136        self.spec = spec;
137        true
138    }
139
140    fn run(
141        &mut self,
142        context: &mut CTX,
143        inputs: &CallInputs,
144    ) -> Result<Option<InterpreterResult>, String> {
145        let Some(precompile) = self.precompiles.get(&inputs.bytecode_address) else {
146            return Ok(None);
147        };
148
149        let output = precompile
150            .execute(
151                &inputs.input.as_bytes(context),
152                inputs.gas_limit,
153                inputs.reservoir,
154            )
155            .map_err(|e| e.to_string())?;
156
157        // If this is a top-level precompile call (depth == 1), persist the error message
158        // into the local context so it can be returned as output in the final result.
159        // Only do this for non-OOG halt errors.
160        if let Some(halt_reason) = output.halt_reason() {
161            if !halt_reason.is_oog() && context.journal().depth() == 1 {
162                context
163                    .local_mut()
164                    .set_precompile_error_context(halt_reason.to_string());
165            }
166        }
167
168        let result = precompile_output_to_interpreter_result(output, inputs.gas_limit);
169        Ok(Some(result))
170    }
171
172    fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
173        Self::warm_addresses(self)
174    }
175
176    fn contains(&self, address: &Address) -> bool {
177        Self::contains(self, address)
178    }
179}