diff --git a/crates/primitives/src/serde_helper/num.rs b/crates/primitives/src/serde_helper/num.rs index beab749ab3..312f287124 100644 --- a/crates/primitives/src/serde_helper/num.rs +++ b/crates/primitives/src/serde_helper/num.rs @@ -1,9 +1,93 @@ //! Numeric helpers -use crate::U256; -use serde::{de, Deserialize, Deserializer}; +use crate::{U256, U64}; +use serde::{de, Deserialize, Deserializer, Serialize}; use std::str::FromStr; +/// A `u64` wrapper type that deserializes from hex or a u64 and serializes as hex. +/// +/// +/// ```rust +/// use reth_primitives::serde_helper::num::U64HexOrNumber; +/// let number_json = "100"; +/// let hex_json = "\"0x64\""; +/// +/// let number: U64HexOrNumber = serde_json::from_str(number_json).unwrap(); +/// let hex: U64HexOrNumber = serde_json::from_str(hex_json).unwrap(); +/// assert_eq!(number, hex); +/// assert_eq!(hex.as_u64(), 100); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct U64HexOrNumber(U64); + +impl U64HexOrNumber { + /// Returns the wrapped u64 + pub fn as_u64(self) -> u64 { + self.0.as_u64() + } +} + +impl From for U64HexOrNumber { + fn from(value: u64) -> Self { + Self(U64::from(value)) + } +} + +impl From for U64HexOrNumber { + fn from(value: U64) -> Self { + Self(value) + } +} + +impl From for u64 { + fn from(value: U64HexOrNumber) -> Self { + value.as_u64() + } +} + +impl From for U64 { + fn from(value: U64HexOrNumber) -> Self { + value.0 + } +} + +impl<'de> Deserialize<'de> for U64HexOrNumber { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum NumberOrHexU64 { + Hex(U64), + Int(u64), + } + match NumberOrHexU64::deserialize(deserializer)? { + NumberOrHexU64::Int(val) => Ok(val.into()), + NumberOrHexU64::Hex(val) => Ok(val.into()), + } + } +} + +/// serde functions for handling primitive `u64` as [U64](crate::U64) +pub mod u64_hex_or_decimal { + use crate::serde_helper::num::U64HexOrNumber; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Deserializes an `u64` accepting a hex quantity string with optional 0x prefix or + /// a number + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + U64HexOrNumber::deserialize(deserializer).map(Into::into) + } + + /// Serializes u64 as hex string + pub fn serialize(value: &u64, s: S) -> Result { + U64HexOrNumber::from(*value).serialize(s) + } +} /// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the /// inner value. pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> @@ -85,4 +169,21 @@ mod tests { assert_eq!(int_val.0, Some(u256_val)); }); } + + #[test] + fn serde_hex_or_number_u64() { + #[derive(Debug, Deserialize, PartialEq, Eq)] + struct V(U64HexOrNumber); + + proptest::proptest!(|(value: u64)| { + let val = U64::from(value); + + let num_obj = serde_json::to_string(&value).unwrap(); + let hex_obj = serde_json::to_string(&val).unwrap(); + + let int_val:V = serde_json::from_str(&num_obj).unwrap(); + let hex_val = serde_json::from_str(&hex_obj).unwrap(); + assert_eq!(int_val, hex_val); + }); + } }