1use alloy::{
2 dyn_abi::{DecodedEvent, DynSolValue, EventExt},
3 hex,
4 json_abi::{Event, EventParam},
5 rpc::types::{Filter, Log},
6};
7use anyhow::Result;
8use clap::Parser;
9use ethl::rpc::{config::ProviderSettings, stream::stream_logs};
10use futures_util::{StreamExt, pin_mut};
11use tracing::info;
12
13use crate::commands::FilterArgs;
14
15#[derive(Parser, Debug)]
16pub struct CatArgs {
17 #[command(flatten)]
19 filter_args: FilterArgs,
20}
21
22pub async fn run_cat_command(providers: &ProviderSettings, args: CatArgs) -> Result<()> {
23 info!("Watching logs");
24
25 let filter: Option<Filter> = (&args.filter_args).try_into()?;
26 let events: Option<Vec<Event>> = (&args.filter_args).try_into()?;
27 let printers: Option<Vec<EventDisplay>> = events
28 .as_ref()
29 .map(|evs| evs.iter().cloned().map(EventDisplay::new).collect());
30
31 let stream = stream_logs(providers, filter.as_ref()).await;
32 pin_mut!(stream);
33 while let Some(result) = stream.next().await {
34 let (from_block, to_block, logs) = result?;
35 info!(
36 "observed {:05} logs for blocks {:012}-{:012}",
37 logs.len(),
38 from_block,
39 to_block
40 );
41 match &printers {
42 Some(evs) => {
43 for log in logs {
44 for ev in evs {
45 if let Some(formatted) = ev.maybe_format(&log) {
46 info!("{}", formatted);
47 }
48 }
49 }
50 }
51 None => {
52 for log in logs {
53 info!("{:?}", log);
54 }
55 }
56 }
57 }
58
59 Ok(())
60}
61
62struct EventDisplay {
63 event: Event,
64 indexed: Vec<EventParam>,
65 data: Vec<EventParam>,
66}
67
68impl EventDisplay {
69 fn new(event: Event) -> Self {
70 let indexed = event
71 .inputs
72 .iter()
73 .filter(|param| param.indexed)
74 .cloned()
75 .collect();
76 let data = event
77 .inputs
78 .iter()
79 .filter(|param| !param.indexed)
80 .cloned()
81 .collect();
82 Self {
83 event,
84 indexed,
85 data,
86 }
87 }
88
89 pub fn maybe_format(&self, log: &Log) -> Option<String> {
90 if log.topics().first() == Some(&self.event.selector()) {
91 let decoded = self.event.decode_log(log.data());
92 if decoded.is_err() {
93 return Some(format!("Failed to decode event: {}", self.event.name));
94 }
95 return Some(self.format(&decoded.unwrap()));
96 }
97 None
98 }
99
100 pub fn format(&self, decoded: &DecodedEvent) -> String {
101 let mut pairs = Vec::new();
102 self.indexed.iter().enumerate().for_each(|(i, param)| {
103 pairs.push(format!(
104 "{}={}",
105 param.name,
106 decoded
107 .indexed
108 .get(i)
109 .map(dyn_sol_value_to_string)
110 .unwrap_or("n/a".to_string())
111 ));
112 });
113 self.data.iter().enumerate().for_each(|(i, param)| {
114 pairs.push(format!(
115 "{}={}",
116 param.name,
117 decoded
118 .body
119 .get(i)
120 .map(dyn_sol_value_to_string)
121 .unwrap_or("n/a".to_string())
122 ));
123 });
124 format!("{}({})", self.event.name, pairs.join(", "),)
125 }
126}
127
128fn dyn_sol_value_to_string(value: &DynSolValue) -> String {
129 match value {
130 DynSolValue::Address(v) => v.to_string(),
131 DynSolValue::Bytes(v) => hex::encode_prefixed(v),
132 DynSolValue::Int(v, _) => v.to_string(),
133 DynSolValue::Uint(v, _) => v.to_string(),
134 DynSolValue::Bool(b) => b.to_string(),
135 DynSolValue::String(s) => s.clone(),
136 DynSolValue::FixedBytes(v, _) => hex::encode_prefixed(v),
137 DynSolValue::Array(v) | DynSolValue::FixedArray(v) => {
138 format!(
139 "[{}]",
140 v.iter()
141 .map(dyn_sol_value_to_string)
142 .collect::<Vec<_>>()
143 .join(", ")
144 )
145 }
146 DynSolValue::Tuple(v) => {
147 format!(
148 "({})",
149 v.iter()
150 .map(dyn_sol_value_to_string)
151 .collect::<Vec<_>>()
152 .join(", ")
153 )
154 }
155 DynSolValue::Function(_) => "fn".to_string(),
156 }
157}