Skip to main content

foundry_evm/inspectors/
logs.rs

1use alloy_primitives::Log;
2use alloy_sol_types::{SolEvent, SolInterface, SolValue};
3use foundry_common::{ErrorExt, fmt::ConsoleFmt, sh_println};
4use foundry_evm_core::{
5    InspectorExt, abi::console, constants::HARDHAT_CONSOLE_ADDRESS, decode::decode_console_log,
6};
7use revm::{
8    Inspector,
9    context::ContextTr,
10    interpreter::{CallInputs, CallOutcome, Gas, InstructionResult, InterpreterResult},
11};
12
13/// An inspector that collects logs during execution.
14///
15/// The inspector collects logs from the `LOG` opcodes as well as Hardhat-style `console.sol` logs.
16#[derive(Clone, Debug)]
17pub enum LogCollector {
18    /// The collected logs. Includes both `LOG` opcodes and Hardhat-style `console.sol` logs.
19    Capture { logs: Vec<Log> },
20    /// Print logs directly to stdout.
21    LiveLogs,
22}
23
24impl LogCollector {
25    pub fn into_captured_logs(self) -> Option<Vec<Log>> {
26        match self {
27            Self::Capture { logs } => Some(logs),
28            Self::LiveLogs => None,
29        }
30    }
31
32    #[cold]
33    fn do_hardhat_log<CTX: ContextTr>(
34        &mut self,
35        context: &mut CTX,
36        inputs: &CallInputs,
37    ) -> Option<CallOutcome> {
38        if let Err(err) = self.hardhat_log(&inputs.input.bytes(context)) {
39            let result = InstructionResult::Revert;
40            let output = err.abi_encode_revert();
41            return Some(CallOutcome {
42                result: InterpreterResult { result, output, gas: Gas::new(inputs.gas_limit) },
43                memory_offset: inputs.return_memory_offset.clone(),
44                was_precompile_called: true,
45                precompile_call_logs: vec![],
46            });
47        }
48        None
49    }
50
51    fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> {
52        let decoded = console::hh::ConsoleCalls::abi_decode(data)?;
53        for line in decoded.fmt(Default::default()).lines() {
54            self.push_msg(line);
55        }
56        Ok(())
57    }
58
59    fn push_raw_log(&mut self, log: Log) {
60        match self {
61            Self::Capture { logs } => logs.push(log),
62            Self::LiveLogs => {
63                if let Some(msg) = decode_console_log(&log) {
64                    sh_println!("{msg}").expect("fail printing to stdout");
65                } else {
66                    // This case should not happen if the users call through forge-std.
67                    // We print the log data for the user nonetheless.
68                    sh_println!("console.log({:?}, {})", log.data.topics(), log.data.data)
69                        .expect("fail printing to stdout");
70                }
71            }
72        }
73    }
74
75    fn push_msg(&mut self, msg: &str) {
76        match self {
77            Self::Capture { logs } => logs.push(new_console_log(msg)),
78            Self::LiveLogs => sh_println!("{msg}").expect("fail printing to stdout"),
79        }
80    }
81}
82
83impl<CTX: ContextTr> Inspector<CTX> for LogCollector {
84    fn log(&mut self, _context: &mut CTX, log: Log) {
85        self.push_raw_log(log);
86    }
87
88    fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
89        if inputs.target_address == HARDHAT_CONSOLE_ADDRESS {
90            return self.do_hardhat_log(context, inputs);
91        }
92        None
93    }
94}
95
96impl InspectorExt for LogCollector {
97    fn console_log(&mut self, msg: &str) {
98        self.push_msg(msg);
99    }
100}
101
102/// Creates a `console.log(string)` event.
103fn new_console_log(msg: &str) -> Log {
104    Log::new_unchecked(
105        HARDHAT_CONSOLE_ADDRESS,
106        vec![console::ds::log::SIGNATURE_HASH],
107        msg.abi_encode().into(),
108    )
109}