hypersync_client/
decode_call.rs1use 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
14pub struct CallDecoder {
16 map: DecoderMap,
18}
19
20impl CallDecoder {
21 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 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}