hypersync_client/
decode.rs

1use crate::simple_types::Log;
2use alloy_dyn_abi::{DecodedEvent, DynSolEvent, Specifier};
3use anyhow::{Context, Result};
4use hypersync_format::LogArgument;
5use std::collections::HashMap;
6
7#[derive(Debug, Hash, Eq, PartialEq, Clone)]
8struct EventKey {
9    topic0: Vec<u8>,
10    num_topics: usize,
11}
12
13type DecoderMap = HashMap<EventKey, DynSolEvent>;
14
15/// Decode logs parsing topics and log data.
16#[derive(Debug)]
17pub struct Decoder {
18    // A map of topic0 => Event decoder
19    map: DecoderMap,
20}
21
22impl Decoder {
23    /// Initialize decoder from event signatures.
24    ///
25    ///     use hypersync_client::Decoder;
26    ///     let decoder = Decoder::from_signatures(&[
27    ///        "Transfer(address indexed from, address indexed to, uint amount)",
28    ///     ]).unwrap();
29    pub fn from_signatures<S: AsRef<str>>(signatures: &[S]) -> Result<Self> {
30        let map: DecoderMap = signatures
31            .iter()
32            .map(|sig| {
33                let event =
34                    alloy_json_abi::Event::parse(sig.as_ref()).context("parse event signature")?;
35                let topic0 = event.selector().to_vec();
36                let num_topics = event.num_topics();
37                let event_key = EventKey { topic0, num_topics };
38                let event = event.resolve().context("resolve event")?;
39                Ok((event_key, event))
40            })
41            .collect::<Result<DecoderMap>>()
42            .context("construct event decoder map")?;
43
44        Ok(Self { map })
45    }
46
47    /// Parse log and return decoded event.
48    ///
49    /// Returns Ok(None) if topic0 not found.
50    pub fn decode_log(&self, log: &Log) -> Result<Option<DecodedEvent>> {
51        let topic0 = log
52            .topics
53            .first()
54            .context("get topic0")?
55            .as_ref()
56            .context("get topic0")?;
57        let data = log.data.as_ref().context("get log.data")?;
58        self.decode(topic0.as_slice(), &log.topics, data)
59    }
60
61    /// Decode log.data into event using parsed topic0 and topics.
62    pub fn decode(
63        &self,
64        topic0: &[u8],
65        topics: &[Option<LogArgument>],
66        data: &[u8],
67    ) -> Result<Option<DecodedEvent>> {
68        let event_key = EventKey {
69            topic0: topic0.into(),
70            num_topics: topics.iter().fold(
71                0,
72                |accum, topic| {
73                    if topic.is_some() {
74                        accum + 1
75                    } else {
76                        accum
77                    }
78                },
79            ),
80        };
81
82        let event = match self.map.get(&event_key) {
83            Some(event) => event,
84            None => return Ok(None),
85        };
86
87        let topics = topics
88            .iter()
89            .take_while(|t| t.is_some())
90            .map(|t| t.as_ref().unwrap().into());
91
92        let decoded = event
93            .decode_log_parts(topics, data)
94            .context("decode log parts")?;
95
96        Ok(Some(decoded))
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::simple_types::Log;
104    use alloy_dyn_abi::{DynSolType, DynSolValue};
105    use alloy_primitives::Signed;
106    use hypersync_format::{Data, FixedSizeData, Hex};
107
108    #[test]
109    fn test_decode_event_with_bytes() {
110        let signature = alloy_json_abi::Event::parse(
111            "CommitmentStored(bytes32 indexed commitmentIndex, address bidder, address commiter, \
112             uint256 bid, uint64 blockNumber, bytes32 bidHash, uint64 decayStartTimeStamp, uint64 \
113             decayEndTimeStamp, string txnHash, string revertingTxHashes, bytes32 commitmentHash, \
114             bytes bidSignature, bytes commitmentSignature, uint64 dispatchTimestamp, bytes \
115             sharedSecretKey)",
116        )
117        .unwrap();
118        let decoder = signature.resolve().unwrap();
119        let decoder = DynSolType::Tuple(decoder.body().to_vec());
120        let data = "0x0000000000000000000000006875d4607c6cb4dfce1300545ab91a4005e33fd00000000000000000000000008280f34750068c67acf5366a5c7caea554c36fb5000000000000000000000000000000000000000000000000001b432e3907129c00000000000000000000000000000000000000000000000000000000001e3cc984c827ef3f2d18d8adca7259b89fde393749d3c2286bcd3e22d7dc8b46166d0b00000000000000000000000000000000000000000000000000000190dc7b0a0b00000000000000000000000000000000000000000000000000000190dc7b488b00000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220a7bf0040bf8800e406be82addb4f1bdc926ab7b7e4634d6cba572c6a981624ee000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000190dc7b2ffd000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000000403433666339623636366532613764306462366235313763306364313439386665366361646261373135376331353038303764616332326633376136326165656300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041dba6cf2d4520c006bbe215eba31cbb3c26f834e6f0eeae8d29c6b86613361930618bc734e88450af5d017aeca32b0ad007368fb6699802501f1260d6a92162611b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004179c7644cf559d284118d09b2f440f19ec6a9bc8138425553229493ac0c2fc12a796d10a77958f6c007ed50d73fa2cf526d0d50ad66c824965681b37a7b3b50b51c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000203a99c7cbb18c68ce8c92ff5ee2087c35b41cf6a0c8412eeef82f5e40c3fbbc90";
121        let data = Data::decode_hex(data).unwrap();
122        let res = decoder.abi_decode_sequence(&data).unwrap();
123        dbg!(res.as_tuple().unwrap().len());
124        for v in res.as_tuple().unwrap().iter() {
125            dbg!(v);
126            if let DynSolValue::Bytes(s) = v {
127                dbg!(Data::from(s.as_slice()).encode_hex());
128            }
129        }
130    }
131    #[test]
132    fn decodes_i24_event() {
133        //https://basescan.org/tx/0x76aeccc2815612c23344557c07fff57aada63625f1977096d5e9c88f63c257a7#eventlog#176
134        //This event was decoding tickLower incorrectly in a users indexer. Setup this test to
135        //validate decoder is working as expected
136        let decoder = Decoder::from_signatures(&["event Mint(address sender, address indexed \
137                                                  owner, int24 indexed tickLower, int24 indexed \
138                                                  tickUpper, uint128 amount, uint256 amount0, \
139                                                  uint256 amount1)"])
140        .unwrap();
141
142        let log = Log {
143         removed: None,
144         log_index: Some(176.into()),
145         transaction_index: Some(0.into()),
146         transaction_hash: Some(FixedSizeData::decode_hex("0x76aeccc2815612c23344557c07fff57aada63625f1977096d5e9c88f63c257a7").unwrap()),
147         block_hash: Some(FixedSizeData::decode_hex("0xd83042b6a32dc9b18d4c7c9819b914bc04470f77458e7276cb72f3d8fde5eb3d").unwrap()),
148         block_number: Some(13899663.into()),
149         address: Some(FixedSizeData::decode_hex("0x98c7A2338336d2d354663246F64676009c7bDa97").unwrap()),
150         data: Some(Data::decode_hex("0x000000000000000000000000827922686190790b37229fd06084350e74485b72000000000000000000000000000000000000000000000000000000000bebae76000000000000000000000000000000000000000000000000000000000000270f000000000000000000000000000000000000000000000000000000000000270f").unwrap()),
151         topics: vec![
152             "0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde", 
153             "0x000000000000000000000000827922686190790b37229fd06084350e74485b72", 
154             "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 
155             "0x0000000000000000000000000000000000000000000000000000000000000001"
156         ].into_iter().map(|s|FixedSizeData::decode_hex(s).ok()).collect(),
157        };
158
159        let decoded = decoder.decode_log(&log).unwrap().unwrap();
160
161        let owner = decoded.indexed[0].clone();
162        let tick_lower = decoded.indexed[1].clone();
163        let tick_upper = decoded.indexed[2].clone();
164        assert_eq!(
165            owner,
166            DynSolValue::Address(
167                "0x827922686190790b37229fd06084350E74485b72"
168                    .parse()
169                    .unwrap()
170            )
171        );
172        assert_eq!(tick_lower, DynSolValue::Int(Signed::MINUS_ONE, 24));
173        assert_eq!(tick_upper, DynSolValue::Int(Signed::ONE, 24));
174    }
175}