Skip to main content

clear_signing/
decoder.rs

1//! Solidity function signature parsing and ABI calldata decoding.
2//! No JSON ABI needed — signatures are parsed directly from descriptor format keys.
3
4use tiny_keccak::{Hasher, Keccak};
5
6use crate::error::DecodeError;
7
8/// Parsed function signature.
9#[derive(Debug, Clone)]
10pub struct FunctionSignature {
11    pub name: String,
12    pub params: Vec<ParamType>,
13    pub param_names: Vec<Option<String>>,
14    pub canonical: String,
15    pub selector: [u8; 4],
16}
17
18/// ABI parameter types — recursive to support tuples and arrays.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum ParamType {
21    Address,
22    Uint(usize),
23    Int(usize),
24    Bool,
25    Bytes,
26    FixedBytes(usize),
27    String,
28    Array(Box<ParamType>),
29    FixedArray(Box<ParamType>, usize),
30    Tuple(Vec<(Option<String>, ParamType)>),
31}
32
33impl ParamType {
34    /// Whether this type is dynamically-sized in ABI encoding.
35    pub fn is_dynamic(&self) -> bool {
36        match self {
37            ParamType::Bytes | ParamType::String => true,
38            ParamType::Array(_) => true,
39            ParamType::FixedArray(inner, _) => inner.is_dynamic(),
40            ParamType::Tuple(members) => members.iter().any(|(_, m)| m.is_dynamic()),
41            _ => false,
42        }
43    }
44
45    /// Number of bytes this type occupies in the ABI head section.
46    ///
47    /// Dynamic types always take 32 bytes (an offset pointer).
48    /// Static types take 32 bytes for atomic types, or the sum of member head sizes
49    /// for static tuples and fixed arrays of static elements.
50    pub fn head_size(&self) -> usize {
51        if self.is_dynamic() {
52            32 // offset pointer
53        } else {
54            match self {
55                ParamType::Tuple(members) => members.iter().map(|(_, m)| m.head_size()).sum(),
56                ParamType::FixedArray(inner, len) => inner.head_size() * len,
57                _ => 32, // address, uint, int, bool, fixedBytes
58            }
59        }
60    }
61}
62
63/// Decoded calldata arguments.
64#[derive(Debug, Clone)]
65pub struct DecodedArguments {
66    pub function_name: String,
67    pub selector: [u8; 4],
68    pub args: Vec<DecodedArgument>,
69}
70
71/// A single decoded argument.
72#[derive(Debug, Clone)]
73pub struct DecodedArgument {
74    pub index: usize,
75    pub name: Option<String>,
76    pub param_type: ParamType,
77    pub value: ArgumentValue,
78}
79
80/// Decoded argument values.
81#[derive(Debug, Clone)]
82pub enum ArgumentValue {
83    Address([u8; 20]),
84    Uint(Vec<u8>),
85    Int(Vec<u8>),
86    Bool(bool),
87    Bytes(Vec<u8>),
88    FixedBytes(Vec<u8>),
89    String(std::string::String),
90    Array(Vec<ArgumentValue>),
91    Tuple(Vec<(Option<String>, ArgumentValue)>),
92}
93
94impl ArgumentValue {
95    /// Convert to a serde_json::Value for visibility rule evaluation.
96    pub fn to_json_value(&self) -> serde_json::Value {
97        match self {
98            ArgumentValue::Address(addr) => {
99                serde_json::Value::String(format!("0x{}", hex::encode(addr)))
100            }
101            ArgumentValue::Uint(bytes) => {
102                let hex_str = format!("0x{}", hex::encode(bytes));
103                serde_json::Value::String(hex_str)
104            }
105            ArgumentValue::Int(bytes) => {
106                let hex_str = format!("0x{}", hex::encode(bytes));
107                serde_json::Value::String(hex_str)
108            }
109            ArgumentValue::Bool(b) => serde_json::Value::Bool(*b),
110            ArgumentValue::Bytes(b) => serde_json::Value::String(format!("0x{}", hex::encode(b))),
111            ArgumentValue::FixedBytes(b) => {
112                serde_json::Value::String(format!("0x{}", hex::encode(b)))
113            }
114            ArgumentValue::String(s) => serde_json::Value::String(s.clone()),
115            ArgumentValue::Array(items) => {
116                serde_json::Value::Array(items.iter().map(|i| i.to_json_value()).collect())
117            }
118            ArgumentValue::Tuple(items) => {
119                serde_json::Value::Array(items.iter().map(|(_, i)| i.to_json_value()).collect())
120            }
121        }
122    }
123
124    /// Get the raw uint256 bytes, zero-extended to 32 bytes.
125    pub fn as_uint_bytes(&self) -> Option<[u8; 32]> {
126        match self {
127            ArgumentValue::Uint(b) | ArgumentValue::Int(b) => {
128                let mut result = [0u8; 32];
129                let start = 32usize.saturating_sub(b.len());
130                let copy_len = b.len().min(32);
131                result[start..start + copy_len].copy_from_slice(&b[b.len() - copy_len..]);
132                Some(result)
133            }
134            _ => None,
135        }
136    }
137}
138
139/// Parse a function signature string into a `FunctionSignature`.
140///
141/// Example: `"transfer(address,uint256)"` → name="transfer", params=[Address, Uint(256)]
142pub fn parse_signature(sig: &str) -> Result<FunctionSignature, DecodeError> {
143    let sig = sig.trim();
144    let open = sig
145        .find('(')
146        .ok_or_else(|| DecodeError::InvalidSignature(format!("missing '(' in: {sig}")))?;
147
148    if !sig.ends_with(')') {
149        return Err(DecodeError::InvalidSignature(format!(
150            "missing ')' in: {sig}"
151        )));
152    }
153
154    let name = sig[..open].to_string();
155    if name.is_empty() {
156        return Err(DecodeError::InvalidSignature(
157            "empty function name".to_string(),
158        ));
159    }
160
161    let params_str = &sig[open + 1..sig.len() - 1];
162    let (params, param_names) = if params_str.is_empty() {
163        (vec![], vec![])
164    } else {
165        let named = parse_param_list_named(params_str)?;
166        let params = named.iter().map(|(p, _)| p.clone()).collect();
167        let names = named.into_iter().map(|(_, n)| n).collect();
168        (params, names)
169    };
170
171    let canonical = format!("{}({})", name, canonical_params(&params));
172    let selector = selector_from_signature(&canonical);
173
174    Ok(FunctionSignature {
175        name,
176        params,
177        param_names,
178        canonical,
179        selector,
180    })
181}
182
183/// Parse a comma-separated list of potentially named params (top-level only).
184fn parse_param_list_named(s: &str) -> Result<Vec<(ParamType, Option<String>)>, DecodeError> {
185    let mut result = Vec::new();
186    let mut depth = 0usize;
187    let mut start = 0;
188
189    for (i, c) in s.char_indices() {
190        match c {
191            '(' => depth += 1,
192            ')' => {
193                depth = depth
194                    .checked_sub(1)
195                    .ok_or_else(|| DecodeError::InvalidSignature("unbalanced ')'".to_string()))?;
196            }
197            ',' if depth == 0 => {
198                result.push(parse_param_with_name(s[start..i].trim())?);
199                start = i + 1;
200            }
201            _ => {}
202        }
203    }
204
205    if depth != 0 {
206        return Err(DecodeError::InvalidSignature(
207            "unbalanced parentheses".to_string(),
208        ));
209    }
210
211    let last = s[start..].trim();
212    if !last.is_empty() {
213        result.push(parse_param_with_name(last)?);
214    }
215
216    Ok(result)
217}
218
219/// Parse a comma-separated list of potentially named params inside a tuple body.
220///
221/// Returns `Vec<(Option<String>, ParamType)>` — name first, type second.
222fn parse_param_list_with_names(s: &str) -> Result<Vec<(Option<String>, ParamType)>, DecodeError> {
223    let mut result = Vec::new();
224    let mut depth = 0usize;
225    let mut start = 0;
226
227    for (i, c) in s.char_indices() {
228        match c {
229            '(' => depth += 1,
230            ')' => {
231                depth = depth
232                    .checked_sub(1)
233                    .ok_or_else(|| DecodeError::InvalidSignature("unbalanced ')'".to_string()))?;
234            }
235            ',' if depth == 0 => {
236                let (pt, name) = parse_param_with_name(s[start..i].trim())?;
237                result.push((name, pt));
238                start = i + 1;
239            }
240            _ => {}
241        }
242    }
243
244    if depth != 0 {
245        return Err(DecodeError::InvalidSignature(
246            "unbalanced parentheses".to_string(),
247        ));
248    }
249
250    let last = s[start..].trim();
251    if !last.is_empty() {
252        let (pt, name) = parse_param_with_name(last)?;
253        result.push((name, pt));
254    }
255
256    Ok(result)
257}
258
259/// Parse a single param that may include a name: `"address asset"` → (Address, Some("asset")).
260fn parse_param_with_name(s: &str) -> Result<(ParamType, Option<String>), DecodeError> {
261    let s = s.trim();
262
263    // For types ending with ')' or ']' (tuples, arrays), check for a name after
264    if let Some(pos) = s.rfind([')', ']']) {
265        let after = s[pos + 1..].trim();
266        if after.is_empty() {
267            return Ok((parse_param_type(s)?, None));
268        }
269        let type_str = &s[..pos + 1];
270        return Ok((parse_param_type(type_str)?, Some(after.to_string())));
271    }
272
273    // For simple types: split on first space
274    if let Some(space_pos) = s.find(' ') {
275        let type_str = &s[..space_pos];
276        let name = s[space_pos..].trim();
277        return Ok((parse_param_type(type_str)?, Some(name.to_string())));
278    }
279
280    // No space, no name
281    Ok((parse_param_type(s)?, None))
282}
283
284/// Parse a single param type string.
285fn parse_param_type(s: &str) -> Result<ParamType, DecodeError> {
286    let s = s.trim();
287
288    // Handle array suffixes: `type[]` or `type[N]`
289    if let Some(bracket_pos) = s.rfind('[') {
290        if s.ends_with(']') {
291            let inner_str = &s[..bracket_pos];
292            let size_str = &s[bracket_pos + 1..s.len() - 1];
293            let inner = parse_param_type(inner_str)?;
294
295            if size_str.is_empty() {
296                return Ok(ParamType::Array(Box::new(inner)));
297            } else {
298                let size: usize = size_str.parse().map_err(|_| {
299                    DecodeError::InvalidSignature(format!("invalid array size: {size_str}"))
300                })?;
301                return Ok(ParamType::FixedArray(Box::new(inner), size));
302            }
303        }
304    }
305
306    // Handle tuples: `(type1,type2,...)` — members may have names like `(uint256 value, uint256 deadline)`
307    if s.starts_with('(') && s.ends_with(')') {
308        let inner = &s[1..s.len() - 1];
309        let members = if inner.is_empty() {
310            vec![]
311        } else {
312            parse_param_list_with_names(inner)?
313        };
314        return Ok(ParamType::Tuple(members));
315    }
316
317    // Primitive types
318    match s {
319        "address" => Ok(ParamType::Address),
320        "bool" => Ok(ParamType::Bool),
321        "string" => Ok(ParamType::String),
322        "bytes" => Ok(ParamType::Bytes),
323        _ if s.starts_with("uint") => {
324            let bits = if s == "uint" {
325                256
326            } else {
327                s[4..].parse::<usize>().map_err(|_| {
328                    DecodeError::InvalidSignature(format!("invalid uint width: {s}"))
329                })?
330            };
331            Ok(ParamType::Uint(bits))
332        }
333        _ if s.starts_with("int") => {
334            let bits = if s == "int" {
335                256
336            } else {
337                s[3..]
338                    .parse::<usize>()
339                    .map_err(|_| DecodeError::InvalidSignature(format!("invalid int width: {s}")))?
340            };
341            Ok(ParamType::Int(bits))
342        }
343        _ if s.starts_with("bytes") => {
344            let size: usize = s[5..]
345                .parse()
346                .map_err(|_| DecodeError::InvalidSignature(format!("invalid bytes width: {s}")))?;
347            Ok(ParamType::FixedBytes(size))
348        }
349        _ => Err(DecodeError::InvalidSignature(format!("unknown type: {s}"))),
350    }
351}
352
353/// Build a canonical param string for selector computation.
354fn canonical_params(params: &[ParamType]) -> String {
355    params
356        .iter()
357        .map(canonical_param)
358        .collect::<Vec<_>>()
359        .join(",")
360}
361
362fn canonical_param(p: &ParamType) -> String {
363    match p {
364        ParamType::Address => "address".to_string(),
365        ParamType::Uint(bits) => format!("uint{bits}"),
366        ParamType::Int(bits) => format!("int{bits}"),
367        ParamType::Bool => "bool".to_string(),
368        ParamType::Bytes => "bytes".to_string(),
369        ParamType::FixedBytes(size) => format!("bytes{size}"),
370        ParamType::String => "string".to_string(),
371        ParamType::Array(inner) => format!("{}[]", canonical_param(inner)),
372        ParamType::FixedArray(inner, size) => format!("{}[{size}]", canonical_param(inner)),
373        ParamType::Tuple(members) => {
374            let inner = members
375                .iter()
376                .map(|(_, p)| canonical_param(p))
377                .collect::<Vec<_>>()
378                .join(",");
379            format!("({inner})")
380        }
381    }
382}
383
384/// Compute the 4-byte selector from a canonical function signature.
385fn selector_from_signature(canonical: &str) -> [u8; 4] {
386    let mut hasher = Keccak::v256();
387    hasher.update(canonical.as_bytes());
388    let mut hash = [0u8; 32];
389    hasher.finalize(&mut hash);
390    [hash[0], hash[1], hash[2], hash[3]]
391}
392
393/// Decode calldata using a parsed function signature.
394pub fn decode_calldata(
395    sig: &FunctionSignature,
396    calldata: &[u8],
397) -> Result<DecodedArguments, DecodeError> {
398    if calldata.len() < 4 {
399        return Err(DecodeError::CalldataTooShort {
400            expected: 4,
401            actual: calldata.len(),
402        });
403    }
404
405    let actual_selector = &calldata[..4];
406    if actual_selector != sig.selector {
407        return Err(DecodeError::SelectorMismatch {
408            expected: hex::encode(sig.selector),
409            actual: hex::encode(actual_selector),
410        });
411    }
412
413    let data = &calldata[4..];
414    let mut args = Vec::with_capacity(sig.params.len());
415
416    // Decode head section — top-level base is 0 (offsets relative to data start)
417    let mut offset = 0;
418    for (i, param) in sig.params.iter().enumerate() {
419        let value = decode_value(param, data, offset, 0)?;
420        args.push(DecodedArgument {
421            index: i,
422            name: sig.param_names.get(i).cloned().flatten(),
423            param_type: param.clone(),
424            value,
425        });
426        offset += param.head_size();
427    }
428
429    Ok(DecodedArguments {
430        function_name: sig.name.clone(),
431        selector: sig.selector,
432        args,
433    })
434}
435
436/// Decode a single value from ABI-encoded data.
437///
438/// `base_offset` is the start of the current encoding scope (0 at top level,
439/// tuple start inside tuples, array body start inside dynamic arrays).
440/// Dynamic-type offsets read from the head are **relative to `base_offset`**
441/// per the Solidity ABI spec.
442fn decode_value(
443    param: &ParamType,
444    data: &[u8],
445    head_offset: usize,
446    base_offset: usize,
447) -> Result<ArgumentValue, DecodeError> {
448    if param.is_dynamic() {
449        // Dynamic types: head contains offset relative to the current scope
450        let relative_offset = read_u256_as_usize(data, head_offset)?;
451        let absolute_offset = base_offset + relative_offset;
452        decode_value_at(param, data, absolute_offset)
453    } else {
454        decode_value_at(param, data, head_offset)
455    }
456}
457
458/// Decode a value at a specific byte offset.
459fn decode_value_at(
460    param: &ParamType,
461    data: &[u8],
462    offset: usize,
463) -> Result<ArgumentValue, DecodeError> {
464    ensure_bytes(data, offset, 32)?;
465
466    match param {
467        ParamType::Address => {
468            let word = &data[offset..offset + 32];
469            let mut addr = [0u8; 20];
470            addr.copy_from_slice(&word[12..32]);
471            Ok(ArgumentValue::Address(addr))
472        }
473        ParamType::Uint(_) | ParamType::Int(_) => {
474            let word = data[offset..offset + 32].to_vec();
475            if matches!(param, ParamType::Uint(_)) {
476                Ok(ArgumentValue::Uint(word))
477            } else {
478                Ok(ArgumentValue::Int(word))
479            }
480        }
481        ParamType::Bool => {
482            let b = data[offset + 31] != 0;
483            Ok(ArgumentValue::Bool(b))
484        }
485        ParamType::FixedBytes(size) => {
486            let bytes = data[offset..offset + size].to_vec();
487            Ok(ArgumentValue::FixedBytes(bytes))
488        }
489        ParamType::Bytes => {
490            let len = read_u256_as_usize(data, offset)?;
491            let start = offset + 32;
492            ensure_bytes(data, start, len)?;
493            Ok(ArgumentValue::Bytes(data[start..start + len].to_vec()))
494        }
495        ParamType::String => {
496            let len = read_u256_as_usize(data, offset)?;
497            let start = offset + 32;
498            ensure_bytes(data, start, len)?;
499            let s = std::str::from_utf8(&data[start..start + len])
500                .map_err(|e| DecodeError::InvalidEncoding(format!("invalid UTF-8: {e}")))?;
501            Ok(ArgumentValue::String(s.to_string()))
502        }
503        ParamType::Array(inner) => {
504            let len = read_u256_as_usize(data, offset)?;
505            let elements_start = offset + 32;
506            decode_array_elements(inner, data, elements_start, len, elements_start)
507        }
508        ParamType::FixedArray(inner, len) => {
509            decode_array_elements(inner, data, offset, *len, offset)
510        }
511        ParamType::Tuple(members) => {
512            let mut values = Vec::with_capacity(members.len());
513            let mut member_offset = offset;
514            // Tuple members' dynamic offsets are relative to the tuple's head start
515            for (name, member_type) in members {
516                let value = decode_value(member_type, data, member_offset, offset)?;
517                values.push((name.clone(), value));
518                member_offset += member_type.head_size();
519            }
520            Ok(ArgumentValue::Tuple(values))
521        }
522    }
523}
524
525fn decode_array_elements(
526    inner: &ParamType,
527    data: &[u8],
528    offset: usize,
529    len: usize,
530    base_offset: usize,
531) -> Result<ArgumentValue, DecodeError> {
532    let mut values = Vec::with_capacity(len);
533    let mut elem_offset = offset;
534    let step = inner.head_size();
535    for _ in 0..len {
536        let value = decode_value(inner, data, elem_offset, base_offset)?;
537        values.push(value);
538        elem_offset += step;
539    }
540    Ok(ArgumentValue::Array(values))
541}
542
543fn read_u256_as_usize(data: &[u8], offset: usize) -> Result<usize, DecodeError> {
544    ensure_bytes(data, offset, 32)?;
545    let word = &data[offset..offset + 32];
546    // Check that high bytes are zero (offset should fit in usize)
547    for &b in &word[..24] {
548        if b != 0 {
549            return Err(DecodeError::InvalidEncoding(
550                "offset too large for usize".to_string(),
551            ));
552        }
553    }
554    let mut bytes = [0u8; 8];
555    bytes.copy_from_slice(&word[24..32]);
556    Ok(u64::from_be_bytes(bytes) as usize)
557}
558
559fn ensure_bytes(data: &[u8], offset: usize, len: usize) -> Result<(), DecodeError> {
560    if offset + len > data.len() {
561        Err(DecodeError::CalldataTooShort {
562            expected: offset + len,
563            actual: data.len(),
564        })
565    } else {
566        Ok(())
567    }
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573
574    #[test]
575    fn test_parse_simple_signature() {
576        let sig = parse_signature("transfer(address,uint256)").unwrap();
577        assert_eq!(sig.name, "transfer");
578        assert_eq!(sig.params.len(), 2);
579        assert_eq!(sig.params[0], ParamType::Address);
580        assert_eq!(sig.params[1], ParamType::Uint(256));
581        assert_eq!(sig.canonical, "transfer(address,uint256)");
582    }
583
584    #[test]
585    fn test_parse_no_params() {
586        let sig = parse_signature("pause()").unwrap();
587        assert_eq!(sig.name, "pause");
588        assert!(sig.params.is_empty());
589    }
590
591    #[test]
592    fn test_parse_tuple_signature() {
593        let sig = parse_signature("foo((address,uint256),bool)").unwrap();
594        assert_eq!(sig.params.len(), 2);
595        assert_eq!(
596            sig.params[0],
597            ParamType::Tuple(vec![
598                (None, ParamType::Address),
599                (None, ParamType::Uint(256))
600            ])
601        );
602        assert_eq!(sig.params[1], ParamType::Bool);
603    }
604
605    #[test]
606    fn test_parse_named_tuple_members() {
607        let sig = parse_signature("foo((uint256 value, uint256 deadline) permit)").unwrap();
608        assert_eq!(sig.params.len(), 1);
609        assert_eq!(sig.param_names[0], Some("permit".to_string()));
610        if let ParamType::Tuple(members) = &sig.params[0] {
611            assert_eq!(members.len(), 2);
612            assert_eq!(members[0].0, Some("value".to_string()));
613            assert_eq!(members[0].1, ParamType::Uint(256));
614            assert_eq!(members[1].0, Some("deadline".to_string()));
615            assert_eq!(members[1].1, ParamType::Uint(256));
616        } else {
617            panic!("expected Tuple");
618        }
619    }
620
621    #[test]
622    fn test_canonical_strips_tuple_names() {
623        let named = parse_signature("foo((uint256 value, uint256 deadline) permit)").unwrap();
624        let unnamed = parse_signature("foo((uint256,uint256))").unwrap();
625        assert_eq!(named.selector, unnamed.selector);
626        assert_eq!(named.canonical, unnamed.canonical);
627    }
628
629    #[test]
630    fn test_parse_array_types() {
631        let sig = parse_signature("foo(uint256[],address[3])").unwrap();
632        assert_eq!(
633            sig.params[0],
634            ParamType::Array(Box::new(ParamType::Uint(256)))
635        );
636        assert_eq!(
637            sig.params[1],
638            ParamType::FixedArray(Box::new(ParamType::Address), 3)
639        );
640    }
641
642    #[test]
643    fn test_selector_computation() {
644        // transfer(address,uint256) selector = 0xa9059cbb
645        let sig = parse_signature("transfer(address,uint256)").unwrap();
646        assert_eq!(hex::encode(sig.selector), "a9059cbb");
647    }
648
649    #[test]
650    fn test_decode_transfer_calldata() {
651        let sig = parse_signature("transfer(address,uint256)").unwrap();
652
653        let mut calldata = Vec::new();
654        calldata.extend_from_slice(&sig.selector);
655        // address: 0x000...0001
656        let mut addr_word = [0u8; 32];
657        addr_word[31] = 1;
658        calldata.extend_from_slice(&addr_word);
659        // uint256: 1000
660        let mut amount_word = [0u8; 32];
661        amount_word[30] = 0x03;
662        amount_word[31] = 0xe8;
663        calldata.extend_from_slice(&amount_word);
664
665        let decoded = decode_calldata(&sig, &calldata).unwrap();
666        assert_eq!(decoded.function_name, "transfer");
667        assert_eq!(decoded.args.len(), 2);
668
669        if let ArgumentValue::Address(addr) = &decoded.args[0].value {
670            assert_eq!(addr[19], 1);
671        } else {
672            panic!("expected Address");
673        }
674
675        if let ArgumentValue::Uint(bytes) = &decoded.args[1].value {
676            assert_eq!(bytes[30], 0x03);
677            assert_eq!(bytes[31], 0xe8);
678        } else {
679            panic!("expected Uint");
680        }
681    }
682
683    #[test]
684    fn test_decode_bool() {
685        let sig = parse_signature("setApproval(bool)").unwrap();
686        let mut calldata = Vec::new();
687        calldata.extend_from_slice(&sig.selector);
688        let mut word = [0u8; 32];
689        word[31] = 1;
690        calldata.extend_from_slice(&word);
691
692        let decoded = decode_calldata(&sig, &calldata).unwrap();
693        if let ArgumentValue::Bool(b) = decoded.args[0].value {
694            assert!(b);
695        } else {
696            panic!("expected Bool");
697        }
698    }
699
700    #[test]
701    fn test_selector_mismatch() {
702        let sig = parse_signature("transfer(address,uint256)").unwrap();
703        let calldata = [0u8; 36]; // wrong selector (all zeros)
704        let result = decode_calldata(&sig, &calldata);
705        assert!(result.is_err());
706    }
707
708    #[test]
709    fn test_parse_all_basic_types() {
710        let sig = parse_signature("f(address,uint256,int128,bool,bytes,bytes32,string)").unwrap();
711        assert_eq!(sig.params[0], ParamType::Address);
712        assert_eq!(sig.params[1], ParamType::Uint(256));
713        assert_eq!(sig.params[2], ParamType::Int(128));
714        assert_eq!(sig.params[3], ParamType::Bool);
715        assert_eq!(sig.params[4], ParamType::Bytes);
716        assert_eq!(sig.params[5], ParamType::FixedBytes(32));
717        assert_eq!(sig.params[6], ParamType::String);
718    }
719
720    #[test]
721    fn test_default_uint_int() {
722        let sig = parse_signature("f(uint,int)").unwrap();
723        assert_eq!(sig.params[0], ParamType::Uint(256));
724        assert_eq!(sig.params[1], ParamType::Int(256));
725    }
726
727    #[test]
728    fn test_parse_named_params() {
729        let sig = parse_signature(
730            "deposit(address asset,uint256 amount,address onBehalfOf,uint16 referralCode)",
731        )
732        .unwrap();
733        assert_eq!(sig.name, "deposit");
734        assert_eq!(sig.params.len(), 4);
735        assert_eq!(sig.params[0], ParamType::Address);
736        assert_eq!(sig.params[1], ParamType::Uint(256));
737        assert_eq!(sig.params[2], ParamType::Address);
738        assert_eq!(sig.params[3], ParamType::Uint(16));
739        assert_eq!(
740            sig.param_names,
741            vec![
742                Some("asset".to_string()),
743                Some("amount".to_string()),
744                Some("onBehalfOf".to_string()),
745                Some("referralCode".to_string()),
746            ]
747        );
748        // Canonical form strips names
749        assert_eq!(sig.canonical, "deposit(address,uint256,address,uint16)");
750    }
751
752    #[test]
753    fn test_parse_mixed_named_unnamed() {
754        let sig = parse_signature("f(address,uint256 amount)").unwrap();
755        assert_eq!(sig.param_names, vec![None, Some("amount".to_string())]);
756    }
757
758    #[test]
759    fn test_named_params_selector_unchanged() {
760        let named = parse_signature(
761            "deposit(address asset,uint256 amount,address onBehalfOf,uint16 referralCode)",
762        )
763        .unwrap();
764        let unnamed = parse_signature("deposit(address,uint256,address,uint16)").unwrap();
765        assert_eq!(named.selector, unnamed.selector);
766        assert_eq!(named.canonical, unnamed.canonical);
767    }
768}