example_custom_precompile_journal/
precompile_provider.rs

1//! Custom precompile provider implementation.
2
3use revm::{
4    context::Cfg,
5    context_interface::{ContextTr, JournalTr, LocalContextTr, Transaction},
6    handler::{EthPrecompiles, PrecompileProvider},
7    interpreter::{CallInputs, Gas, InstructionResult, InterpreterResult},
8    precompile::{PrecompileError, PrecompileOutput, PrecompileResult},
9    primitives::{address, hardfork::SpecId, Address, Bytes, U256},
10};
11use std::boxed::Box;
12use std::string::String;
13
14// Define our custom precompile address
15pub const CUSTOM_PRECOMPILE_ADDRESS: Address = address!("0000000000000000000000000000000000000100");
16
17// Custom storage key for our example
18const STORAGE_KEY: U256 = U256::ZERO;
19
20/// Custom precompile provider that includes journal access functionality
21#[derive(Debug, Clone)]
22pub struct CustomPrecompileProvider {
23    inner: EthPrecompiles,
24    spec: SpecId,
25}
26
27impl CustomPrecompileProvider {
28    pub fn new_with_spec(spec: SpecId) -> Self {
29        Self {
30            inner: EthPrecompiles::default(),
31            spec,
32        }
33    }
34}
35
36impl<CTX> PrecompileProvider<CTX> for CustomPrecompileProvider
37where
38    CTX: ContextTr<Cfg: Cfg<Spec = SpecId>>,
39{
40    type Output = InterpreterResult;
41
42    fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
43        if spec == self.spec {
44            return false;
45        }
46        self.spec = spec;
47        // Create a new inner provider with the new spec
48        self.inner = EthPrecompiles::default();
49        true
50    }
51
52    fn run(
53        &mut self,
54        context: &mut CTX,
55        inputs: &CallInputs,
56    ) -> Result<Option<Self::Output>, String> {
57        // Check if this is our custom precompile
58        if inputs.bytecode_address == CUSTOM_PRECOMPILE_ADDRESS {
59            return Ok(Some(run_custom_precompile(context, inputs)?));
60        }
61
62        // Otherwise, delegate to standard Ethereum precompiles
63        self.inner.run(context, inputs)
64    }
65
66    fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
67        // Include our custom precompile address along with standard ones
68        let mut addresses = vec![CUSTOM_PRECOMPILE_ADDRESS];
69        addresses.extend(self.inner.warm_addresses());
70        Box::new(addresses.into_iter())
71    }
72
73    fn contains(&self, address: &Address) -> bool {
74        *address == CUSTOM_PRECOMPILE_ADDRESS || self.inner.contains(address)
75    }
76}
77
78/// Runs our custom precompile
79fn run_custom_precompile<CTX: ContextTr>(
80    context: &mut CTX,
81    inputs: &CallInputs,
82) -> Result<InterpreterResult, String> {
83    let input_bytes = match &inputs.input {
84        revm::interpreter::CallInput::SharedBuffer(range) => {
85            if let Some(slice) = context.local().shared_memory_buffer_slice(range.clone()) {
86                slice.to_vec()
87            } else {
88                vec![]
89            }
90        }
91        revm::interpreter::CallInput::Bytes(bytes) => bytes.0.to_vec(),
92    };
93
94    // For this example, we'll implement a simple precompile that:
95    // - If called with empty data: reads a storage value
96    // - If called with 32 bytes: writes that value to storage and transfers 1 wei to the caller
97
98    let result = if input_bytes.is_empty() {
99        // Read storage operation
100        handle_read_storage(context, inputs.gas_limit)
101    } else if input_bytes.len() == 32 {
102        if inputs.is_static {
103            return Err("Cannot modify state in static context".to_string());
104        }
105        // Write storage operation
106        handle_write_storage(context, &input_bytes, inputs.gas_limit)
107    } else {
108        Err(PrecompileError::Other("Invalid input length".to_string()))
109    };
110
111    match result {
112        Ok(output) => {
113            let mut interpreter_result = InterpreterResult {
114                result: if output.reverted {
115                    InstructionResult::Revert
116                } else {
117                    InstructionResult::Return
118                },
119                gas: Gas::new(inputs.gas_limit),
120                output: output.bytes,
121            };
122            let underflow = interpreter_result.gas.record_cost(output.gas_used);
123            if !underflow {
124                interpreter_result.result = InstructionResult::PrecompileOOG;
125            }
126            Ok(interpreter_result)
127        }
128        Err(e) => {
129            // If this is a top-level precompile call and error is non-OOG, record the message
130            if !e.is_oog() && context.journal().depth() == 1 {
131                context
132                    .local_mut()
133                    .set_precompile_error_context(e.to_string());
134            }
135            Ok(InterpreterResult {
136                result: if e.is_oog() {
137                    InstructionResult::PrecompileOOG
138                } else {
139                    InstructionResult::PrecompileError
140                },
141                gas: Gas::new(inputs.gas_limit),
142                output: Bytes::new(),
143            })
144        }
145    }
146}
147
148/// Handles reading from storage
149fn handle_read_storage<CTX: ContextTr>(context: &mut CTX, gas_limit: u64) -> PrecompileResult {
150    // Base gas cost for reading storage
151    const BASE_GAS: u64 = 2_100;
152
153    if gas_limit < BASE_GAS {
154        return Err(PrecompileError::OutOfGas);
155    }
156
157    // Read from storage using the journal
158    let value = context
159        .journal_mut()
160        .sload(CUSTOM_PRECOMPILE_ADDRESS, STORAGE_KEY)
161        .map_err(|e| PrecompileError::Other(format!("Storage read failed: {e:?}")))?
162        .data;
163
164    // Return the value as output
165    Ok(PrecompileOutput::new(
166        BASE_GAS,
167        value.to_be_bytes_vec().into(),
168    ))
169}
170
171/// Handles writing to storage and transferring balance
172fn handle_write_storage<CTX: ContextTr>(
173    context: &mut CTX,
174    input: &[u8],
175    gas_limit: u64,
176) -> PrecompileResult {
177    // Base gas cost for the operation
178    const BASE_GAS: u64 = 21_000;
179    const SSTORE_GAS: u64 = 20_000;
180
181    if gas_limit < BASE_GAS + SSTORE_GAS {
182        return Err(PrecompileError::OutOfGas);
183    }
184
185    // Parse the input as a U256 value
186    let value = U256::from_be_slice(input);
187
188    // Store the value in the precompile's storage
189    context
190        .journal_mut()
191        .sstore(CUSTOM_PRECOMPILE_ADDRESS, STORAGE_KEY, value)
192        .map_err(|e| PrecompileError::Other(format!("Storage write failed: {e:?}")))?;
193
194    // Get the caller address
195    let caller = context.tx().caller();
196
197    // Transfer 1 wei from the precompile to the caller as a reward
198    // First, ensure the precompile has balance
199    context
200        .journal_mut()
201        .balance_incr(CUSTOM_PRECOMPILE_ADDRESS, U256::from(1))
202        .map_err(|e| PrecompileError::Other(format!("Balance increment failed: {e:?}")))?;
203
204    // Then transfer to caller
205    let transfer_result = context
206        .journal_mut()
207        .transfer(CUSTOM_PRECOMPILE_ADDRESS, caller, U256::from(1))
208        .map_err(|e| PrecompileError::Other(format!("Transfer failed: {e:?}")))?;
209
210    if let Some(error) = transfer_result {
211        return Err(PrecompileError::Other(format!("Transfer error: {error:?}")));
212    }
213
214    // Return success with empty output
215    Ok(PrecompileOutput::new(BASE_GAS + SSTORE_GAS, Bytes::new()))
216}