foundry_block_explorers/
serde_helpers.rs

1use crate::block_number::BlockNumber;
2use alloy_primitives::{U256, U64};
3use serde::{Deserialize, Deserializer};
4use std::str::FromStr;
5
6/// Helper type to parse numeric strings, `u64` and `U256`
7#[derive(Deserialize, Debug, Clone)]
8#[serde(untagged)]
9pub enum StringifiedNumeric {
10    String(String),
11    U256(U256),
12    Num(serde_json::Number),
13}
14
15impl TryFrom<StringifiedNumeric> for U256 {
16    type Error = String;
17
18    fn try_from(value: StringifiedNumeric) -> Result<Self, Self::Error> {
19        match value {
20            StringifiedNumeric::U256(n) => Ok(n),
21            StringifiedNumeric::Num(n) => {
22                Ok(U256::from_str(&n.to_string()).map_err(|err| err.to_string())?)
23            }
24            StringifiedNumeric::String(s) => {
25                if let Ok(val) = s.parse::<u128>() {
26                    Ok(U256::from(val))
27                } else if s.starts_with("0x") {
28                    U256::from_str_radix(&s, 16).map_err(|err| err.to_string())
29                } else {
30                    U256::from_str(&s).map_err(|err| err.to_string())
31                }
32            }
33        }
34    }
35}
36
37impl TryFrom<StringifiedNumeric> for U64 {
38    type Error = String;
39
40    fn try_from(value: StringifiedNumeric) -> Result<Self, Self::Error> {
41        let value = U256::try_from(value)?;
42        Ok(value.wrapping_to::<U64>())
43    }
44}
45
46#[derive(Deserialize)]
47#[serde(untagged)]
48enum BoolOrU64 {
49    #[serde(deserialize_with = "deserialize_stringified_u64")]
50    U64(u64),
51    Bool(String),
52}
53
54/// Supports parsing either a u64 or a boolean (which will then be converted to u64)
55///
56/// Implemented to binary fields such as "OptimizationUsed" which are formatted either as 0/1 or
57/// "true/"false" by different block explorers (e.g. etherscan vs blockscout)
58pub fn deserialize_stringified_bool_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
59where
60    D: Deserializer<'de>,
61{
62    let num = BoolOrU64::deserialize(deserializer)?;
63    match num {
64        BoolOrU64::Bool(b) => {
65            let b = b.parse::<bool>().map_err(serde::de::Error::custom)?;
66            let u = if b { 1 } else { 0 };
67            Ok(u)
68        }
69        BoolOrU64::U64(u) => Ok(u),
70    }
71}
72
73/// Supports parsing u64
74///
75/// See <https://github.com/gakonst/ethers-rs/issues/1507>
76pub fn deserialize_stringified_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
77where
78    D: Deserializer<'de>,
79{
80    let num = StringifiedNumeric::deserialize(deserializer)?;
81    let num: U256 = num.try_into().map_err(serde::de::Error::custom)?;
82    num.try_into().map_err(serde::de::Error::custom)
83}
84
85/// Supports parsing numbers as strings
86///
87/// See <https://github.com/gakonst/ethers-rs/issues/1507>
88pub fn deserialize_stringified_numeric<'de, D>(deserializer: D) -> Result<U256, D::Error>
89where
90    D: Deserializer<'de>,
91{
92    let num = StringifiedNumeric::deserialize(deserializer)?;
93    num.try_into().map_err(serde::de::Error::custom)
94}
95
96/// Supports parsing numbers as strings
97///
98/// See <https://github.com/gakonst/ethers-rs/issues/1507>
99pub fn deserialize_stringified_numeric_opt<'de, D>(
100    deserializer: D,
101) -> Result<Option<U256>, D::Error>
102where
103    D: Deserializer<'de>,
104{
105    if let Some(num) = Option::<StringifiedNumeric>::deserialize(deserializer)? {
106        num.try_into().map(Some).map_err(serde::de::Error::custom)
107    } else {
108        Ok(None)
109    }
110}
111
112/// Supports parsing u64
113///
114/// See <https://github.com/gakonst/ethers-rs/issues/1507>
115pub fn deserialize_stringified_u64_opt<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
116where
117    D: Deserializer<'de>,
118{
119    if let Some(num) = Option::<StringifiedNumeric>::deserialize(deserializer)? {
120        let num: U256 = num.try_into().map_err(serde::de::Error::custom)?;
121        let num: u64 = num.try_into().map_err(serde::de::Error::custom)?;
122        Ok(Some(num))
123    } else {
124        Ok(None)
125    }
126}
127
128/// Helper type to parse numeric strings, `u64` and `U256`
129#[derive(Deserialize, Debug, Clone)]
130#[serde(untagged)]
131pub enum StringifiedBlockNumber {
132    Numeric(StringifiedNumeric),
133    BlockNumber(BlockNumber),
134}
135
136impl TryFrom<StringifiedBlockNumber> for BlockNumber {
137    type Error = String;
138
139    fn try_from(value: StringifiedBlockNumber) -> Result<Self, Self::Error> {
140        match value {
141            StringifiedBlockNumber::BlockNumber(b) => Ok(b),
142            StringifiedBlockNumber::Numeric(num) => match num {
143                StringifiedNumeric::String(s) => BlockNumber::from_str(&s),
144                other => {
145                    let u256 = U256::try_from(other)?;
146                    let n = u64::try_from(u256).map_err(|e| e.to_string())?;
147                    Ok(BlockNumber::Number(U64::from(n)))
148                }
149            },
150        }
151    }
152}
153
154/// Supports parsing block number as strings
155///
156/// See <https://github.com/gakonst/ethers-rs/issues/1507>
157pub fn deserialize_stringified_block_number<'de, D>(
158    deserializer: D,
159) -> Result<BlockNumber, D::Error>
160where
161    D: Deserializer<'de>,
162{
163    let num = StringifiedBlockNumber::deserialize(deserializer)?;
164    num.try_into().map_err(serde::de::Error::custom)
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use serde::{Deserialize, Serialize};
171
172    #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
173    struct Txn {
174        #[serde(deserialize_with = "deserialize_stringified_block_number")]
175        bn: BlockNumber,
176    }
177
178    #[test]
179    fn deserializes_hex_with_0x_prefix() {
180        let json = r#"{ "bn": "0x1" }"#;
181        let tx: Txn = serde_json::from_str(json).unwrap();
182        assert_eq!(tx.bn, BlockNumber::Number(U64::from(1)));
183    }
184
185    #[test]
186    fn deserializes_decimal_string() {
187        let json = r#"{ "bn": "42" }"#;
188        let tx: Txn = serde_json::from_str(json).unwrap();
189        assert_eq!(tx.bn, BlockNumber::Number(U64::from(42)));
190    }
191
192    #[test]
193    fn deserializes_tag_latest() {
194        let json = r#"{ "bn": "latest" }"#;
195        let tx: Txn = serde_json::from_str(json).unwrap();
196        assert_eq!(tx.bn, BlockNumber::Latest);
197    }
198    #[test]
199    fn deserializes_large_hex_u64_max() {
200        let json = r#"{ "bn": "0xffffffffffffffff" }"#;
201        let tx: Txn = serde_json::from_str(json).unwrap();
202        assert_eq!(tx.bn, BlockNumber::Number(U64::MAX));
203    }
204
205    #[test]
206    fn roundtrip_serialized_hex_still_deserializes() {
207        let tx = Txn { bn: BlockNumber::Number(U64::from(42)) };
208        let s = serde_json::to_string(&tx).unwrap();
209        let de: Txn = serde_json::from_str(&s).unwrap();
210        assert_eq!(de, tx);
211    }
212}