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