diff --git a/evm_rpc_types/src/lib.rs b/evm_rpc_types/src/lib.rs index 98064d1e..bae7ae7e 100644 --- a/evm_rpc_types/src/lib.rs +++ b/evm_rpc_types/src/lib.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::Formatter; use std::str::FromStr; -pub use request::{FeeHistoryArgs, GetLogsArgs, GetTransactionCountArgs}; +pub use request::{BlockTag, FeeHistoryArgs, GetLogsArgs, GetTransactionCountArgs}; pub use response::{Block, FeeHistory, LogEntry, SendRawTransactionStatus, TransactionReceipt}; pub use result::{ HttpOutcallError, JsonRpcError, MultiRpcResult, ProviderError, RpcError, RpcResult, @@ -25,17 +25,6 @@ pub use rpc_client::{ RpcConfig, RpcService, RpcServices, }; -#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize, Default)] -pub enum BlockTag { - #[default] - Latest, - Finalized, - Safe, - Earliest, - Pending, - Number(Nat256), -} - /// A `Nat` that is guaranteed to fit in 256 bits. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(try_from = "candid::Nat", into = "candid::Nat")] @@ -195,7 +184,13 @@ impl_hex_string!(Hex(Vec)); /// `FromHex::from_hex` will return `Err(FromHexError::OddLength)` /// when trying to decode such strings. #[derive(Clone, Debug, PartialEq, Eq)] -struct Byte([u8; 1]); +pub struct Byte([u8; 1]); + +impl Byte { + pub fn into_byte(self) -> u8 { + self.0[0] + } +} impl AsRef<[u8]> for Byte { fn as_ref(&self) -> &[u8] { diff --git a/evm_rpc_types/src/request/mod.rs b/evm_rpc_types/src/request/mod.rs index 15e655f7..3312215a 100644 --- a/evm_rpc_types/src/request/mod.rs +++ b/evm_rpc_types/src/request/mod.rs @@ -1,7 +1,18 @@ -use crate::{BlockTag, Hex20, Hex32, Nat256}; +use crate::{Hex20, Hex32, Nat256}; use candid::CandidType; use serde::Deserialize; +#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize, Default)] +pub enum BlockTag { + #[default] + Latest, + Finalized, + Safe, + Earliest, + Pending, + Number(Nat256), +} + #[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize)] pub struct FeeHistoryArgs { /// Number of blocks in the requested range. diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index 3bd2d79c..83480062 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -1,10 +1,9 @@ -//! Conversion between ckETH types and EVM RPC types. -//! This module is meant to be temporary and should be removed once the dependency on ckETH is removed, -//! see +//! Conversion between JSON types and Candid EVM RPC types. use crate::rpc_client::json::requests::BlockSpec; use crate::rpc_client::json::Hash; -use evm_rpc_types::{BlockTag, Hex, Hex20, Hex256, Hex32, HexByte, Nat256}; +use evm_rpc_types::BlockTag; +use evm_rpc_types::{Hex, Hex256, Hex32, HexByte, Nat256}; pub(super) fn into_block_spec(value: BlockTag) -> BlockSpec { use crate::rpc_client::json::requests; @@ -36,7 +35,7 @@ pub(super) fn into_get_logs_param( .map(|topic| { topic .into_iter() - .map(|t| crate::rpc_client::json::FixedSizeData(t.into())) + .map(|t| crate::rpc_client::json::FixedSizeData::new(t.into())) .collect() }) .collect(), @@ -52,11 +51,15 @@ pub(super) fn from_log_entries( fn from_log_entry(value: crate::rpc_client::json::responses::LogEntry) -> evm_rpc_types::LogEntry { evm_rpc_types::LogEntry { address: from_address(value.address), - topics: value.topics.into_iter().map(|t| t.0.into()).collect(), + topics: value + .topics + .into_iter() + .map(|t| t.into_bytes().into()) + .collect(), data: value.data.0.into(), - block_hash: value.block_hash.map(|x| x.0.into()), + block_hash: value.block_hash.map(|x| x.into_bytes().into()), block_number: value.block_number.map(Nat256::from), - transaction_hash: value.transaction_hash.map(|x| x.0.into()), + transaction_hash: value.transaction_hash.map(|x| x.into_bytes().into()), transaction_index: value.transaction_index.map(Nat256::from), log_index: value.log_index.map(Nat256::from), removed: value.removed, @@ -105,7 +108,7 @@ pub(super) fn from_transaction_receipt( value: crate::rpc_client::json::responses::TransactionReceipt, ) -> evm_rpc_types::TransactionReceipt { evm_rpc_types::TransactionReceipt { - block_hash: Hex32::from(value.block_hash.0), + block_hash: Hex32::from(value.block_hash.into_bytes()), block_number: value.block_number.into(), effective_gas_price: value.effective_gas_price.into(), gas_used: value.gas_used.into(), @@ -113,18 +116,14 @@ pub(super) fn from_transaction_receipt( crate::rpc_client::json::responses::TransactionStatus::Success => Nat256::from(1_u8), crate::rpc_client::json::responses::TransactionStatus::Failure => Nat256::from(0_u8), }), - transaction_hash: Hex32::from(value.transaction_hash.0), - // TODO 243: responses types from querying JSON-RPC providers should be strongly typed - // for all the following fields: contract_address, from, logs_bloom, to, transaction_index, tx_type - contract_address: value - .contract_address - .map(|address| Hex20::try_from(address).unwrap()), - from: Hex20::try_from(value.from).unwrap(), + transaction_hash: Hex32::from(value.transaction_hash.into_bytes()), + contract_address: value.contract_address.map(from_address), + from: from_address(value.from), logs: from_log_entries(value.logs), - logs_bloom: Hex256::try_from(value.logs_bloom).unwrap(), - to: value.to.map(|v| Hex20::try_from(v).unwrap()), + logs_bloom: Hex256::from(value.logs_bloom.into_bytes()), + to: value.to.map(from_address), transaction_index: value.transaction_index.into(), - tx_type: HexByte::try_from(value.r#type).unwrap(), + tx_type: HexByte::from(value.tx_type.into_byte()), } } @@ -133,31 +132,31 @@ pub(super) fn from_block(value: crate::rpc_client::json::responses::Block) -> ev base_fee_per_gas: value.base_fee_per_gas.map(Nat256::from), number: value.number.into(), difficulty: value.difficulty.map(Nat256::from), - extra_data: Hex::try_from(value.extra_data).unwrap(), + extra_data: Hex::from(value.extra_data.0), gas_limit: value.gas_limit.into(), gas_used: value.gas_used.into(), - hash: Hex32::try_from(value.hash).unwrap(), - logs_bloom: Hex256::try_from(value.logs_bloom).unwrap(), - miner: Hex20::try_from(value.miner).unwrap(), - mix_hash: Hex32::try_from(value.mix_hash).unwrap(), + hash: Hex32::from(value.hash.into_bytes()), + logs_bloom: Hex256::from(value.logs_bloom.into_bytes()), + miner: from_address(value.miner), + mix_hash: Hex32::from(value.mix_hash.into_bytes()), nonce: value.nonce.into(), - parent_hash: Hex32::try_from(value.parent_hash).unwrap(), - receipts_root: Hex32::try_from(value.receipts_root).unwrap(), - sha3_uncles: Hex32::try_from(value.sha3_uncles).unwrap(), + parent_hash: Hex32::from(value.parent_hash.into_bytes()), + receipts_root: Hex32::from(value.receipts_root.into_bytes()), + sha3_uncles: Hex32::from(value.sha3_uncles.into_bytes()), size: value.size.into(), - state_root: Hex32::try_from(value.state_root).unwrap(), + state_root: Hex32::from(value.state_root.into_bytes()), timestamp: value.timestamp.into(), total_difficulty: value.total_difficulty.map(Nat256::from), transactions: value .transactions .into_iter() - .map(|tx| Hex32::try_from(tx).unwrap()) + .map(|tx| Hex32::from(tx.into_bytes())) .collect(), - transactions_root: value.transactions_root.map(|x| Hex32::try_from(x).unwrap()), + transactions_root: value.transactions_root.map(|x| Hex32::from(x.into_bytes())), uncles: value .uncles .into_iter() - .map(|tx| Hex32::try_from(tx).unwrap()) + .map(|tx| Hex32::from(tx.into_bytes())) .collect(), } } @@ -183,7 +182,7 @@ pub(super) fn from_send_raw_transaction_result( } pub(super) fn into_hash(value: Hex32) -> Hash { - Hash(value.into()) + Hash::new(value.into()) } fn from_address(value: ic_ethereum_types::Address) -> evm_rpc_types::Hex20 { diff --git a/src/candid_rpc.rs b/src/candid_rpc/mod.rs similarity index 70% rename from src/candid_rpc.rs rename to src/candid_rpc/mod.rs index 65dc99da..fa2721bc 100644 --- a/src/candid_rpc.rs +++ b/src/candid_rpc/mod.rs @@ -1,4 +1,6 @@ mod cketh_conversion; +#[cfg(test)] +mod tests; use crate::rpc_client::{EthRpcClient, MultiCallError}; use crate::{ @@ -42,6 +44,7 @@ fn process_result(method: RpcMethod, result: Result>) -> } } +/// Adapt the `EthRpcClient` to the `Candid` interface used by the EVM-RPC canister. pub struct CandidRpcClient { client: EthRpcClient, } @@ -161,78 +164,3 @@ fn get_transaction_hash(raw_signed_transaction_hex: &Hex) -> Option { let transaction: Transaction = rlp::decode(raw_signed_transaction_hex.as_ref()).ok()?; Some(Hex32::from(transaction.hash.0)) } - -#[cfg(test)] -mod test { - use super::*; - use crate::rpc_client::{MultiCallError, MultiCallResults}; - use evm_rpc_types::{ProviderError, RpcError}; - - #[test] - fn test_process_result_mapping() { - use evm_rpc_types::{EthMainnetService, RpcService}; - - let method = RpcMethod::EthGetTransactionCount; - - assert_eq!( - process_result(method, Ok(5)), - MultiRpcResult::Consistent(Ok(5)) - ); - assert_eq!( - process_result( - method, - Err(MultiCallError::<()>::ConsistentError( - RpcError::ProviderError(ProviderError::MissingRequiredProvider) - )) - ), - MultiRpcResult::Consistent(Err(RpcError::ProviderError( - ProviderError::MissingRequiredProvider - ))) - ); - assert_eq!( - process_result( - method, - Err(MultiCallError::<()>::InconsistentResults( - MultiCallResults::default() - )) - ), - MultiRpcResult::Inconsistent(vec![]) - ); - assert_eq!( - process_result( - method, - Err(MultiCallError::InconsistentResults( - MultiCallResults::from_non_empty_iter(vec![( - RpcService::EthMainnet(EthMainnetService::Ankr), - Ok(5) - )]) - )) - ), - MultiRpcResult::Inconsistent(vec![( - RpcService::EthMainnet(EthMainnetService::Ankr), - Ok(5) - )]) - ); - assert_eq!( - process_result( - method, - Err(MultiCallError::InconsistentResults( - MultiCallResults::from_non_empty_iter(vec![ - (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)), - ( - RpcService::EthMainnet(EthMainnetService::Cloudflare), - Err(RpcError::ProviderError(ProviderError::NoPermission)) - ) - ]) - )) - ), - MultiRpcResult::Inconsistent(vec![ - (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)), - ( - RpcService::EthMainnet(EthMainnetService::Cloudflare), - Err(RpcError::ProviderError(ProviderError::NoPermission)) - ) - ]) - ); - } -} diff --git a/src/candid_rpc/tests.rs b/src/candid_rpc/tests.rs new file mode 100644 index 00000000..2a1c5b00 --- /dev/null +++ b/src/candid_rpc/tests.rs @@ -0,0 +1,73 @@ +use crate::candid_rpc::process_result; +use crate::rpc_client::{MultiCallError, MultiCallResults}; +use crate::types::RpcMethod; +use evm_rpc_types::MultiRpcResult; +use evm_rpc_types::{ProviderError, RpcError}; + +#[test] +fn test_process_result_mapping() { + use evm_rpc_types::{EthMainnetService, RpcService}; + + let method = RpcMethod::EthGetTransactionCount; + + assert_eq!( + process_result(method, Ok(5)), + MultiRpcResult::Consistent(Ok(5)) + ); + assert_eq!( + process_result( + method, + Err(MultiCallError::<()>::ConsistentError( + RpcError::ProviderError(ProviderError::MissingRequiredProvider) + )) + ), + MultiRpcResult::Consistent(Err(RpcError::ProviderError( + ProviderError::MissingRequiredProvider + ))) + ); + assert_eq!( + process_result( + method, + Err(MultiCallError::<()>::InconsistentResults( + MultiCallResults::default() + )) + ), + MultiRpcResult::Inconsistent(vec![]) + ); + assert_eq!( + process_result( + method, + Err(MultiCallError::InconsistentResults( + MultiCallResults::from_non_empty_iter(vec![( + RpcService::EthMainnet(EthMainnetService::Ankr), + Ok(5) + )]) + )) + ), + MultiRpcResult::Inconsistent(vec![( + RpcService::EthMainnet(EthMainnetService::Ankr), + Ok(5) + )]) + ); + assert_eq!( + process_result( + method, + Err(MultiCallError::InconsistentResults( + MultiCallResults::from_non_empty_iter(vec![ + (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)), + ( + RpcService::EthMainnet(EthMainnetService::Cloudflare), + Err(RpcError::ProviderError(ProviderError::NoPermission)) + ) + ]) + )) + ), + MultiRpcResult::Inconsistent(vec![ + (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)), + ( + RpcService::EthMainnet(EthMainnetService::Cloudflare), + Err(RpcError::ProviderError(ProviderError::NoPermission)) + ) + ]) + ); +} diff --git a/src/rpc_client/json/mod.rs b/src/rpc_client/json/mod.rs index 1c514f02..8c80fb78 100644 --- a/src/rpc_client/json/mod.rs +++ b/src/rpc_client/json/mod.rs @@ -1,100 +1,90 @@ //! Types used for JSON-RPC requests and responses with Ethereum JSON-RPC providers. -use crate::rpc_client::eth_rpc::HttpResponsePayload; use candid::Deserialize; +use evm_rpc_types::Byte; use serde::Serialize; use std::fmt::{Debug, Display, Formatter, LowerHex, UpperHex}; pub mod requests; pub mod responses; -#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] -#[serde(transparent)] -pub struct FixedSizeData(#[serde(with = "ic_ethereum_types::serde_data")] pub [u8; 32]); - -impl AsRef<[u8]> for FixedSizeData { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl std::str::FromStr for FixedSizeData { - type Err = String; - - fn from_str(s: &str) -> Result { - if !s.starts_with("0x") { - return Err("Ethereum hex string doesn't start with 0x".to_string()); +macro_rules! bytes_array { + ($name: ident, $size: expr) => { + #[doc = concat!("Ethereum byte array (hex representation is prefixed by 0x) wrapping a `[u8; ", stringify!($size), "]`. ")] + #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] + #[serde(transparent)] + pub struct $name(#[serde(with = "ic_ethereum_types::serde_data")] [u8; $size]); + + impl $name { + pub fn new(value: [u8; $size]) -> Self { + Self(value) + } + + pub fn into_bytes(self) -> [u8; $size] { + self.0 + } } - let mut bytes = [0u8; 32]; - hex::decode_to_slice(&s[2..], &mut bytes) - .map_err(|e| format!("failed to decode hash from hex: {}", e))?; - Ok(Self(bytes)) - } -} -impl Debug for FixedSizeData { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:x}", self) - } -} + impl AsRef<[u8]> for $name { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } -impl Display for FixedSizeData { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:x}", self) - } -} + impl std::str::FromStr for $name { + type Err = String; + + fn from_str(s: &str) -> Result { + if !s.starts_with("0x") { + return Err("Ethereum hex string doesn't start with 0x".to_string()); + } + let mut bytes = [0u8; $size]; + hex::decode_to_slice(&s[2..], &mut bytes) + .map_err(|e| format!("failed to decode hash from hex: {}", e))?; + Ok(Self(bytes)) + } + } -impl LowerHex for FixedSizeData { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "0x{}", hex::encode(self.0)) - } -} + impl Debug for $name { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:x}", self) + } + } -impl UpperHex for FixedSizeData { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "0x{}", hex::encode_upper(self.0)) - } -} + impl Display for $name { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:x}", self) + } + } -#[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct Hash(#[serde(with = "ic_ethereum_types::serde_data")] pub [u8; 32]); + impl LowerHex for $name { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "0x{}", hex::encode(self.0)) + } + } -impl Debug for Hash { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:x}", self) - } + impl UpperHex for $name { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "0x{}", hex::encode_upper(self.0)) + } + } + }; } -impl Display for Hash { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:x}", self) - } -} +bytes_array!(FixedSizeData, 32); +bytes_array!(Hash, 32); +bytes_array!(LogsBloom, 256); -impl LowerHex for Hash { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "0x{}", hex::encode(self.0)) - } -} +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct JsonByte(#[serde(with = "ic_ethereum_types::serde_data")] Byte); -impl UpperHex for Hash { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "0x{}", hex::encode_upper(self.0)) +impl JsonByte { + pub fn new(value: u8) -> Self { + Self(Byte::from(value)) } -} - -impl std::str::FromStr for Hash { - type Err = String; - fn from_str(s: &str) -> Result { - if !s.starts_with("0x") { - return Err("Ethereum hash doesn't start with 0x".to_string()); - } - let mut bytes = [0u8; 32]; - hex::decode_to_slice(&s[2..], &mut bytes) - .map_err(|e| format!("failed to decode hash from hex: {}", e))?; - Ok(Self(bytes)) + pub fn into_byte(self) -> u8 { + self.0.into_byte() } } - -impl HttpResponsePayload for Hash {} diff --git a/src/rpc_client/json/responses.rs b/src/rpc_client/json/responses.rs index 2287db90..6453f31f 100644 --- a/src/rpc_client/json/responses.rs +++ b/src/rpc_client/json/responses.rs @@ -1,8 +1,8 @@ -use crate::rpc_client::amount::Amount; use crate::rpc_client::eth_rpc::{HttpResponsePayload, ResponseTransform}; -use crate::rpc_client::json::{FixedSizeData, Hash}; +use crate::rpc_client::json::{FixedSizeData, Hash, JsonByte, LogsBloom}; use crate::rpc_client::numeric::{ - BlockNonce, BlockNumber, Difficulty, GasAmount, LogIndex, NumBytes, Timestamp, Wei, WeiPerGas, + BlockNonce, BlockNumber, Difficulty, GasAmount, LogIndex, NumBytes, Timestamp, + TransactionIndex, Wei, WeiPerGas, }; use candid::Deserialize; use evm_rpc_types::{JsonRpcError, RpcError}; @@ -35,17 +35,30 @@ pub struct TransactionReceipt { #[serde(rename = "transactionHash")] pub transaction_hash: Hash, + /// The contract address created, if the transaction was a contract creation, otherwise null. #[serde(rename = "contractAddress")] - pub contract_address: Option, + pub contract_address: Option
, - pub from: String, + /// Address of the sender. + pub from: Address, + + /// An array of log objects that generated this transaction pub logs: Vec, + + /// The bloom filter which is used to retrieve related logs #[serde(rename = "logsBloom")] - pub logs_bloom: String, - pub to: Option, + pub logs_bloom: LogsBloom, + + /// Address of the receiver or null in a contract creation transaction. + pub to: Option
, + + /// The transactions index position in the block #[serde(rename = "transactionIndex")] - pub transaction_index: Amount<()>, - pub r#type: String, + pub transaction_index: TransactionIndex, + + /// The type of the transaction (e.g. "0x0" for legacy transactions, "0x2" for EIP-1559 transactions) + #[serde(rename = "type")] + pub tx_type: JsonByte, } impl HttpResponsePayload for TransactionReceipt { @@ -130,14 +143,14 @@ pub struct LogEntry { /// None if the block is pending. #[serde(rename = "blockNumber")] pub block_number: Option, - // 32 Bytes - hash of the transactions from which this log was created. - // None when its pending log. + /// 32 Bytes - hash of the transactions from which this log was created. + /// None when its pending log. #[serde(rename = "transactionHash")] pub transaction_hash: Option, - // Integer of the transactions position within the block the log was created from. - // None if the log is pending. + /// Integer of the transactions position within the block the log was created from. + /// None if the log is pending. #[serde(rename = "transactionIndex")] - pub transaction_index: Option>, + pub transaction_index: Option, /// 32 Bytes - hash of the block in which this log appeared. /// None if the block is pending. #[serde(rename = "blockHash")] @@ -160,42 +173,89 @@ impl HttpResponsePayload for Vec { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Block { + /// Base fee per gas + /// Only included for blocks after the London Upgrade / EIP-1559. #[serde(rename = "baseFeePerGas")] pub base_fee_per_gas: Option, + + /// Block number pub number: BlockNumber, + + /// Difficulty pub difficulty: Option, + + /// Extra data #[serde(rename = "extraData")] - pub extra_data: String, + pub extra_data: Data, + + /// Maximum gas allowed in this block #[serde(rename = "gasLimit")] pub gas_limit: GasAmount, + + /// Gas used by all transactions in this block #[serde(rename = "gasUsed")] pub gas_used: GasAmount, - pub hash: String, + + /// Block hash + pub hash: Hash, + + /// Bloom filter for the logs. #[serde(rename = "logsBloom")] - pub logs_bloom: String, - pub miner: String, + pub logs_bloom: LogsBloom, + + /// Miner + pub miner: Address, + + /// Mix hash #[serde(rename = "mixHash")] - pub mix_hash: String, + pub mix_hash: Hash, + + /// Nonce pub nonce: BlockNonce, + + /// Parent block hash #[serde(rename = "parentHash")] - pub parent_hash: String, + pub parent_hash: Hash, + + /// Receipts root #[serde(rename = "receiptsRoot")] - pub receipts_root: String, + pub receipts_root: Hash, + + /// Ommers hash #[serde(rename = "sha3Uncles")] - pub sha3_uncles: String, + pub sha3_uncles: Hash, + + /// Block size pub size: NumBytes, + + /// State root #[serde(rename = "stateRoot")] - pub state_root: String, + pub state_root: Hash, + + /// Timestamp #[serde(rename = "timestamp")] pub timestamp: Timestamp, + + /// Total difficulty is the sum of all difficulty values up to and including this block. + /// + /// Note: this field was removed from the official JSON-RPC specification in + /// https://github.com/ethereum/execution-apis/pull/570 and may no longer be served by providers. #[serde(rename = "totalDifficulty")] pub total_difficulty: Option, + + /// List of transactions in the block. + /// Note that since `eth_get_block_by_number` sets `include_full_transactions` to false, + /// this field only contains the transaction hashes and not the full transactions. #[serde(default)] - pub transactions: Vec, + pub transactions: Vec, + + /// Transactions root #[serde(rename = "transactionsRoot")] - pub transactions_root: Option, + pub transactions_root: Option, + + /// Uncles #[serde(default)] - pub uncles: Vec, + pub uncles: Vec, } impl HttpResponsePayload for Block { diff --git a/src/rpc_client/numeric/mod.rs b/src/rpc_client/numeric/mod.rs index 99e55a74..597e6659 100644 --- a/src/rpc_client/numeric/mod.rs +++ b/src/rpc_client/numeric/mod.rs @@ -17,6 +17,9 @@ pub enum TransactionCountTag {} /// but depending on the block height the two may differ. pub type TransactionCount = Amount; +pub enum TransactionIndexTag {} +pub type TransactionIndex = Amount; + pub enum BlockNumberTag {} pub type BlockNumber = Amount; diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs index ca3a7fef..1005626c 100644 --- a/src/rpc_client/tests.rs +++ b/src/rpc_client/tests.rs @@ -409,7 +409,7 @@ mod multi_call_results { mod eth_get_transaction_receipt { use crate::rpc_client::json::responses::{TransactionReceipt, TransactionStatus}; - use crate::rpc_client::json::Hash; + use crate::rpc_client::json::{Hash, JsonByte}; use crate::rpc_client::numeric::{BlockNumber, GasAmount, WeiPerGas}; use assert_matches::assert_matches; use proptest::proptest; @@ -452,12 +452,12 @@ mod eth_get_transaction_receipt { ) .unwrap(), contract_address: None, - from: "0x1789f79e95324a47c5fd6693071188e82e9a3558".to_string(), + from: "0x1789f79e95324a47c5fd6693071188e82e9a3558".parse().unwrap(), logs: vec![], - logs_bloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(), - to: Some("0xdd2851cdd40ae6536831558dd46db62fac7a844d".to_string()), + logs_bloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(), + to: Some("0xdd2851cdd40ae6536831558dd46db62fac7a844d".parse().unwrap()), transaction_index: 0x32_u32.into(), - r#type: "0x2".to_string(), + tx_type: JsonByte::new(2), } ) }