Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ tokio = "1.44.1"

[workspace.dependencies]
alloy-consensus = "1.0.26"
alloy-dyn-abi = "1.3.1"
alloy-primitives = "1.3.0"
alloy-rpc-types = "1.0.23"
assert_matches = "1.5.0"
Expand Down
1 change: 1 addition & 0 deletions evm_rpc_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ serde = { workspace = true }
strum = { workspace = true }

[dev-dependencies]
alloy-dyn-abi = { workspace = true }
tokio = { workspace = true, features = ["full"] }
68 changes: 63 additions & 5 deletions evm_rpc_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,14 @@ mod request;
mod runtime;

use crate::request::{
FeeHistoryRequest, FeeHistoryRequestBuilder, GetBlockByNumberRequest,
GetBlockByNumberRequestBuilder, GetTransactionCountRequest, GetTransactionCountRequestBuilder,
Request, RequestBuilder,
CallRequest, CallRequestBuilder, FeeHistoryRequest, FeeHistoryRequestBuilder,
GetBlockByNumberRequest, GetBlockByNumberRequestBuilder, GetTransactionCountRequest,
GetTransactionCountRequestBuilder, Request, RequestBuilder,
};
use candid::{CandidType, Principal};
use evm_rpc_types::{
BlockTag, ConsensusStrategy, FeeHistoryArgs, GetLogsArgs, GetTransactionCountArgs, RpcConfig,
RpcServices,
BlockTag, CallArgs, ConsensusStrategy, FeeHistoryArgs, GetLogsArgs, GetTransactionCountArgs,
RpcConfig, RpcServices,
};
use ic_error_types::RejectCode;
use request::{GetLogsRequest, GetLogsRequestBuilder};
Expand Down Expand Up @@ -250,6 +250,64 @@ impl<R> ClientBuilder<R> {
}

impl<R> EvmRpcClient<R> {
/// Call `eth_call` on the EVM RPC canister.
///
/// # Examples
///
/// This example sends an `eth_call` to the USDC ERC-20 contract to fetch its symbol,
/// then decodes the ABI-encoded response into the human-readable string `USDC`.
///
/// ```rust
/// use alloy_dyn_abi::{DynSolType, DynSolValue};
/// use alloy_primitives::{address, bytes};
/// use alloy_rpc_types::BlockNumberOrTag;
/// use evm_rpc_client::EvmRpcClient;
///
/// # use evm_rpc_types::{Hex, MultiRpcResult};
/// # use std::str::FromStr;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = EvmRpcClient::builder_for_ic()
/// # .with_default_stub_response(MultiRpcResult::Consistent(Ok(
/// # Hex::from_str("0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000").unwrap()
/// # )))
/// .build();
///
/// let tx_request = alloy_rpc_types::TransactionRequest::default()
/// // USDC address
/// .from(address!("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"))
/// // Selector for `symbol()`
/// .input(bytes!(0x95, 0xd8, 0x9b, 0x41).into());
///
/// let result = client
/// .call(tx_request)
/// .with_block(BlockNumberOrTag::Latest)
/// .send()
/// .await
/// .expect_consistent()
/// .unwrap();
///
/// let decoded = DynSolType::String.abi_decode(&result);
/// assert_eq!(decoded, Ok(DynSolValue::from("USDC".to_string())));
/// # Ok(())
/// # }
/// ```
pub fn call<T>(&self, params: T) -> CallRequestBuilder<R>
where
T: TryInto<CallArgs>,
<T as TryInto<CallArgs>>::Error: std::fmt::Debug,
{
RequestBuilder::new(
self.clone(),
CallRequest::new(
params
.try_into()
.unwrap_or_else(|e| panic!("Invalid transaction request: {e:?}")),
),
10_000_000_000,
)
}

/// Call `eth_getBlockByNumber` on the EVM RPC canister.
///
/// # Examples
Expand Down
47 changes: 45 additions & 2 deletions evm_rpc_client/src/request/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,54 @@
use crate::{EvmRpcClient, Runtime};
use candid::CandidType;
use evm_rpc_types::{
BlockTag, FeeHistoryArgs, GetLogsArgs, GetLogsRpcConfig, GetTransactionCountArgs, Hex20, Hex32,
MultiRpcResult, Nat256, RpcConfig, RpcServices,
BlockTag, CallArgs, FeeHistoryArgs, GetLogsArgs, GetLogsRpcConfig, GetTransactionCountArgs,
Hex, Hex20, Hex32, MultiRpcResult, Nat256, RpcConfig, RpcServices,
};
use ic_error_types::RejectCode;
use serde::de::DeserializeOwned;
use std::fmt::{Debug, Formatter};
use strum::EnumIter;

#[derive(Debug, Clone)]
pub struct CallRequest(CallArgs);

impl CallRequest {
pub fn new(params: CallArgs) -> Self {
Self(params)
}
}

impl EvmRpcRequest for CallRequest {
type Config = RpcConfig;
type Params = CallArgs;
type CandidOutput = MultiRpcResult<Hex>;
type Output = MultiRpcResult<alloy_primitives::Bytes>;

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

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

pub type CallRequestBuilder<R> = RequestBuilder<
R,
RpcConfig,
CallArgs,
MultiRpcResult<Hex>,
MultiRpcResult<alloy_primitives::Bytes>,
>;

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

#[derive(Debug, Clone)]
pub struct FeeHistoryRequest(FeeHistoryArgs);

Expand Down Expand Up @@ -223,6 +263,8 @@ pub trait EvmRpcRequest {
/// Endpoint on the EVM RPC canister triggering a call to EVM providers.
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, EnumIter)]
pub enum EvmRpcEndpoint {
/// `eth_call` endpoint.
Call,
/// `eth_feeHistory` endpoint.
FeeHistory,
/// `eth_getBlockByNumber` endpoint.
Expand All @@ -237,6 +279,7 @@ impl EvmRpcEndpoint {
/// Method name on the EVM RPC canister
pub fn rpc_method(&self) -> &'static str {
match &self {
Self::Call => "eth_call",
Self::FeeHistory => "eth_feeHistory",
Self::GetBlockByNumber => "eth_getBlockByNumber",
Self::GetLogs => "eth_getLogs",
Expand Down
6 changes: 6 additions & 0 deletions evm_rpc_types/src/alloy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ impl From<alloy_primitives::B256> for Hex32 {
}
}

impl<const N: usize> From<alloy_primitives::FixedBytes<N>> for Hex {
fn from(value: alloy_primitives::FixedBytes<N>) -> Self {
Self::from(value.to_vec())
}
}

impl From<Hex> for alloy_primitives::Bytes {
fn from(value: Hex) -> Self {
Self::from_iter(Vec::<u8>::from(value))
Expand Down
67 changes: 66 additions & 1 deletion evm_rpc_types/src/request/alloy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{BlockTag, GetLogsArgs, Hex20, RpcError};
use crate::{
AccessList, AccessListEntry, BlockTag, CallArgs, GetLogsArgs, Hex, Hex20, Hex32, HexByte,
Nat256, RpcError, TransactionRequest, ValidationError,
};
use alloy_primitives::TxKind;

impl From<alloy_rpc_types::BlockNumberOrTag> for BlockTag {
fn from(tag: alloy_rpc_types::BlockNumberOrTag) -> Self {
Expand Down Expand Up @@ -40,4 +44,65 @@ impl<T: IntoIterator<Item = S>, S: Into<Hex20>> From<T> for GetLogsArgs {
}
}

impl TryFrom<alloy_rpc_types::TransactionRequest> for CallArgs {
type Error = RpcError;

fn try_from(request: alloy_rpc_types::TransactionRequest) -> Result<Self, Self::Error> {
Ok(Self {
transaction: TransactionRequest::try_from(request)?,
block: None,
})
}
}

impl TryFrom<alloy_rpc_types::TransactionRequest> for TransactionRequest {
type Error = RpcError;

fn try_from(tx_request: alloy_rpc_types::TransactionRequest) -> Result<Self, Self::Error> {
Ok(Self {
tx_type: tx_request.transaction_type.map(HexByte::from),
nonce: tx_request.nonce.map(Nat256::from),
to: tx_request.to.and_then(|kind| match kind {
TxKind::Create => None,
TxKind::Call(address) => Some(Hex20::from(address)),
}),
from: tx_request.from.map(Hex20::from),
gas: tx_request.gas.map(Nat256::from),
value: tx_request.value.map(Nat256::from),
input: tx_request
.input
.try_into_unique_input()
.map_err(|e| RpcError::ValidationError(ValidationError::Custom(e.to_string())))?
.map(Hex::from),
gas_price: tx_request.gas_price.map(Nat256::from),
max_priority_fee_per_gas: tx_request.max_priority_fee_per_gas.map(Nat256::from),
max_fee_per_gas: tx_request.max_fee_per_gas.map(Nat256::from),
max_fee_per_blob_gas: tx_request.max_fee_per_blob_gas.map(Nat256::from),
access_list: tx_request.access_list.map(AccessList::from),
blob_versioned_hashes: tx_request
.blob_versioned_hashes
.map(|hashes| hashes.into_iter().map(Hex32::from).collect()),
blobs: tx_request
.sidecar
.map(|sidecar| sidecar.blobs.into_iter().map(Hex::from).collect()),
chain_id: tx_request.chain_id.map(Nat256::from),
})
}
}

impl From<alloy_rpc_types::AccessList> for AccessList {
fn from(access_list: alloy_rpc_types::AccessList) -> Self {
Self(
access_list
.0
.into_iter()
.map(|item| AccessListEntry {
address: Hex20::from(item.address),
storage_keys: item.storage_keys.into_iter().map(Hex32::from).collect(),
})
.collect(),
)
}
}

// TODO XC-412: impl From<alloy_rpc_types::Filter> for GetLogsArgs
8 changes: 8 additions & 0 deletions evm_rpc_types/src/request/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#[cfg(test)]
mod tests;

#[cfg(feature = "alloy")]
mod alloy;

use crate::{Hex, Hex20, Hex32, HexByte, Nat256};
use candid::CandidType;
use serde::Deserialize;
#[cfg(test)]
use serde::Serialize;

#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize, Default)]
pub enum BlockTag {
Expand Down Expand Up @@ -96,6 +101,7 @@ pub struct CallArgs {
pub block: Option<BlockTag>,
}

#[cfg_attr(test, derive(Serialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq, CandidType, Deserialize)]
pub struct TransactionRequest {
/// The type of the transaction:
Expand Down Expand Up @@ -155,10 +161,12 @@ pub struct TransactionRequest {
pub chain_id: Option<Nat256>,
}

#[cfg_attr(test, derive(Serialize))]
#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize)]
#[serde(transparent)]
pub struct AccessList(pub Vec<AccessListEntry>);

#[cfg_attr(test, derive(Serialize))]
#[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize)]
pub struct AccessListEntry {
pub address: Hex20,
Expand Down
Loading