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