foundry_common_fmt/
dynamic.rs

1use super::{format_int_exp, format_uint_exp};
2use alloy_dyn_abi::{DynSolType, DynSolValue};
3use alloy_primitives::hex;
4use eyre::Result;
5use serde_json::Value;
6use std::fmt;
7
8/// [`DynSolValue`] formatter.
9struct DynValueFormatter {
10    raw: bool,
11}
12
13impl DynValueFormatter {
14    /// Recursively formats a [`DynSolValue`].
15    fn value(&self, value: &DynSolValue, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        match value {
17            DynSolValue::Address(inner) => write!(f, "{inner}"),
18            DynSolValue::Function(inner) => write!(f, "{inner}"),
19            DynSolValue::Bytes(inner) => f.write_str(&hex::encode_prefixed(inner)),
20            DynSolValue::FixedBytes(word, size) => {
21                f.write_str(&hex::encode_prefixed(&word[..*size]))
22            }
23            DynSolValue::Uint(inner, _) => {
24                if self.raw {
25                    write!(f, "{inner}")
26                } else {
27                    f.write_str(&format_uint_exp(*inner))
28                }
29            }
30            DynSolValue::Int(inner, _) => {
31                if self.raw {
32                    write!(f, "{inner}")
33                } else {
34                    f.write_str(&format_int_exp(*inner))
35                }
36            }
37            DynSolValue::Array(values) | DynSolValue::FixedArray(values) => {
38                f.write_str("[")?;
39                self.list(values, f)?;
40                f.write_str("]")
41            }
42            DynSolValue::Tuple(values) => self.tuple(values, f),
43            DynSolValue::String(inner) => {
44                if self.raw {
45                    write!(f, "{}", inner.escape_debug())
46                } else {
47                    write!(f, "{inner:?}") // escape strings
48                }
49            }
50            DynSolValue::Bool(inner) => write!(f, "{inner}"),
51            DynSolValue::CustomStruct { name, prop_names, tuple } => {
52                if self.raw {
53                    return self.tuple(tuple, f);
54                }
55
56                f.write_str(name)?;
57
58                if prop_names.len() == tuple.len() {
59                    f.write_str("({ ")?;
60
61                    for (i, (prop_name, value)) in std::iter::zip(prop_names, tuple).enumerate() {
62                        if i > 0 {
63                            f.write_str(", ")?;
64                        }
65                        f.write_str(prop_name)?;
66                        f.write_str(": ")?;
67                        self.value(value, f)?;
68                    }
69
70                    f.write_str(" })")
71                } else {
72                    self.tuple(tuple, f)
73                }
74            }
75        }
76    }
77
78    /// Recursively formats a comma-separated list of [`DynSolValue`]s.
79    fn list(&self, values: &[DynSolValue], f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        for (i, value) in values.iter().enumerate() {
81            if i > 0 {
82                f.write_str(", ")?;
83            }
84            self.value(value, f)?;
85        }
86        Ok(())
87    }
88
89    /// Formats the given values as a tuple.
90    fn tuple(&self, values: &[DynSolValue], f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.write_str("(")?;
92        self.list(values, f)?;
93        f.write_str(")")
94    }
95}
96
97/// Wrapper that implements [`Display`](fmt::Display) for a [`DynSolValue`].
98struct DynValueDisplay<'a> {
99    /// The value to display.
100    value: &'a DynSolValue,
101    /// The formatter.
102    formatter: DynValueFormatter,
103}
104
105impl<'a> DynValueDisplay<'a> {
106    /// Creates a new [`Display`](fmt::Display) wrapper for the given value.
107    fn new(value: &'a DynSolValue, raw: bool) -> Self {
108        Self { value, formatter: DynValueFormatter { raw } }
109    }
110}
111
112impl fmt::Display for DynValueDisplay<'_> {
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/// Serializes given [DynSolValue] into a [serde_json::Value].
150pub fn serialize_value_as_json(value: DynSolValue) -> Result<Value> {
151    match value {
152        DynSolValue::Bool(b) => Ok(Value::Bool(b)),
153        DynSolValue::String(s) => {
154            // Strings are allowed to contain stringified JSON objects, so we try to parse it like
155            // one first.
156            if let Ok(map) = serde_json::from_str(&s) {
157                Ok(Value::Object(map))
158            } else {
159                Ok(Value::String(s))
160            }
161        }
162        DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))),
163        DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))),
164        DynSolValue::Int(i, _) => {
165            if let Ok(n) = i64::try_from(i) {
166                // Use `serde_json::Number` if the number can be accurately represented.
167                Ok(Value::Number(n.into()))
168            } else {
169                // Otherwise, fallback to its string representation to preserve precision and ensure
170                // compatibility with alloy's `DynSolType` coercion.
171                Ok(Value::String(i.to_string()))
172            }
173        }
174        DynSolValue::Uint(i, _) => {
175            if let Ok(n) = u64::try_from(i) {
176                // Use `serde_json::Number` if the number can be accurately represented.
177                Ok(Value::Number(n.into()))
178            } else {
179                // Otherwise, fallback to its string representation to preserve precision and ensure
180                // compatibility with alloy's `DynSolType` coercion.
181                Ok(Value::String(i.to_string()))
182            }
183        }
184        DynSolValue::Address(a) => Ok(Value::String(a.to_string())),
185        DynSolValue::Array(e) | DynSolValue::FixedArray(e) => {
186            Ok(Value::Array(e.into_iter().map(serialize_value_as_json).collect::<Result<_>>()?))
187        }
188        DynSolValue::CustomStruct { name: _, prop_names, tuple } => {
189            let values =
190                tuple.into_iter().map(serialize_value_as_json).collect::<Result<Vec<_>>>()?;
191            let map = prop_names.into_iter().zip(values).collect();
192
193            Ok(Value::Object(map))
194        }
195        DynSolValue::Tuple(values) => Ok(Value::Array(
196            values.into_iter().map(serialize_value_as_json).collect::<Result<_>>()?,
197        )),
198        DynSolValue::Function(_) => eyre::bail!("cannot serialize function pointer"),
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use alloy_primitives::{U256, address};
206
207    #[test]
208    fn parse_hex_uint() {
209        let ty = DynSolType::Uint(256);
210
211        let values = parse_tokens(std::iter::once((&ty, "100"))).unwrap();
212        assert_eq!(values, [DynSolValue::Uint(U256::from(100), 256)]);
213
214        let val: U256 = U256::from(100u64);
215        let hex_val = format!("0x{val:x}");
216        let values = parse_tokens(std::iter::once((&ty, hex_val.as_str()))).unwrap();
217        assert_eq!(values, [DynSolValue::Uint(U256::from(100), 256)]);
218    }
219
220    #[test]
221    fn format_addr() {
222        // copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
223        assert_eq!(
224            format_token(&DynSolValue::Address(address!(
225                "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
226            ))),
227            "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
228        );
229
230        // copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1191.md
231        assert_ne!(
232            format_token(&DynSolValue::Address(address!(
233                "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359"
234            ))),
235            "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359"
236        );
237    }
238}