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::Numeric(num) => {
142                let num = U256::try_from(num)
143                    .and_then(|num| u64::try_from(num).map_err(|e| e.to_string()))?;
144                Ok(BlockNumber::Number(U64::from(num)))
145            }
146            StringifiedBlockNumber::BlockNumber(b) => Ok(b),
147        }
148    }
149}
150
151/// Supports parsing block number as strings
152///
153/// See <https://github.com/gakonst/ethers-rs/issues/1507>
154pub fn deserialize_stringified_block_number<'de, D>(
155    deserializer: D,
156) -> Result<BlockNumber, D::Error>
157where
158    D: Deserializer<'de>,
159{
160    let num = StringifiedBlockNumber::deserialize(deserializer)?;
161    num.try_into().map_err(serde::de::Error::custom)
162}