diff --git a/crates/primitives/src/serde_helper/mod.rs b/crates/primitives/src/serde_helper/mod.rs index 7efbed1dac..b2d4479787 100644 --- a/crates/primitives/src/serde_helper/mod.rs +++ b/crates/primitives/src/serde_helper/mod.rs @@ -3,6 +3,8 @@ mod jsonu256; pub use jsonu256::*; +pub mod num; + /// serde functions for handling primitive `u64` as [U64](crate::U64) pub mod u64_hex { use crate::U64; diff --git a/crates/primitives/src/serde_helper/num.rs b/crates/primitives/src/serde_helper/num.rs new file mode 100644 index 0000000000..beab749ab3 --- /dev/null +++ b/crates/primitives/src/serde_helper/num.rs @@ -0,0 +1,88 @@ +//! Numeric helpers + +use crate::U256; +use serde::{de, Deserialize, Deserializer}; +use std::str::FromStr; + +/// 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> +where + D: Deserializer<'de>, +{ + match Option::::deserialize(deserializer)? { + Some(val) => val.try_into_u256().map(Some), + None => Ok(None), + } +} + +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with +/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). +pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + NumberOrHexU256::deserialize(deserializer)?.try_into_u256() +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum NumberOrHexU256 { + Int(serde_json::Number), + Hex(U256), +} + +impl NumberOrHexU256 { + fn try_into_u256(self) -> Result { + match self { + NumberOrHexU256::Int(num) => { + U256::from_str(num.to_string().as_str()).map_err(E::custom) + } + NumberOrHexU256::Hex(val) => Ok(val), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_u256_int_or_hex() { + #[derive(Debug, Deserialize, PartialEq, Eq)] + struct V(#[serde(deserialize_with = "from_int_or_hex")] U256); + + proptest::proptest!(|(value: u64)| { + let u256_val = U256::from(value); + + let num_obj = serde_json::to_string(&value).unwrap(); + let hex_obj = serde_json::to_string(&u256_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); + }); + } + + #[test] + fn test_u256_int_or_hex_opt() { + #[derive(Debug, Deserialize, PartialEq, Eq)] + struct V(#[serde(deserialize_with = "from_int_or_hex_opt")] Option); + + let null = serde_json::to_string(&None::).unwrap(); + let val: V = serde_json::from_str(&null).unwrap(); + assert!(val.0.is_none()); + + proptest::proptest!(|(value: u64)| { + let u256_val = U256::from(value); + + let num_obj = serde_json::to_string(&value).unwrap(); + let hex_obj = serde_json::to_string(&u256_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); + assert_eq!(int_val.0, Some(u256_val)); + }); + } +} diff --git a/crates/rpc/rpc-types/src/eth/trace/geth.rs b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs similarity index 100% rename from crates/rpc/rpc-types/src/eth/trace/geth.rs rename to crates/rpc/rpc-types/src/eth/trace/geth/mod.rs