example_custom_precompile_journal/
custom_evm.rs1use crate::precompile_provider::CustomPrecompileProvider;
4use revm::{
5 context::{ContextError, ContextSetters, ContextTr, Evm, FrameStack},
6 handler::{
7 evm::FrameTr, instructions::EthInstructions, EthFrame, EvmTr, FrameInitOrResult,
8 ItemOrResult,
9 },
10 inspector::{InspectorEvmTr, JournalExt},
11 interpreter::interpreter::EthInterpreter,
12 primitives::hardfork::SpecId,
13 Database, Inspector,
14};
15
16#[derive(Debug)]
22pub struct CustomEvm<CTX, INSP>(
23 pub Evm<
24 CTX,
25 INSP,
26 EthInstructions<EthInterpreter, CTX>,
27 CustomPrecompileProvider,
28 EthFrame<EthInterpreter>,
29 >,
30);
31
32impl<CTX, INSP> CustomEvm<CTX, INSP>
33where
34 CTX: ContextTr<Cfg: revm::context::Cfg<Spec = SpecId>>,
35{
36 pub fn new(ctx: CTX, inspector: INSP) -> Self {
51 Self(Evm {
52 ctx,
53 inspector,
54 instruction: EthInstructions::new_mainnet(),
55 precompiles: CustomPrecompileProvider::new_with_spec(SpecId::CANCUN),
56 frame_stack: FrameStack::new(),
57 })
58 }
59}
60
61impl<CTX, INSP> EvmTr for CustomEvm<CTX, INSP>
62where
63 CTX: ContextTr<Cfg: revm::context::Cfg<Spec = SpecId>>,
64{
65 type Context = CTX;
66 type Instructions = EthInstructions<EthInterpreter, CTX>;
67 type Precompiles = CustomPrecompileProvider;
68 type Frame = EthFrame<EthInterpreter>;
69
70 fn all(
71 &self,
72 ) -> (
73 &Self::Context,
74 &Self::Instructions,
75 &Self::Precompiles,
76 &FrameStack<Self::Frame>,
77 ) {
78 self.0.all()
79 }
80
81 fn all_mut(
82 &mut self,
83 ) -> (
84 &mut Self::Context,
85 &mut Self::Instructions,
86 &mut Self::Precompiles,
87 &mut FrameStack<Self::Frame>,
88 ) {
89 self.0.all_mut()
90 }
91
92 fn frame_init(
93 &mut self,
94 frame_input: <Self::Frame as FrameTr>::FrameInit,
95 ) -> Result<
96 ItemOrResult<&mut Self::Frame, <Self::Frame as FrameTr>::FrameResult>,
97 ContextError<<<Self::Context as ContextTr>::Db as Database>::Error>,
98 > {
99 self.0.frame_init(frame_input)
100 }
101
102 fn frame_run(
103 &mut self,
104 ) -> Result<
105 FrameInitOrResult<Self::Frame>,
106 ContextError<<<Self::Context as ContextTr>::Db as Database>::Error>,
107 > {
108 self.0.frame_run()
109 }
110
111 fn frame_return_result(
112 &mut self,
113 frame_result: <Self::Frame as FrameTr>::FrameResult,
114 ) -> Result<
115 Option<<Self::Frame as FrameTr>::FrameResult>,
116 ContextError<<<Self::Context as ContextTr>::Db as Database>::Error>,
117 > {
118 self.0.frame_return_result(frame_result)
119 }
120}
121
122impl<CTX, INSP> InspectorEvmTr for CustomEvm<CTX, INSP>
123where
124 CTX: ContextSetters<Cfg: revm::context::Cfg<Spec = SpecId>, Journal: JournalExt>,
125 INSP: Inspector<CTX, EthInterpreter>,
126{
127 type Inspector = INSP;
128
129 fn all_inspector(
130 &self,
131 ) -> (
132 &Self::Context,
133 &Self::Instructions,
134 &Self::Precompiles,
135 &FrameStack<Self::Frame>,
136 &Self::Inspector,
137 ) {
138 self.0.all_inspector()
139 }
140
141 fn all_mut_inspector(
142 &mut self,
143 ) -> (
144 &mut Self::Context,
145 &mut Self::Instructions,
146 &mut Self::Precompiles,
147 &mut FrameStack<Self::Frame>,
148 &mut Self::Inspector,
149 ) {
150 self.0.all_mut_inspector()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use crate::{custom_evm::CustomEvm, precompile_provider::CUSTOM_PRECOMPILE_ADDRESS};
157 use revm::{
158 context::{Context, ContextSetters, TxEnv},
159 context_interface::{result::EVMError, ContextTr},
160 database::InMemoryDB,
161 handler::{Handler, MainnetHandler},
162 inspector::{Inspector, JournalExt},
163 interpreter::interpreter::EthInterpreter,
164 primitives::{address, Log, TxKind, U256},
165 state::AccountInfo,
166 MainContext,
167 };
168 use std::vec::Vec;
169
170 #[derive(Debug, Default)]
172 struct LogCapturingInspector {
173 captured_logs: Vec<Log>,
174 }
175
176 impl LogCapturingInspector {
177 fn new() -> Self {
178 Self {
179 captured_logs: Vec::new(),
180 }
181 }
182
183 fn logs(&self) -> &[Log] {
184 &self.captured_logs
185 }
186 }
187
188 impl<CTX> Inspector<CTX, EthInterpreter> for LogCapturingInspector
189 where
190 CTX: ContextTr + ContextSetters<Journal: JournalExt>,
191 {
192 fn log(&mut self, _context: &mut CTX, log: Log) {
193 self.captured_logs.push(log);
195 }
196 }
197
198 #[test]
199 fn test_custom_precompile_creates_log() {
200 let user_address = address!("0000000000000000000000000000000000000001");
202 let mut db = InMemoryDB::default();
203
204 let user_balance = U256::from(10).pow(U256::from(18)); db.insert_account_info(
207 user_address,
208 AccountInfo {
209 balance: user_balance,
210 nonce: 0,
211 code_hash: revm::primitives::KECCAK_EMPTY,
212 code: None,
213 account_id: None,
214 },
215 );
216
217 db.insert_account_info(
219 CUSTOM_PRECOMPILE_ADDRESS,
220 AccountInfo {
221 balance: U256::from(1000), nonce: 0,
223 code_hash: revm::primitives::KECCAK_EMPTY,
224 code: None,
225 account_id: None,
226 },
227 );
228
229 let context = Context::mainnet().with_db(db);
231 let inspector = LogCapturingInspector::new();
232 let mut evm = CustomEvm::new(context, inspector);
233
234 let storage_value = U256::from(42);
236 evm.0.ctx.set_tx(
237 TxEnv::builder()
238 .caller(user_address)
239 .kind(TxKind::Call(CUSTOM_PRECOMPILE_ADDRESS))
240 .data(storage_value.to_be_bytes_vec().into())
241 .gas_limit(100_000)
242 .build()
243 .unwrap(),
244 );
245
246 let result: Result<
247 _,
248 EVMError<core::convert::Infallible, revm::context::result::InvalidTransaction>,
249 > = MainnetHandler::default().run(&mut evm);
250
251 assert!(
253 result.is_ok(),
254 "Transaction should succeed, got: {result:?}"
255 );
256
257 match result.unwrap() {
258 revm::context::result::ExecutionResult::Success { logs, .. } => {
259 let inspector_logs = evm.0.inspector.logs();
265
266 let all_logs = if inspector_logs.is_empty() {
268 &logs
269 } else {
270 inspector_logs
271 };
272
273 assert!(
275 !all_logs.is_empty(),
276 "Should have captured at least one log (either from inspector or execution result)"
277 );
278
279 let precompile_log = all_logs
281 .iter()
282 .find(|log| log.address == CUSTOM_PRECOMPILE_ADDRESS);
283
284 assert!(
285 precompile_log.is_some(),
286 "Should have a log from the custom precompile. Found {} total logs",
287 all_logs.len()
288 );
289
290 let log = precompile_log.unwrap();
291
292 assert_eq!(log.address, CUSTOM_PRECOMPILE_ADDRESS);
294 assert_eq!(log.data.topics().len(), 2, "Should have 2 topics");
295
296 let topic1 = log.data.topics()[1];
298 let mut expected_caller_bytes = [0u8; 32];
299 expected_caller_bytes[12..32].copy_from_slice(user_address.as_slice());
300 let expected_caller_topic = revm::primitives::B256::from(expected_caller_bytes);
301 assert_eq!(
302 topic1, expected_caller_topic,
303 "Second topic should be caller address"
304 );
305
306 let log_data_bytes = &log.data.data;
308 let logged_value = U256::from_be_slice(log_data_bytes);
309 assert_eq!(
310 logged_value,
311 U256::from(42),
312 "Log data should contain the written value (42)"
313 );
314
315 println!("✅ Test passed! Log was successfully created and captured");
316 println!(" Log address: {}", log.address);
317 println!(" Number of topics: {}", log.data.topics().len());
318 println!(" Logged value: {logged_value}");
319 println!(
320 " Inspector logs: {}, Execution result logs: {}",
321 inspector_logs.len(),
322 logs.len()
323 );
324 }
325 revm::context::result::ExecutionResult::Revert { .. } => {
326 panic!("Transaction reverted unexpectedly");
327 }
328 revm::context::result::ExecutionResult::Halt { reason, .. } => {
329 panic!("Transaction halted unexpectedly: {reason:?}");
330 }
331 }
332 }
333
334 #[test]
335 fn test_read_operation_does_not_create_log() {
336 let user_address = address!("0000000000000000000000000000000000000001");
338 let mut db = InMemoryDB::default();
339
340 let user_balance = U256::from(10).pow(U256::from(18)); db.insert_account_info(
343 user_address,
344 AccountInfo {
345 balance: user_balance,
346 nonce: 0,
347 code_hash: revm::primitives::KECCAK_EMPTY,
348 code: None,
349 account_id: None,
350 },
351 );
352
353 let context = Context::mainnet().with_db(db);
355 let inspector = LogCapturingInspector::new();
356 let mut evm = CustomEvm::new(context, inspector);
357
358 evm.0.ctx.set_tx(
360 TxEnv::builder()
361 .caller(user_address)
362 .kind(TxKind::Call(CUSTOM_PRECOMPILE_ADDRESS))
363 .data(revm::primitives::Bytes::new()) .gas_limit(100_000)
365 .build()
366 .unwrap(),
367 );
368
369 let result: Result<
370 _,
371 EVMError<core::convert::Infallible, revm::context::result::InvalidTransaction>,
372 > = MainnetHandler::default().run(&mut evm);
373
374 assert!(
376 result.is_ok(),
377 "Transaction should succeed, got: {result:?}"
378 );
379
380 match result.unwrap() {
381 revm::context::result::ExecutionResult::Success { .. } => {
382 let logs = evm.0.inspector.logs();
384
385 let precompile_log = logs
387 .iter()
388 .find(|log| log.address == CUSTOM_PRECOMPILE_ADDRESS);
389
390 assert!(
391 precompile_log.is_none(),
392 "Read operation should not create any logs"
393 );
394
395 println!("✅ Test passed! Read operation correctly did not create any logs");
396 }
397 revm::context::result::ExecutionResult::Revert { .. } => {
398 panic!("Transaction reverted unexpectedly");
399 }
400 revm::context::result::ExecutionResult::Halt { reason, .. } => {
401 panic!("Transaction halted unexpectedly: {reason:?}");
402 }
403 }
404 }
405}