foundry_common_fmt/
dynamic.rs

1use super::{format_int_exp, format_uint_exp};
2use alloy_dyn_abi::{DynSolType, DynSolValue};
3use alloy_primitives::hex;
4use std::fmt;
5
6/// [`DynSolValue`] formatter.
7struct DynValueFormatter {
8    raw: bool,
9}
10
11impl DynValueFormatter {
12    /// Recursively formats a [`DynSolValue`].
13    fn value(&self, value: &DynSolValue, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        match value {
15            DynSolValue::Address(inner) => write!(f, "{inner}"),
16            DynSolValue::Function(inner) => write!(f, "{inner}"),
17            DynSolValue::Bytes(inner) => f.write_str(&hex::encode_prefixed(inner)),
18            DynSolValue::FixedBytes(word, size) => {
19                f.write_str(&hex::encode_prefixed(&word[..*size]))
20            }
21            DynSolValue::Uint(inner, _) => {
22                if self.raw {
23                    write!(f, "{inner}")
24                } else {
25                    f.write_str(&format_uint_exp(*inner))
26                }
27            }
28            DynSolValue::Int(inner, _) => {
29                if self.raw {
30                    write!(f, "{inner}")
31                } else {
32                    f.write_str(&format_int_exp(*inner))
33                }
34            }
35            DynSolValue::Array(values) | DynSolValue::FixedArray(values) => {
36                f.write_str("[")?;
37                self.list(values, f)?;
38                f.write_str("]")
39            }
40            DynSolValue::Tuple(values) => self.tuple(values, f),
41            DynSolValue::String(inner) => {
42                if self.raw {
43                    write!(f, "{}", inner.escape_debug())
44                } else {
45                    write!(f, "{inner:?}") // escape strings
46                }
47            }
48            DynSolValue::Bool(inner) => write!(f, "{inner}"),
49            DynSolValue::CustomStruct { name, prop_names, tuple } => {
50                if self.raw {
51                    return self.tuple(tuple, f);
52                }
53
54                f.write_str(name)?;
55
56                if prop_names.len() == tuple.len() {
57                    f.write_str("({ ")?;
58
59                    for (i, (prop_name, value)) in std::iter::zip(prop_names, tuple).enumerate() {
60                        if i > 0 {
61                            f.write_str(", ")?;
62                        }
63                        f.write_str(prop_name)?;
64                        f.write_str(": ")?;
65                        self.value(value, f)?;
66                    }
67
68                    f.write_str(" })")
69                } else {
70                    self.tuple(tuple, f)
71                }
72            }
73        }
74    }
75
76    /// Recursively formats a comma-separated list of [`DynSolValue`]s.
77    fn list(&self, values: &[DynSolValue], f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        for (i, value) in values.iter().enumerate() {
79            if i > 0 {
80                f.write_str(", ")?;
81            }
82            self.value(value, f)?;
83        }
84        Ok(())
85    }
86
87    /// Formats the given values as a tuple.
88    fn tuple(&self, values: &[DynSolValue], f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        f.write_str("(")?;
90        self.list(values, f)?;
91        f.write_str(")")
92    }
93}
94
95/// Wrapper that implements [`Display`](fmt::Display) for a [`DynSolValue`].
96struct DynValueDisplay<'a> {
97    /// The value to display.
98    value: &'a DynSolValue,
99    /// The formatter.
100    formatter: DynValueFormatter,
101}
102
103impl<'a> DynValueDisplay<'a> {
104    /// Creates a new [`Display`](fmt::Display) wrapper for the given value.
105    #[inline]
106    fn new(value: &'a DynSolValue, raw: bool) -> Self {
107        Self { value, formatter: DynValueFormatter { raw } }
108    }
109}
110
111impl fmt::Display for DynValueDisplay<'_> {
112    #[inline]
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        self.formatter.value(self.value, f)
115    }
116}
117
118/// Parses string input as Token against the expected ParamType
119pub fn parse_tokens<'a, I: IntoIterator<Item = (&'a DynSolType, &'a str)>>(
120    params: I,
121) -> alloy_dyn_abi::Result<Vec<DynSolValue>> {
122    params.into_iter().map(|(param, value)| DynSolType::coerce_str(param, value)).collect()
123}
124
125/// Pretty-prints a slice of tokens using [`format_token`].
126pub fn format_tokens(tokens: &[DynSolValue]) -> impl Iterator<Item = String> + '_ {
127    tokens.iter().map(format_token)
128}
129
130/// Pretty-prints a slice of tokens using [`format_token_raw`].
131pub fn format_tokens_raw(tokens: &[DynSolValue]) -> impl Iterator<Item = String> + '_ {
132    tokens.iter().map(format_token_raw)
133}
134
135/// Pretty-prints the given value into a string suitable for user output.
136pub fn format_token(value: &DynSolValue) -> String {
137    DynValueDisplay::new(value, false).to_string()
138}
139
140/// Pretty-prints the given value into a string suitable for re-parsing as values later.
141///
142/// This means:
143/// - integers are not formatted with exponential notation hints
144/// - structs are formatted as tuples, losing the struct and property names
145pub fn format_token_raw(value: &DynSolValue) -> String {
146    DynValueDisplay::new(value, true).to_string()
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use alloy_primitives::{address, U256};
153
154    #[test]
155    fn parse_hex_uint() {
156        let ty = DynSolType::Uint(256);
157
158        let values = parse_tokens(std::iter::once((&ty, "100"))).unwrap();
159        assert_eq!(values, [DynSolValue::Uint(U256::from(100), 256)]);
160
161        let val: U256 = U256::from(100u64);
162        let hex_val = format!("0x{val:x}");
163        let values = parse_tokens(std::iter::once((&ty, hex_val.as_str()))).unwrap();
164        assert_eq!(values, [DynSolValue::Uint(U256::from(100), 256)]);
165    }
166
167    #[test]
168    fn format_addr() {
169        // copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
170        assert_eq!(
171            format_token(&DynSolValue::Address(address!(
172                "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
173            ))),
174            "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
175        );
176
177        // copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1191.md
178        assert_ne!(
179            format_token(&DynSolValue::Address(address!(
180                "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359"
181            ))),
182            "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359"
183        );
184    }
185}