foundry_evm/inspectors/
logs.rs1use 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#[derive(Clone, Debug)]
17pub enum LogCollector {
18 Capture { logs: Vec<Log> },
20 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 charged_new_account_state_gas: false,
47 });
48 }
49 None
50 }
51
52 fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> {
53 let decoded = console::hh::ConsoleCalls::abi_decode(data)?;
54 for line in decoded.fmt(Default::default()).lines() {
55 self.push_msg(line);
56 }
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 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: ContextTr> Inspector<CTX> for LogCollector {
85 fn log(&mut self, _context: &mut CTX, log: Log) {
86 self.push_raw_log(log);
87 }
88
89 fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
90 if inputs.target_address == HARDHAT_CONSOLE_ADDRESS {
91 return self.do_hardhat_log(context, inputs);
92 }
93 None
94 }
95}
96
97impl InspectorExt for LogCollector {
98 fn console_log(&mut self, msg: &str) {
99 self.push_msg(msg);
100 }
101}
102
103fn new_console_log(msg: &str) -> Log {
105 Log::new_unchecked(
106 HARDHAT_CONSOLE_ADDRESS,
107 vec![console::ds::log::SIGNATURE_HASH],
108 msg.abi_encode().into(),
109 )
110}