Skip to main content

example_custom_precompile_journal/
custom_evm.rs

1//! Custom EVM implementation with journal-accessing precompiles.
2
3use 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/// Custom EVM variant with journal-accessing precompiles.
17///
18/// This EVM extends the standard behavior by using a custom precompile provider
19/// that includes journal access functionality. It follows the same pattern as MyEvm
20/// but uses CustomPrecompileProvider instead of EthPrecompiles.
21#[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    /// Creates a new instance of CustomEvm with the provided context and inspector.
37    ///
38    /// # Arguments
39    ///
40    /// * `ctx` - The execution context that manages state, environment, and journaling
41    /// * `inspector` - The inspector for debugging and tracing execution
42    ///
43    /// # Returns
44    ///
45    /// A new CustomEvm instance configured with:
46    /// - The provided context and inspector
47    /// - Mainnet instruction set
48    /// - Custom precompiles with journal access
49    /// - A fresh frame stack for execution
50    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    /// Custom inspector that captures logs
173    #[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            // Capture logs as they're created
196            self.captured_logs.push(log);
197        }
198    }
199
200    #[test]
201    fn test_custom_precompile_creates_log() {
202        // Setup initial accounts
203        let user_address = address!("0000000000000000000000000000000000000001");
204        let mut db = InMemoryDB::default();
205
206        // Give the user some ETH for gas
207        let user_balance = U256::from(10).pow(U256::from(18)); // 1 ETH
208        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        // Give the precompile some initial balance for transfers
220        db.insert_account_info(
221            CUSTOM_PRECOMPILE_ADDRESS,
222            AccountInfo {
223                balance: U256::from(1000), // 1000 wei
224                nonce: 0,
225                code_hash: revm::primitives::KECCAK_EMPTY,
226                code: None,
227                account_id: None,
228            },
229        );
230
231        // Create custom EVM with log capturing inspector
232        let context = Context::mainnet().with_db(db);
233        let inspector = LogCapturingInspector::new();
234        let mut evm = CustomEvm::new(context, inspector);
235
236        // Write value 42 to storage (this should create a log)
237        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        // Verify transaction succeeded
254        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                // Transaction succeeded, now check logs from execution result
262                // Note: Inspector might not be called for precompile logs,
263                // so we check the execution result logs instead
264
265                // Also check inspector logs (though they may be empty)
266                let inspector_logs = evm.0.inspector.logs();
267
268                // Combine both sources - use execution result logs if inspector logs are empty
269                let all_logs = if inspector_logs.is_empty() {
270                    &logs
271                } else {
272                    inspector_logs
273                };
274
275                // Verify that at least one log was captured
276                assert!(
277                    !all_logs.is_empty(),
278                    "Should have captured at least one log (either from inspector or execution result)"
279                );
280
281                // Find the log from our custom precompile
282                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                // Verify log structure
295                assert_eq!(log.address, CUSTOM_PRECOMPILE_ADDRESS);
296                assert_eq!(log.data.topics().len(), 2, "Should have 2 topics");
297
298                // Topic 1 should be the caller address (left-padded to 32 bytes)
299                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                // Data should contain the value that was written (42)
309                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        // Setup initial accounts
339        let user_address = address!("0000000000000000000000000000000000000001");
340        let mut db = InMemoryDB::default();
341
342        // Give the user some ETH for gas
343        let user_balance = U256::from(10).pow(U256::from(18)); // 1 ETH
344        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        // Create custom EVM with log capturing inspector
356        let context = Context::mainnet().with_db(db);
357        let inspector = LogCapturingInspector::new();
358        let mut evm = CustomEvm::new(context, inspector);
359
360        // Read from storage (empty input - should not create a log)
361        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()) // Empty data for read operation
366                .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        // Verify transaction succeeded
377        assert!(
378            result.is_ok(),
379            "Transaction should succeed, got: {result:?}"
380        );
381
382        match result.unwrap() {
383            revm::context::result::ExecutionResult::Success { .. } => {
384                // Transaction succeeded, check that no logs were created
385                let logs = evm.0.inspector.logs();
386
387                // Verify that no logs from the precompile were captured
388                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}