Skip to main content

chaincodec_core/
call.rs

1//! Types for decoded function calls and constructor invocations.
2//!
3//! These are the output types when decoding transaction calldata
4//! (as opposed to event logs, which produce `DecodedEvent`).
5
6use crate::types::NormalizedValue;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// Result of decoding a function call's calldata.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct DecodedCall {
13    /// Function name (e.g. "transfer", "swap")
14    pub function_name: String,
15    /// First 4 bytes of calldata (keccak256 of signature)
16    pub selector: Option<[u8; 4]>,
17    /// Decoded input parameters in declaration order
18    pub inputs: Vec<(String, NormalizedValue)>,
19    /// Raw calldata bytes (including selector)
20    pub raw_data: Vec<u8>,
21    /// Fields that failed to decode (field_name → error message)
22    pub decode_errors: HashMap<String, String>,
23}
24
25impl DecodedCall {
26    /// Selector as a hex string ("0xaabbccdd")
27    pub fn selector_hex(&self) -> Option<String> {
28        self.selector
29            .map(|s| format!("0x{}", hex::encode(s)))
30    }
31
32    /// Look up a decoded input by name
33    pub fn input(&self, name: &str) -> Option<&NormalizedValue> {
34        self.inputs
35            .iter()
36            .find(|(n, _)| n == name)
37            .map(|(_, v)| v)
38    }
39
40    /// Returns true if all inputs decoded without error
41    pub fn is_clean(&self) -> bool {
42        self.decode_errors.is_empty()
43    }
44}
45
46/// Result of decoding constructor calldata (no function selector).
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct DecodedConstructor {
49    /// Decoded constructor arguments in declaration order
50    pub args: Vec<(String, NormalizedValue)>,
51    /// Raw constructor calldata
52    pub raw_data: Vec<u8>,
53    /// Decode errors
54    pub decode_errors: HashMap<String, String>,
55}
56
57impl DecodedConstructor {
58    /// Look up a decoded arg by name
59    pub fn arg(&self, name: &str) -> Option<&NormalizedValue> {
60        self.args
61            .iter()
62            .find(|(n, _)| n == name)
63            .map(|(_, v)| v)
64    }
65}
66
67/// A human-readable representation of a decoded call or event.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct HumanReadable {
70    /// e.g. "ERC20.transfer(to=0x..., amount=1000000)"
71    pub summary: String,
72    /// e.g. "Transfer 1000000 USDC to 0x..."
73    pub description: Option<String>,
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn selector_hex_format() {
82        let call = DecodedCall {
83            function_name: "transfer".into(),
84            selector: Some([0xa9, 0x05, 0x9c, 0xbb]),
85            inputs: vec![],
86            raw_data: vec![],
87            decode_errors: HashMap::new(),
88        };
89        assert_eq!(call.selector_hex(), Some("0xa9059cbb".to_string()));
90    }
91
92    #[test]
93    fn input_lookup() {
94        let call = DecodedCall {
95            function_name: "transfer".into(),
96            selector: None,
97            inputs: vec![
98                ("to".into(), NormalizedValue::Address("0xabc".into())),
99                ("amount".into(), NormalizedValue::Uint(1000)),
100            ],
101            raw_data: vec![],
102            decode_errors: HashMap::new(),
103        };
104        assert!(call.input("to").is_some());
105        assert!(call.input("nonexistent").is_none());
106    }
107}