foundry_evm/inspectors/
logs.rs

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