diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index c31b1d3d..338ebbd1 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -4,24 +4,25 @@ use crate::rpc_client::checked_amount::CheckedAmountOf; use crate::rpc_client::eth_rpc::{Hash, Quantity}; +use crate::rpc_client::json::requests::BlockSpec; use evm_rpc_types::{BlockTag, Hex, Hex20, Hex256, Hex32, HexByte, Nat256}; -/**/ -pub(super) fn into_block_spec(value: BlockTag) -> crate::rpc_client::eth_rpc::BlockSpec { - use crate::rpc_client::eth_rpc::{self, BlockSpec}; + +pub(super) fn into_block_spec(value: BlockTag) -> BlockSpec { + use crate::rpc_client::json::requests; match value { BlockTag::Number(n) => BlockSpec::Number(into_checked_amount_of(n)), - BlockTag::Latest => BlockSpec::Tag(eth_rpc::BlockTag::Latest), - BlockTag::Safe => BlockSpec::Tag(eth_rpc::BlockTag::Safe), - BlockTag::Finalized => BlockSpec::Tag(eth_rpc::BlockTag::Finalized), - BlockTag::Earliest => BlockSpec::Tag(eth_rpc::BlockTag::Earliest), - BlockTag::Pending => BlockSpec::Tag(eth_rpc::BlockTag::Pending), + BlockTag::Latest => BlockSpec::Tag(requests::BlockTag::Latest), + BlockTag::Safe => BlockSpec::Tag(requests::BlockTag::Safe), + BlockTag::Finalized => BlockSpec::Tag(requests::BlockTag::Finalized), + BlockTag::Earliest => BlockSpec::Tag(requests::BlockTag::Earliest), + BlockTag::Pending => BlockSpec::Tag(requests::BlockTag::Pending), } } pub(super) fn into_get_logs_param( value: evm_rpc_types::GetLogsArgs, -) -> crate::rpc_client::eth_rpc::GetLogsParam { - crate::rpc_client::eth_rpc::GetLogsParam { +) -> crate::rpc_client::json::requests::GetLogsParam { + crate::rpc_client::json::requests::GetLogsParam { from_block: value.from_block.map(into_block_spec).unwrap_or_default(), to_block: value.to_block.map(into_block_spec).unwrap_or_default(), address: value @@ -44,12 +45,12 @@ pub(super) fn into_get_logs_param( } pub(super) fn from_log_entries( - value: Vec, + value: Vec, ) -> Vec { value.into_iter().map(from_log_entry).collect() } -fn from_log_entry(value: crate::rpc_client::eth_rpc::LogEntry) -> evm_rpc_types::LogEntry { +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(), @@ -65,8 +66,8 @@ fn from_log_entry(value: crate::rpc_client::eth_rpc::LogEntry) -> evm_rpc_types: pub(super) fn into_fee_history_params( value: evm_rpc_types::FeeHistoryArgs, -) -> crate::rpc_client::eth_rpc::FeeHistoryParams { - crate::rpc_client::eth_rpc::FeeHistoryParams { +) -> crate::rpc_client::json::requests::FeeHistoryParams { + crate::rpc_client::json::requests::FeeHistoryParams { block_count: into_quantity(value.block_count), highest_block: into_block_spec(value.newest_block), reward_percentiles: value.reward_percentiles.unwrap_or_default(), @@ -74,7 +75,7 @@ pub(super) fn into_fee_history_params( } pub(super) fn from_fee_history( - value: crate::rpc_client::eth_rpc::FeeHistory, + value: crate::rpc_client::json::responses::FeeHistory, ) -> evm_rpc_types::FeeHistory { evm_rpc_types::FeeHistory { oldest_block: from_checked_amount_of(value.oldest_block), @@ -94,15 +95,15 @@ pub(super) fn from_fee_history( pub(super) fn into_get_transaction_count_params( value: evm_rpc_types::GetTransactionCountArgs, -) -> crate::rpc_client::requests::GetTransactionCountParams { - crate::rpc_client::requests::GetTransactionCountParams { +) -> crate::rpc_client::json::requests::GetTransactionCountParams { + crate::rpc_client::json::requests::GetTransactionCountParams { address: ic_ethereum_types::Address::new(value.address.into()), block: into_block_spec(value.block), } } pub(super) fn from_transaction_receipt( - value: crate::rpc_client::responses::TransactionReceipt, + value: crate::rpc_client::json::responses::TransactionReceipt, ) -> evm_rpc_types::TransactionReceipt { evm_rpc_types::TransactionReceipt { block_hash: Hex32::from(value.block_hash.0), @@ -110,8 +111,8 @@ pub(super) fn from_transaction_receipt( effective_gas_price: from_checked_amount_of(value.effective_gas_price), gas_used: from_checked_amount_of(value.gas_used), status: match value.status { - crate::rpc_client::responses::TransactionStatus::Success => Nat256::from(1_u8), - crate::rpc_client::responses::TransactionStatus::Failure => Nat256::from(0_u8), + 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 @@ -128,7 +129,7 @@ pub(super) fn from_transaction_receipt( } } -pub(super) fn from_block(value: crate::rpc_client::eth_rpc::Block) -> evm_rpc_types::Block { +pub(super) fn from_block(value: crate::rpc_client::json::responses::Block) -> evm_rpc_types::Block { evm_rpc_types::Block { base_fee_per_gas: value.base_fee_per_gas.map(from_checked_amount_of), number: from_checked_amount_of(value.number), @@ -164,19 +165,19 @@ pub(super) fn from_block(value: crate::rpc_client::eth_rpc::Block) -> evm_rpc_ty pub(super) fn from_send_raw_transaction_result( transaction_hash: Option, - value: crate::rpc_client::eth_rpc::SendRawTransactionResult, + value: crate::rpc_client::json::responses::SendRawTransactionResult, ) -> evm_rpc_types::SendRawTransactionStatus { match value { - crate::rpc_client::eth_rpc::SendRawTransactionResult::Ok => { + crate::rpc_client::json::responses::SendRawTransactionResult::Ok => { evm_rpc_types::SendRawTransactionStatus::Ok(transaction_hash) } - crate::rpc_client::eth_rpc::SendRawTransactionResult::InsufficientFunds => { + crate::rpc_client::json::responses::SendRawTransactionResult::InsufficientFunds => { evm_rpc_types::SendRawTransactionStatus::InsufficientFunds } - crate::rpc_client::eth_rpc::SendRawTransactionResult::NonceTooLow => { + crate::rpc_client::json::responses::SendRawTransactionResult::NonceTooLow => { evm_rpc_types::SendRawTransactionStatus::NonceTooLow } - crate::rpc_client::eth_rpc::SendRawTransactionResult::NonceTooHigh => { + crate::rpc_client::json::responses::SendRawTransactionResult::NonceTooHigh => { evm_rpc_types::SendRawTransactionStatus::NonceTooHigh } } diff --git a/src/rpc_client/eth_rpc/mod.rs b/src/rpc_client/eth_rpc/mod.rs index dc600327..f8e6bcf8 100644 --- a/src/rpc_client/eth_rpc/mod.rs +++ b/src/rpc_client/eth_rpc/mod.rs @@ -5,10 +5,9 @@ use crate::accounting::get_http_request_cost; use crate::logs::{DEBUG, TRACE_HTTP}; use crate::memory::next_request_id; use crate::providers::resolve_rpc_service; -use crate::rpc_client::checked_amount::CheckedAmountOf; use crate::rpc_client::eth_rpc_error::{sanitize_send_raw_transaction_result, Parser}; -use crate::rpc_client::numeric::{BlockNumber, LogIndex, TransactionCount, Wei, WeiPerGas}; -use crate::rpc_client::responses::TransactionReceipt; +use crate::rpc_client::json::responses::{Block, FeeHistory, LogEntry, TransactionReceipt}; +use crate::rpc_client::numeric::{TransactionCount, Wei}; use crate::types::MetricRpcMethod; use candid::candid_method; use ethnum; @@ -20,7 +19,6 @@ use ic_cdk::api::management_canister::http_request::{ TransformContext, }; use ic_cdk_macros::query; -use ic_ethereum_types::Address; use minicbor::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt; @@ -37,8 +35,8 @@ mod tests; pub const HEADER_SIZE_LIMIT: u64 = 2 * 1024; // This constant comes from the IC specification: -// > If provided, the value must not exceed 2MB -const HTTP_MAX_SIZE: u64 = 2_000_000; +// > If provided, the value must not exceed 2MiB +const HTTP_MAX_SIZE: u64 = 2 * 1024 * 1024; pub const MAX_PAYLOAD_SIZE: u64 = HTTP_MAX_SIZE - HEADER_SIZE_LIMIT; @@ -102,20 +100,6 @@ impl UpperHex for FixedSizeData { } } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] -pub enum SendRawTransactionResult { - Ok, - InsufficientFunds, - NonceTooLow, - NonceTooHigh, -} - -impl HttpResponsePayload for SendRawTransactionResult { - fn response_transform() -> Option { - Some(ResponseTransform::SendRawTransaction) - } -} - #[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct Hash(#[serde(with = "ic_ethereum_types::serde_data")] pub [u8; 32]); @@ -159,278 +143,8 @@ impl std::str::FromStr for Hash { impl HttpResponsePayload for Hash {} -/// Block tags. -/// See -#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum BlockTag { - /// The latest mined block. - #[default] - #[serde(rename = "latest")] - Latest, - /// The latest safe head block. - /// See - /// - #[serde(rename = "safe")] - Safe, - /// The latest finalized block. - /// See - /// - #[serde(rename = "finalized")] - Finalized, - #[serde(rename = "earliest")] - Earliest, - #[serde(rename = "pending")] - Pending, -} - -impl Display for BlockTag { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Latest => write!(f, "latest"), - Self::Safe => write!(f, "safe"), - Self::Finalized => write!(f, "finalized"), - Self::Earliest => write!(f, "earliest"), - Self::Pending => write!(f, "pending"), - } - } -} - -/// The block specification indicating which block to query. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(untagged)] -pub enum BlockSpec { - /// Query the block with the specified index. - Number(BlockNumber), - /// Query the block with the specified tag. - Tag(BlockTag), -} - -impl Default for BlockSpec { - fn default() -> Self { - Self::Tag(BlockTag::default()) - } -} - -impl std::str::FromStr for BlockSpec { - type Err = String; - - fn from_str(s: &str) -> Result { - if s.starts_with("0x") { - let block_number = BlockNumber::from_str_hex(s) - .map_err(|e| format!("failed to parse block number '{s}': {e}"))?; - return Ok(BlockSpec::Number(block_number)); - } - Ok(BlockSpec::Tag(match s { - "latest" => BlockTag::Latest, - "safe" => BlockTag::Safe, - "finalized" => BlockTag::Finalized, - _ => return Err(format!("unknown block tag '{s}'")), - })) - } -} - -/// Parameters of the [`eth_getLogs`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs) call. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GetLogsParam { - /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. - #[serde(rename = "fromBlock")] - pub from_block: BlockSpec, - /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. - #[serde(rename = "toBlock")] - pub to_block: BlockSpec, - /// Contract address or a list of addresses from which logs should originate. - pub address: Vec
, - /// Array of 32 Bytes DATA topics. - /// Topics are order-dependent. - /// Each topic can also be an array of DATA with "or" options. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub topics: Vec>, -} - -/// An entry of the [`eth_getLogs`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs) call reply. -/// -/// Example: -/// ```json -/// { -/// "address": "0x7e41257f7b5c3dd3313ef02b1f4c864fe95bec2b", -/// "topics": [ -/// "0x2a2607d40f4a6feb97c36e0efd57e0aa3e42e0332af4fceb78f21b7dffcbd657" -/// ], -/// "data": "0x00000000000000000000000055654e7405fcb336386ea8f36954a211b2cda764000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003f62327071372d71677a7a692d74623564622d72357363692d637736736c2d6e646f756c2d666f7435742d347a7732702d657a6677692d74616a32792d76716500", -/// "blockNumber": "0x3aa4f4", -/// "transactionHash": "0x5618f72c485bd98a3df58d900eabe9e24bfaa972a6fe5227e02233fad2db1154", -/// "transactionIndex": "0x6", -/// "blockHash": "0x908e6b84d26d71421bfaa08e7966e0afcef3883a28a53a0a7a31104caf1e94c2", -/// "logIndex": "0x8", -/// "removed": false -/// } -/// ``` -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] -pub struct LogEntry { - /// The address from which this log originated. - pub address: Address, - /// Array of 0 to 4 32 Bytes DATA of indexed log arguments. - /// In solidity: The first topic is the event signature hash (e.g. Deposit(address,bytes32,uint256)), - /// unless you declared the event with the anonymous specifier. - pub topics: Vec, - /// Contains one or more 32-byte non-indexed log arguments. - pub data: Data, - /// The block number in which this log appeared. - /// 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 if the transaction is pending. - #[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. - #[serde(rename = "transactionIndex")] - pub transaction_index: Option>, - /// 32 Bytes - hash of the block in which this log appeared. - /// None if the block is pending. - #[serde(rename = "blockHash")] - pub block_hash: Option, - /// Integer of the log index position in the block. - /// None if the log is pending. - #[serde(rename = "logIndex")] - pub log_index: Option, - /// "true" when the log was removed due to a chain reorganization. - /// "false" if it's a valid log. - #[serde(default)] - pub removed: bool, -} - -impl HttpResponsePayload for Vec { - fn response_transform() -> Option { - Some(ResponseTransform::LogEntries) - } -} - -/// Parameters of the [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) call. -#[derive(Debug, Serialize, Clone)] -#[serde(into = "(BlockSpec, bool)")] -pub struct GetBlockByNumberParams { - /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. - pub block: BlockSpec, - /// If true, returns the full transaction objects. If false, returns only the hashes of the transactions. - pub include_full_transactions: bool, -} - -impl From for (BlockSpec, bool) { - fn from(value: GetBlockByNumberParams) -> Self { - (value.block, value.include_full_transactions) - } -} - -/// Parameters of the [`eth_feeHistory`](https://ethereum.github.io/execution-apis/api-documentation/) call. -#[derive(Debug, Serialize, Clone)] -#[serde(into = "(Quantity, BlockSpec, Vec)")] -pub struct FeeHistoryParams { - /// Number of blocks in the requested range. - /// Typically providers request this to be between 1 and 1024. - pub block_count: Quantity, - /// Highest block of the requested range. - /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. - pub highest_block: BlockSpec, - /// A monotonically increasing list of percentile values between 0 and 100. - /// For each block in the requested range, the transactions will be sorted in ascending order - /// by effective tip per gas and the corresponding effective tip for the percentile - /// will be determined, accounting for gas consumed. - pub reward_percentiles: Vec, -} - -impl From for (Quantity, BlockSpec, Vec) { - fn from(value: FeeHistoryParams) -> Self { - ( - value.block_count, - value.highest_block, - value.reward_percentiles, - ) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct FeeHistory { - /// Lowest number block of the returned range. - #[serde(rename = "oldestBlock")] - pub oldest_block: BlockNumber, - /// An array of block base fees per gas. - /// This includes the next block after the newest of the returned range, - /// because this value can be derived from the newest block. - /// Zeroes are returned for pre-EIP-1559 blocks. - #[serde(rename = "baseFeePerGas")] - pub base_fee_per_gas: Vec, - /// An array of block gas used ratios (gasUsed / gasLimit). - #[serde(default)] - #[serde(rename = "gasUsedRatio")] - pub gas_used_ratio: Vec, - /// A two-dimensional array of effective priority fees per gas at the requested block percentiles. - #[serde(default)] - #[serde(rename = "reward")] - pub reward: Vec>, -} - -impl HttpResponsePayload for FeeHistory { - fn response_transform() -> Option { - Some(ResponseTransform::FeeHistory) - } -} - impl HttpResponsePayload for Wei {} -impl From for BlockSpec { - fn from(value: BlockNumber) -> Self { - BlockSpec::Number(value) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct Block { - #[serde(rename = "baseFeePerGas")] - pub base_fee_per_gas: Option, - pub number: BlockNumber, - pub difficulty: Option>, - #[serde(rename = "extraData")] - pub extra_data: String, - #[serde(rename = "gasLimit")] - pub gas_limit: CheckedAmountOf<()>, - #[serde(rename = "gasUsed")] - pub gas_used: CheckedAmountOf<()>, - pub hash: String, - #[serde(rename = "logsBloom")] - pub logs_bloom: String, - pub miner: String, - #[serde(rename = "mixHash")] - pub mix_hash: String, - pub nonce: CheckedAmountOf<()>, - #[serde(rename = "parentHash")] - pub parent_hash: String, - #[serde(rename = "receiptsRoot")] - pub receipts_root: String, - #[serde(rename = "sha3Uncles")] - pub sha3_uncles: String, - pub size: CheckedAmountOf<()>, - #[serde(rename = "stateRoot")] - pub state_root: String, - #[serde(rename = "timestamp")] - pub timestamp: CheckedAmountOf<()>, - #[serde(rename = "totalDifficulty")] - pub total_difficulty: Option>, - #[serde(default)] - pub transactions: Vec, - #[serde(rename = "transactionsRoot")] - pub transactions_root: Option, - #[serde(default)] - pub uncles: Vec, -} - -impl HttpResponsePayload for Block { - fn response_transform() -> Option { - Some(ResponseTransform::Block) - } -} - /// An envelope for all JSON-RPC requests. #[derive(Clone, Serialize, Deserialize)] pub struct JsonRpcRequest { diff --git a/src/rpc_client/eth_rpc_error/mod.rs b/src/rpc_client/eth_rpc_error/mod.rs index 86191da5..21fc57ec 100644 --- a/src/rpc_client/eth_rpc_error/mod.rs +++ b/src/rpc_client/eth_rpc_error/mod.rs @@ -1,5 +1,6 @@ use crate::logs::DEBUG; -use crate::rpc_client::eth_rpc::{Hash, JsonRpcReply, JsonRpcResult, SendRawTransactionResult}; +use crate::rpc_client::eth_rpc::{Hash, JsonRpcReply, JsonRpcResult}; +use crate::rpc_client::json::responses::SendRawTransactionResult; use ic_canister_log::log; #[cfg(test)] diff --git a/src/rpc_client/json/mod.rs b/src/rpc_client/json/mod.rs new file mode 100644 index 00000000..6c55067b --- /dev/null +++ b/src/rpc_client/json/mod.rs @@ -0,0 +1,4 @@ +//! Types used for JSON-RPC requests and responses with Ethereum JSON-RPC providers. + +pub mod requests; +pub mod responses; diff --git a/src/rpc_client/json/requests.rs b/src/rpc_client/json/requests.rs new file mode 100644 index 00000000..14daec21 --- /dev/null +++ b/src/rpc_client/json/requests.rs @@ -0,0 +1,144 @@ +use crate::rpc_client::eth_rpc::{FixedSizeData, Quantity}; +use crate::rpc_client::numeric::BlockNumber; +use candid::Deserialize; +use ic_ethereum_types::Address; +use serde::Serialize; +use std::fmt; +use std::fmt::{Display, Formatter}; + +/// Parameters of the [`eth_getTransactionCount`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactioncount) call. +#[derive(Debug, Serialize, Clone)] +#[serde(into = "(Address, BlockSpec)")] +pub struct GetTransactionCountParams { + /// The address for which the transaction count is requested. + pub address: Address, + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + pub block: BlockSpec, +} + +impl From for (Address, BlockSpec) { + fn from(params: GetTransactionCountParams) -> Self { + (params.address, params.block) + } +} + +/// Parameters of the [`eth_getLogs`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs) call. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GetLogsParam { + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + #[serde(rename = "fromBlock")] + pub from_block: BlockSpec, + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + #[serde(rename = "toBlock")] + pub to_block: BlockSpec, + /// Contract address or a list of addresses from which logs should originate. + pub address: Vec
, + /// Array of 32 Bytes DATA topics. + /// Topics are order-dependent. + /// Each topic can also be an array of DATA with "or" options. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub topics: Vec>, +} + +/// Parameters of the [`eth_feeHistory`](https://ethereum.github.io/execution-apis/api-documentation/) call. +#[derive(Debug, Serialize, Clone)] +#[serde(into = "(Quantity, BlockSpec, Vec)")] +pub struct FeeHistoryParams { + /// Number of blocks in the requested range. + /// Typically providers request this to be between 1 and 1024. + pub block_count: Quantity, + /// Highest block of the requested range. + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + pub highest_block: BlockSpec, + /// A monotonically increasing list of percentile values between 0 and 100. + /// For each block in the requested range, the transactions will be sorted in ascending order + /// by effective tip per gas and the corresponding effective tip for the percentile + /// will be determined, accounting for gas consumed. + pub reward_percentiles: Vec, +} + +impl From for (Quantity, BlockSpec, Vec) { + fn from(value: FeeHistoryParams) -> Self { + ( + value.block_count, + value.highest_block, + value.reward_percentiles, + ) + } +} + +/// The block specification indicating which block to query. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum BlockSpec { + /// Query the block with the specified index. + Number(BlockNumber), + /// Query the block with the specified tag. + Tag(BlockTag), +} + +impl Default for BlockSpec { + fn default() -> Self { + Self::Tag(BlockTag::default()) + } +} + +impl From for BlockSpec { + fn from(value: BlockNumber) -> Self { + BlockSpec::Number(value) + } +} + +/// Block tags. +/// See +#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum BlockTag { + /// The latest mined block. + #[default] + #[serde(rename = "latest")] + Latest, + /// The latest safe head block. + /// See + /// + #[serde(rename = "safe")] + Safe, + /// The latest finalized block. + /// See + /// + #[serde(rename = "finalized")] + Finalized, + /// Earliest/genesis block + #[serde(rename = "earliest")] + Earliest, + /// Pending state/transactions + #[serde(rename = "pending")] + Pending, +} + +impl Display for BlockTag { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Latest => write!(f, "latest"), + Self::Safe => write!(f, "safe"), + Self::Finalized => write!(f, "finalized"), + Self::Earliest => write!(f, "earliest"), + Self::Pending => write!(f, "pending"), + } + } +} + +/// Parameters of the [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) call. +#[derive(Debug, Serialize, Clone)] +#[serde(into = "(BlockSpec, bool)")] +pub struct GetBlockByNumberParams { + /// Integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + pub block: BlockSpec, + /// If true, returns the full transaction objects. If false, returns only the hashes of the transactions. + pub include_full_transactions: bool, +} + +impl From for (BlockSpec, bool) { + fn from(value: GetBlockByNumberParams) -> Self { + (value.block, value.include_full_transactions) + } +} diff --git a/src/rpc_client/json/responses.rs b/src/rpc_client/json/responses.rs new file mode 100644 index 00000000..6a20cfae --- /dev/null +++ b/src/rpc_client/json/responses.rs @@ -0,0 +1,244 @@ +use crate::rpc_client::checked_amount::CheckedAmountOf; +use crate::rpc_client::eth_rpc::{ + Data, FixedSizeData, Hash, HttpResponsePayload, ResponseTransform, +}; +use crate::rpc_client::numeric::{BlockNumber, GasAmount, LogIndex, Wei, WeiPerGas}; +use candid::Deserialize; +use ic_ethereum_types::Address; +use serde::Serialize; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct TransactionReceipt { + /// The hash of the block containing the transaction. + #[serde(rename = "blockHash")] + pub block_hash: Hash, + + /// The number of the block containing the transaction. + #[serde(rename = "blockNumber")] + pub block_number: BlockNumber, + + /// The total base charge plus tip paid for each unit of gas + #[serde(rename = "effectiveGasPrice")] + pub effective_gas_price: WeiPerGas, + + /// The amount of gas used by this specific transaction alone + #[serde(rename = "gasUsed")] + pub gas_used: GasAmount, + + /// Status of the transaction. + pub status: TransactionStatus, + + /// The hash of the transaction + #[serde(rename = "transactionHash")] + pub transaction_hash: Hash, + + #[serde(rename = "contractAddress")] + pub contract_address: Option, + + pub from: String, + pub logs: Vec, + #[serde(rename = "logsBloom")] + pub logs_bloom: String, + pub to: String, + #[serde(rename = "transactionIndex")] + pub transaction_index: CheckedAmountOf<()>, + pub r#type: String, +} + +impl HttpResponsePayload for TransactionReceipt { + fn response_transform() -> Option { + Some(ResponseTransform::TransactionReceipt) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] +#[serde(try_from = "ethnum::u256", into = "ethnum::u256")] +pub enum TransactionStatus { + /// Transaction was mined and executed successfully. + Success, + + /// Transaction was mined but execution failed (e.g., out-of-gas error). + /// The amount of the transaction is returned to the sender but gas is consumed. + /// Note that this is different from a transaction that is not mined at all: a failed transaction + /// is part of the blockchain and the next transaction from the same sender should have an incremented + /// transaction nonce. + Failure, +} + +impl From for ethnum::u256 { + fn from(value: TransactionStatus) -> Self { + match value { + TransactionStatus::Success => ethnum::u256::ONE, + TransactionStatus::Failure => ethnum::u256::ZERO, + } + } +} + +impl TryFrom for TransactionStatus { + type Error = String; + + fn try_from(value: ethnum::u256) -> Result { + match value { + ethnum::u256::ZERO => Ok(TransactionStatus::Failure), + ethnum::u256::ONE => Ok(TransactionStatus::Success), + _ => Err(format!("invalid transaction status: {}", value)), + } + } +} + +impl Display for TransactionStatus { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TransactionStatus::Success => write!(f, "Success"), + TransactionStatus::Failure => write!(f, "Failure"), + } + } +} + +/// An entry of the [`eth_getLogs`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs) call reply. +/// +/// Example: +/// ```json +/// { +/// "address": "0x7e41257f7b5c3dd3313ef02b1f4c864fe95bec2b", +/// "topics": [ +/// "0x2a2607d40f4a6feb97c36e0efd57e0aa3e42e0332af4fceb78f21b7dffcbd657" +/// ], +/// "data": "0x00000000000000000000000055654e7405fcb336386ea8f36954a211b2cda764000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003f62327071372d71677a7a692d74623564622d72357363692d637736736c2d6e646f756c2d666f7435742d347a7732702d657a6677692d74616a32792d76716500", +/// "blockNumber": "0x3aa4f4", +/// "transactionHash": "0x5618f72c485bd98a3df58d900eabe9e24bfaa972a6fe5227e02233fad2db1154", +/// "transactionIndex": "0x6", +/// "blockHash": "0x908e6b84d26d71421bfaa08e7966e0afcef3883a28a53a0a7a31104caf1e94c2", +/// "logIndex": "0x8", +/// "removed": false +/// } +/// ``` +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct LogEntry { + /// The address from which this log originated. + pub address: Address, + /// Array of 0 to 4 32 Bytes DATA of indexed log arguments. + /// In solidity: The first topic is the event signature hash (e.g. Deposit(address,bytes32,uint256)), + /// unless you declared the event with the anonymous specifier. + pub topics: Vec, + /// Contains one or more 32-byte non-indexed log arguments. + pub data: Data, + /// The block number in which this log appeared. + /// 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. + #[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. + #[serde(rename = "transactionIndex")] + pub transaction_index: Option>, + /// 32 Bytes - hash of the block in which this log appeared. + /// None if the block is pending. + #[serde(rename = "blockHash")] + pub block_hash: Option, + /// Integer of the log index position in the block. + /// None if the log is pending. + #[serde(rename = "logIndex")] + pub log_index: Option, + /// "true" when the log was removed due to a chain reorganization. + /// "false" if it's a valid log. + #[serde(default)] + pub removed: bool, +} + +impl HttpResponsePayload for Vec { + fn response_transform() -> Option { + Some(ResponseTransform::LogEntries) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Block { + #[serde(rename = "baseFeePerGas")] + pub base_fee_per_gas: Option, + pub number: BlockNumber, + pub difficulty: Option>, + #[serde(rename = "extraData")] + pub extra_data: String, + #[serde(rename = "gasLimit")] + pub gas_limit: CheckedAmountOf<()>, + #[serde(rename = "gasUsed")] + pub gas_used: CheckedAmountOf<()>, + pub hash: String, + #[serde(rename = "logsBloom")] + pub logs_bloom: String, + pub miner: String, + #[serde(rename = "mixHash")] + pub mix_hash: String, + pub nonce: CheckedAmountOf<()>, + #[serde(rename = "parentHash")] + pub parent_hash: String, + #[serde(rename = "receiptsRoot")] + pub receipts_root: String, + #[serde(rename = "sha3Uncles")] + pub sha3_uncles: String, + pub size: CheckedAmountOf<()>, + #[serde(rename = "stateRoot")] + pub state_root: String, + #[serde(rename = "timestamp")] + pub timestamp: CheckedAmountOf<()>, + #[serde(rename = "totalDifficulty")] + pub total_difficulty: Option>, + #[serde(default)] + pub transactions: Vec, + #[serde(rename = "transactionsRoot")] + pub transactions_root: Option, + #[serde(default)] + pub uncles: Vec, +} + +impl HttpResponsePayload for Block { + fn response_transform() -> Option { + Some(ResponseTransform::Block) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct FeeHistory { + /// Lowest number block of the returned range. + #[serde(rename = "oldestBlock")] + pub oldest_block: BlockNumber, + /// An array of block base fees per gas. + /// This includes the next block after the newest of the returned range, + /// because this value can be derived from the newest block. + /// Zeroes are returned for pre-EIP-1559 blocks. + #[serde(rename = "baseFeePerGas")] + pub base_fee_per_gas: Vec, + /// An array of block gas used ratios (gasUsed / gasLimit). + #[serde(default)] + #[serde(rename = "gasUsedRatio")] + pub gas_used_ratio: Vec, + /// A two-dimensional array of effective priority fees per gas at the requested block percentiles. + #[serde(default)] + #[serde(rename = "reward")] + pub reward: Vec>, +} + +impl HttpResponsePayload for FeeHistory { + fn response_transform() -> Option { + Some(ResponseTransform::FeeHistory) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub enum SendRawTransactionResult { + Ok, + InsufficientFunds, + NonceTooLow, + NonceTooHigh, +} + +impl HttpResponsePayload for SendRawTransactionResult { + fn response_transform() -> Option { + Some(ResponseTransform::SendRawTransaction) + } +} diff --git a/src/rpc_client/mod.rs b/src/rpc_client/mod.rs index d881f17a..73c070d4 100644 --- a/src/rpc_client/mod.rs +++ b/src/rpc_client/mod.rs @@ -1,17 +1,17 @@ use crate::logs::{DEBUG, INFO}; use crate::rpc_client::eth_rpc::{ - are_errors_consistent, Block, BlockSpec, FeeHistory, FeeHistoryParams, GetBlockByNumberParams, - GetLogsParam, Hash, HttpResponsePayload, LogEntry, ResponseSizeEstimate, - SendRawTransactionResult, HEADER_SIZE_LIMIT, + are_errors_consistent, Hash, HttpResponsePayload, ResponseSizeEstimate, HEADER_SIZE_LIMIT, }; use crate::rpc_client::numeric::TransactionCount; -use crate::rpc_client::requests::GetTransactionCountParams; -use crate::rpc_client::responses::TransactionReceipt; use evm_rpc_types::{ EthMainnetService, EthSepoliaService, HttpOutcallError, JsonRpcError, L2MainnetService, ProviderError, RpcConfig, RpcError, RpcService, RpcServices, }; use ic_canister_log::log; +use json::requests::{ + BlockSpec, FeeHistoryParams, GetBlockByNumberParams, GetLogsParam, GetTransactionCountParams, +}; +use json::responses::{Block, FeeHistory, LogEntry, SendRawTransactionResult, TransactionReceipt}; use serde::{de::DeserializeOwned, Serialize}; use std::collections::BTreeMap; use std::fmt::Debug; @@ -19,9 +19,8 @@ use std::fmt::Debug; pub mod checked_amount; pub(crate) mod eth_rpc; mod eth_rpc_error; +pub(crate) mod json; mod numeric; -pub(crate) mod requests; -pub(crate) mod responses; #[cfg(test)] mod tests; diff --git a/src/rpc_client/tests.rs b/src/rpc_client/tests.rs index e1918142..78bca350 100644 --- a/src/rpc_client/tests.rs +++ b/src/rpc_client/tests.rs @@ -215,7 +215,8 @@ mod multi_call_results { } mod reduce_with_stable_majority_by_key { - use crate::rpc_client::eth_rpc::{FeeHistory, JsonRpcResult}; + use crate::rpc_client::eth_rpc::JsonRpcResult; + use crate::rpc_client::json::responses::FeeHistory; use crate::rpc_client::numeric::{BlockNumber, WeiPerGas}; use crate::rpc_client::tests::multi_call_results::{ANKR, CLOUDFLARE, PUBLIC_NODE}; use crate::rpc_client::{MultiCallError, MultiCallResults}; @@ -420,8 +421,8 @@ mod multi_call_results { mod eth_get_transaction_receipt { use crate::rpc_client::eth_rpc::Hash; + use crate::rpc_client::json::responses::{TransactionReceipt, TransactionStatus}; use crate::rpc_client::numeric::{BlockNumber, GasAmount, WeiPerGas}; - use crate::rpc_client::responses::{TransactionReceipt, TransactionStatus}; use assert_matches::assert_matches; use proptest::proptest; use std::str::FromStr; @@ -513,9 +514,8 @@ mod eth_get_transaction_receipt { } mod eth_get_transaction_count { - use crate::rpc_client::eth_rpc::{BlockSpec, BlockTag}; + use crate::rpc_client::json::requests::{BlockSpec, BlockTag, GetTransactionCountParams}; use crate::rpc_client::numeric::TransactionCount; - use crate::rpc_client::requests::GetTransactionCountParams; use ic_ethereum_types::Address; use std::str::FromStr;