Skip to main content

foundry_common/
serde_helpers.rs

1//! Misc Serde helpers for foundry crates.
2
3use alloy_primitives::{U64, U256};
4use serde::{Deserialize, Deserializer, de};
5use std::str::FromStr;
6
7/// Helper type to parse both `u64` and `U256`
8#[derive(Copy, Clone, Deserialize)]
9#[serde(untagged)]
10pub enum Numeric {
11    /// A [U256] value.
12    U256(U256),
13    /// A `u64` value.
14    Num(u64),
15}
16
17impl From<Numeric> for U256 {
18    fn from(n: Numeric) -> Self {
19        match n {
20            Numeric::U256(n) => n,
21            Numeric::Num(n) => Self::from(n),
22        }
23    }
24}
25
26impl FromStr for Numeric {
27    type Err = String;
28
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        if let Ok(val) = s.parse::<u128>() {
31            Ok(Self::U256(U256::from(val)))
32        } else if s.starts_with("0x") {
33            U256::from_str_radix(s, 16).map(Numeric::U256).map_err(|err| err.to_string())
34        } else {
35            U256::from_str(s).map(Numeric::U256).map_err(|err| err.to_string())
36        }
37    }
38}
39
40/// Helper type to parse a `u64` from a JSON number or a hex/decimal string.
41#[derive(Copy, Clone, Deserialize)]
42#[serde(untagged)]
43pub enum Numeric64 {
44    /// A JSON number.
45    Num(u64),
46    /// A hex or decimal string.
47    U64(U64),
48}
49
50impl From<Numeric64> for u64 {
51    fn from(n: Numeric64) -> Self {
52        match n {
53            Numeric64::Num(n) => n,
54            Numeric64::U64(n) => n.to::<Self>(),
55        }
56    }
57}
58
59/// Helper type to deserialize sequence of numbers
60#[derive(Deserialize)]
61#[serde(untagged)]
62pub enum NumericSeq {
63    /// Single parameter sequence (e.g `[1]`).
64    Seq([Numeric; 1]),
65    /// `U256`.
66    U256(U256),
67    /// Native `u64`.
68    Num(u64),
69}
70
71/// Helper type to deserialize a single `u64` from either a direct value or a one-element sequence.
72#[derive(Deserialize)]
73#[serde(untagged)]
74pub enum Numeric64ValueOrSeq {
75    /// Single parameter sequence (e.g `[1]`).
76    Seq([Numeric64; 1]),
77    /// Single value.
78    Value(Numeric64),
79}
80
81/// Deserializes a number from hex or int
82pub fn deserialize_number<'de, D>(deserializer: D) -> Result<U256, D::Error>
83where
84    D: Deserializer<'de>,
85{
86    Numeric::deserialize(deserializer).map(Into::into)
87}
88
89/// Deserializes a number from hex or int, but optionally
90pub fn deserialize_number_opt<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
91where
92    D: Deserializer<'de>,
93{
94    let num = match Option::<Numeric>::deserialize(deserializer)? {
95        Some(Numeric::U256(n)) => Some(n),
96        Some(Numeric::Num(n)) => Some(U256::from(n)),
97        _ => None,
98    };
99
100    Ok(num)
101}
102
103/// Deserializes single integer params: `1, [1], ["0x01"]`
104pub fn deserialize_number_seq<'de, D>(deserializer: D) -> Result<U256, D::Error>
105where
106    D: Deserializer<'de>,
107{
108    let num = match NumericSeq::deserialize(deserializer)? {
109        NumericSeq::Seq(seq) => seq[0].into(),
110        NumericSeq::U256(n) => n,
111        NumericSeq::Num(n) => U256::from(n),
112    };
113
114    Ok(num)
115}
116
117/// Deserializes single `u64` params: `1, [1], ["0x01"]`.
118pub fn deserialize_u64_seq<'de, D>(deserializer: D) -> Result<u64, D::Error>
119where
120    D: Deserializer<'de>,
121{
122    let num = match Numeric64ValueOrSeq::deserialize(deserializer)? {
123        Numeric64ValueOrSeq::Seq(seq) => seq[0].into(),
124        Numeric64ValueOrSeq::Value(num) => num.into(),
125    };
126
127    Ok(num)
128}
129
130/// Deserializes an optional integer from a single-element params sequence.
131/// Accepts `[]`, `[null]`, `[n]`, `["0x.."]`.
132pub fn deserialize_u64_seq_opt<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
133where
134    D: Deserializer<'de>,
135{
136    let seq = Vec::<Option<Numeric64>>::deserialize(deserializer)?;
137    if seq.len() > 1 {
138        return Err(de::Error::custom(format!(
139            "expected params sequence with length 0 or 1 but got {}",
140            seq.len()
141        )));
142    }
143    Ok(seq.into_iter().next().flatten().map(Into::into))
144}
145
146pub mod duration {
147    use serde::{Deserialize, Deserializer};
148    use std::time::Duration;
149
150    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
151    where
152        S: serde::Serializer,
153    {
154        let d = jiff::SignedDuration::try_from(*duration).map_err(serde::ser::Error::custom)?;
155        serializer.serialize_str(&format!("{d:#}"))
156    }
157
158    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
159    where
160        D: Deserializer<'de>,
161    {
162        let s = String::deserialize(deserializer)?;
163        let d = s.parse::<jiff::SignedDuration>().map_err(serde::de::Error::custom)?;
164        d.try_into().map_err(serde::de::Error::custom)
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use serde::de::IntoDeserializer;
172    use serde_json::json;
173
174    fn parse_u64_param(value: serde_json::Value) -> Result<u64, serde_json::Error> {
175        deserialize_u64_seq(value.into_deserializer())
176    }
177
178    fn parse_optional_u64_param(
179        value: serde_json::Value,
180    ) -> Result<Option<u64>, serde_json::Error> {
181        deserialize_u64_seq_opt(value.into_deserializer())
182    }
183
184    #[test]
185    fn deserialize_u64_seq_accepts_single_param_sequence_and_direct_value() {
186        let valid_values = [
187            json!([100]),
188            json!(100),
189            json!(["0x64"]),
190            json!("0x64"),
191            json!(["100"]),
192            json!("100"),
193        ];
194        for value in valid_values {
195            assert_eq!(parse_u64_param(value).unwrap(), 100);
196        }
197
198        for value in [json!([u64::MAX]), json!(u64::MAX)] {
199            assert_eq!(parse_u64_param(value).unwrap(), u64::MAX);
200        }
201    }
202
203    #[test]
204    fn deserialize_u64_seq_rejects_invalid_shape_and_overflow() {
205        for value in [
206            json!([]),
207            json!([1, 2]),
208            json!([null]),
209            json!(null),
210            json!(["0x10000000000000000"]),
211            json!("0x10000000000000000"),
212            json!(["18446744073709551616"]),
213            json!("18446744073709551616"),
214        ] {
215            assert!(parse_u64_param(value).is_err());
216        }
217    }
218
219    #[test]
220    fn deserialize_u64_seq_opt_accepts_empty_null_and_single_param_sequence() {
221        for value in [json!([]), json!([null])] {
222            assert_eq!(parse_optional_u64_param(value).unwrap(), None);
223        }
224
225        for value in [json!([100]), json!(["0x64"]), json!(["100"])] {
226            assert_eq!(parse_optional_u64_param(value).unwrap(), Some(100));
227        }
228
229        assert_eq!(parse_optional_u64_param(json!([u64::MAX])).unwrap(), Some(u64::MAX));
230    }
231
232    #[test]
233    fn deserialize_u64_seq_opt_rejects_invalid_shape_and_overflow() {
234        for value in [
235            json!([1, 2]),
236            json!(100),
237            json!("0x64"),
238            json!([["0x64"]]),
239            json!(["0x10000000000000000"]),
240            json!(["18446744073709551616"]),
241        ] {
242            assert!(parse_optional_u64_param(value).is_err());
243        }
244    }
245}