Skip to main content

ethl_cli/commands/
cat.rs

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    /// Filter arguments for scoping log queries
18    #[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}