alloy_dyn_parser/
lib.rs

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/// A decoded event which is self-describing through String keys.
16#[derive(Debug, Serialize, Deserialize)]
17pub struct KeyedEvent {
18    /// The name of the event.
19    name: String,
20
21    /// The data of the emitted event, both indexed and body.
22    data: serde_json::Value,
23}
24
25#[derive(Error, Debug)]
26pub enum ParsingError {
27    /// The name of the decoded event is not found in the ABI. This might
28    /// indicate an ABI mismatch.
29    #[error("event not found for given abi")]
30    UnknownEvent { selector: H256 },
31    /// The name of the event IS found in the ABI, yet decoding still failed.
32    /// This might indicate an out-of-date ABI.
33    #[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}