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::new(spec),
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::new(spec);
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 = inputs.input.bytes(context);
83
84 let result = if input_bytes.is_empty() {
89 handle_read_storage(context, inputs.gas_limit)
91 } else if input_bytes.len() == 32 {
92 if inputs.is_static {
93 return Err("Cannot modify state in static context".to_string());
94 }
95 handle_write_storage(context, &input_bytes, inputs.gas_limit)
97 } else {
98 Err(PrecompileError::Other("Invalid input length".into()))
99 };
100
101 match result {
102 Ok(output) => {
103 let mut interpreter_result = InterpreterResult {
104 result: if output.reverted {
105 InstructionResult::Revert
106 } else {
107 InstructionResult::Return
108 },
109 gas: Gas::new(inputs.gas_limit),
110 output: output.bytes,
111 };
112 let underflow = interpreter_result.gas.record_cost(output.gas_used);
113 if !underflow {
114 interpreter_result.result = InstructionResult::PrecompileOOG;
115 }
116 Ok(interpreter_result)
117 }
118 Err(e) => {
119 if !e.is_oog() && context.journal().depth() == 1 {
121 context
122 .local_mut()
123 .set_precompile_error_context(e.to_string());
124 }
125 Ok(InterpreterResult {
126 result: if e.is_oog() {
127 InstructionResult::PrecompileOOG
128 } else {
129 InstructionResult::PrecompileError
130 },
131 gas: Gas::new(inputs.gas_limit),
132 output: Bytes::new(),
133 })
134 }
135 }
136}
137
138fn handle_read_storage<CTX: ContextTr>(context: &mut CTX, gas_limit: u64) -> PrecompileResult {
140 const BASE_GAS: u64 = 2_100;
142
143 if gas_limit < BASE_GAS {
144 return Err(PrecompileError::OutOfGas);
145 }
146
147 let value = context
149 .journal_mut()
150 .sload(CUSTOM_PRECOMPILE_ADDRESS, STORAGE_KEY)
151 .map_err(|e| PrecompileError::Other(format!("Storage read failed: {e:?}").into()))?
152 .data;
153
154 Ok(PrecompileOutput::new(
156 BASE_GAS,
157 value.to_be_bytes_vec().into(),
158 ))
159}
160
161fn handle_write_storage<CTX: ContextTr>(
163 context: &mut CTX,
164 input: &[u8],
165 gas_limit: u64,
166) -> PrecompileResult {
167 const BASE_GAS: u64 = 21_000;
169 const SSTORE_GAS: u64 = 20_000;
170
171 if gas_limit < BASE_GAS + SSTORE_GAS {
172 return Err(PrecompileError::OutOfGas);
173 }
174
175 let value = U256::from_be_slice(input);
177
178 context
180 .journal_mut()
181 .sstore(CUSTOM_PRECOMPILE_ADDRESS, STORAGE_KEY, value)
182 .map_err(|e| PrecompileError::Other(format!("Storage write failed: {e:?}").into()))?;
183
184 let caller = context.tx().caller();
186
187 context
190 .journal_mut()
191 .balance_incr(CUSTOM_PRECOMPILE_ADDRESS, U256::from(1))
192 .map_err(|e| PrecompileError::Other(format!("Balance increment failed: {e:?}").into()))?;
193
194 let transfer_result = context
196 .journal_mut()
197 .transfer(CUSTOM_PRECOMPILE_ADDRESS, caller, U256::from(1))
198 .map_err(|e| PrecompileError::Other(format!("Transfer failed: {e:?}").into()))?;
199
200 if let Some(error) = transfer_result {
201 return Err(PrecompileError::Other(
202 format!("Transfer error: {error:?}").into(),
203 ));
204 }
205
206 let topic0 = B256::from_slice(&[
209 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde,
210 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
211 0xde, 0xf0,
212 ]);
213 let mut topic1_bytes = [0u8; 32];
215 topic1_bytes[12..32].copy_from_slice(caller.as_slice());
216 let topic1 = B256::from(topic1_bytes);
217 let log_data = value.to_be_bytes_vec();
219
220 let log = Log::new(
221 CUSTOM_PRECOMPILE_ADDRESS,
222 vec![topic0, topic1],
223 log_data.into(),
224 )
225 .expect("Failed to create log");
226
227 context.journal_mut().log(log);
228
229 Ok(PrecompileOutput::new(BASE_GAS + SSTORE_GAS, Bytes::new()))
231}