airnode_events/
logreader.rs

1use airnode_abi::{DecodingError, ABI};
2use ethereum_types::{H160, H256, U256};
3use std::str::FromStr;
4use thiserror::Error;
5
6#[derive(Error, Debug, Clone)]
7pub enum EventParseError {
8    #[error("tx {0}: no topics")]
9    NoTopics(H256),
10    #[error("tx {0}: {1} topics found, {2} expected")]
11    InvalidTopics(H256, usize, usize),
12    #[error("tx {0}: {1} data length, {2} bytes expected")]
13    InvalidDataSize(H256, usize, usize),
14}
15
16pub struct LogReader {
17    pub topics: Vec<H256>,
18    pub data: Vec<u8>,
19    // mutable iterator for topics
20    pub current_topic: usize,
21    // mutable data offset
22    pub data_offset: usize,
23}
24
25impl LogReader {
26    pub fn new(
27        log: &web3::types::Log,
28        expected_topics: usize,
29        expected_data_size: Option<usize>,
30    ) -> Result<Self, EventParseError> {
31        let hash = log.transaction_hash.unwrap();
32        if log.topics.len() == 0 {
33            return Err(EventParseError::NoTopics(hash));
34        }
35        if log.topics.len() - 1 != expected_topics {
36            return Err(EventParseError::InvalidTopics(
37                hash,
38                log.topics.len() - 1,
39                expected_topics,
40            ));
41        }
42        let data: &Vec<u8> = &log.data.0;
43        if let Some(sz) = expected_data_size {
44            if data.len() != sz * 32 {
45                return Err(EventParseError::InvalidDataSize(hash, data.len(), sz));
46            }
47        }
48        Ok(Self {
49            current_topic: 1,
50            data_offset: 0,
51            topics: log.topics.clone(),
52            data: data.clone(),
53        })
54    }
55
56    fn has_topics(&self) -> bool {
57        self.topics.len() > self.current_topic
58    }
59
60    fn has_data(&self) -> bool {
61        self.data.len() > self.data_offset
62    }
63
64    fn next32(&mut self) -> String {
65        if self.has_topics() {
66            let hex_str = format!("{:?}", self.topics.get(self.current_topic).unwrap()); // string of 64
67                                                                                         // println!("next topic={}", hex_str);
68            self.current_topic += 1;
69            hex_str[2..].to_owned()
70        } else {
71            let hex_str = hex::encode(&self.data);
72            let offs: usize = 2 * self.data_offset;
73            let res: String = hex_str.chars().skip(offs).take(64).collect();
74            self.data_offset += 32;
75            res
76        }
77    }
78
79    // pop meta data as text
80    pub fn text(&mut self) -> String {
81        let _hex_size = self.next32(); // first byte is number
82        let _str_size = self.next32(); // second piece is actual size of the string
83        let mut s = String::from("");
84        while self.has_data() {
85            let nextword = self.next32();
86            let bts: Vec<u8> = hex::decode(nextword).unwrap();
87            bts.iter().filter(|ch| **ch != 0).for_each(|ch| {
88                if *ch == 0x1F {
89                    s.push('|');
90                } else if *ch == '\\' as u8
91                    || *ch == '"' as u8
92                    || *ch == '\'' as u8
93                    || *ch == '<' as u8
94                    || *ch == '>' as u8
95                {
96                    // preventing HTML injection
97                    s.push(' ');
98                } else if *ch > 0x1F && *ch < 0x80 {
99                    s.push(*ch as char);
100                }
101            });
102        }
103        s
104    }
105
106    // pop address from the latest topic
107    pub fn address(&mut self) -> H160 {
108        let hex_str = self.next32();
109        // println!("address={}", hex_str);
110        H160::from_str(&hex_str[24..]).unwrap()
111    }
112
113    // pop array of addresses
114    pub fn addresses(&mut self) -> Vec<H160> {
115        let _ = self.next32();
116        let _ = self.next32();
117        let mut res = vec![];
118        while self.has_data() {
119            res.push(self.address());
120        }
121        res
122    }
123
124    // pop value from the latest topic or data
125    pub fn value(&mut self) -> U256 {
126        // let offs = self.data_offset;
127        let hex_str = self.next32();
128        // println!("offset={} data={}", offs, hex_str);
129        U256::from_str(hex_str.as_str()).unwrap()
130    }
131
132    pub fn value224_32(&mut self) -> (U256, u64) {
133        // TODO: this must be properly decoded
134        let packed = self.value();
135        let mut value = packed;
136        value.0[0] = value.0[0] & 0xFFFFFFFF;
137        let timestamp = packed.0[0];
138        (value, timestamp)
139    }
140
141    pub fn skip(&mut self) {
142        let _ = self.value();
143    }
144
145    // pop all remaining values
146    pub fn values(&mut self) -> Vec<U256> {
147        let mut res = vec![];
148        while self.has_data() {
149            res.push(self.value());
150        }
151        res
152    }
153
154    // pop bool value
155    pub fn bool(&mut self) -> bool {
156        let val = self.value().as_u64();
157        val > 0
158    }
159
160    // pop ABI-decoded values
161    pub fn decoded(&mut self) -> (Option<ABI>, Option<DecodingError>, Option<Vec<U256>>) {
162        self.skip();
163        self.skip();
164        let chunks = self.values();
165        if chunks.len() == 0 {
166            return (None, None, None);
167        }
168        let (parameters, error, data) = match ABI::decode(&chunks, false) {
169            Ok(x) => (Some(x), None, None),
170            Err(e) => (
171                None,
172                Some(e),
173                if chunks.len() > 0 {
174                    Some(chunks.clone())
175                } else {
176                    None
177                },
178            ),
179        };
180        (parameters, error, data)
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use ethereum_types::Address;
188    use hex_literal::hex;
189    use web3::types::Log;
190
191    #[test]
192    pub fn test_it_reads() {
193        let log = Log {
194            address: Address::from_low_u64_be(1),
195            topics: vec![
196                hex!("06fbd2297e6f6f7701a9cf99685a6af911cab275ec5c75ac7aaaf13b5cf3d61f").into(),
197                hex!("000000000000000000000000061b8335e1d2042975c4ed849943334bd07fb504").into(),
198            ],
199            data: hex!("0000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000056bb73f60696ee4160000000000000000000000000000000000000000000000000000000060da02bd").into(),
200            block_hash: Some(H256::from_low_u64_be(2)),
201            block_number: Some(1.into()),
202            transaction_hash: Some(H256::from_low_u64_be(3)),
203            transaction_index: Some(0.into()),
204            log_index: Some(0.into()),
205            transaction_log_index: Some(0.into()),
206            log_type: None,
207            removed: Some(true),
208        };
209        let mut r = LogReader::new(&log, 1, Some(3)).unwrap();
210        assert_eq!(
211            r.address(),
212            hex!("061b8335e1d2042975c4ed849943334bd07fb504").into()
213        );
214        assert_eq!(
215            r.value(),
216            hex!("0000000000000000000000000000000000000000000000056bc75e2d63100000").into()
217        );
218        assert_eq!(
219            r.value(),
220            hex!("0000000000000000000000000000000000000000000000056bb73f60696ee416").into()
221        );
222        assert_eq!(
223            r.value(),
224            hex!("0000000000000000000000000000000000000000000000000000000060da02bd").into()
225        );
226    }
227
228    #[test]
229    pub fn test_reads_meta_data() {
230        let log = Log {
231            address: Address::from_low_u64_be(1),
232            topics: vec![
233                hex!("4d72fe0577a3a3f7da968d7b892779dde102519c25527b29cf7054f245c791b9").into(),
234                hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
235                hex!("000000000000000000000000061b8335e1d2042975c4ed849943334bd07fb504").into()
236            ],
237            data: hex!(
238                "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000047311f7472616e7366657228616464726573732c75696e74323536291f4d7920666972737420415049332070726f706f73616c1f466f722074657374696e6720707572706f73657300000000000000000000000000000000000000000000000000"
239            ).into(),
240            block_hash: Some(H256::from_low_u64_be(2)),
241            block_number: Some(1.into()),
242            transaction_hash: Some(H256::from_low_u64_be(3)),
243            transaction_index: Some(0.into()),
244            log_index: Some(0.into()),
245            transaction_log_index: Some(0.into()),
246            log_type: None,
247            removed: Some(true),
248        };
249        let mut r = LogReader::new(&log, 2, None).unwrap();
250        assert_eq!(
251            r.value(),
252            hex!("0000000000000000000000000000000000000000000000000000000000000000").into()
253        );
254        assert_eq!(
255            r.address(),
256            hex!("061b8335e1d2042975c4ed849943334bd07fb504").into()
257        );
258        assert_eq!(
259            r.text(),
260            "1|transfer(address,uint256)|My first API3 proposal|For testing purposes"
261        );
262    }
263}