hypersync_client/
decode_call.rs1use alloy_dyn_abi::{DynSolType, 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())
55 .context("decoding input data")?;
56 Ok(Some(decoded))
57 }
58
59 pub fn decode_output(
66 &self,
67 data: &Data,
68 function_signature: &str,
69 ) -> Result<Option<Vec<DynSolValue>>> {
70 let function = Function::parse(function_signature).context("parsing function signature")?;
72
73 let output_types = function.outputs;
75
76 let output_types: Vec<DynSolType> = output_types
78 .into_iter()
79 .map(|param| param.ty.parse::<DynSolType>())
80 .collect::<Result<_, _>>() .context("parsing output types")?;
82
83 let tuple_type = DynSolType::Tuple(output_types);
85
86 match tuple_type.abi_decode(data.as_ref()) {
88 Ok(decoded) => {
90 if let DynSolValue::Tuple(values) = decoded {
91 Ok(Some(values)) } else {
93 Ok(Some(vec![decoded])) }
95 }
96 Err(_) => {
98 Ok(None) }
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use hypersync_format::{Data, Hex};
108
109 #[test]
110 fn test_decode_input_with_single_signature() {
111 let function =
112 alloy_json_abi::Function::parse("transfer(address dst, uint256 wad)").unwrap();
113 let input = "0xa9059cbb000000000000000000000000dc4bde73fa35b7478a574f78d5dfd57a0b2e22810000000000000000000000000000000000000000000000004710ca26d3eeae0a";
114 let input = Data::decode_hex(input).unwrap();
115 let expected = function.abi_decode_input(input.as_ref()).unwrap();
116
117 let decoder =
118 CallDecoder::from_signatures(&["transfer(address dst, uint256 wad)"]).unwrap();
119 let got = decoder.decode_input(&input).unwrap().unwrap();
120
121 for (expected, got) in expected.iter().zip(got.iter()) {
122 assert_eq!(expected, got, "Checking that decodes are the same");
123 }
124 }
125
126 #[test]
127 fn test_decode_input_with_multiple_signature() {
128 let function =
129 alloy_json_abi::Function::parse("transfer(address dst, uint256 wad)").unwrap();
130 let input = "0xa9059cbb000000000000000000000000dc4bde73fa35b7478a574f78d5dfd57a0b2e22810000000000000000000000000000000000000000000000004710ca26d3eeae0a";
131 let input = Data::decode_hex(input).unwrap();
132 let expected = function.abi_decode_input(input.as_ref()).unwrap();
133
134 let decoder = CallDecoder::from_signatures(&[
135 "transfer(address dst, uint256 wad)",
136 "approve(address usr, uint256 wad)",
137 ])
138 .unwrap();
139 let got = decoder.decode_input(&input).unwrap().unwrap();
140
141 for (expected, got) in expected.iter().zip(got.iter()) {
142 assert_eq!(expected, got, "Checking that decodes are the same");
143 }
144 }
145
146 #[test]
147 #[should_panic]
148 fn test_decode_input_with_incorrect_signature() {
149 let _function = alloy_json_abi::Function::parse("incorrect signature").unwrap();
150 }
151
152 #[test]
153 fn test_decode_output_single_value() {
154 let output = "0x0000000000000000000000000000000000000000000000056bc75e2d63100000";
155 let output = Data::decode_hex(output).unwrap();
156 let function_signature = "balanceOf(address)(uint256)";
157
158 let decoder = CallDecoder::from_signatures(&["balanceOf(address)"]).unwrap();
159 let result = decoder
160 .decode_output(&output, function_signature)
161 .unwrap()
162 .unwrap();
163
164 assert_eq!(result.len(), 1, "Should return a single value");
165 assert!(
166 matches!(result[0], DynSolValue::Uint(..)),
167 "Should be a uint value"
168 );
169 }
170
171 #[test]
172 fn test_decode_output_multiple_values() {
173 let output = "0x000000000000000000000000dc4bde73fa35b7478a574f78d5dfd57a0b2e22810000000000000000000000000000000000000000000000004710ca26d3eeae0a";
174 let output = Data::decode_hex(output).unwrap();
175 let function_signature = "someFunction()(address,uint256)";
176
177 let decoder = CallDecoder::from_signatures(&["someFunction()"]).unwrap();
178 let result = decoder
179 .decode_output(&output, function_signature)
180 .unwrap()
181 .unwrap();
182
183 assert_eq!(result.len(), 2, "Should return two values");
184 assert!(
185 matches!(result[0], DynSolValue::Address(..)),
186 "First value should be an address"
187 );
188 assert!(
189 matches!(result[1], DynSolValue::Uint(..)),
190 "Second value should be a uint"
191 );
192 }
193
194 #[test]
195 fn test_decode_output_invalid_data() {
196 let output = "invalid_data";
197 let _output = Data::decode_hex(output).unwrap_err();
198 let function_signature = "balanceOf(address)(uint256)";
199
200 let decoder = CallDecoder::from_signatures(&["balanceOf(address)"]).unwrap();
201 let result = decoder
202 .decode_output(&Data::default(), function_signature)
203 .unwrap();
204
205 assert!(result.is_none(), "Should return None for invalid data");
206 }
207
208 #[test]
209 #[should_panic(expected = "parsing function signature")]
210 fn test_decode_output_invalid_signature() {
211 let output = "0x0000000000000000000000000000000000000000000000056bc75e2d63100000";
212 let output = Data::decode_hex(output).unwrap();
213 let function_signature = "invalid signature";
214
215 let decoder = CallDecoder::from_signatures(&["balanceOf(address)"]).unwrap();
216 decoder.decode_output(&output, function_signature).unwrap();
217 }
218}