use alloy::{
dyn_abi::{DecodedEvent, DynSolValue, EventExt},
hex,
json_abi::{Event, EventParam},
rpc::types::{Filter, Log},
};
use anyhow::Result;
use clap::Parser;
use ethl::rpc::{config::ProviderSettings, stream::stream_logs};
use futures_util::{StreamExt, pin_mut};
use tracing::info;
use crate::commands::FilterArgs;
#[derive(Parser, Debug)]
pub struct CatArgs {
#[command(flatten)]
filter_args: FilterArgs,
}
pub async fn run_cat_command(providers: &ProviderSettings, args: CatArgs) -> Result<()> {
info!("Watching logs");
let filter: Option<Filter> = (&args.filter_args).try_into()?;
let events: Option<Vec<Event>> = (&args.filter_args).try_into()?;
let printers: Option<Vec<EventDisplay>> = events
.as_ref()
.map(|evs| evs.iter().cloned().map(EventDisplay::new).collect());
let stream = stream_logs(providers, filter.as_ref()).await;
pin_mut!(stream);
while let Some(result) = stream.next().await {
let (from_block, to_block, logs) = result?;
info!(
"observed {:05} logs for blocks {:012}-{:012}",
logs.len(),
from_block,
to_block
);
match &printers {
Some(evs) => {
for log in logs {
for ev in evs {
if let Some(formatted) = ev.maybe_format(&log) {
info!("{}", formatted);
}
}
}
}
None => {
for log in logs {
info!("{:?}", log);
}
}
}
}
Ok(())
}
struct EventDisplay {
event: Event,
indexed: Vec<EventParam>,
data: Vec<EventParam>,
}
impl EventDisplay {
fn new(event: Event) -> Self {
let indexed = event
.inputs
.iter()
.filter(|param| param.indexed)
.cloned()
.collect();
let data = event
.inputs
.iter()
.filter(|param| !param.indexed)
.cloned()
.collect();
Self {
event,
indexed,
data,
}
}
pub fn maybe_format(&self, log: &Log) -> Option<String> {
if log.topics().first() == Some(&self.event.selector()) {
let decoded = self.event.decode_log(log.data());
if decoded.is_err() {
return Some(format!("Failed to decode event: {}", self.event.name));
}
return Some(self.format(&decoded.unwrap()));
}
None
}
pub fn format(&self, decoded: &DecodedEvent) -> String {
let mut pairs = Vec::new();
self.indexed.iter().enumerate().for_each(|(i, param)| {
pairs.push(format!(
"{}={}",
param.name,
decoded
.indexed
.get(i)
.map(dyn_sol_value_to_string)
.unwrap_or("n/a".to_string())
));
});
self.data.iter().enumerate().for_each(|(i, param)| {
pairs.push(format!(
"{}={}",
param.name,
decoded
.body
.get(i)
.map(dyn_sol_value_to_string)
.unwrap_or("n/a".to_string())
));
});
format!("{}({})", self.event.name, pairs.join(", "),)
}
}
fn dyn_sol_value_to_string(value: &DynSolValue) -> String {
match value {
DynSolValue::Address(v) => v.to_string(),
DynSolValue::Bytes(v) => hex::encode_prefixed(v),
DynSolValue::Int(v, _) => v.to_string(),
DynSolValue::Uint(v, _) => v.to_string(),
DynSolValue::Bool(b) => b.to_string(),
DynSolValue::String(s) => s.clone(),
DynSolValue::FixedBytes(v, _) => hex::encode_prefixed(v),
DynSolValue::Array(v) | DynSolValue::FixedArray(v) => {
format!(
"[{}]",
v.iter()
.map(dyn_sol_value_to_string)
.collect::<Vec<_>>()
.join(", ")
)
}
DynSolValue::Tuple(v) => {
format!(
"({})",
v.iter()
.map(dyn_sol_value_to_string)
.collect::<Vec<_>>()
.join(", ")
)
}
DynSolValue::Function(_) => "fn".to_string(),
}
}