example_custom_precompile_journal/
precompile_provider.rs1use revm::{
4 context::Cfg,
5 context_interface::{ContextTr, JournalTr, LocalContextTr, Transaction},
6 handler::{precompile_output_to_interpreter_result, EthPrecompiles, PrecompileProvider},
7 interpreter::{CallInputs, InterpreterResult},
8 precompile::{EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, PrecompileOutput},
9 primitives::{address, hardfork::SpecId, Address, AddressSet, Bytes, Log, B256, U256},
10};
11use std::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 addresses: AddressSet,
24 spec: SpecId,
25}
26
27impl CustomPrecompileProvider {
28 pub fn new_with_spec(spec: SpecId) -> Self {
29 let mut this = Self {
30 inner: EthPrecompiles::new(spec),
31 addresses: AddressSet::default(),
32 spec,
33 };
34 this.renew();
35 this
36 }
37
38 fn renew(&mut self) {
39 self.addresses.clone_from(self.inner.warm_addresses());
41 self.addresses.insert(CUSTOM_PRECOMPILE_ADDRESS);
42 }
43}
44
45impl<CTX> PrecompileProvider<CTX> for CustomPrecompileProvider
46where
47 CTX: ContextTr<Cfg: Cfg<Spec = SpecId>>,
48{
49 type Output = InterpreterResult;
50
51 fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
52 if spec == self.spec {
53 return false;
54 }
55 self.spec = spec;
56 self.inner = EthPrecompiles::new(spec);
57 self.renew();
58 true
59 }
60
61 fn run(
62 &mut self,
63 context: &mut CTX,
64 inputs: &CallInputs,
65 ) -> Result<Option<Self::Output>, String> {
66 if inputs.bytecode_address == CUSTOM_PRECOMPILE_ADDRESS {
68 return Ok(Some(run_custom_precompile(context, inputs)?));
69 }
70
71 self.inner.run(context, inputs)
73 }
74
75 fn warm_addresses(&self) -> &AddressSet {
76 &self.addresses
77 }
78}
79
80fn run_custom_precompile<CTX: ContextTr>(
82 context: &mut CTX,
83 inputs: &CallInputs,
84) -> Result<InterpreterResult, String> {
85 let input_bytes = inputs.input.bytes(context);
86
87 let result = if input_bytes.is_empty() {
92 handle_read_storage(context, inputs.gas_limit)
94 } else if input_bytes.len() == 32 {
95 if inputs.is_static {
96 return Err("Cannot modify state in static context".to_string());
97 }
98 handle_write_storage(context, &input_bytes, inputs.gas_limit)
100 } else {
101 Err(PrecompileHalt::Other("Invalid input length".into()))
102 };
103
104 let output = PrecompileOutput::from_eth_result(result, inputs.reservoir);
107
108 if let Some(halt_reason) = output.halt_reason() {
110 if !halt_reason.is_oog() && context.journal().depth() == 1 {
111 context
112 .local_mut()
113 .set_precompile_error_context(halt_reason.to_string());
114 }
115 }
116
117 Ok(precompile_output_to_interpreter_result(
118 output,
119 inputs.gas_limit,
120 ))
121}
122
123fn handle_read_storage<CTX: ContextTr>(context: &mut CTX, gas_limit: u64) -> EthPrecompileResult {
125 const BASE_GAS: u64 = 2_100;
127
128 if gas_limit < BASE_GAS {
129 return Err(PrecompileHalt::OutOfGas);
130 }
131
132 let value = context
134 .journal_mut()
135 .sload(CUSTOM_PRECOMPILE_ADDRESS, STORAGE_KEY)
136 .map_err(|e| PrecompileHalt::Other(format!("Storage read failed: {e:?}").into()))?
137 .data;
138
139 Ok(EthPrecompileOutput::new(
141 BASE_GAS,
142 value.to_be_bytes_vec().into(),
143 ))
144}
145
146fn handle_write_storage<CTX: ContextTr>(
148 context: &mut CTX,
149 input: &[u8],
150 gas_limit: u64,
151) -> EthPrecompileResult {
152 const BASE_GAS: u64 = 21_000;
154 const SSTORE_GAS: u64 = 20_000;
155
156 if gas_limit < BASE_GAS + SSTORE_GAS {
157 return Err(PrecompileHalt::OutOfGas);
158 }
159
160 let value = U256::from_be_slice(input);
162
163 context
165 .journal_mut()
166 .sstore(CUSTOM_PRECOMPILE_ADDRESS, STORAGE_KEY, value)
167 .map_err(|e| PrecompileHalt::Other(format!("Storage write failed: {e:?}").into()))?;
168
169 let caller = context.tx().caller();
171
172 context
175 .journal_mut()
176 .balance_incr(CUSTOM_PRECOMPILE_ADDRESS, U256::from(1))
177 .map_err(|e| PrecompileHalt::Other(format!("Balance increment failed: {e:?}").into()))?;
178
179 let transfer_result = context
181 .journal_mut()
182 .transfer(CUSTOM_PRECOMPILE_ADDRESS, caller, U256::from(1))
183 .map_err(|e| PrecompileHalt::Other(format!("Transfer failed: {e:?}").into()))?;
184
185 if let Some(error) = transfer_result {
186 return Err(PrecompileHalt::Other(
187 format!("Transfer error: {error:?}").into(),
188 ));
189 }
190
191 let topic0 = B256::from_slice(&[
194 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde,
195 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc,
196 0xde, 0xf0,
197 ]);
198 let mut topic1_bytes = [0u8; 32];
200 topic1_bytes[12..32].copy_from_slice(caller.as_slice());
201 let topic1 = B256::from(topic1_bytes);
202 let log_data = value.to_be_bytes_vec();
204
205 let log = Log::new(
206 CUSTOM_PRECOMPILE_ADDRESS,
207 vec![topic0, topic1],
208 log_data.into(),
209 )
210 .expect("Failed to create log");
211
212 context.journal_mut().log(log);
213
214 Ok(EthPrecompileOutput::new(
216 BASE_GAS + SSTORE_GAS,
217 Bytes::new(),
218 ))
219}