foundry_evm/inspectors/
logs.rs

1use alloy_primitives::Log;
2use alloy_sol_types::{SolEvent, SolInterface, SolValue};
3use foundry_common::{ErrorExt, fmt::ConsoleFmt};
4use foundry_evm_core::{InspectorExt, abi::console, constants::HARDHAT_CONSOLE_ADDRESS};
5use revm::{
6    Inspector,
7    context::ContextTr,
8    interpreter::{
9        CallInputs, CallOutcome, Gas, InstructionResult, InterpreterResult,
10        interpreter::EthInterpreter,
11    },
12};
13
14/// An inspector that collects logs during execution.
15///
16/// The inspector collects logs from the `LOG` opcodes as well as Hardhat-style `console.sol` logs.
17#[derive(Clone, Debug, Default)]
18pub struct LogCollector {
19    /// The collected logs. Includes both `LOG` opcodes and Hardhat-style `console.sol` logs.
20    pub logs: Vec<Log>,
21}
22
23impl LogCollector {
24    #[cold]
25    fn do_hardhat_log<CTX>(&mut self, context: &mut CTX, inputs: &CallInputs) -> Option<CallOutcome>
26    where
27        CTX: ContextTr,
28    {
29        if let Err(err) = self.hardhat_log(&inputs.input.bytes(context)) {
30            let result = InstructionResult::Revert;
31            let output = err.abi_encode_revert();
32            return Some(CallOutcome {
33                result: InterpreterResult { result, output, gas: Gas::new(inputs.gas_limit) },
34                memory_offset: inputs.return_memory_offset.clone(),
35                was_precompile_called: true,
36                precompile_call_logs: vec![],
37            });
38        }
39        None
40    }
41
42    fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> {
43        let decoded = console::hh::ConsoleCalls::abi_decode(data)?;
44        self.logs.push(hh_to_ds(&decoded));
45        Ok(())
46    }
47}
48
49impl<CTX> Inspector<CTX, EthInterpreter> for LogCollector
50where
51    CTX: ContextTr,
52{
53    fn log(&mut self, _context: &mut CTX, log: Log) {
54        self.logs.push(log);
55    }
56
57    fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
58        if inputs.target_address == HARDHAT_CONSOLE_ADDRESS {
59            return self.do_hardhat_log(context, inputs);
60        }
61        None
62    }
63}
64
65impl InspectorExt for LogCollector {
66    fn console_log(&mut self, msg: &str) {
67        self.logs.push(new_console_log(msg));
68    }
69}
70
71/// Converts a Hardhat `console.log` call to a DSTest `log(string)` event.
72fn hh_to_ds(call: &console::hh::ConsoleCalls) -> Log {
73    // Convert the parameters of the call to their string representation using `ConsoleFmt`.
74    let msg = call.fmt(Default::default());
75    new_console_log(&msg)
76}
77
78/// Creates a `console.log(string)` event.
79fn new_console_log(msg: &str) -> Log {
80    Log::new_unchecked(
81        HARDHAT_CONSOLE_ADDRESS,
82        vec![console::ds::log::SIGNATURE_HASH],
83        msg.abi_encode().into(),
84    )
85}