1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use std::collections::HashMap as StdHashMap;

use alloy_dyn_abi::{DecodedEvent, DynSolEvent, DynSolType, ResolveSolEvent};
use alloy_json_abi::JsonAbi;
use anyhow::{anyhow, Context, Result};
use arrow2::array::BinaryArray;
use skar_format::Hex;
use xxhash_rust::xxh3::Xxh3Builder;

use crate::ArrowBatch;

pub type FastMap<K, V> = StdHashMap<K, V, Xxh3Builder>;

pub struct Decoder {
    contracts: FastMap<Vec<u8>, FastMap<Vec<u8>, DynSolEvent>>,
}

impl Decoder {
    pub fn new(json_abis: &[(skar_format::Address, JsonAbi)]) -> Result<Self> {
        let mut contracts = FastMap::default();

        for (addr, abi) in json_abis.iter() {
            let mut event_map = FastMap::default();

            for (_, events) in abi.events.iter() {
                for event in events {
                    event_map.insert(
                        event.selector().to_vec(),
                        event.resolve().context("resolve event")?,
                    );
                }
            }

            if contracts.insert(addr.to_vec(), event_map).is_some() {
                return Err(anyhow!("duplicate contract address {}", addr.encode_hex()));
            }
        }

        Ok(Self { contracts })
    }

    #[inline]
    pub fn decode(
        &self,
        address: &[u8],
        topic0: &[u8],
        topics: &[Option<&[u8]>],
        data: &[u8],
    ) -> Result<Option<DecodedEvent>> {
        let contract = match self.contracts.get(address) {
            Some(contract) => contract,
            None => return Ok(None),
        };
        let event = match contract.get(topic0) {
            Some(event) => event,
            None => return Ok(None),
        };

        let topics = topics
            .iter()
            .filter_map(|&t| t.map(|t| t.try_into().unwrap()));

        // Check if we are decoding a single u256 and the body is empty
        //
        // This case can happen when decoding zero value erc20 transfers
        let decoded = if data.is_empty() && event.body() == [DynSolType::Uint(256)] {
            event
                .decode_log_parts(topics, [0; 32].as_slice(), false)
                .context("decode log parts")?
        } else {
            event
                .decode_log_parts(topics, data, false)
                .context("decode log parts")?
        };

        Ok(Some(decoded))
    }

    pub fn decode_logs(&self, logs: &[ArrowBatch]) -> Result<Option<Vec<Option<DecodedEvent>>>> {
        let mut events = Vec::new();

        for batch in logs {
            let address = match batch.column::<BinaryArray<i32>>("address") {
                Ok(address) => address,
                Err(_) => return Ok(None),
            };
            let data = match batch.column::<BinaryArray<i32>>("data") {
                Ok(data) => data,
                Err(_) => return Ok(None),
            };
            let topic0 = match batch.column::<BinaryArray<i32>>("topic0") {
                Ok(topic0) => topic0,
                Err(_) => return Ok(None),
            };
            let topic1 = match batch.column::<BinaryArray<i32>>("topic1") {
                Ok(topic1) => topic1,
                Err(_) => return Ok(None),
            };
            let topic2 = match batch.column::<BinaryArray<i32>>("topic2") {
                Ok(topic2) => topic2,
                Err(_) => return Ok(None),
            };
            let topic3 = match batch.column::<BinaryArray<i32>>("topic3") {
                Ok(topic3) => topic3,
                Err(_) => return Ok(None),
            };

            for (((((address, data), topic0), topic1), topic2), topic3) in address
                .values_iter()
                .zip(data.values_iter())
                .zip(topic0.iter())
                .zip(topic1.iter())
                .zip(topic2.iter())
                .zip(topic3.iter())
            {
                let topic0 = match topic0 {
                    Some(topic0) => topic0,
                    None => {
                        events.push(None);
                        continue;
                    }
                };

                let decoded = self
                    .decode(
                        address,
                        topic0,
                        &[Some(topic0), topic1, topic2, topic3],
                        data,
                    )
                    .context("decode event")?;

                events.push(decoded);
            }
        }

        Ok(Some(events))
    }
}