1use alloy_dyn_abi::DynSolValue;
2use alloy_dyn_abi::EventExt;
3use alloy_json_abi::JsonAbi;
4use alloy_primitives::B256;
5use ethers::core::abi::ethabi::ethereum_types::H256;
6use ethers::core::types::Log;
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value};
9use thiserror::Error;
10
11pub struct Parser<'a> {
12 abi: &'a JsonAbi,
13}
14
15#[derive(Debug, Serialize, Deserialize)]
17pub struct KeyedEvent {
18 name: String,
20
21 data: serde_json::Value,
23}
24
25#[derive(Error, Debug)]
26pub enum ParsingError {
27 #[error("event not found for given abi")]
30 UnknownEvent { selector: H256 },
31 #[error("could not decode, abi might mismatch data")]
34 DecodingError(#[from] alloy_dyn_abi::Error),
35}
36
37impl<'a> Parser<'a> {
38 pub fn new(abi: &'a JsonAbi) -> Self {
39 Self { abi }
40 }
41
42 pub fn parse(&self, log: &Log) -> Result<KeyedEvent, ParsingError> {
43 let selector = log.topics.first().unwrap();
44 let definition = self
45 .abi
46 .events()
47 .find(|e| e.selector().0 == selector.0)
48 .ok_or(ParsingError::UnknownEvent {
49 selector: *selector,
50 })?;
51
52 let topics = log.topics.iter().map(|t| B256::from_slice(&t.0));
53 let decoded = definition
54 .decode_log_parts(topics, &log.data, true)
55 .map_err(ParsingError::DecodingError)?;
56 let indexed = definition.inputs.iter().filter(|e| e.indexed);
57 let body = definition.inputs.iter().filter(|e| !e.indexed);
58
59 let indexed = indexed.zip(decoded.indexed);
60 let body = body.zip(decoded.body);
61
62 let values: Map<String, Value> = indexed
63 .chain(body)
64 .map(|(k, v)| {
65 (k.name.clone(), dyn_sol_to_json(v))
66 })
67 .collect();
68
69 Ok(KeyedEvent {
70 name: definition.name.clone(),
71 data: Value::Object(values),
72 })
73 }
74}
75
76pub fn dyn_sol_to_json(val: DynSolValue) -> Value {
77 use base64::prelude::*;
78
79 match val {
80 DynSolValue::Bool(b) => Value::Bool(b),
81 DynSolValue::Int(i, _) => Value::String(i.to_dec_string()),
82 DynSolValue::Uint(i, _) => Value::String(i.to_string()),
83 DynSolValue::FixedBytes(v, _) => Value::String(BASE64_STANDARD.encode(v.0)),
84 DynSolValue::Address(a) => Value::String(a.to_string()),
85 DynSolValue::Function(p) => Value::String(p.to_string()),
86 DynSolValue::Bytes(b) => Value::String(BASE64_STANDARD.encode(b)),
87 DynSolValue::String(s) => Value::String(s),
88 DynSolValue::Array(a) => Value::Array(a.into_iter().map(dyn_sol_to_json).collect()),
89 DynSolValue::FixedArray(a) => Value::Array(a.into_iter().map(dyn_sol_to_json).collect()),
90 DynSolValue::Tuple(a) => Value::Array(a.into_iter().map(dyn_sol_to_json).collect()),
91 DynSolValue::CustomStruct {
92 name: _,
93 prop_names,
94 tuple,
95 } => {
96 let map = prop_names
97 .into_iter()
98 .zip(tuple.into_iter().map(dyn_sol_to_json))
99 .collect();
100 Value::Object(map)
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use serde::Deserialize;
109
110 fn logs() -> Vec<Log> {
111 #[derive(Deserialize)]
112 struct ApiResponse {
113 pub result: Vec<Log>,
114 }
115 let file = include_str!("../testdata/logs.json");
116 let response: ApiResponse = serde_json::from_str(&file).unwrap();
117 response.result
118 }
119
120 fn erc20_abi() -> JsonAbi {
121 let json = include_str!("../testdata/erc20.json");
122 serde_json::from_str(&json).unwrap()
123 }
124
125 #[test]
126 fn erc20_parsing_works() {
127 let abi = erc20_abi();
128 let parser = Parser::new(&abi);
129 for log in logs() {
130 parser.parse(&log).unwrap();
131 }
132 }
133
134 mod ibc {
135 use super::*;
136
137 fn logs() -> Vec<Log> {
138 let file = include_str!("../testdata/ibc/logs.json");
139 let response: Vec<Log> = serde_json::from_str(&file).unwrap();
140 response
141 }
142
143 fn abi() -> JsonAbi {
144 let json = include_str!("../testdata/ibc/abi.json");
145 serde_json::from_str(&json).unwrap()
146 }
147
148 #[test]
149 fn ibc_parsing_works() {
150 let abi = abi();
151 let parser = Parser::new(&abi);
152 for log in logs() {
153 parser.parse(&log).unwrap();
154 }
155 }
156 }
157}