diff --git a/ethers-core/src/types/serde_helpers.rs b/ethers-core/src/types/serde_helpers.rs index bd83a573..5672279a 100644 --- a/ethers-core/src/types/serde_helpers.rs +++ b/ethers-core/src/types/serde_helpers.rs @@ -1,9 +1,11 @@ //! Some convenient serde helpers use crate::types::{BlockNumber, U256}; -use ethabi::ethereum_types::FromDecStrErr; use serde::{Deserialize, Deserializer}; -use std::convert::TryFrom; +use std::{ + convert::{TryFrom, TryInto}, + str::FromStr, +}; /// Helper type to parse both `u64` and `U256` #[derive(Copy, Clone, Deserialize)] @@ -22,7 +24,21 @@ impl From for U256 { } } -/// Helper type to parse both `u64` and `U256` +impl FromStr for Numeric { + type Err = String; + + fn from_str(s: &str) -> Result { + if let Ok(val) = s.parse::() { + Ok(Numeric::U256(val.into())) + } else if s.starts_with("0x") { + U256::from_str(s).map(Numeric::U256).map_err(|err| err.to_string()) + } else { + U256::from_dec_str(s).map(Numeric::U256).map_err(|err| err.to_string()) + } + } +} + +/// Helper type to parse numeric strings, `u64` and `U256` #[derive(Deserialize, Debug, Clone)] #[serde(untagged)] pub enum StringifiedNumeric { @@ -32,7 +48,7 @@ pub enum StringifiedNumeric { } impl TryFrom for U256 { - type Error = FromDecStrErr; + type Error = String; fn try_from(value: StringifiedNumeric) -> Result { match value { @@ -41,14 +57,71 @@ impl TryFrom for U256 { StringifiedNumeric::String(s) => { if let Ok(val) = s.parse::() { Ok(val.into()) + } else if s.starts_with("0x") { + U256::from_str(&s).map_err(|err| err.to_string()) } else { - U256::from_dec_str(&s) + U256::from_dec_str(&s).map_err(|err| err.to_string()) } } } } } +/// Supports parsing numbers as strings +/// +/// See +pub fn deserialize_stringified_numeric<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let num = StringifiedNumeric::deserialize(deserializer)?; + num.try_into().map_err(serde::de::Error::custom) +} + +/// Supports parsing numbers as strings +/// +/// See +pub fn deserialize_stringified_numeric_opt<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + if let Some(num) = Option::::deserialize(deserializer)? { + num.try_into().map(Some).map_err(serde::de::Error::custom) + } else { + Ok(None) + } +} + +/// Supports parsing u64 +/// +/// See +pub fn deserialize_stringified_u64<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let num = StringifiedNumeric::deserialize(deserializer)?; + let num: U256 = num.try_into().map_err(serde::de::Error::custom)?; + num.try_into().map_err(serde::de::Error::custom) +} + +/// Supports parsing u64 +/// +/// See +pub fn deserialize_stringified_u64_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + if let Some(num) = Option::::deserialize(deserializer)? { + let num: U256 = num.try_into().map_err(serde::de::Error::custom)?; + let num: u64 = num.try_into().map_err(serde::de::Error::custom)?; + Ok(Some(num)) + } else { + Ok(None) + } +} + /// Helper type to deserialize sequence of numbers #[derive(Deserialize)] #[serde(untagged)] @@ -94,6 +167,42 @@ where Ok(num) } +/// Helper type to parse numeric strings, `u64` and `U256` +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum StringifiedBlockNumber { + Numeric(StringifiedNumeric), + BlockNumber(BlockNumber), +} + +impl TryFrom for BlockNumber { + type Error = String; + + fn try_from(value: StringifiedBlockNumber) -> Result { + match value { + StringifiedBlockNumber::Numeric(num) => { + let num = U256::try_from(num) + .and_then(|num| u64::try_from(num).map_err(str::to_string))?; + Ok(BlockNumber::Number(num.into())) + } + StringifiedBlockNumber::BlockNumber(b) => Ok(b), + } + } +} + +/// Supports parsing block number as strings +/// +/// See +pub fn deserialize_stringified_block_number<'de, D>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + let num = StringifiedBlockNumber::deserialize(deserializer)?; + num.try_into().map_err(serde::de::Error::custom) +} + /// Various block number representations, See [`lenient_block_number()`] #[derive(Clone, Copy, Deserialize)] #[serde(untagged)] diff --git a/ethers-etherscan/src/account.rs b/ethers-etherscan/src/account.rs index 7f01c578..a33f4484 100644 --- a/ethers-etherscan/src/account.rs +++ b/ethers-etherscan/src/account.rs @@ -1,17 +1,15 @@ +use crate::{Client, EtherscanError, Query, Response, Result}; +use ethers_core::{ + abi::Address, + types::{serde_helpers::*, BlockNumber, Bytes, H256, U256}, +}; +use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, collections::HashMap, fmt::{Display, Error, Formatter}, }; -use ethers_core::{ - abi::Address, - types::{BlockNumber, Bytes, H256, U256, U64}, -}; -use serde::{Deserialize, Serialize}; - -use crate::{Client, EtherscanError, Query, Response, Result}; - /// The raw response from the balance-related API endpoints #[derive(Debug, Serialize, Deserialize)] pub struct AccountBalance { @@ -100,6 +98,7 @@ impl GenesisOption { #[serde(rename_all = "camelCase")] pub struct NormalTransaction { pub is_error: String, + #[serde(deserialize_with = "deserialize_stringified_block_number")] pub block_number: BlockNumber, pub time_stamp: String, #[serde(with = "jsonstring")] @@ -108,12 +107,16 @@ pub struct NormalTransaction { pub nonce: GenesisOption, #[serde(with = "jsonstring")] pub block_hash: GenesisOption, - pub transaction_index: Option, + #[serde(deserialize_with = "deserialize_stringified_u64_opt")] + pub transaction_index: Option, #[serde(with = "jsonstring")] pub from: GenesisOption
, pub to: Option
, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub value: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric_opt")] pub gas_price: Option, #[serde(rename = "txreceipt_status")] pub tx_receipt_status: String, @@ -121,21 +124,26 @@ pub struct NormalTransaction { pub input: GenesisOption, #[serde(with = "jsonstring")] pub contract_address: GenesisOption
, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas_used: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub cumulative_gas_used: U256, - pub confirmations: U64, + #[serde(deserialize_with = "deserialize_stringified_u64")] + pub confirmations: u64, } /// The raw response from the internal transaction list API endpoint #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct InternalTransaction { + #[serde(deserialize_with = "deserialize_stringified_block_number")] pub block_number: BlockNumber, pub time_stamp: String, pub hash: H256, pub from: Address, #[serde(with = "jsonstring")] pub to: GenesisOption
, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub value: U256, #[serde(with = "jsonstring")] pub contract_address: GenesisOption
, @@ -143,7 +151,9 @@ pub struct InternalTransaction { pub input: GenesisOption, #[serde(rename = "type")] pub result_type: String, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas_used: U256, pub trace_id: String, pub is_error: String, @@ -154,35 +164,46 @@ pub struct InternalTransaction { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ERC20TokenTransferEvent { + #[serde(deserialize_with = "deserialize_stringified_block_number")] pub block_number: BlockNumber, pub time_stamp: String, pub hash: H256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub nonce: U256, pub block_hash: H256, pub from: Address, pub contract_address: Address, pub to: Option
, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub value: U256, pub token_name: String, pub token_symbol: String, pub token_decimal: String, - pub transaction_index: U64, + #[serde(deserialize_with = "deserialize_stringified_u64")] + pub transaction_index: u64, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric_opt")] pub gas_price: Option, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas_used: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub cumulative_gas_used: U256, /// deprecated pub input: String, - pub confirmations: U64, + #[serde(deserialize_with = "deserialize_stringified_u64")] + pub confirmations: u64, } /// The raw response from the ERC721 transfer list API endpoint #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ERC721TokenTransferEvent { + #[serde(deserialize_with = "deserialize_stringified_block_number")] pub block_number: BlockNumber, pub time_stamp: String, pub hash: H256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub nonce: U256, pub block_hash: H256, pub from: Address, @@ -193,23 +214,31 @@ pub struct ERC721TokenTransferEvent { pub token_name: String, pub token_symbol: String, pub token_decimal: String, - pub transaction_index: U64, + #[serde(deserialize_with = "deserialize_stringified_u64")] + pub transaction_index: u64, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric_opt")] pub gas_price: Option, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas_used: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub cumulative_gas_used: U256, /// deprecated pub input: String, - pub confirmations: U64, + #[serde(deserialize_with = "deserialize_stringified_u64")] + pub confirmations: u64, } /// The raw response from the ERC1155 transfer list API endpoint #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ERC1155TokenTransferEvent { + #[serde(deserialize_with = "deserialize_stringified_block_number")] pub block_number: BlockNumber, pub time_stamp: String, pub hash: H256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub nonce: U256, pub block_hash: H256, pub from: Address, @@ -220,20 +249,27 @@ pub struct ERC1155TokenTransferEvent { pub token_value: String, pub token_name: String, pub token_symbol: String, - pub transaction_index: U64, + #[serde(deserialize_with = "deserialize_stringified_u64")] + pub transaction_index: u64, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric_opt")] pub gas_price: Option, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub gas_used: U256, + #[serde(deserialize_with = "deserialize_stringified_numeric")] pub cumulative_gas_used: U256, /// deprecated pub input: String, - pub confirmations: U64, + #[serde(deserialize_with = "deserialize_stringified_u64")] + pub confirmations: u64, } /// The raw response from the mined blocks API endpoint #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MinedBlock { + #[serde(deserialize_with = "deserialize_stringified_block_number")] pub block_number: BlockNumber, pub time_stamp: String, pub block_reward: String, @@ -739,8 +775,12 @@ mod tests { ), None, ) - .await; - assert!(txs.is_ok()); + .await + .unwrap(); + let tx = txs.get(0).unwrap(); + assert_eq!(tx.gas_used, 93657u64.into()); + assert_eq!(tx.nonce, 10u64.into()); + assert_eq!(tx.block_number, 2228258u64.into()); }) .await } diff --git a/ethers-etherscan/src/gas.rs b/ethers-etherscan/src/gas.rs index e9c7e1de..94e93b1e 100644 --- a/ethers-etherscan/src/gas.rs +++ b/ethers-etherscan/src/gas.rs @@ -70,15 +70,11 @@ impl Client { #[cfg(test)] mod tests { - use std::time::Duration; - - use serial_test::serial; - - use ethers_core::types::Chain; - - use crate::tests::run_at_least_duration; - use super::*; + use crate::tests::run_at_least_duration; + use ethers_core::types::Chain; + use serial_test::serial; + use std::time::Duration; #[tokio::test] #[serial] diff --git a/ethers-etherscan/src/transaction.rs b/ethers-etherscan/src/transaction.rs index e916d55a..9259b097 100644 --- a/ethers-etherscan/src/transaction.rs +++ b/ethers-etherscan/src/transaction.rs @@ -52,13 +52,10 @@ impl Client { #[cfg(test)] mod tests { - use std::time::Duration; - - use serial_test::serial; - - use crate::{tests::run_at_least_duration, Chain}; - use super::*; + use crate::{tests::run_at_least_duration, Chain}; + use serial_test::serial; + use std::time::Duration; #[tokio::test] #[serial]