example_custom_precompile_journal/
precompile_provider.rs1use 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, Log, B256, U256},
10};
11use std::{boxed::Box, string::String};
12
13pub const CUSTOM_PRECOMPILE_ADDRESS: Address = address!("0000000000000000000000000000000000000100");
15
16const STORAGE_KEY: U256 = U256::ZERO;
18
19#[derive(Debug, Clone)]
21pub struct CustomPrecompileProvider {
22 inner: EthPrecompiles,
23 spec: SpecId,
24}
25
26impl CustomPrecompileProvider {
27 pub fn new_with_spec(spec: SpecId) -> Self {
28 Self {
29 inner: EthPrecompiles::default(),
30 spec,
31 }
32 }
33}
34
35impl<CTX> PrecompileProvider<CTX> for CustomPrecompileProvider
36where
37 CTX: ContextTr<Cfg: Cfg<Spec = SpecId>>,
38{
39 type Output = InterpreterResult;
40
41 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
42 if spec == self.spec {
43 return false;
44 }
45 self.spec = spec;
46 self.inner = EthPrecompiles::default();
48 true
49 }
50
51 fn run(
52 &mut self,
53 context: &mut CTX,
54 inputs: &CallInputs,
55 ) -> Result<Option<Self::Output>, String> {
56 if inputs.bytecode_address == CUSTOM_PRECOMPILE_ADDRESS {
58 return Ok(Some(run_custom_precompile(context, inputs)?));
59 }
60
61 self.inner.run(context, inputs)
63 }
64
65 fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
66 let mut addresses = vec![CUSTOM_PRECOMPILE_ADDRESS];
68 addresses.extend(self.inner.warm_addresses());
69 Box::new(addresses.into_iter())
70 }
71
72 fn contains(&self, address: &Address) -> bool {
73 *address == CUSTOM_PRECOMPILE_ADDRESS || self.inner.contains(address)
74 }
75}
76
77fn run_custom_precompile<CTX: ContextTr>(
79 context: &mut CTX,
80 inputs: &CallInputs,
81) -> Result<InterpreterResult, String> {
82 let input_bytes = match &inputs.input {
83 revm::interpreter::CallInput::SharedBuffer(range) => {
84 if let Some(slice) = context.local().shared_memory_buffer_slice(range.clone()) {
85 slice.to_vec()
86 } else {
87 vec![]
88 }
89 }
90 revm::interpreter::CallInput::Bytes(bytes) => bytes.0.to_vec(),
91 };
92
93 let result = if input_bytes.is_empty() {
98 handle_read_storage(context, inputs.gas_limit)
100 } else if input_bytes.len() == 32 {
101 if inputs.is_static {
102 return Err("Cannot modify state in static context".to_string());
103 }
104 handle_write_storage(context, &input_bytes, inputs.gas_limit)
106 } else {
107 Err(PrecompileError::Other("Invalid input length".into()))
108 };
109
110 match result {
111 Ok(output) => {
112 let mut interpreter_result = InterpreterResult {
113 result: if output.reverted {
114 InstructionResult::Revert
115 } else {
116 InstructionResult::Return
117 },
118 gas: Gas::new(inputs.gas_limit),
119 output: output.bytes,
120 };
121 let underflow = interpreter_result.gas.record_cost(output.gas_used);
122 if !underflow {
123 interpreter_result.result = InstructionResult::PrecompileOOG;
124 }
125 Ok(interpreter_result)
126 }
127 Err(e) => {
128 if !e.is_oog() && context.journal().depth() == 1 {
130 context
131 .local_mut()
132 .set_precompile_error_context(e.to_string());
133 }
134 Ok(InterpreterResult {
135 result: if e.is_oog() {
136 InstructionResult::PrecompileOOG
137 } else {
138 InstructionResult::PrecompileError
139 },
140 gas: Gas::new(inputs.gas_limit),
141 output: Bytes::new(),
142 })
143 }
144 }
145}
146
147fn handle_read_storage<CTX: ContextTr>(context: &mut CTX, gas_limit: u64) -> PrecompileResult {
149 const BASE_GAS: u64 = 2_100;
151
152 if gas_limit < BASE_GAS {
153 return Err(PrecompileError::OutOfGas);
154 }
155
156 let value = context
158 .journal_mut()
159 .sload(CUSTOM_PRECOMPILE_ADDRESS, STORAGE_KEY)
160 .map_err(|e| PrecompileError::Other(format!("Storage read failed: {e:?}").into()))?
161 .data;
162
163 Ok(PrecompileOutput::new(
165 BASE_GAS,
166 value.to_be_bytes_vec().into(),
167 ))
168}
169
170fn handle_write_storage<CTX: ContextTr>(
172 context: &mut CTX,
173 input: &[u8],
174 gas_limit: u64,
175) -> PrecompileResult {
176 const BASE_GAS: u64 = 21_000;
178 const SSTORE_GAS: u64 = 20_000;
179
180 if gas_limit < BASE_GAS + SSTORE_GAS {
181 return Err(PrecompileError::OutOfGas);
182 }
183
184 let value = U256::from_be_slice(input);
186
187 context
189 .journal_mut()
190 .sstore(CUSTOM_PRECOMPILE_ADDRESS, STORAGE_KEY, value)
191 .map_err(|e| PrecompileError::Other(format!("Storage write failed: {e:?}").into()))?;
192
193 let caller = context.tx().caller();
195
196 context
199 .journal_mut()
200 .balance_incr(CUSTOM_PRECOMPILE_ADDRESS, U256::from(1))
201 .map_err(|e| PrecompileError::Other(format!("Balance increment failed: {e:?}").into()))?;
202
203 let transfer_result = context
205 .journal_mut()
206 .transfer(CUSTOM_PRECOMPILE_ADDRESS, caller, U256::from(1))
207 .map_err(|e| PrecompileError::Other(format!("Transfer failed: {e:?}").into()))?;
208
209 if let Some(error) = transfer_result {
210 return Err(PrecompileError::Other(
211 format!("Transfer error: {error:?}").into(),
212 ));
213 }
214
215 let topic0 = B256::from_slice(&[
218 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde,
219 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
220 0xde, 0xf0,
221 ]);
222 let mut topic1_bytes = [0u8; 32];
224 topic1_bytes[12..32].copy_from_slice(caller.as_slice());
225 let topic1 = B256::from(topic1_bytes);
226 let log_data = value.to_be_bytes_vec();
228
229 let log = Log::new(
230 CUSTOM_PRECOMPILE_ADDRESS,
231 vec![topic0, topic1],
232 log_data.into(),
233 )
234 .expect("Failed to create log");
235
236 context.journal_mut().log(log);
237
238 Ok(PrecompileOutput::new(BASE_GAS + SSTORE_GAS, Bytes::new()))
240}