From a449c216042c89a30f339600366108f72988f559 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 11:04:15 +0200 Subject: [PATCH 01/10] add Index --- crates/rpc-types-anvil/src/lib.rs | 63 ++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index 02469f2d363..1b803bad426 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -7,7 +7,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use alloy_primitives::{BlockHash, ChainId, TxHash, B256, U256}; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{de::Error, Deserialize, Deserializer, Serialize}; use std::collections::BTreeMap; /// Represents the params to set forking which can take various forms: @@ -164,6 +164,67 @@ impl Default for MineOptions { } } +/// A hex encoded or decimal index +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Index(usize); + +impl From for usize { + fn from(idx: Index) -> Self { + idx.0 + } +} + +impl<'a> serde::Deserialize<'a> for Index { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + use std::fmt; + + struct IndexVisitor; + + impl<'a> serde::de::Visitor<'a> for IndexVisitor { + type Value = Index; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "hex-encoded or decimal index") + } + + fn visit_u64(self, value: u64) -> Result + where + E: Error, + { + Ok(Index(value as usize)) + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + if let Some(val) = value.strip_prefix("0x") { + usize::from_str_radix(val, 16).map(Index).map_err(|e| { + Error::custom(format!("Failed to parse hex encoded index value: {e}")) + }) + } else { + value + .parse::() + .map(Index) + .map_err(|e| Error::custom(format!("Failed to parse numeric index: {e}"))) + } + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + self.visit_str(value.as_ref()) + } + } + + deserializer.deserialize_any(IndexVisitor) + } +} + #[cfg(test)] mod tests { use super::*; From b3c3ce186060417a6eaaab6150d9ad46ca464e85 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 11:11:53 +0200 Subject: [PATCH 02/10] add test --- crates/rpc-types-anvil/src/lib.rs | 59 +++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index 1b803bad426..6296c7a0dec 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -228,20 +228,65 @@ impl<'a> serde::Deserialize<'a> for Index { #[cfg(test)] mod tests { use super::*; + use serde_json::json; #[test] - fn serde_forking() { - let s = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com", - "blockNumber": "18441649" - } - }"#; - let f: Forking = serde_json::from_str(s).unwrap(); + fn test_forking_deserialization() { + let json_data = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com","blockNumber": "18441649"}}"#; + let deserialized: Forking = serde_json::from_str(json_data).unwrap(); assert_eq!( - f, + deserialized, Forking { json_rpc_url: Some("https://ethereumpublicnode.com".into()), block_number: Some(18441649) } ); } + + #[test] + fn test_index_deserialization() { + // Test decimal index + let json_data = json!(42); + let index: Index = + serde_json::from_value(json_data).expect("Failed to deserialize decimal index"); + assert_eq!(index, Index(42)); + + // Test hex index + let json_data = json!("0x2A"); + let index: Index = + serde_json::from_value(json_data).expect("Failed to deserialize hex index"); + assert_eq!(index, Index(42)); + + // Test invalid hex index + let json_data = json!("0xGHI"); + let result: Result = serde_json::from_value(json_data); + assert!(result.is_err()); + + // Test invalid decimal index + let json_data = json!("abc"); + let result: Result = serde_json::from_value(json_data); + assert!(result.is_err()); + + // Test string decimal index + let json_data = json!("123"); + let index: Index = + serde_json::from_value(json_data).expect("Failed to deserialize string decimal index"); + assert_eq!(index, Index(123)); + + // Test invalid numeric string + let json_data = json!("123abc"); + let result: Result = serde_json::from_value(json_data); + assert!(result.is_err()); + + // Test negative index + let json_data = json!(-1); + let result: Result = serde_json::from_value(json_data); + assert!(result.is_err()); + + // Test large index + let json_data = json!(u64::MAX); + let index: Index = + serde_json::from_value(json_data).expect("Failed to deserialize large index"); + assert_eq!(index, Index(u64::MAX as usize)); + } } From 4fcde5ffb4b0ab0c63a724153f7a3589432cfc9f Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 11:15:49 +0200 Subject: [PATCH 03/10] fix clippy warning --- crates/rpc-types-anvil/src/lib.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index 6296c7a0dec..d2c9d7f1bed 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -201,16 +201,18 @@ impl<'a> serde::Deserialize<'a> for Index { where E: Error, { - if let Some(val) = value.strip_prefix("0x") { - usize::from_str_radix(val, 16).map(Index).map_err(|e| { - Error::custom(format!("Failed to parse hex encoded index value: {e}")) - }) - } else { - value - .parse::() - .map(Index) - .map_err(|e| Error::custom(format!("Failed to parse numeric index: {e}"))) - } + value.strip_prefix("0x").map_or_else( + || { + value.parse::().map(Index).map_err(|e| { + Error::custom(format!("Failed to parse numeric index: {e}")) + }) + }, + |val| { + usize::from_str_radix(val, 16).map(Index).map_err(|e| { + Error::custom(format!("Failed to parse hex encoded index value: {e}")) + }) + }, + ) } fn visit_string(self, value: String) -> Result From 014ea4aab1d20d61e2d3d16f41dff220775fe4ec Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 11:17:42 +0200 Subject: [PATCH 04/10] improve serialization test --- crates/rpc-types-anvil/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index d2c9d7f1bed..bb919febcf4 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -234,6 +234,7 @@ mod tests { #[test] fn test_forking_deserialization() { + // Test full forking object let json_data = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com","blockNumber": "18441649"}}"#; let deserialized: Forking = serde_json::from_str(json_data).unwrap(); assert_eq!( @@ -243,6 +244,23 @@ mod tests { block_number: Some(18441649) } ); + + // Test forking object with only jsonRpcUrl + let json_data = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com"}}"#; + let deserialized: Forking = serde_json::from_str(json_data).unwrap(); + assert_eq!( + deserialized, + Forking { + json_rpc_url: Some("https://ethereumpublicnode.com".into()), + block_number: None + } + ); + + // Test forking object with only blockNumber + let json_data = r#"{"forking": {"blockNumber": "18441649"}}"#; + let deserialized: Forking = + serde_json::from_str(json_data).expect("Failed to deserialize forking object"); + assert_eq!(deserialized, Forking { json_rpc_url: None, block_number: Some(18441649) }); } #[test] From 69dd0c34b4a4a127c03546ae99c30f9cf09aab5f Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 11:22:53 +0200 Subject: [PATCH 05/10] prefer in-line de::Error --- crates/rpc-types-anvil/src/lib.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index bb919febcf4..78c52496cdb 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -7,7 +7,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use alloy_primitives::{BlockHash, ChainId, TxHash, B256, U256}; -use serde::{de::Error, Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use std::collections::BTreeMap; /// Represents the params to set forking which can take various forms: @@ -192,24 +192,26 @@ impl<'a> serde::Deserialize<'a> for Index { fn visit_u64(self, value: u64) -> Result where - E: Error, + E: serde::de::Error, { Ok(Index(value as usize)) } fn visit_str(self, value: &str) -> Result where - E: Error, + E: serde::de::Error, { value.strip_prefix("0x").map_or_else( || { value.parse::().map(Index).map_err(|e| { - Error::custom(format!("Failed to parse numeric index: {e}")) + serde::de::Error::custom(format!("Failed to parse numeric index: {e}")) }) }, |val| { usize::from_str_radix(val, 16).map(Index).map_err(|e| { - Error::custom(format!("Failed to parse hex encoded index value: {e}")) + serde::de::Error::custom(format!( + "Failed to parse hex encoded index value: {e}" + )) }) }, ) @@ -217,7 +219,7 @@ impl<'a> serde::Deserialize<'a> for Index { fn visit_string(self, value: String) -> Result where - E: Error, + E: serde::de::Error, { self.visit_str(value.as_ref()) } From 7d0a09898c24e33fb011c16f44d39e72a0a93012 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 11:31:14 +0200 Subject: [PATCH 06/10] prefer name vs generic deserialized --- crates/rpc-types-anvil/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index 78c52496cdb..80487866530 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -238,9 +238,9 @@ mod tests { fn test_forking_deserialization() { // Test full forking object let json_data = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com","blockNumber": "18441649"}}"#; - let deserialized: Forking = serde_json::from_str(json_data).unwrap(); + let forking: Forking = serde_json::from_str(json_data).unwrap(); assert_eq!( - deserialized, + forking, Forking { json_rpc_url: Some("https://ethereumpublicnode.com".into()), block_number: Some(18441649) @@ -249,9 +249,9 @@ mod tests { // Test forking object with only jsonRpcUrl let json_data = r#"{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com"}}"#; - let deserialized: Forking = serde_json::from_str(json_data).unwrap(); + let forking: Forking = serde_json::from_str(json_data).unwrap(); assert_eq!( - deserialized, + forking, Forking { json_rpc_url: Some("https://ethereumpublicnode.com".into()), block_number: None @@ -260,9 +260,9 @@ mod tests { // Test forking object with only blockNumber let json_data = r#"{"forking": {"blockNumber": "18441649"}}"#; - let deserialized: Forking = + let forking: Forking = serde_json::from_str(json_data).expect("Failed to deserialize forking object"); - assert_eq!(deserialized, Forking { json_rpc_url: None, block_number: Some(18441649) }); + assert_eq!(forking, Forking { json_rpc_url: None, block_number: Some(18441649) }); } #[test] From 0f8b8e526d89332735c78db7ea1038831d2f39c7 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 11:53:39 +0200 Subject: [PATCH 07/10] mark as untagged, handle block serialization --- crates/rpc-types-anvil/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index 80487866530..991f783e40e 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -143,6 +143,7 @@ pub struct ForkedNetwork { /// Additional `evm_mine` options #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[serde(untagged)] pub enum MineOptions { /// The options for mining Options { @@ -151,6 +152,7 @@ pub enum MineOptions { timestamp: Option, /// If `blocks` is given, it will mine exactly blocks number of blocks, regardless of any /// other blocks mined or reverted during it's operation + #[serde(with = "alloy_serde::quantity::opt")] blocks: Option, }, /// The timestamp the block should be mined with From 767cd22cf912fcadbc0e6c656142c9e1e3045c6c Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 12:10:06 +0200 Subject: [PATCH 08/10] attempt at more compatibility fixes --- crates/rpc-types-anvil/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index 991f783e40e..e1495aee168 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -31,7 +31,7 @@ impl<'de> serde::Deserialize<'de> for Forking { #[serde(rename_all = "camelCase")] struct ForkOpts { json_rpc_url: Option, - #[serde(default, with = "alloy_serde::quantity::opt")] + #[serde(default, with = "alloy_serde::num::u64_opt_via_ruint")] block_number: Option, } @@ -148,15 +148,15 @@ pub enum MineOptions { /// The options for mining Options { /// The timestamp the block should be mined with - #[serde(with = "alloy_serde::quantity::opt")] + #[serde(with = "alloy_serde::num::u64_opt_via_ruint")] timestamp: Option, /// If `blocks` is given, it will mine exactly blocks number of blocks, regardless of any /// other blocks mined or reverted during it's operation - #[serde(with = "alloy_serde::quantity::opt")] + #[serde(with = "alloy_serde::num::u64_opt_via_ruint")] blocks: Option, }, /// The timestamp the block should be mined with - #[serde(with = "alloy_serde::quantity::opt")] + #[serde(with = "alloy_serde::num::u64_opt_via_ruint")] Timestamp(Option), } From 0d6eacd8afccb52cfbe751d398e25661e67adfc2 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 13:10:39 +0200 Subject: [PATCH 09/10] add test cases with intended use --- crates/rpc-types-anvil/src/lib.rs | 46 ++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index e1495aee168..87bd663d023 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -142,7 +142,6 @@ pub struct ForkedNetwork { /// Additional `evm_mine` options #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] #[serde(untagged)] pub enum MineOptions { /// The options for mining @@ -313,4 +312,49 @@ mod tests { serde_json::from_value(json_data).expect("Failed to deserialize large index"); assert_eq!(index, Index(u64::MAX as usize)); } + + #[test] + fn test_deserialize_options_with_values() { + let data = r#"{"timestamp": 1620000000, "blocks": 10}"#; + let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed"); + assert_eq!( + deserialized, + MineOptions::Options { timestamp: Some(1620000000), blocks: Some(10) } + ); + + let data = r#"{"timestamp": "0x608f3d00", "blocks": "0xa"}"#; + let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed"); + assert_eq!( + deserialized, + MineOptions::Options { timestamp: Some(1620000000), blocks: Some(10) } + ); + } + + #[test] + fn test_deserialize_options_with_timestamp() { + let data = r#"{"timestamp":"1620000000"}"#; + let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed"); + assert_eq!( + deserialized, + MineOptions::Options { timestamp: Some(1620000000), blocks: None } + ); + + let data = r#"{"timestamp":"0x608f3d00"}"#; + let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed"); + assert_eq!( + deserialized, + MineOptions::Options { timestamp: Some(1620000000), blocks: None } + ); + } + + #[test] + fn test_deserialize_timestamp() { + let data = r#""1620000000""#; + let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed"); + assert_eq!(deserialized, MineOptions::Timestamp(Some(1620000000))); + + let data = r#""0x608f3d00""#; + let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed"); + assert_eq!(deserialized, MineOptions::Timestamp(Some(1620000000))); + } } From 1aae90987f3af9c12429b4adee1530adc45cc858 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 18 Jun 2024 13:17:13 +0200 Subject: [PATCH 10/10] switch back to quantity::opt, do not tag blocks --- crates/rpc-types-anvil/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/rpc-types-anvil/src/lib.rs b/crates/rpc-types-anvil/src/lib.rs index 87bd663d023..63cac691a61 100644 --- a/crates/rpc-types-anvil/src/lib.rs +++ b/crates/rpc-types-anvil/src/lib.rs @@ -31,7 +31,7 @@ impl<'de> serde::Deserialize<'de> for Forking { #[serde(rename_all = "camelCase")] struct ForkOpts { json_rpc_url: Option, - #[serde(default, with = "alloy_serde::num::u64_opt_via_ruint")] + #[serde(default, with = "alloy_serde::quantity::opt")] block_number: Option, } @@ -147,15 +147,14 @@ pub enum MineOptions { /// The options for mining Options { /// The timestamp the block should be mined with - #[serde(with = "alloy_serde::num::u64_opt_via_ruint")] + #[serde(with = "alloy_serde::quantity::opt")] timestamp: Option, /// If `blocks` is given, it will mine exactly blocks number of blocks, regardless of any /// other blocks mined or reverted during it's operation - #[serde(with = "alloy_serde::num::u64_opt_via_ruint")] blocks: Option, }, /// The timestamp the block should be mined with - #[serde(with = "alloy_serde::num::u64_opt_via_ruint")] + #[serde(with = "alloy_serde::quantity::opt")] Timestamp(Option), } @@ -322,7 +321,7 @@ mod tests { MineOptions::Options { timestamp: Some(1620000000), blocks: Some(10) } ); - let data = r#"{"timestamp": "0x608f3d00", "blocks": "0xa"}"#; + let data = r#"{"timestamp": "0x608f3d00", "blocks": 10}"#; let deserialized: MineOptions = serde_json::from_str(data).expect("Deserialization failed"); assert_eq!( deserialized,