hypersync_client/
decode_call.rs

1use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
2use alloy_json_abi::Function;
3use anyhow::{Context, Result};
4use hypersync_format::Data;
5use std::collections::HashMap;
6
7#[derive(Debug, Hash, Eq, PartialEq)]
8struct FunctionKey {
9    signature: Vec<u8>,
10}
11
12type DecoderMap = HashMap<FunctionKey, Function>;
13
14/// Decode input data parsing input data.
15pub struct CallDecoder {
16    // A map of signature => Function decoder
17    map: DecoderMap,
18}
19
20impl CallDecoder {
21    /// Initialize decoder from event signatures.
22    ///
23    ///     use hypersync_client::CallDecoder;
24    ///     let decoder = CallDecoder::from_signatures(&[
25    ///        "transfer(address to,uint256 amount)",
26    ///     ]).unwrap();
27    pub fn from_signatures<S: AsRef<str>>(signatures: &[S]) -> Result<Self> {
28        let map: DecoderMap = signatures
29            .iter()
30            .map(|sig| {
31                let function = Function::parse(sig.as_ref()).context("parse event signature")?;
32                let signature = function.selector().to_vec();
33                let event_key = FunctionKey { signature };
34                Ok((event_key, function))
35            })
36            .collect::<Result<DecoderMap>>()
37            .context("construct function decoder map")?;
38
39        Ok(Self { map })
40    }
41
42    /// Parse input data and return result
43    ///
44    /// Returns Ok(None) if signature not found.
45    pub fn decode_input(&self, data: &Data) -> Result<Option<Vec<DynSolValue>>> {
46        let function_key = FunctionKey {
47            signature: data[0..4].to_vec(),
48        };
49        let function = match self.map.get(&function_key) {
50            Some(function) => function,
51            None => return Ok(None),
52        };
53        let decoded = function
54            .abi_decode_input(data.as_ref(), false)
55            .context("decoding input data")?;
56        Ok(Some(decoded))
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use hypersync_format::{Data, Hex};
64
65    #[test]
66    fn test_decode_input_with_single_signature() {
67        let function =
68            alloy_json_abi::Function::parse("transfer(address dst, uint256 wad)").unwrap();
69        let input = "0xa9059cbb000000000000000000000000dc4bde73fa35b7478a574f78d5dfd57a0b2e22810000000000000000000000000000000000000000000000004710ca26d3eeae0a";
70        let input = Data::decode_hex(input).unwrap();
71        let expected = function.abi_decode_input(input.as_ref(), false).unwrap();
72
73        let decoder =
74            CallDecoder::from_signatures(&["transfer(address dst, uint256 wad)"]).unwrap();
75        let got = decoder.decode_input(&input).unwrap().unwrap();
76
77        for (expected, got) in expected.iter().zip(got.iter()) {
78            assert_eq!(expected, got, "Checking that decodes are the same");
79        }
80    }
81
82    #[test]
83    fn test_decode_input_with_multiple_signature() {
84        let function =
85            alloy_json_abi::Function::parse("transfer(address dst, uint256 wad)").unwrap();
86        let input = "0xa9059cbb000000000000000000000000dc4bde73fa35b7478a574f78d5dfd57a0b2e22810000000000000000000000000000000000000000000000004710ca26d3eeae0a";
87        let input = Data::decode_hex(input).unwrap();
88        let expected = function.abi_decode_input(input.as_ref(), false).unwrap();
89
90        let decoder = CallDecoder::from_signatures(&[
91            "transfer(address dst, uint256 wad)",
92            "approve(address usr, uint256 wad)",
93        ])
94        .unwrap();
95        let got = decoder.decode_input(&input).unwrap().unwrap();
96
97        for (expected, got) in expected.iter().zip(got.iter()) {
98            assert_eq!(expected, got, "Checking that decodes are the same");
99        }
100    }
101
102    #[test]
103    #[should_panic]
104    fn test_decode_input_with_incorrect_signature() {
105        let _function = alloy_json_abi::Function::parse("incorrect signature").unwrap();
106    }
107}