alloy_serde/
ttd.rs

1//! Json U256 serde helpers.
2
3use alloy_primitives::U256;
4use serde::{de::Error, Deserialize, Deserializer};
5use serde_json::Value;
6
7/// Supports parsing the TTD as an `Option<u64>`, or `Option<f64>` specifically for the mainnet TTD
8/// (5.875e22).
9pub fn deserialize_json_ttd_opt<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
10where
11    D: Deserializer<'de>,
12{
13    Option::<Value>::deserialize(deserializer)?.map(ttd_from_value::<D>).transpose()
14}
15
16/// Converts the given [serde_json::Value] into a `U256` value for TTD deserialization.
17fn ttd_from_value<'de, D>(val: Value) -> Result<U256, D::Error>
18where
19    D: Deserializer<'de>,
20{
21    let val = match val {
22        Value::Number(num) => num,
23        Value::String(raw) => return raw.parse().map_err(D::Error::custom),
24        _ => return Err(Error::custom("TTD must be a number or string")),
25    };
26
27    let num = if let Some(val) = val.as_u64() {
28        U256::from(val)
29    } else if let Some(value) = val.as_f64() {
30        // The ethereum mainnet TTD is 58750000000000000000000, and geth serializes this
31        // without quotes, because that is how golang `big.Int`s marshal in JSON. Numbers
32        // are arbitrary precision in JSON, so this is valid JSON. This number is also
33        // greater than a `u64`.
34        //
35        // Unfortunately, serde_json only supports parsing up to `u64`, resorting to `f64`
36        // once `u64` overflows:
37        // <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L1411-L1415>
38        // <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L479-L484>
39        // <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L102-L108>
40        //
41        // serde_json does have an arbitrary precision feature, but this breaks untagged
42        // enums in serde:
43        // <https://github.com/serde-rs/serde/issues/2230>
44        // <https://github.com/serde-rs/serde/issues/1183>
45        //
46        // To solve this, we use the captured float and return the TTD as a U256 if it's equal.
47        if value == 5.875e22 {
48            U256::from(58750000000000000000000u128)
49        } else {
50            // We could try to convert to a u128 here but there would probably be loss of
51            // precision, so we just return an error.
52            return Err(Error::custom("Deserializing a large non-mainnet TTD is not supported"));
53        }
54    } else {
55        // must be i64 - negative numbers are not supported
56        return Err(Error::custom("Negative TTD values are invalid and will not be deserialized"));
57    };
58
59    Ok(num)
60}
61
62#[cfg(test)]
63mod tests {
64    #[cfg(not(feature = "std"))]
65    use alloc::{vec, vec::Vec};
66    use alloy_primitives::U256;
67    use serde::{Deserialize, Serialize};
68    use serde_json::json;
69
70    #[test]
71    fn jsonu256_deserialize() {
72        let deserialized: Vec<U256> =
73            serde_json::from_str(r#"["","0", "0x","10",10,"0x10"]"#).unwrap();
74        assert_eq!(
75            deserialized,
76            vec![
77                U256::ZERO,
78                U256::ZERO,
79                U256::ZERO,
80                U256::from(10),
81                U256::from(10),
82                U256::from(16),
83            ]
84        );
85    }
86
87    #[test]
88    fn jsonu256_serialize() {
89        let data = U256::from(16);
90        let serialized = serde_json::to_string(&data).unwrap();
91
92        assert_eq!(serialized, r#""0x10""#);
93    }
94
95    #[test]
96    fn deserialize_ttd() {
97        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
98        struct Ttd(#[serde(deserialize_with = "super::deserialize_json_ttd_opt")] Option<U256>);
99
100        let deserialized: Vec<Ttd> = serde_json::from_str(
101            r#"["",0,"0","0x0","58750000000000000000000",58750000000000000000000]"#,
102        )
103        .unwrap();
104        assert_eq!(
105            deserialized,
106            vec![
107                Ttd(Some(U256::ZERO)),
108                Ttd(Some(U256::ZERO)),
109                Ttd(Some(U256::ZERO)),
110                Ttd(Some(U256::ZERO)),
111                Ttd(Some(U256::from(58750000000000000000000u128))),
112                Ttd(Some(U256::from(58750000000000000000000u128))),
113            ]
114        );
115    }
116
117    #[test]
118    fn deserialize_ttd_none() {
119        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
120        struct Ttd(#[serde(deserialize_with = "super::deserialize_json_ttd_opt")] Option<U256>);
121
122        // Deserialize null as None
123        let deserialized: Ttd = serde_json::from_value(json!(null)).unwrap();
124        assert_eq!(deserialized, Ttd(None));
125    }
126
127    #[test]
128    fn deserialize_ttd_invalid_string() {
129        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
130        struct Ttd(#[serde(deserialize_with = "super::deserialize_json_ttd_opt")] Option<U256>);
131
132        // Invalid string that cannot be parsed into U256
133        let result: Result<Ttd, _> = serde_json::from_value(json!("invalid_string"));
134        assert!(result.is_err());
135    }
136
137    #[test]
138    fn deserialize_ttd_large_non_mainnet() {
139        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
140        struct Ttd(#[serde(deserialize_with = "super::deserialize_json_ttd_opt")] Option<U256>);
141
142        // Test for a large number not equal to 5.875e22, which should result in an error
143        let result: Result<Ttd, _> = serde_json::from_value(json!(6.0e22));
144        assert!(result.is_err());
145    }
146
147    #[test]
148    fn deserialize_ttd_negative_number() {
149        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
150        struct Ttd(#[serde(deserialize_with = "super::deserialize_json_ttd_opt")] Option<U256>);
151
152        // Test for a negative number which should not be allowed
153        let result: Result<Ttd, _> = serde_json::from_value(json!(-1));
154        assert!(result.is_err());
155    }
156
157    #[test]
158    fn deserialize_ttd_as_string() {
159        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
160        struct Ttd(#[serde(deserialize_with = "super::deserialize_json_ttd_opt")] Option<U256>);
161
162        // Test for valid TTD as a string
163        let deserialized: Ttd = serde_json::from_value(json!("0x12345")).unwrap();
164        assert_eq!(deserialized, Ttd(Some(U256::from(0x12345))));
165    }
166}