alloy_serde/
ttd.rs

1//! Serde functions for encoding the TTD using a Geth compatible format.
2//!
3//! In Go `big.Int` is marshalled as a JSON number without quotes. Numbers are arbitrary
4//! precision in JSON, so this is valid JSON.
5//!
6//! These functions serialize the TTD as a JSON number, if the value fits within `u128`.
7//!
8//! The TTD is parsed from:
9//!   - JSON numbers: direct `u64` values, or the specific Ethereum mainnet TTD (e.g., `5.875e22` if
10//!     represented as a float). Other floats or negative numbers will error.
11//!   - JSON strings: these are parsed as `U256` (allowing hex or decimal strings).
12//!
13//! For non-human-readable formats, the default `serde` behavior for `Option<U256>` is used.
14
15use alloy_primitives::U256;
16use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
17use serde_json::Value;
18
19/// Serializes an optional TTD as a JSON number.
20///
21/// It returns an error, if the TTD value is larger than 128-bit.
22pub fn serialize<S>(value: &Option<U256>, serializer: S) -> Result<S::Ok, S::Error>
23where
24    S: Serializer,
25{
26    if serializer.is_human_readable() {
27        match value {
28            Some(value) => {
29                // serialize as an u128 when possible, otherwise as a hex string
30                match value.try_into() {
31                    Ok(value) => serializer.serialize_u128(value),
32                    Err(_) => value.serialize(serializer),
33                }
34            }
35            None => serializer.serialize_none(),
36        }
37    } else {
38        value.serialize(serializer)
39    }
40}
41
42/// Deserializes an optional TTD value from JSON number or string.
43pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
44where
45    D: Deserializer<'de>,
46{
47    if deserializer.is_human_readable() {
48        Option::<Value>::deserialize(deserializer)?
49            .map_or_else(|| Ok(None), |value| ttd_from_value::<D>(value).map(Some))
50    } else {
51        Option::<U256>::deserialize(deserializer)
52    }
53}
54
55/// Supports parsing the TTD as an `Option<u64>`, or `Option<f64>` specifically for the mainnet TTD
56/// (5.875e22).
57pub fn deserialize_json_ttd_opt<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
58where
59    D: Deserializer<'de>,
60{
61    deserialize(deserializer)
62}
63
64/// Converts the given [serde_json::Value] into a `U256` value for TTD deserialization.
65fn ttd_from_value<'de, D>(val: Value) -> Result<U256, D::Error>
66where
67    D: Deserializer<'de>,
68{
69    let val = match val {
70        Value::Number(num) => num,
71        Value::String(raw) => return raw.parse().map_err(<D::Error as de::Error>::custom),
72        _ => return Err(de::Error::custom("TTD must be a number or string")),
73    };
74
75    let num = if let Some(val) = val.as_u64() {
76        U256::from(val)
77    } else if let Some(value) = val.as_f64() {
78        // The ethereum mainnet TTD is 58750000000000000000000, and geth serializes this
79        // without quotes, because that is how golang `big.Int`s marshal in JSON. Numbers
80        // are arbitrary precision in JSON, so this is valid JSON. This number is also
81        // greater than a `u64`.
82        //
83        // Unfortunately, serde_json only supports parsing up to `u64`, resorting to `f64`
84        // once `u64` overflows:
85        // <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L1411-L1415>
86        // <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L479-L484>
87        // <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L102-L108>
88        //
89        // serde_json does have an arbitrary precision feature, but this breaks untagged
90        // enums in serde:
91        // <https://github.com/serde-rs/serde/issues/2230>
92        // <https://github.com/serde-rs/serde/issues/1183>
93        //
94        // To solve this, we use the captured float and return the TTD as a U256 if it's equal.
95        if value == 5.875e22 {
96            U256::from(58750000000000000000000u128)
97        } else {
98            // We could try to convert to a u128 here but there would probably be loss of
99            // precision, so we just return an error.
100            return Err(de::Error::custom(
101                "Deserializing a large non-mainnet TTD is not supported",
102            ));
103        }
104    } else {
105        // must be i64 - negative numbers are not supported
106        return Err(de::Error::custom(
107            "Negative TTD values are invalid and will not be deserialized",
108        ));
109    };
110
111    Ok(num)
112}
113#[cfg(test)]
114mod tests {
115    #[cfg(not(feature = "std"))]
116    use alloc::{vec, vec::Vec};
117    use alloy_primitives::U256;
118    use serde::{Deserialize, Serialize};
119    use serde_json::json;
120
121    #[test]
122    fn jsonu256_deserialize() {
123        let deserialized: Vec<U256> =
124            serde_json::from_str(r#"["","0", "0x","10",10,"0x10"]"#).unwrap();
125        assert_eq!(
126            deserialized,
127            vec![
128                U256::ZERO,
129                U256::ZERO,
130                U256::ZERO,
131                U256::from(10),
132                U256::from(10),
133                U256::from(16),
134            ]
135        );
136    }
137
138    #[test]
139    fn jsonu256_serialize() {
140        let data = U256::from(16);
141        let serialized = serde_json::to_string(&data).unwrap();
142
143        assert_eq!(serialized, r#""0x10""#);
144    }
145
146    #[test]
147    fn deserialize_ttd() {
148        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
149        struct Ttd(#[serde(with = "super")] Option<U256>);
150
151        let deserialized: Vec<Ttd> = serde_json::from_str(
152            r#"["",0,"0","0x0",18446744073709551615,58750000000000000000000,"58750000000000000000000","0xC70D808A128D7380000"]"#,
153        )
154        .unwrap();
155        assert_eq!(
156            deserialized,
157            vec![
158                Ttd(Some(U256::ZERO)),
159                Ttd(Some(U256::ZERO)),
160                Ttd(Some(U256::ZERO)),
161                Ttd(Some(U256::ZERO)),
162                Ttd(Some(U256::from(18446744073709551615u64))),
163                Ttd(Some(U256::from(58750000000000000000000u128))),
164                Ttd(Some(U256::from(58750000000000000000000u128))),
165                Ttd(Some(U256::from(58750000000000000000000u128))),
166            ]
167        );
168    }
169
170    #[test]
171    fn serialize_ttd() {
172        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
173        struct Ttd(#[serde(with = "super")] Option<U256>);
174
175        let tests = vec![
176            Ttd(Some(U256::ZERO)),
177            Ttd(Some(U256::from(17000000000000000u64))),
178            Ttd(Some(U256::from(58750000000000000000000u128))),
179            Ttd(Some(U256::from(u128::MAX))),
180        ];
181
182        for test in tests {
183            let str = serde_json::to_string(&test).unwrap();
184            // should be serialized as a decimal number and not a quoted string
185            let num = str.parse::<u128>().unwrap();
186            assert!(matches!(test, Ttd(Some(ttd)) if ttd == U256::from(num)));
187        }
188    }
189
190    #[test]
191    fn deserialize_ttd_untagged_enum() {
192        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
193        #[serde(untagged)]
194        enum Ttd {
195            Ttd(#[serde(with = "super")] Option<U256>),
196        }
197        let test = Ttd::Ttd(Some(U256::from(58750000000000000000000u128)));
198        let str = serde_json::to_string(&test).unwrap();
199        // should be serialized as an integer, not a float or a quoted string
200        assert_eq!(str, r#"58750000000000000000000"#);
201
202        let deserialized: Ttd = serde_json::from_str(&str).unwrap();
203        assert_eq!(deserialized, test);
204    }
205
206    #[test]
207    fn deserialize_ttd_none() {
208        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
209        struct Ttd(#[serde(with = "super")] Option<U256>);
210
211        // Deserialize null as None
212        let deserialized: Ttd = serde_json::from_value(json!(null)).unwrap();
213        assert_eq!(deserialized, Ttd(None));
214    }
215
216    #[test]
217    fn deserialize_ttd_invalid_string() {
218        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
219        struct Ttd(#[serde(with = "super")] Option<U256>);
220
221        // Invalid string that cannot be parsed into U256
222        let result: Result<Ttd, _> = serde_json::from_value(json!("invalid_string"));
223        assert!(result.is_err());
224    }
225
226    #[test]
227    fn deserialize_ttd_large_non_mainnet() {
228        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
229        struct Ttd(#[serde(with = "super")] Option<U256>);
230
231        // Test for a large number not equal to 5.875e22, which should result in an error
232        let result: Result<Ttd, _> = serde_json::from_value(json!(6.0e22));
233        assert!(result.is_err());
234    }
235
236    #[test]
237    fn deserialize_ttd_negative_number() {
238        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
239        struct Ttd(#[serde(with = "super")] Option<U256>);
240
241        // Test for a negative number which should not be allowed
242        let result: Result<Ttd, _> = serde_json::from_value(json!(-1));
243        assert!(result.is_err());
244    }
245
246    #[test]
247    fn deserialize_ttd_as_string() {
248        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
249        struct Ttd(#[serde(with = "super")] Option<U256>);
250
251        // Test for valid TTD as a string
252        let deserialized: Ttd = serde_json::from_value(json!("0x12345")).unwrap();
253        assert_eq!(deserialized, Ttd(Some(U256::from(0x12345))));
254    }
255}