1use std::collections::HashMap as StdHashMap;
2
3use alloy_dyn_abi::{DecodedEvent, DynSolEvent, DynSolType, Specifier};
4use alloy_json_abi::JsonAbi;
5use anyhow::{anyhow, Context, Result};
6use polars_arrow::array::BinaryViewArray;
7use skar_format::Hex;
8use xxhash_rust::xxh3::Xxh3Builder;
9
10use crate::ArrowBatch;
11
12pub type FastMap<K, V> = StdHashMap<K, V, Xxh3Builder>;
13
14pub struct Decoder {
15 contracts: FastMap<Vec<u8>, FastMap<Vec<u8>, DynSolEvent>>,
16}
17
18impl Decoder {
19 pub fn new(json_abis: &[(skar_format::Address, JsonAbi)]) -> Result<Self> {
20 let mut contracts = FastMap::default();
21
22 for (addr, abi) in json_abis.iter() {
23 let mut event_map = FastMap::default();
24
25 for (_, events) in abi.events.iter() {
26 for event in events {
27 event_map.insert(
28 event.selector().to_vec(),
29 event.resolve().context("resolve event")?,
30 );
31 }
32 }
33
34 if contracts.insert(addr.to_vec(), event_map).is_some() {
35 return Err(anyhow!("duplicate contract address {}", addr.encode_hex()));
36 }
37 }
38
39 Ok(Self { contracts })
40 }
41
42 #[inline]
43 pub fn decode(
44 &self,
45 address: &[u8],
46 topic0: &[u8],
47 topics: &[Option<&[u8]>],
48 data: &[u8],
49 ) -> Result<Option<DecodedEvent>> {
50 let contract = match self.contracts.get(address) {
51 Some(contract) => contract,
52 None => return Ok(None),
53 };
54 let event = match contract.get(topic0) {
55 Some(event) => event,
56 None => return Ok(None),
57 };
58
59 let topics = topics
60 .iter()
61 .filter_map(|&t| t.map(|t| t.try_into().unwrap()));
62
63 let decoded = if data.is_empty() && event.body() == [DynSolType::Uint(256)] {
67 event
68 .decode_log_parts(topics, [0; 32].as_slice(), false)
69 .context("decode log parts")?
70 } else {
71 event
72 .decode_log_parts(topics, data, false)
73 .context("decode log parts")?
74 };
75
76 Ok(Some(decoded))
77 }
78
79 pub fn decode_logs(&self, logs: &[ArrowBatch]) -> Result<Option<Vec<Option<DecodedEvent>>>> {
80 let mut events = Vec::new();
81
82 for batch in logs {
83 let address = match batch.column::<BinaryViewArray>("address") {
84 Ok(address) => address,
85 Err(_) => return Ok(None),
86 };
87 let data = match batch.column::<BinaryViewArray>("data") {
88 Ok(data) => data,
89 Err(_) => return Ok(None),
90 };
91 let topic0 = match batch.column::<BinaryViewArray>("topic0") {
92 Ok(topic0) => topic0,
93 Err(_) => return Ok(None),
94 };
95 let topic1 = match batch.column::<BinaryViewArray>("topic1") {
96 Ok(topic1) => topic1,
97 Err(_) => return Ok(None),
98 };
99 let topic2 = match batch.column::<BinaryViewArray>("topic2") {
100 Ok(topic2) => topic2,
101 Err(_) => return Ok(None),
102 };
103 let topic3 = match batch.column::<BinaryViewArray>("topic3") {
104 Ok(topic3) => topic3,
105 Err(_) => return Ok(None),
106 };
107
108 for (((((address, data), topic0), topic1), topic2), topic3) in address
109 .values_iter()
110 .zip(data.values_iter())
111 .zip(topic0.iter())
112 .zip(topic1.iter())
113 .zip(topic2.iter())
114 .zip(topic3.iter())
115 {
116 let topic0 = match topic0 {
117 Some(topic0) => topic0,
118 None => {
119 events.push(None);
120 continue;
121 }
122 };
123
124 let decoded = self
125 .decode(
126 address,
127 topic0,
128 &[Some(topic0), topic1, topic2, topic3],
129 data,
130 )
131 .context("decode event")?;
132
133 events.push(decoded);
134 }
135 }
136
137 Ok(Some(events))
138 }
139}