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