Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4d2c5a5
XC-412: Initialize new empty `evm_rpc_client` crate
lpahlavi Aug 6, 2025
d10ee6a
XC-412: Add skeleton for client
lpahlavi Aug 6, 2025
4b71bd1
XC-412: Add `eth_getLogs` to new client
lpahlavi Aug 6, 2025
07c8712
XC-412: Add empty changelog
lpahlavi Aug 6, 2025
d5d3413
XC-412: Add more type conversions to `alloy`
lpahlavi Aug 7, 2025
99d82b2
XC-412: Use `try_from` instead of `from`
lpahlavi Aug 7, 2025
34c70b9
XC-412: Add `get_logs` to client
lpahlavi Aug 7, 2025
fb4a545
XC-412: Don't expose private types
lpahlavi Aug 7, 2025
041a47b
XC-412: Remove new `ProviderId` type
lpahlavi Aug 7, 2025
33e50df
XC-412: Refactor `and_then`
lpahlavi Aug 7, 2025
4619b97
XC-412: Make `f` mutable
lpahlavi Aug 8, 2025
5e8aeed
XC-412: Fix repository link
lpahlavi Aug 20, 2025
fff88e0
XC-412: Require docs
lpahlavi Aug 20, 2025
a547409
XC-412: Add rustdoc and examples
lpahlavi Aug 20, 2025
5e5c55a
XC-412: Move type conversions to separate files
lpahlavi Aug 20, 2025
4a73233
XC-412: Flesh out examples
lpahlavi Aug 20, 2025
99d031c
XC-412: Move more type conversions to separate files
lpahlavi Aug 20, 2025
ab47bb8
XC-412: Add TODO for conversion from `alloy_rpc_types::Filter` to `Ge…
lpahlavi Aug 20, 2025
59801f2
XC-412: Add some alloy conversion unit tests
lpahlavi Aug 21, 2025
ccb0f5d
XC-412: Add unit tests for `and_then` method
lpahlavi Aug 21, 2025
438dd53
Merge branch 'main' into lpahlavi/XC-412-evm-rpc-client
lpahlavi Aug 21, 2025
b152927
XC-412: Formatting
lpahlavi Aug 21, 2025
5de395d
XC-412: Add more unit tests
lpahlavi Aug 21, 2025
db3d6f5
XC-412: Improve rustdocs
lpahlavi Aug 22, 2025
71b37d4
Merge branch 'main' into lpahlavi/XC-412-evm-rpc-client
lpahlavi Aug 25, 2025
c729d72
XC-412: Add `collapse` to `ande_then`
lpahlavi Aug 25, 2025
9a97a60
XC-412: Add explanation for `hex_to_u32_digits`
lpahlavi Aug 25, 2025
ee6ab99
XC-412: Add unit tests for error consolidation
lpahlavi Aug 25, 2025
3efc9c1
XC-412: FOrMaTtInG
lpahlavi Aug 25, 2025
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
1,320 changes: 1,214 additions & 106 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ zeroize = { version = "1.8", features = ["zeroize_derive"] }
regex = "1.11"

[dev-dependencies]
assert_matches = "1.5"
assert_matches = { workspace = true }
candid_parser = { workspace = true }
ic-crypto-test-utils-reproducible-rng = { git = "https://github.com/dfinity/ic", rev = "release-2024-09-26_01-31-base" }
ic-management-canister-types = { workspace = true }
Expand All @@ -61,7 +61,10 @@ proptest = { workspace = true }
rand = "0.8"

[workspace.dependencies]
alloy-primitives = "1.3.0"
alloy-rpc-types = "1.0.23"
assert_matches = "1.5.0"
async-trait = "0.1.88"
candid = { version = "0.10.13" }
canlog = { version = "0.2.0", features = ["derive"] }
candid_parser = { version = "0.1.4" }
Expand Down Expand Up @@ -103,4 +106,4 @@ thiserror = "2.0.12"
url = "2.5"

[workspace]
members = ["e2e/rust", "evm_rpc_types"]
members = ["e2e/rust", "evm_rpc_types", "evm_rpc_client"]
8 changes: 8 additions & 0 deletions evm_rpc_client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
25 changes: 25 additions & 0 deletions evm_rpc_client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "evm_rpc_client"
version = "1.4.0"
description = "Rust client for interacting with the EVM RPC canister"
license = "Apache-2.0"
readme = "README.md"
authors = ["DFINITY Foundation"]
edition = "2021"
include = ["src", "Cargo.toml", "CHANGELOG.md", "LICENSE", "README.md"]
repository = "https://github.com/dfinity/evm-rpc-canister"
documentation = "https://docs.rs/evm_rpc_client"

[dependencies]
alloy-primitives = { workspace = true }
alloy-rpc-types = { workspace = true }
async-trait = { workspace = true }
candid = { workspace = true }
evm_rpc_types = { path = "../evm_rpc_types", features = ["alloy"] }
ic-cdk = { workspace = true }
ic-error-types = { workspace = true }
serde = { workspace = true }
strum = { workspace = true }

[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
1 change: 1 addition & 0 deletions evm_rpc_client/LICENSE
1 change: 1 addition & 0 deletions evm_rpc_client/NOTICE
3 changes: 3 additions & 0 deletions evm_rpc_client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# EVM RPC Client

This crate defines a client for interacting with the EVM RPC canister.
132 changes: 132 additions & 0 deletions evm_rpc_client/src/fixtures/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//! Simple types to create basic unit tests for the [`crate::EvmRpcClient`].
//!
//! Types and methods for this module are only available for non-canister architecture (non `wasm32`).

use crate::{ClientBuilder, Runtime};
use async_trait::async_trait;
use candid::{utils::ArgumentEncoder, CandidType, Decode, Encode, Principal};
use ic_error_types::RejectCode;
use serde::de::DeserializeOwned;
use std::collections::BTreeMap;

impl<R> ClientBuilder<R> {
/// Set the runtime to a [`StubRuntime`].
pub fn with_stub_responses(self) -> ClientBuilder<StubRuntime> {
self.with_runtime(|_runtime| StubRuntime::default())
}

/// Change the runtime to return the given stub response for all calls.
pub fn with_default_stub_response<Out: CandidType>(
self,
stub_response: Out,
) -> ClientBuilder<StubRuntime> {
self.with_stub_responses()
.with_default_response(stub_response)
}
}

impl ClientBuilder<StubRuntime> {
/// Change the runtime to return the given stub response for all calls.
pub fn with_default_response<Out: CandidType>(
self,
stub_response: Out,
) -> ClientBuilder<StubRuntime> {
self.with_runtime(|runtime| runtime.with_default_response(stub_response))
}

/// Change the runtime to return the given stub response for calls to the given method.
pub fn with_response_for_method<Out: CandidType>(
self,
method_name: &str,
stub_response: Out,
) -> ClientBuilder<StubRuntime> {
self.with_runtime(|runtime| runtime.with_response_for_method(method_name, stub_response))
}
}

/// An implementation of [`Runtime`] that always returns the same candid-encoded response
/// for a given method.
///
/// Implement your own [`Runtime`] in case a more refined approach is needed.
pub struct StubRuntime {
default_call_result: Option<Vec<u8>>,
method_to_call_result_map: BTreeMap<String, Vec<u8>>,
}

impl StubRuntime {
/// Create a new [`StubRuntime`] with the given default stub response.
pub fn new() -> Self {
Self {
default_call_result: None,
method_to_call_result_map: BTreeMap::new(),
}
}

/// Create a new [`StubRuntime`] with the given default stub response.
pub fn with_default_response<Out: CandidType>(mut self, stub_response: Out) -> Self {
let result = Encode!(&stub_response).expect("Failed to encode Candid stub response");
self.default_call_result = Some(result);
self
}

/// Modify a [`StubRuntime`] to return the given response for the given method
pub fn with_response_for_method<Out: CandidType>(
mut self,
method: &str,
stub_response: Out,
) -> Self {
self.method_to_call_result_map.insert(
method.to_string(),
Encode!(&stub_response).expect("Failed to encode Candid stub response"),
);
self
}

fn call<Out>(&self, method: &str) -> Result<Out, (RejectCode, String)>
where
Out: CandidType + DeserializeOwned,
{
let bytes = self
.method_to_call_result_map
.get(method)
.or(self.default_call_result.as_ref())
.unwrap_or_else(|| panic!("No available call response value for method `{method}`"));
Ok(Decode!(bytes, Out).expect("Failed to decode Candid stub response"))
}
}

impl Default for StubRuntime {
fn default() -> Self {
Self::new()
}
}

#[async_trait]
impl Runtime for StubRuntime {
async fn update_call<In, Out>(
&self,
_id: Principal,
method: &str,
_args: In,
_cycles: u128,
) -> Result<Out, (RejectCode, String)>
where
In: ArgumentEncoder + Send,
Out: CandidType + DeserializeOwned,
{
self.call(method)
}

async fn query_call<In, Out>(
&self,
_id: Principal,
method: &str,
_args: In,
) -> Result<Out, (RejectCode, String)>
where
In: ArgumentEncoder + Send,
Out: CandidType + DeserializeOwned,
{
self.call(method)
}
}
Loading