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/// An enum that represents either a [serde_json::Number] integer, or a hex [U256].
60#[derive(Debug, Deserialize)]
61#[serde(untagged)]
62pub enum NumberOrHexU256 {
63    /// An integer
64    Int(serde_json::Number),
65    /// A hex U256
66    Hex(U256),
67}
68
69impl NumberOrHexU256 {
70    /// Tries to convert this into a [U256]].
71    pub fn try_into_u256<E: de::Error>(self) -> Result<U256, E> {
72        match self {
73            Self::Int(num) => U256::from_str(&num.to_string()).map_err(E::custom),
74            Self::Hex(val) => Ok(val),
75        }
76    }
77}
78
79/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with
80/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number).
81pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result<U256, D::Error>
82where
83    D: Deserializer<'de>,
84{
85    NumberOrHexU256::deserialize(deserializer)?.try_into_u256()
86}
87
88/// Helper type to deserialize sequence of numbers
89#[derive(Deserialize)]
90#[serde(untagged)]
91pub enum NumericSeq {
92    /// Single parameter sequence (e.g `[1]`).
93    Seq([Numeric; 1]),
94    /// `U256`.
95    U256(U256),
96    /// Native `u64`.
97    Num(u64),
98}
99
100/// Helper type to deserialize a single `u64` from either a direct value or a one-element sequence.
101#[derive(Deserialize)]
102#[serde(untagged)]
103pub enum Numeric64ValueOrSeq {
104    /// Single parameter sequence (e.g `[1]`).
105    Seq([Numeric64; 1]),
106    /// Single value.
107    Value(Numeric64),
108}
109
110/// Deserializes a number from hex or int
111pub fn deserialize_number<'de, D>(deserializer: D) -> Result<U256, D::Error>
112where
113    D: Deserializer<'de>,
114{
115    Numeric::deserialize(deserializer).map(Into::into)
116}
117
118/// Deserializes a number from hex or int, but optionally
119pub fn deserialize_number_opt<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
120where
121    D: Deserializer<'de>,
122{
123    let num = match Option::<Numeric>::deserialize(deserializer)? {
124        Some(Numeric::U256(n)) => Some(n),
125        Some(Numeric::Num(n)) => Some(U256::from(n)),
126        _ => None,
127    };
128
129    Ok(num)
130}
131
132/// Deserializes single integer params: `1, [1], ["0x01"]`
133pub fn deserialize_number_seq<'de, D>(deserializer: D) -> Result<U256, D::Error>
134where
135    D: Deserializer<'de>,
136{
137    let num = match NumericSeq::deserialize(deserializer)? {
138        NumericSeq::Seq(seq) => seq[0].into(),
139        NumericSeq::U256(n) => n,
140        NumericSeq::Num(n) => U256::from(n),
141    };
142
143    Ok(num)
144}
145
146/// Deserializes single `u64` params: `1, [1], ["0x01"]`.
147pub fn deserialize_u64_seq<'de, D>(deserializer: D) -> Result<u64, D::Error>
148where
149    D: Deserializer<'de>,
150{
151    let num = match Numeric64ValueOrSeq::deserialize(deserializer)? {
152        Numeric64ValueOrSeq::Seq(seq) => seq[0].into(),
153        Numeric64ValueOrSeq::Value(num) => num.into(),
154    };
155
156    Ok(num)
157}
158
159/// Deserializes an optional integer from a single-element params sequence.
160/// Accepts `[]`, `[null]`, `[n]`, `["0x.."]`.
161pub fn deserialize_u64_seq_opt<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
162where
163    D: Deserializer<'de>,
164{
165    let seq = Vec::<Option<Numeric64>>::deserialize(deserializer)?;
166    if seq.len() > 1 {
167        return Err(de::Error::custom(format!(
168            "expected params sequence with length 0 or 1 but got {}",
169            seq.len()
170        )));
171    }
172    Ok(seq.into_iter().next().flatten().map(Into::into))
173}
174
175pub mod duration {
176    use serde::{Deserialize, Deserializer};
177    use std::time::Duration;
178
179    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
180    where
181        S: serde::Serializer,
182    {
183        let d = jiff::SignedDuration::try_from(*duration).map_err(serde::ser::Error::custom)?;
184        serializer.serialize_str(&format!("{d:#}"))
185    }
186
187    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
188    where
189        D: Deserializer<'de>,
190    {
191        let s = String::deserialize(deserializer)?;
192        let d = s.parse::<jiff::SignedDuration>().map_err(serde::de::Error::custom)?;
193        d.try_into().map_err(serde::de::Error::custom)
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use serde::de::IntoDeserializer;
201    use serde_json::json;
202
203    fn parse_u64_param(value: serde_json::Value) -> Result<u64, serde_json::Error> {
204        deserialize_u64_seq(value.into_deserializer())
205    }
206
207    fn parse_optional_u64_param(
208        value: serde_json::Value,
209    ) -> Result<Option<u64>, serde_json::Error> {
210        deserialize_u64_seq_opt(value.into_deserializer())
211    }
212
213    #[test]
214    fn deserialize_u64_seq_accepts_single_param_sequence_and_direct_value() {
215        let valid_values = [
216            json!([100]),
217            json!(100),
218            json!(["0x64"]),
219            json!("0x64"),
220            json!(["100"]),
221            json!("100"),
222        ];
223        for value in valid_values {
224            assert_eq!(parse_u64_param(value).unwrap(), 100);
225        }
226
227        for value in [json!([u64::MAX]), json!(u64::MAX)] {
228            assert_eq!(parse_u64_param(value).unwrap(), u64::MAX);
229        }
230    }
231
232    #[test]
233    fn deserialize_u64_seq_rejects_invalid_shape_and_overflow() {
234        for value in [
235            json!([]),
236            json!([1, 2]),
237            json!([null]),
238            json!(null),
239            json!(["0x10000000000000000"]),
240            json!("0x10000000000000000"),
241            json!(["18446744073709551616"]),
242            json!("18446744073709551616"),
243        ] {
244            assert!(parse_u64_param(value).is_err());
245        }
246    }
247
248    #[test]
249    fn deserialize_u64_seq_opt_accepts_empty_null_and_single_param_sequence() {
250        for value in [json!([]), json!([null])] {
251            assert_eq!(parse_optional_u64_param(value).unwrap(), None);
252        }
253
254        for value in [json!([100]), json!(["0x64"]), json!(["100"])] {
255            assert_eq!(parse_optional_u64_param(value).unwrap(), Some(100));
256        }
257
258        assert_eq!(parse_optional_u64_param(json!([u64::MAX])).unwrap(), Some(u64::MAX));
259    }
260
261    #[test]
262    fn deserialize_u64_seq_opt_rejects_invalid_shape_and_overflow() {
263        for value in [
264            json!([1, 2]),
265            json!(100),
266            json!("0x64"),
267            json!([["0x64"]]),
268            json!(["0x10000000000000000"]),
269            json!(["18446744073709551616"]),
270        ] {
271            assert!(parse_optional_u64_param(value).is_err());
272        }
273    }
274}