Skip to content
Merged
164 changes: 65 additions & 99 deletions evm_rpc_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,63 +31,28 @@
//! actually send *more* cycles than required, since *unused cycles will be refunded*.
//!
//! ```rust
//! # // TODO XC-412: Use simpler example e.g. `eth_getBalance`
//! use alloy_primitives::{address, b256, bytes};
//! use alloy_primitives::{address, U256};
//! use alloy_rpc_types::BlockNumberOrTag;
//! use evm_rpc_client::EvmRpcClient;
//!
//! # use evm_rpc_types::{Hex, Hex20, Hex32, MultiRpcResult};
//! # use std::str::FromStr;
//! # use evm_rpc_types::{MultiRpcResult, Nat256};
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let client = EvmRpcClient::builder_for_ic()
//! # .with_default_stub_response(MultiRpcResult::Consistent(Ok(vec![
//! # evm_rpc_types::LogEntry {
//! # address: Hex20::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
//! # topics: vec![
//! # Hex32::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(),
//! # Hex32::from_str("0x000000000000000000000000000000000004444c5dc75cb358380d2e3de08a90").unwrap(),
//! # Hex32::from_str("0x0000000000000000000000000000000aa232009084bd71a5797d089aa4edfad4").unwrap(),
//! # ],
//! # data: Hex::from_str("0x00000000000000000000000000000000000000000000000000000000cd566ae8").unwrap(),
//! # block_number: Some(0x161bd70_u64.into()),
//! # transaction_hash: Some(Hex32::from_str("0xfe5bc88d0818b66a67b0619b1b4d81bfe38029e3799c7f0eb86b33ca7dc4c811").unwrap()),
//! # transaction_index: Some(0x0_u64.into()),
//! # block_hash: Some(Hex32::from_str("0x0bbd9b12140e674cdd55e63539a25df8280a70cee3676c94d8e05fa5f868a914").unwrap()),
//! # log_index: Some(0x0_u64.into()),
//! # removed: false,
//! # }
//! # ])))
//! # .with_default_stub_response(MultiRpcResult::Consistent(Ok(Nat256::from(1_u64))))
//! .build();
//!
//! let result = client
//! .get_logs(vec![address!("0xdac17f958d2ee523a2206206994597c13d831ec7")])
//! .with_cycles(10_000_000_000)
//! .get_transaction_count((
//! address!("0xdac17f958d2ee523a2206206994597c13d831ec7"),
//! BlockNumberOrTag::Latest,
//! ))
//! .with_cycles(20_000_000_000)
//! .send()
//! .await
//! .expect_consistent();
//!
//! assert_eq!(result.unwrap().first(), Some(
//! &alloy_rpc_types::Log {
//! inner: alloy_primitives::Log {
//! address: address!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
//! data: alloy_primitives::LogData::new(
//! vec![
//! b256!("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
//! b256!("0x000000000000000000000000000000000004444c5dc75cb358380d2e3de08a90"),
//! b256!("0x0000000000000000000000000000000aa232009084bd71a5797d089aa4edfad4"),
//! ],
//! bytes!("0x00000000000000000000000000000000000000000000000000000000cd566ae8"),
//! ).unwrap(),
//! },
//! block_hash: Some(b256!("0x0bbd9b12140e674cdd55e63539a25df8280a70cee3676c94d8e05fa5f868a914")),
//! block_number: Some(0x161bd70_u64),
//! block_timestamp: None,
//! transaction_hash: Some(b256!("0xfe5bc88d0818b66a67b0619b1b4d81bfe38029e3799c7f0eb86b33ca7dc4c811")),
//! transaction_index: Some(0x0_u64),
//! log_index: Some(0x0_u64),
//! removed: false,
//! },
//! ));
//! assert_eq!(result, Ok(U256::ONE));
//! # Ok(())
//! # }
//! ```
Expand All @@ -102,33 +67,16 @@
//! your application requires a higher threshold and more robustness with a 3-out-of-5 :
//!
//! ```rust
//! # // TODO XC-412: Use simpler example e.g. `eth_getBalance`
//! use alloy_primitives::{address, b256, bytes};
//! use alloy_primitives::{address, U256};
//! use alloy_rpc_types::BlockNumberOrTag;
//! use evm_rpc_client::EvmRpcClient;
//! use evm_rpc_types::{ConsensusStrategy, GetLogsRpcConfig , RpcServices};
//! use evm_rpc_types::{ConsensusStrategy, RpcServices};
//!
//! # use evm_rpc_types::{Hex, Hex20, Hex32, MultiRpcResult};
//! # use std::str::FromStr;
//! # use evm_rpc_types::{MultiRpcResult, Nat256};
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let client = EvmRpcClient::builder_for_ic()
//! # .with_default_stub_response(MultiRpcResult::Consistent(Ok(vec![
//! # evm_rpc_types::LogEntry {
//! # address: Hex20::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
//! # topics: vec![
//! # Hex32::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(),
//! # Hex32::from_str("0x000000000000000000000000000000000004444c5dc75cb358380d2e3de08a90").unwrap(),
//! # Hex32::from_str("0x0000000000000000000000000000000aa232009084bd71a5797d089aa4edfad4").unwrap(),
//! # ],
//! # data: Hex::from_str("0x00000000000000000000000000000000000000000000000000000000cd566ae8").unwrap(),
//! # block_number: Some(0x161bd70_u64.into()),
//! # transaction_hash: Some(Hex32::from_str("0xfe5bc88d0818b66a67b0619b1b4d81bfe38029e3799c7f0eb86b33ca7dc4c811").unwrap()),
//! # transaction_index: Some(0x0_u64.into()),
//! # block_hash: Some(Hex32::from_str("0x0bbd9b12140e674cdd55e63539a25df8280a70cee3676c94d8e05fa5f868a914").unwrap()),
//! # log_index: Some(0x0_u64.into()),
//! # removed: false,
//! # }
//! # ])))
//! # .with_default_stub_response(MultiRpcResult::Consistent(Ok(Nat256::from(1_u64))))
//! .with_rpc_sources(RpcServices::EthMainnet(None))
//! .with_consensus_strategy(ConsensusStrategy::Threshold {
//! total: Some(3),
Expand All @@ -137,40 +85,16 @@
//! .build();
//!
//! let result = client
//! .get_logs(vec![address!("0xdac17f958d2ee523a2206206994597c13d831ec7")])
//! .with_rpc_config(GetLogsRpcConfig {
//! response_consensus: Some(ConsensusStrategy::Threshold {
//! total: Some(5),
//! min: 3,
//! }),
//! ..Default::default()
//! })
//! .get_transaction_count((
//! address!("0xdac17f958d2ee523a2206206994597c13d831ec7"),
//! BlockNumberOrTag::Latest,
//! ))
//! .with_cycles(20_000_000_000)
//! .send()
//! .await
//! .expect_consistent();
//!
//! assert_eq!(result.unwrap().first(), Some(
//! &alloy_rpc_types::Log {
//! inner: alloy_primitives::Log {
//! address: address!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
//! data: alloy_primitives::LogData::new(
//! vec![
//! b256!("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
//! b256!("0x000000000000000000000000000000000004444c5dc75cb358380d2e3de08a90"),
//! b256!("0x0000000000000000000000000000000aa232009084bd71a5797d089aa4edfad4"),
//! ],
//! bytes!("0x00000000000000000000000000000000000000000000000000000000cd566ae8"),
//! ).unwrap(),
//! },
//! block_hash: Some(b256!("0x0bbd9b12140e674cdd55e63539a25df8280a70cee3676c94d8e05fa5f868a914")),
//! block_number: Some(0x161bd70_u64),
//! block_timestamp: None,
//! transaction_hash: Some(b256!("0xfe5bc88d0818b66a67b0619b1b4d81bfe38029e3799c7f0eb86b33ca7dc4c811")),
//! transaction_index: Some(0x0_u64),
//! log_index: Some(0x0_u64),
//! removed: false,
//! },
//! ));
//! assert_eq!(result, Ok(U256::ONE));
//! # Ok(())
//! # }
//! ```
Expand All @@ -185,11 +109,13 @@ mod runtime;

use crate::request::{
FeeHistoryRequest, FeeHistoryRequestBuilder, GetBlockByNumberRequest,
GetBlockByNumberRequestBuilder, Request, RequestBuilder,
GetBlockByNumberRequestBuilder, GetTransactionCountRequest, GetTransactionCountRequestBuilder,
Request, RequestBuilder,
};
use candid::{CandidType, Principal};
use evm_rpc_types::{
BlockTag, ConsensusStrategy, FeeHistoryArgs, GetLogsArgs, RpcConfig, RpcServices,
BlockTag, ConsensusStrategy, FeeHistoryArgs, GetLogsArgs, GetTransactionCountArgs, RpcConfig,
RpcServices,
};
use ic_error_types::RejectCode;
use request::{GetLogsRequest, GetLogsRequestBuilder};
Expand Down Expand Up @@ -529,6 +455,46 @@ impl<R> EvmRpcClient<R> {
10_000_000_000,
)
}

/// Call `eth_getTransactionCount` on the EVM RPC canister.
///
/// # Examples
///
/// ```rust
/// use alloy_primitives::{address, U256};
/// use alloy_rpc_types::BlockNumberOrTag;
/// use evm_rpc_client::EvmRpcClient;
///
/// # use evm_rpc_types::{MultiRpcResult, Nat256};
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = EvmRpcClient::builder_for_ic()
/// # .with_default_stub_response(MultiRpcResult::Consistent(Ok(Nat256::from(1_u64))))
/// .build();
///
/// let result = client
/// .get_transaction_count((
/// address!("0xdac17f958d2ee523a2206206994597c13d831ec7"),
/// BlockNumberOrTag::Latest,
/// ))
/// .send()
/// .await
/// .expect_consistent();
///
/// assert_eq!(result, Ok(U256::ONE));
/// # Ok(())
/// # }
/// ```
pub fn get_transaction_count(
&self,
params: impl Into<GetTransactionCountArgs>,
) -> GetTransactionCountRequestBuilder<R> {
RequestBuilder::new(
self.clone(),
GetTransactionCountRequest::new(params.into()),
10_000_000_000,
)
}
}

impl<R: Runtime> EvmRpcClient<R> {
Expand Down
53 changes: 51 additions & 2 deletions evm_rpc_client/src/request/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{EvmRpcClient, Runtime};
use candid::CandidType;
use evm_rpc_types::{
BlockTag, FeeHistoryArgs, GetLogsArgs, GetLogsRpcConfig, Hex20, Hex32, MultiRpcResult, Nat256,
RpcConfig, RpcServices,
BlockTag, FeeHistoryArgs, GetLogsArgs, GetLogsRpcConfig, GetTransactionCountArgs, Hex20, Hex32,
MultiRpcResult, Nat256, RpcConfig, RpcServices,
};
use ic_error_types::RejectCode;
use serde::de::DeserializeOwned;
Expand Down Expand Up @@ -156,6 +156,52 @@ impl<R> GetLogsRequestBuilder<R> {
}
}

#[derive(Debug, Clone)]
pub struct GetTransactionCountRequest(GetTransactionCountArgs);

impl GetTransactionCountRequest {
pub fn new(params: GetTransactionCountArgs) -> Self {
Self(params)
}
}

impl EvmRpcRequest for GetTransactionCountRequest {
type Config = RpcConfig;
type Params = GetTransactionCountArgs;
type CandidOutput = MultiRpcResult<Nat256>;
type Output = MultiRpcResult<alloy_primitives::U256>;

fn endpoint(&self) -> EvmRpcEndpoint {
EvmRpcEndpoint::GetTransactionCount
}

fn params(self) -> Self::Params {
self.0
}
}

pub type GetTransactionCountRequestBuilder<R> = RequestBuilder<
R,
RpcConfig,
GetTransactionCountArgs,
MultiRpcResult<Nat256>,
MultiRpcResult<alloy_primitives::U256>,
>;

impl<R> GetTransactionCountRequestBuilder<R> {
/// Change the `address` parameter for an `eth_getTransactionCount` request.
pub fn with_address(mut self, address: impl Into<Hex20>) -> Self {
self.request.params.address = address.into();
self
}

/// Change the `block` parameter for an `eth_getTransactionCount` request.
pub fn with_block(mut self, block: impl Into<BlockTag>) -> Self {
self.request.params.block = block.into();
self
}
}

/// Ethereum RPC endpoint supported by the EVM RPC canister.
pub trait EvmRpcRequest {
/// Type of RPC config for that request.
Expand Down Expand Up @@ -183,6 +229,8 @@ pub enum EvmRpcEndpoint {
GetBlockByNumber,
/// `eth_getLogs` endpoint.
GetLogs,
/// `eth_getTransactionCount` endpoint.
GetTransactionCount,
}

impl EvmRpcEndpoint {
Expand All @@ -192,6 +240,7 @@ impl EvmRpcEndpoint {
Self::FeeHistory => "eth_feeHistory",
Self::GetBlockByNumber => "eth_getBlockByNumber",
Self::GetLogs => "eth_getLogs",
Self::GetTransactionCount => "eth_getTransactionCount",
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions evm_rpc_types/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ pub struct GetTransactionCountArgs {
pub block: BlockTag,
}

impl<T, U> From<(T, U)> for GetTransactionCountArgs
where
T: Into<Hex20>,
U: Into<BlockTag>,
{
fn from((address, block): (T, U)) -> Self {
Self {
address: address.into(),
block: block.into(),
}
}
}

#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize)]
pub struct CallArgs {
pub transaction: TransactionRequest,
Expand Down
8 changes: 7 additions & 1 deletion evm_rpc_types/src/result/alloy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Block, FeeHistory, LogEntry, MultiRpcResult};
use crate::{Block, FeeHistory, LogEntry, MultiRpcResult, Nat256};

impl From<MultiRpcResult<Vec<LogEntry>>> for MultiRpcResult<Vec<alloy_rpc_types::Log>> {
fn from(result: MultiRpcResult<Vec<LogEntry>>) -> Self {
Expand All @@ -21,3 +21,9 @@ impl From<MultiRpcResult<FeeHistory>> for MultiRpcResult<alloy_rpc_types::FeeHis
result.and_then(alloy_rpc_types::FeeHistory::try_from)
}
}

impl From<MultiRpcResult<Nat256>> for MultiRpcResult<alloy_primitives::U256> {
fn from(result: MultiRpcResult<Nat256>) -> Self {
result.map(alloy_primitives::U256::from)
}
}
33 changes: 31 additions & 2 deletions tests/mock_http_runtime/mock/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,45 @@ impl JsonRpcRequestMatcher {
}
}

pub fn with_id(self, id: impl Into<Id>) -> Self {
Self {
id: Some(id.into()),
..self
}
}

pub fn with_params(self, params: impl Into<Value>) -> Self {
Self {
params: Some(params.into()),
..self
}
}

pub fn with_id(self, id: impl Into<Id>) -> Self {
pub fn with_url(self, url: &str) -> Self {
Self {
id: Some(id.into()),
url: Some(Url::parse(url).expect("BUG: invalid URL")),
..self
}
}

pub fn with_host(self, host: &str) -> Self {
Self {
host: Some(Host::parse(host).expect("BUG: invalid host for a URL")),
..self
}
}

pub fn with_request_headers(self, headers: Vec<(impl ToString, impl ToString)>) -> Self {
Self {
request_headers: Some(
headers
.into_iter()
.map(|(name, value)| CanisterHttpHeader {
name: name.to_string(),
value: value.to_string(),
})
.collect(),
),
..self
}
}
Expand Down
Loading