example_custom_precompile_journal/
precompile_provider.rs1use revm::{
4 context::Cfg,
5 context_interface::{ContextTr, JournalTr, LocalContextTr, Transaction},
6 handler::{EthPrecompiles, PrecompileProvider},
7 interpreter::{Gas, InputsImpl, 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
14pub const CUSTOM_PRECOMPILE_ADDRESS: Address = address!("0000000000000000000000000000000000000100");
16
17const STORAGE_KEY: U256 = U256::ZERO;
19
20#[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 self.inner = EthPrecompiles::default();
49 true
50 }
51
52 fn run(
53 &mut self,
54 context: &mut CTX,
55 address: &Address,
56 inputs: &InputsImpl,
57 is_static: bool,
58 gas_limit: u64,
59 ) -> Result<Option<Self::Output>, String> {
60 if *address == CUSTOM_PRECOMPILE_ADDRESS {
62 return Ok(Some(run_custom_precompile(
63 context, inputs, is_static, gas_limit,
64 )?));
65 }
66
67 self.inner
69 .run(context, address, inputs, is_static, gas_limit)
70 }
71
72 fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
73 let mut addresses = vec![CUSTOM_PRECOMPILE_ADDRESS];
75 addresses.extend(self.inner.warm_addresses());
76 Box::new(addresses.into_iter())
77 }
78
79 fn contains(&self, address: &Address) -> bool {
80 *address == CUSTOM_PRECOMPILE_ADDRESS || self.inner.contains(address)
81 }
82}
83
84fn run_custom_precompile<CTX: ContextTr>(
86 context: &mut CTX,
87 inputs: &InputsImpl,
88 is_static: bool,
89 gas_limit: u64,
90) -> Result<InterpreterResult, String> {
91 let input_bytes = match &inputs.input {
92 revm::interpreter::CallInput::SharedBuffer(range) => {
93 if let Some(slice) = context.local().shared_memory_buffer_slice(range.clone()) {
94 slice.to_vec()
95 } else {
96 vec![]
97 }
98 }
99 revm::interpreter::CallInput::Bytes(bytes) => bytes.0.to_vec(),
100 };
101
102 let result = if input_bytes.is_empty() {
107 handle_read_storage(context, gas_limit)
109 } else if input_bytes.len() == 32 {
110 if is_static {
111 return Err("Cannot modify state in static context".to_string());
112 }
113 handle_write_storage(context, &input_bytes, gas_limit)
115 } else {
116 Err(PrecompileError::Other("Invalid input length".to_string()))
117 };
118
119 match result {
120 Ok(output) => {
121 let mut interpreter_result = InterpreterResult {
122 result: if output.reverted {
123 InstructionResult::Revert
124 } else {
125 InstructionResult::Return
126 },
127 gas: Gas::new(gas_limit),
128 output: output.bytes,
129 };
130 let underflow = interpreter_result.gas.record_cost(output.gas_used);
131 if !underflow {
132 interpreter_result.result = InstructionResult::PrecompileOOG;
133 }
134 Ok(interpreter_result)
135 }
136 Err(e) => Ok(InterpreterResult {
137 result: if e.is_oog() {
138 InstructionResult::PrecompileOOG
139 } else {
140 InstructionResult::PrecompileError
141 },
142 gas: Gas::new(gas_limit),
143 output: Bytes::new(),
144 }),
145 }
146}
147
148fn handle_read_storage<CTX: ContextTr>(context: &mut CTX, gas_limit: u64) -> PrecompileResult {
150 const BASE_GAS: u64 = 2_100;
152
153 if gas_limit < BASE_GAS {
154 return Err(PrecompileError::OutOfGas);
155 }
156
157 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 Ok(PrecompileOutput::new(
166 BASE_GAS,
167 value.to_be_bytes_vec().into(),
168 ))
169}
170
171fn handle_write_storage<CTX: ContextTr>(
173 context: &mut CTX,
174 input: &[u8],
175 gas_limit: u64,
176) -> PrecompileResult {
177 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 let value = U256::from_be_slice(input);
187
188 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 let caller = context.tx().caller();
196
197 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 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 Ok(PrecompileOutput::new(BASE_GAS + SSTORE_GAS, Bytes::new()))
216}