1use ethabi::ParamType;
2use ethabi::Token;
3
4use crate::eth::codegen::SIGNATURES_4_BYTES;
5use crate::eth::primitives::DecodeInputError;
6
7pub fn decode_input_arguments(input: impl AsRef<[u8]>) -> Result<String, DecodeInputError> {
9 let Some(selector) = input.as_ref().get(..4) else {
10 return Err(DecodeInputError::InputTooShort {
11 message: format!("expected at least 4 bytes for function selector, got {} bytes", input.as_ref().len()),
12 });
13 };
14 let Some(signature) = SIGNATURES_4_BYTES.get(selector) else {
15 return Err(DecodeInputError::FunctionUnknown {
16 message: format!("selector 0x{} not found in signature mapping", const_hex::encode(selector)),
17 });
18 };
19 let param_types = parse_to_param_types(signature)?;
20 let param_data = input.as_ref().get(4..).ok_or(DecodeInputError::InputTooShort {
21 message: "input data too short for parameters after function selector".to_string(),
22 })?;
23 let tokens = ethabi::decode(¶m_types, param_data)?;
24 Ok(format_tokens_human_readable(&tokens))
25}
26
27fn parse_to_param_types(signature: &str) -> Result<Vec<ParamType>, DecodeInputError> {
30 let start = signature.find('(').ok_or_else(|| DecodeInputError::InvalidAbi {
31 message: format!("invalid signature format: {signature} (missing opening parenthesis)"),
32 })?;
33 let end = signature.rfind(')').ok_or_else(|| DecodeInputError::InvalidAbi {
34 message: format!("invalid signature format: {signature} (missing closing parenthesis)"),
35 })?;
36 let params_str = &signature[start + 1..end];
37 if params_str.is_empty() {
38 return Ok(Vec::new());
39 }
40 tokenize_parameters(params_str)
41}
42
43fn tokenize_parameters(params_str: &str) -> Result<Vec<ParamType>, DecodeInputError> {
46 let mut tokens = Vec::new();
47 let mut current_token = String::new();
48 let mut paren_depth = 0;
49 let mut bracket_open = false;
50
51 for ch in params_str.chars() {
52 match ch {
53 '(' => {
54 paren_depth += 1;
55 current_token.push(ch);
56 }
57 ')' => {
58 paren_depth -= 1;
59 current_token.push(ch);
60 if paren_depth < 0 {
61 return Err(DecodeInputError::InvalidAbi {
62 message: "unmatched closing parenthesis".to_string(),
63 });
64 }
65 }
66 '[' => {
67 if bracket_open {
68 return Err(DecodeInputError::InvalidAbi {
69 message: "nested brackets are not allowed".to_string(),
70 });
71 }
72 bracket_open = true;
73 current_token.push(ch);
74 }
75 ']' => {
76 if !bracket_open {
77 return Err(DecodeInputError::InvalidAbi {
78 message: "unmatched closing bracket".to_string(),
79 });
80 }
81 bracket_open = false;
82 current_token.push(ch);
83 }
84 ',' => {
85 if paren_depth == 0 && !bracket_open {
86 tokens.push(parse_solidity_type(current_token.trim())?);
88 current_token.clear();
89 } else {
90 current_token.push(ch);
92 }
93 }
94 _ => {
95 current_token.push(ch);
96 }
97 }
98 }
99
100 if !current_token.trim().is_empty() {
102 tokens.push(parse_solidity_type(current_token.trim())?);
103 }
104
105 if paren_depth != 0 {
107 return Err(DecodeInputError::InvalidAbi {
108 message: "unmatched parentheses".to_string(),
109 });
110 }
111 if bracket_open {
112 return Err(DecodeInputError::InvalidAbi {
113 message: "unmatched brackets".to_string(),
114 });
115 }
116
117 Ok(tokens)
118}
119
120fn parse_solidity_type(type_str: impl AsRef<str>) -> Result<ParamType, DecodeInputError> {
121 let type_str = type_str.as_ref();
122 match type_str {
123 s if s.ends_with("[]") => {
124 let inner_type = parse_solidity_type(&s[..s.len() - 2])?;
125 Ok(ParamType::Array(Box::new(inner_type)))
126 }
127 s if s.contains('[') && s.ends_with(']') => {
128 let bracket_pos = s.find('[').unwrap();
129 let inner_type = parse_solidity_type(&s[..bracket_pos])?;
130 let size_str = &s[bracket_pos + 1..s.len() - 1];
131 let size = size_str.parse::<usize>().map_err(|_| DecodeInputError::InvalidAbi {
132 message: format!("invalid array size in type: {s}"),
133 })?;
134 Ok(ParamType::FixedArray(Box::new(inner_type), size))
135 }
136 s if s.starts_with('(') && s.ends_with(')') => {
137 let inner_str = &s[1..s.len() - 1]; if inner_str.is_empty() {
141 return Ok(ParamType::Tuple(Vec::new()));
142 }
143
144 Ok(ParamType::Tuple(tokenize_parameters(inner_str)?))
145 }
146 "address" => Ok(ParamType::Address),
147 "bool" => Ok(ParamType::Bool),
148 "string" => Ok(ParamType::String),
149 "bytes" => Ok(ParamType::Bytes),
150 s if s.starts_with("uint") => {
151 let size = if s == "uint" {
152 256
153 } else {
154 s[4..].parse::<usize>().map_err(|_| DecodeInputError::InvalidAbi {
155 message: format!("invalid uint size in type: {s}"),
156 })?
157 };
158 Ok(ParamType::Uint(size))
159 }
160 s if s.starts_with("int") => {
161 let size = if s == "int" {
162 256
163 } else {
164 s[3..].parse::<usize>().map_err(|_| DecodeInputError::InvalidAbi {
165 message: format!("invalid int size in type: {s}"),
166 })?
167 };
168 Ok(ParamType::Int(size))
169 }
170 s if s.starts_with("bytes") && s.len() > 5 => {
171 let size = s[5..].parse::<usize>().map_err(|_| DecodeInputError::InvalidAbi {
172 message: format!("invalid bytes size in type: {s}"),
173 })?;
174 Ok(ParamType::FixedBytes(size))
175 }
176 _ => Err(DecodeInputError::InvalidAbi {
177 message: format!("unsupported Solidity type: {type_str}"),
178 }),
179 }
180}
181
182fn format_tokens_human_readable(tokens: &[Token]) -> String {
184 let items: Vec<String> = tokens.iter().map(format_token).collect();
185 format!("({})", items.join(", "))
186}
187
188fn format_token(token: &Token) -> String {
189 match token {
190 Token::Address(addr) => format!("0x{addr:x}"),
191 Token::Uint(val) | Token::Int(val) => format!("{val}"),
192 Token::Bool(val) => val.to_string(),
193 Token::String(val) => format!("\"{val}\""),
194 Token::Bytes(val) => format!("0x{}", const_hex::encode(val)),
195 Token::FixedBytes(val) => format!("0x{}", const_hex::encode(val)),
196 Token::Array(arr) | Token::FixedArray(arr) => {
197 let items: Vec<String> = arr.iter().map(format_token).collect();
198 format!("[{}]", items.join(", "))
199 }
200 Token::Tuple(tuple) => {
201 let items: Vec<String> = tuple.iter().map(format_token).collect();
202 format!("({})", items.join(", "))
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use ethabi::Address;
210 use ethabi::ParamType;
211 use hex_literal::hex;
212
213 use super::*;
214
215 #[test]
216 fn test_parse_transfer_transaction_input() {
217 let mut tx_transfer = Vec::from(hex!("a9059cbb"));
219 tx_transfer.extend_from_slice(ðabi::encode(&[
220 Token::Address(Address::from(hex!("1234567890123456789012345678901234567890"))),
221 Token::Uint(1000000000000000000u64.into()),
222 ]));
223 let result = decode_input_arguments(&tx_transfer).unwrap();
224
225 assert_eq!(result, "(0x1234567890123456789012345678901234567890, 1000000000000000000)");
226 }
227
228 #[test]
229 fn test_no_parameter_input() {
230 let tx_no_parameter = Vec::from(hex!("18160ddd"));
232 let result = decode_input_arguments(&tx_no_parameter).unwrap();
233
234 assert_eq!(result, "()");
235 }
236
237 #[test]
238 fn test_complex_input() {
239 let signature = "test(uint256,(string,bool,(int256,uint256[]))[])";
241 let param_types = parse_to_param_types(signature).unwrap();
242 assert_eq!(
243 param_types,
244 vec![
245 ParamType::Uint(256),
246 ParamType::Array(Box::new(ParamType::Tuple(vec![
247 ParamType::String,
248 ParamType::Bool,
249 ParamType::Tuple(vec![ParamType::Int(256), ParamType::Array(Box::new(ParamType::Uint(256)))]),
250 ])))
251 ]
252 );
253 let param_data = ethabi::encode(&[
254 Token::Uint(1000000000000000000u64.into()),
255 Token::Array(vec![Token::Tuple(vec![
256 Token::String("test".to_string()),
257 Token::Bool(true),
258 Token::Tuple(vec![
259 Token::Int(200000000000000000i128.into()),
260 Token::Array(vec![Token::Uint(3000000000000000000u64.into())]),
261 ]),
262 ])]),
263 ]);
264
265 let tokens = ethabi::decode(¶m_types, ¶m_data).expect("failed to decode parameters");
266 let result = format_tokens_human_readable(&tokens);
267 assert_eq!(
268 result,
269 r#"(1000000000000000000, [("test", true, (200000000000000000, [3000000000000000000]))])"#
270 );
271 }
272
273 #[test]
274 fn test_invalid_input() {
275 let invalid_input = Vec::from(hex!("a9059cbb"));
276 let result = decode_input_arguments(&invalid_input);
277 assert!(matches!(result, Err(DecodeInputError::InvalidInput { source: _ })));
278 }
279
280 #[test]
281 fn test_invalid_length() {
282 let invalid_input = Vec::from(hex!("a9059c"));
283 let result = decode_input_arguments(&invalid_input);
284 assert!(matches!(result, Err(DecodeInputError::InputTooShort { message: _ })));
285 }
286
287 #[test]
288 fn test_invalid_signature() {
289 let invalid_input = Vec::from(hex!("42000042"));
290 let result = decode_input_arguments(&invalid_input);
291 assert!(matches!(result, Err(DecodeInputError::FunctionUnknown { message: _ })));
292 }
293}