From 27f43f07460a111c0bf30e41217407f6aa93cf4e Mon Sep 17 00:00:00 2001 From: gregorydemay <112856886+gregorydemay@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:38:25 +0200 Subject: [PATCH] refactor: move types related to providers to `evm-rpc-types` crate (#271) Follow-up on #257 to move the following types to the `evm_rpc_types` crate: 1. `EthMainnetService` 2. `EthSepoliaService` 3. `HttpHeader` 4. `L2MainnetService` 5. `RpcApi` 6. `RpcConfig` 7. `RpcService` 8. `RpcServices` so that the public API of all methods in `main.rs` only use types from that crate as arguments. --- Cargo.lock | 1 + evm_rpc_types/CHANGELOG.md | 8 +- evm_rpc_types/Cargo.toml | 1 + evm_rpc_types/src/lib.rs | 5 + evm_rpc_types/src/rpc_client/mod.rs | 91 +++++++ src/candid_rpc.rs | 136 ++++------ src/candid_rpc/cketh_conversion.rs | 402 ++++++++++++++++++++++++++++ src/constants.rs | 4 +- src/main.rs | 34 ++- src/providers.rs | 9 +- src/types.rs | 87 +++--- tests/tests.rs | 32 +-- 12 files changed, 636 insertions(+), 174 deletions(-) create mode 100644 evm_rpc_types/src/rpc_client/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1d0d62b9..878a130a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,6 +1623,7 @@ version = "0.1.0" dependencies = [ "candid", "hex", + "ic-cdk", "num-bigint 0.4.6", "proptest", "serde", diff --git a/evm_rpc_types/CHANGELOG.md b/evm_rpc_types/CHANGELOG.md index 73f41dce..96e82652 100644 --- a/evm_rpc_types/CHANGELOG.md +++ b/evm_rpc_types/CHANGELOG.md @@ -10,11 +10,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - v1.0 `Nat256`: transparent wrapper around a `Nat` to guarantee that it fits in 256 bits. -- v1.0 `HexByte`, `Hex20`, `Hex32`, `Hex256` and `Hex` : Candid types wrapping an amount of bytes (`u8` for `HexByte`, `[u8; N]` for `HexN`, and `Vec` for `Hex`) that can be represented as an hexadecimal string (prefixed by `0x`) when serialized. +- v1.0 `HexByte`, `Hex20`, `Hex32`, `Hex256` and `Hex` : Candid types wrapping an amount of bytes (`u8` for `HexByte`, + `[u8; N]` for `HexN`, and `Vec` for `Hex`) that can be represented as an hexadecimal string (prefixed by `0x`) + when serialized. - v1.0 Move `Block` to this crate. - v1.0 Move `BlockTag` to this crate. - v1.0 Move `FeeHistoryArgs` and `FeeHistory` to this crate. - v1.0 Move `GetLogsArgs` and `LogEntry` to this crate. - v1.0 Move `GetTransactionCountArgs` to this crate. +- v1.0 Move `RpcConfig` to this crate. - v1.0 Move `SendRawTransactionStatus` to this crate. - v1.0 Move `TransactionReceipt` to this crate. +- v1.0 Move providers-related struct `EthMainnetService`, `EthSepoliaService`, `HttpHeader`, `L2MainnetService`, + `RpcApi`, `RpcConfig`, + `RpcService`, `RpcServices` to this crate. diff --git a/evm_rpc_types/Cargo.toml b/evm_rpc_types/Cargo.toml index 55037c7b..298b4d71 100644 --- a/evm_rpc_types/Cargo.toml +++ b/evm_rpc_types/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [dependencies] candid = { workspace = true } hex = { workspace = true } +ic-cdk = { workspace = true } num-bigint = { workspace = true } serde = { workspace = true } diff --git a/evm_rpc_types/src/lib.rs b/evm_rpc_types/src/lib.rs index 733d83c1..33740f92 100644 --- a/evm_rpc_types/src/lib.rs +++ b/evm_rpc_types/src/lib.rs @@ -11,9 +11,14 @@ use std::str::FromStr; mod request; mod response; +mod rpc_client; pub use request::{FeeHistoryArgs, GetLogsArgs, GetTransactionCountArgs}; pub use response::{Block, FeeHistory, LogEntry, SendRawTransactionStatus, TransactionReceipt}; +pub use rpc_client::{ + EthMainnetService, EthSepoliaService, HttpHeader, L2MainnetService, RpcApi, RpcConfig, + RpcService, RpcServices, +}; #[derive(Clone, Debug, PartialEq, Eq, CandidType, Deserialize, Default)] pub enum BlockTag { diff --git a/evm_rpc_types/src/rpc_client/mod.rs b/evm_rpc_types/src/rpc_client/mod.rs new file mode 100644 index 00000000..a49298c2 --- /dev/null +++ b/evm_rpc_types/src/rpc_client/mod.rs @@ -0,0 +1,91 @@ +pub use ic_cdk::api::management_canister::http_request::HttpHeader; + +use candid::{CandidType, Deserialize}; +use serde::Serialize; + +#[derive(Clone, Debug, PartialEq, Eq, Default, CandidType, Deserialize)] +pub struct RpcConfig { + #[serde(rename = "responseSizeEstimate")] + pub response_size_estimate: Option, +} + +#[derive(Clone, CandidType, Deserialize)] +pub enum RpcServices { + Custom { + #[serde(rename = "chainId")] + chain_id: u64, + services: Vec, + }, + EthMainnet(Option>), + EthSepolia(Option>), + ArbitrumOne(Option>), + BaseMainnet(Option>), + OptimismMainnet(Option>), +} + +#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType)] +pub struct RpcApi { + pub url: String, + pub headers: Option>, +} + +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType, +)] +pub enum EthMainnetService { + Alchemy, + Ankr, + BlockPi, + PublicNode, + Cloudflare, + Llama, +} + +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType, +)] +pub enum EthSepoliaService { + Alchemy, + Ankr, + BlockPi, + PublicNode, + Sepolia, +} + +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType, +)] +pub enum L2MainnetService { + Alchemy, + Ankr, + BlockPi, + PublicNode, + Llama, +} + +#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, CandidType)] +pub enum RpcService { + Chain(u64), + Provider(u64), + Custom(RpcApi), + EthMainnet(EthMainnetService), + EthSepolia(EthSepoliaService), + ArbitrumOne(L2MainnetService), + BaseMainnet(L2MainnetService), + OptimismMainnet(L2MainnetService), +} + +impl std::fmt::Debug for RpcService { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RpcService::Chain(chain_id) => write!(f, "Chain({})", chain_id), + RpcService::Provider(provider_id) => write!(f, "Provider({})", provider_id), + RpcService::Custom(_) => write!(f, "Custom(..)"), // Redact credentials + RpcService::EthMainnet(service) => write!(f, "{:?}", service), + RpcService::EthSepolia(service) => write!(f, "{:?}", service), + RpcService::ArbitrumOne(service) + | RpcService::BaseMainnet(service) + | RpcService::OptimismMainnet(service) => write!(f, "{:?}", service), + } + } +} diff --git a/src/candid_rpc.rs b/src/candid_rpc.rs index 56316ed2..8525c156 100644 --- a/src/candid_rpc.rs +++ b/src/candid_rpc.rs @@ -4,14 +4,10 @@ use async_trait::async_trait; use candid::Nat; use cketh_common::{ eth_rpc::{ProviderError, ValidationError}, - eth_rpc_client::{ - providers::{RpcApi, RpcService}, - EthRpcClient as CkEthRpcClient, MultiCallError, RpcConfig, RpcTransport, - }, - lifecycle::EthereumNetwork, + eth_rpc_client::{EthRpcClient as CkEthRpcClient, MultiCallError, RpcTransport}, }; use ethers_core::{types::Transaction, utils::rlp}; -use evm_rpc_types::{Hex, Hex32}; +use evm_rpc_types::{Hex, Hex32, RpcServices}; use ic_cdk::api::management_canister::http_request::{CanisterHttpRequestArgument, HttpResponse}; use crate::{ @@ -25,7 +21,6 @@ use crate::{ providers::resolve_rpc_service, types::{ MetricRpcHost, MetricRpcMethod, MultiRpcResult, ResolvedRpcService, RpcMethod, RpcResult, - RpcServices, }, }; @@ -35,17 +30,23 @@ struct CanisterTransport; #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl RpcTransport for CanisterTransport { - fn resolve_api(service: &RpcService) -> Result { - Ok(resolve_rpc_service(service.clone())?.api()) + fn resolve_api( + service: &cketh_common::eth_rpc_client::providers::RpcService, + ) -> Result { + use crate::candid_rpc::cketh_conversion::{from_rpc_service, into_rpc_api}; + Ok(into_rpc_api( + resolve_rpc_service(from_rpc_service(service.clone()))?.api(), + )) } async fn http_request( - service: &RpcService, + service: &cketh_common::eth_rpc_client::providers::RpcService, method: &str, request: CanisterHttpRequestArgument, effective_response_size_estimate: u64, ) -> RpcResult { - let service = resolve_rpc_service(service.clone())?; + use crate::candid_rpc::cketh_conversion::from_rpc_service; + let service = resolve_rpc_service(from_rpc_service(service.clone()))?; let cycles_cost = get_http_request_cost( request .body @@ -68,73 +69,25 @@ fn check_services(services: Vec) -> RpcResult> { fn get_rpc_client( source: RpcServices, - config: RpcConfig, + config: evm_rpc_types::RpcConfig, ) -> RpcResult> { - Ok(match source { - RpcServices::Custom { chain_id, services } => CkEthRpcClient::new( - EthereumNetwork(chain_id), - Some( - check_services(services)? - .into_iter() - .map(RpcService::Custom) - .collect(), - ), - config, - ), - RpcServices::EthMainnet(services) => CkEthRpcClient::new( - EthereumNetwork::MAINNET, - Some( - check_services(services.unwrap_or_else(|| DEFAULT_ETH_MAINNET_SERVICES.to_vec()))? - .into_iter() - .map(RpcService::EthMainnet) - .collect(), - ), - config, - ), - RpcServices::EthSepolia(services) => CkEthRpcClient::new( - EthereumNetwork::SEPOLIA, - Some( - check_services(services.unwrap_or_else(|| DEFAULT_ETH_SEPOLIA_SERVICES.to_vec()))? - .into_iter() - .map(RpcService::EthSepolia) - .collect(), - ), - config, - ), - RpcServices::ArbitrumOne(services) => CkEthRpcClient::new( - EthereumNetwork::ARBITRUM, - Some( - check_services(services.unwrap_or_else(|| DEFAULT_L2_MAINNET_SERVICES.to_vec()))? - .into_iter() - .map(RpcService::ArbitrumOne) - .collect(), - ), - config, - ), - RpcServices::BaseMainnet(services) => CkEthRpcClient::new( - EthereumNetwork::BASE, - Some( - check_services(services.unwrap_or_else(|| DEFAULT_L2_MAINNET_SERVICES.to_vec()))? - .into_iter() - .map(RpcService::BaseMainnet) - .collect(), - ), - config, - ), - RpcServices::OptimismMainnet(services) => CkEthRpcClient::new( - EthereumNetwork::OPTIMISM, - Some( - check_services(services.unwrap_or_else(|| DEFAULT_L2_MAINNET_SERVICES.to_vec()))? - .into_iter() - .map(RpcService::OptimismMainnet) - .collect(), - ), - config, - ), - }) + use crate::candid_rpc::cketh_conversion::{ + into_ethereum_network, into_rpc_config, into_rpc_services, + }; + + let config = into_rpc_config(config); + let chain = into_ethereum_network(&source); + let providers = check_services(into_rpc_services( + source, + DEFAULT_ETH_MAINNET_SERVICES, + DEFAULT_ETH_SEPOLIA_SERVICES, + DEFAULT_L2_MAINNET_SERVICES, + ))?; + Ok(CkEthRpcClient::new(chain, Some(providers), config)) } fn process_result(method: RpcMethod, result: Result>) -> MultiRpcResult { + use crate::candid_rpc::cketh_conversion::from_rpc_service; match result { Ok(value) => MultiRpcResult::Consistent(Ok(value)), Err(err) => match err { @@ -142,7 +95,7 @@ fn process_result(method: RpcMethod, result: Result>) -> MultiCallError::InconsistentResults(multi_call_results) => { multi_call_results.results.iter().for_each(|(service, _)| { if let Ok(ResolvedRpcService::Provider(provider)) = - resolve_rpc_service(service.clone()) + resolve_rpc_service(from_rpc_service(service.clone())) { add_metric_entry!( inconsistent_responses, @@ -158,7 +111,13 @@ fn process_result(method: RpcMethod, result: Result>) -> ) } }); - MultiRpcResult::Inconsistent(multi_call_results.results.into_iter().collect()) + MultiRpcResult::Inconsistent( + multi_call_results + .results + .into_iter() + .map(|(service, result)| (from_rpc_service(service), result)) + .collect(), + ) } }, } @@ -169,7 +128,10 @@ pub struct CandidRpcClient { } impl CandidRpcClient { - pub fn new(source: RpcServices, config: Option) -> RpcResult { + pub fn new( + source: evm_rpc_types::RpcServices, + config: Option, + ) -> RpcResult { Ok(Self { client: get_rpc_client(source, config.unwrap_or_default())?, }) @@ -287,11 +249,13 @@ fn get_transaction_hash(raw_signed_transaction_hex: &Hex) -> Option { #[cfg(test)] mod test { use super::*; + use crate::candid_rpc::cketh_conversion::into_rpc_service; use cketh_common::eth_rpc::RpcError; #[test] fn test_process_result_mapping() { - use cketh_common::eth_rpc_client::{providers::EthMainnetService, MultiCallResults}; + use cketh_common::eth_rpc_client::MultiCallResults; + use evm_rpc_types::{EthMainnetService, RpcService}; let method = RpcMethod::EthGetTransactionCount; @@ -325,9 +289,12 @@ mod test { process_result( method, Err(MultiCallError::InconsistentResults(MultiCallResults { - results: vec![(RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5))] - .into_iter() - .collect(), + results: vec![( + into_rpc_service(RpcService::EthMainnet(EthMainnetService::Ankr)), + Ok(5) + )] + .into_iter() + .collect(), })) ), MultiRpcResult::Inconsistent(vec![( @@ -340,9 +307,12 @@ mod test { method, Err(MultiCallError::InconsistentResults(MultiCallResults { results: vec![ - (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)), ( - RpcService::EthMainnet(EthMainnetService::Cloudflare), + into_rpc_service(RpcService::EthMainnet(EthMainnetService::Ankr)), + Ok(5) + ), + ( + into_rpc_service(RpcService::EthMainnet(EthMainnetService::Cloudflare)), Err(RpcError::ProviderError(ProviderError::NoPermission)) ) ] diff --git a/src/candid_rpc/cketh_conversion.rs b/src/candid_rpc/cketh_conversion.rs index 9412a420..ffab9b9d 100644 --- a/src/candid_rpc/cketh_conversion.rs +++ b/src/candid_rpc/cketh_conversion.rs @@ -186,6 +186,408 @@ pub(super) fn from_send_raw_transaction_result( } } +pub(super) fn into_rpc_config( + value: evm_rpc_types::RpcConfig, +) -> cketh_common::eth_rpc_client::RpcConfig { + cketh_common::eth_rpc_client::RpcConfig { + response_size_estimate: value.response_size_estimate, + } +} + +pub(super) fn into_ethereum_network( + source: &evm_rpc_types::RpcServices, +) -> cketh_common::lifecycle::EthereumNetwork { + match &source { + evm_rpc_types::RpcServices::Custom { chain_id, .. } => { + cketh_common::lifecycle::EthereumNetwork(*chain_id) + } + evm_rpc_types::RpcServices::EthMainnet(_) => { + cketh_common::lifecycle::EthereumNetwork::MAINNET + } + evm_rpc_types::RpcServices::EthSepolia(_) => { + cketh_common::lifecycle::EthereumNetwork::SEPOLIA + } + evm_rpc_types::RpcServices::ArbitrumOne(_) => { + cketh_common::lifecycle::EthereumNetwork::ARBITRUM + } + evm_rpc_types::RpcServices::BaseMainnet(_) => { + cketh_common::lifecycle::EthereumNetwork::BASE + } + evm_rpc_types::RpcServices::OptimismMainnet(_) => { + cketh_common::lifecycle::EthereumNetwork::OPTIMISM + } + } +} + +#[cfg(test)] +pub(super) fn into_rpc_service( + source: evm_rpc_types::RpcService, +) -> cketh_common::eth_rpc_client::providers::RpcService { + fn map_eth_mainnet_service( + service: evm_rpc_types::EthMainnetService, + ) -> cketh_common::eth_rpc_client::providers::EthMainnetService { + match service { + evm_rpc_types::EthMainnetService::Alchemy => { + cketh_common::eth_rpc_client::providers::EthMainnetService::Alchemy + } + evm_rpc_types::EthMainnetService::Ankr => { + cketh_common::eth_rpc_client::providers::EthMainnetService::Ankr + } + evm_rpc_types::EthMainnetService::BlockPi => { + cketh_common::eth_rpc_client::providers::EthMainnetService::BlockPi + } + evm_rpc_types::EthMainnetService::PublicNode => { + cketh_common::eth_rpc_client::providers::EthMainnetService::PublicNode + } + evm_rpc_types::EthMainnetService::Cloudflare => { + cketh_common::eth_rpc_client::providers::EthMainnetService::Cloudflare + } + evm_rpc_types::EthMainnetService::Llama => { + cketh_common::eth_rpc_client::providers::EthMainnetService::Llama + } + } + } + + fn map_eth_sepolia_service( + service: evm_rpc_types::EthSepoliaService, + ) -> cketh_common::eth_rpc_client::providers::EthSepoliaService { + match service { + evm_rpc_types::EthSepoliaService::Alchemy => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::Alchemy + } + evm_rpc_types::EthSepoliaService::Ankr => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::Ankr + } + evm_rpc_types::EthSepoliaService::BlockPi => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::BlockPi + } + evm_rpc_types::EthSepoliaService::PublicNode => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::PublicNode + } + evm_rpc_types::EthSepoliaService::Sepolia => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::Sepolia + } + } + } + + fn map_l2_mainnet_service( + service: evm_rpc_types::L2MainnetService, + ) -> cketh_common::eth_rpc_client::providers::L2MainnetService { + match service { + evm_rpc_types::L2MainnetService::Alchemy => { + cketh_common::eth_rpc_client::providers::L2MainnetService::Alchemy + } + evm_rpc_types::L2MainnetService::Ankr => { + cketh_common::eth_rpc_client::providers::L2MainnetService::Ankr + } + evm_rpc_types::L2MainnetService::BlockPi => { + cketh_common::eth_rpc_client::providers::L2MainnetService::BlockPi + } + evm_rpc_types::L2MainnetService::PublicNode => { + cketh_common::eth_rpc_client::providers::L2MainnetService::PublicNode + } + evm_rpc_types::L2MainnetService::Llama => { + cketh_common::eth_rpc_client::providers::L2MainnetService::Llama + } + } + } + + match source { + evm_rpc_types::RpcService::Chain(id) => { + cketh_common::eth_rpc_client::providers::RpcService::Chain(id) + } + evm_rpc_types::RpcService::Provider(id) => { + cketh_common::eth_rpc_client::providers::RpcService::Provider(id) + } + evm_rpc_types::RpcService::Custom(rpc) => { + cketh_common::eth_rpc_client::providers::RpcService::Custom( + cketh_common::eth_rpc_client::providers::RpcApi { + url: rpc.url, + headers: rpc.headers, + }, + ) + } + evm_rpc_types::RpcService::EthMainnet(service) => { + cketh_common::eth_rpc_client::providers::RpcService::EthMainnet( + map_eth_mainnet_service(service), + ) + } + evm_rpc_types::RpcService::EthSepolia(service) => { + cketh_common::eth_rpc_client::providers::RpcService::EthSepolia( + map_eth_sepolia_service(service), + ) + } + evm_rpc_types::RpcService::ArbitrumOne(service) => { + cketh_common::eth_rpc_client::providers::RpcService::ArbitrumOne( + map_l2_mainnet_service(service), + ) + } + evm_rpc_types::RpcService::BaseMainnet(service) => { + cketh_common::eth_rpc_client::providers::RpcService::BaseMainnet( + map_l2_mainnet_service(service), + ) + } + evm_rpc_types::RpcService::OptimismMainnet(service) => { + cketh_common::eth_rpc_client::providers::RpcService::OptimismMainnet( + map_l2_mainnet_service(service), + ) + } + } +} + +pub(super) fn into_rpc_api( + rpc: evm_rpc_types::RpcApi, +) -> cketh_common::eth_rpc_client::providers::RpcApi { + cketh_common::eth_rpc_client::providers::RpcApi { + url: rpc.url, + headers: rpc.headers, + } +} + +pub(super) fn into_rpc_services( + source: evm_rpc_types::RpcServices, + default_eth_mainnet_services: &[evm_rpc_types::EthMainnetService], + default_eth_sepolia_services: &[evm_rpc_types::EthSepoliaService], + default_l2_mainnet_services: &[evm_rpc_types::L2MainnetService], +) -> Vec { + fn map_eth_mainnet_service( + service: evm_rpc_types::EthMainnetService, + ) -> cketh_common::eth_rpc_client::providers::EthMainnetService { + match service { + evm_rpc_types::EthMainnetService::Alchemy => { + cketh_common::eth_rpc_client::providers::EthMainnetService::Alchemy + } + evm_rpc_types::EthMainnetService::Ankr => { + cketh_common::eth_rpc_client::providers::EthMainnetService::Ankr + } + evm_rpc_types::EthMainnetService::BlockPi => { + cketh_common::eth_rpc_client::providers::EthMainnetService::BlockPi + } + evm_rpc_types::EthMainnetService::PublicNode => { + cketh_common::eth_rpc_client::providers::EthMainnetService::PublicNode + } + evm_rpc_types::EthMainnetService::Cloudflare => { + cketh_common::eth_rpc_client::providers::EthMainnetService::Cloudflare + } + evm_rpc_types::EthMainnetService::Llama => { + cketh_common::eth_rpc_client::providers::EthMainnetService::Llama + } + } + } + + fn map_eth_sepolia_service( + service: evm_rpc_types::EthSepoliaService, + ) -> cketh_common::eth_rpc_client::providers::EthSepoliaService { + match service { + evm_rpc_types::EthSepoliaService::Alchemy => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::Alchemy + } + evm_rpc_types::EthSepoliaService::Ankr => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::Ankr + } + evm_rpc_types::EthSepoliaService::BlockPi => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::BlockPi + } + evm_rpc_types::EthSepoliaService::PublicNode => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::PublicNode + } + evm_rpc_types::EthSepoliaService::Sepolia => { + cketh_common::eth_rpc_client::providers::EthSepoliaService::Sepolia + } + } + } + + fn map_l2_mainnet_service( + service: evm_rpc_types::L2MainnetService, + ) -> cketh_common::eth_rpc_client::providers::L2MainnetService { + match service { + evm_rpc_types::L2MainnetService::Alchemy => { + cketh_common::eth_rpc_client::providers::L2MainnetService::Alchemy + } + evm_rpc_types::L2MainnetService::Ankr => { + cketh_common::eth_rpc_client::providers::L2MainnetService::Ankr + } + evm_rpc_types::L2MainnetService::BlockPi => { + cketh_common::eth_rpc_client::providers::L2MainnetService::BlockPi + } + evm_rpc_types::L2MainnetService::PublicNode => { + cketh_common::eth_rpc_client::providers::L2MainnetService::PublicNode + } + evm_rpc_types::L2MainnetService::Llama => { + cketh_common::eth_rpc_client::providers::L2MainnetService::Llama + } + } + } + + match source { + evm_rpc_types::RpcServices::Custom { + chain_id: _, + services, + } => services + .into_iter() + .map(|service| { + cketh_common::eth_rpc_client::providers::RpcService::Custom(into_rpc_api(service)) + }) + .collect(), + evm_rpc_types::RpcServices::EthMainnet(services) => services + .unwrap_or_else(|| default_eth_mainnet_services.to_vec()) + .into_iter() + .map(|service| { + cketh_common::eth_rpc_client::providers::RpcService::EthMainnet( + map_eth_mainnet_service(service), + ) + }) + .collect(), + evm_rpc_types::RpcServices::EthSepolia(services) => services + .unwrap_or_else(|| default_eth_sepolia_services.to_vec()) + .into_iter() + .map(|service| { + cketh_common::eth_rpc_client::providers::RpcService::EthSepolia( + map_eth_sepolia_service(service), + ) + }) + .collect(), + evm_rpc_types::RpcServices::ArbitrumOne(services) => services + .unwrap_or_else(|| default_l2_mainnet_services.to_vec()) + .into_iter() + .map(|service| { + cketh_common::eth_rpc_client::providers::RpcService::ArbitrumOne( + map_l2_mainnet_service(service), + ) + }) + .collect(), + evm_rpc_types::RpcServices::BaseMainnet(services) => services + .unwrap_or_else(|| default_l2_mainnet_services.to_vec()) + .into_iter() + .map(|service| { + cketh_common::eth_rpc_client::providers::RpcService::BaseMainnet( + map_l2_mainnet_service(service), + ) + }) + .collect(), + evm_rpc_types::RpcServices::OptimismMainnet(services) => services + .unwrap_or_else(|| default_l2_mainnet_services.to_vec()) + .into_iter() + .map(|service| { + cketh_common::eth_rpc_client::providers::RpcService::OptimismMainnet( + map_l2_mainnet_service(service), + ) + }) + .collect(), + } +} + +pub(super) fn from_rpc_service( + service: cketh_common::eth_rpc_client::providers::RpcService, +) -> evm_rpc_types::RpcService { + fn map_eth_mainnet_service( + service: cketh_common::eth_rpc_client::providers::EthMainnetService, + ) -> evm_rpc_types::EthMainnetService { + match service { + cketh_common::eth_rpc_client::providers::EthMainnetService::Alchemy => { + evm_rpc_types::EthMainnetService::Alchemy + } + cketh_common::eth_rpc_client::providers::EthMainnetService::Ankr => { + evm_rpc_types::EthMainnetService::Ankr + } + cketh_common::eth_rpc_client::providers::EthMainnetService::BlockPi => { + evm_rpc_types::EthMainnetService::BlockPi + } + cketh_common::eth_rpc_client::providers::EthMainnetService::PublicNode => { + evm_rpc_types::EthMainnetService::PublicNode + } + cketh_common::eth_rpc_client::providers::EthMainnetService::Cloudflare => { + evm_rpc_types::EthMainnetService::Cloudflare + } + cketh_common::eth_rpc_client::providers::EthMainnetService::Llama => { + evm_rpc_types::EthMainnetService::Llama + } + } + } + + fn map_eth_sepolia_service( + service: cketh_common::eth_rpc_client::providers::EthSepoliaService, + ) -> evm_rpc_types::EthSepoliaService { + match service { + cketh_common::eth_rpc_client::providers::EthSepoliaService::Alchemy => { + evm_rpc_types::EthSepoliaService::Alchemy + } + cketh_common::eth_rpc_client::providers::EthSepoliaService::Ankr => { + evm_rpc_types::EthSepoliaService::Ankr + } + cketh_common::eth_rpc_client::providers::EthSepoliaService::BlockPi => { + evm_rpc_types::EthSepoliaService::BlockPi + } + cketh_common::eth_rpc_client::providers::EthSepoliaService::PublicNode => { + evm_rpc_types::EthSepoliaService::PublicNode + } + cketh_common::eth_rpc_client::providers::EthSepoliaService::Sepolia => { + evm_rpc_types::EthSepoliaService::Sepolia + } + } + } + + fn map_l2_mainnet_service( + service: cketh_common::eth_rpc_client::providers::L2MainnetService, + ) -> evm_rpc_types::L2MainnetService { + match service { + cketh_common::eth_rpc_client::providers::L2MainnetService::Alchemy => { + evm_rpc_types::L2MainnetService::Alchemy + } + cketh_common::eth_rpc_client::providers::L2MainnetService::Ankr => { + evm_rpc_types::L2MainnetService::Ankr + } + cketh_common::eth_rpc_client::providers::L2MainnetService::BlockPi => { + evm_rpc_types::L2MainnetService::BlockPi + } + cketh_common::eth_rpc_client::providers::L2MainnetService::PublicNode => { + evm_rpc_types::L2MainnetService::PublicNode + } + cketh_common::eth_rpc_client::providers::L2MainnetService::Llama => { + evm_rpc_types::L2MainnetService::Llama + } + } + } + + match service { + cketh_common::eth_rpc_client::providers::RpcService::Chain(id) => { + evm_rpc_types::RpcService::Chain(id) + } + cketh_common::eth_rpc_client::providers::RpcService::Provider(id) => { + evm_rpc_types::RpcService::Provider(id) + } + cketh_common::eth_rpc_client::providers::RpcService::Custom(rpc) => { + evm_rpc_types::RpcService::Custom(evm_rpc_types::RpcApi { + url: rpc.url, + headers: rpc.headers.map(|headers| { + headers + .into_iter() + .map(|header| evm_rpc_types::HttpHeader { + name: header.name, + value: header.value, + }) + .collect() + }), + }) + } + cketh_common::eth_rpc_client::providers::RpcService::EthMainnet(service) => { + evm_rpc_types::RpcService::EthMainnet(map_eth_mainnet_service(service)) + } + cketh_common::eth_rpc_client::providers::RpcService::EthSepolia(service) => { + evm_rpc_types::RpcService::EthSepolia(map_eth_sepolia_service(service)) + } + cketh_common::eth_rpc_client::providers::RpcService::ArbitrumOne(service) => { + evm_rpc_types::RpcService::ArbitrumOne(map_l2_mainnet_service(service)) + } + cketh_common::eth_rpc_client::providers::RpcService::BaseMainnet(service) => { + evm_rpc_types::RpcService::BaseMainnet(map_l2_mainnet_service(service)) + } + cketh_common::eth_rpc_client::providers::RpcService::OptimismMainnet(service) => { + evm_rpc_types::RpcService::OptimismMainnet(map_l2_mainnet_service(service)) + } + } +} + pub(super) fn into_hash(value: Hex32) -> Hash { Hash(value.into()) } diff --git a/src/constants.rs b/src/constants.rs index 30982288..37809f80 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,6 +1,4 @@ -use cketh_common::eth_rpc_client::providers::{ - EthMainnetService, EthSepoliaService, L2MainnetService, -}; +use evm_rpc_types::{EthMainnetService, EthSepoliaService, L2MainnetService}; // HTTP outcall cost calculation // See https://internetcomputer.org/docs/current/developer-docs/gas-cost#special-features diff --git a/src/main.rs b/src/main.rs index aa3d61ca..b4e44d62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,5 @@ use candid::candid_method; -use cketh_common::eth_rpc_client::providers::RpcService; -use cketh_common::eth_rpc_client::RpcConfig; use cketh_common::logs::INFO; use evm_rpc::accounting::{get_cost_with_collateral, get_http_request_cost}; use evm_rpc::candid_rpc::CandidRpcClient; @@ -26,7 +24,7 @@ use ic_nervous_system_common::serve_metrics; use evm_rpc::{ http::{json_rpc_request, transform_http_request}, memory::UNSTABLE_METRICS, - types::{InitArgs, MetricRpcMethod, Metrics, MultiRpcResult, RpcServices}, + types::{InitArgs, MetricRpcMethod, Metrics, MultiRpcResult}, }; use evm_rpc_types::Hex32; @@ -42,8 +40,8 @@ pub fn require_api_key_principal_or_controller() -> Result<(), String> { #[update(name = "eth_getLogs")] #[candid_method(rename = "eth_getLogs")] pub async fn eth_get_logs( - source: RpcServices, - config: Option, + source: evm_rpc_types::RpcServices, + config: Option, args: evm_rpc_types::GetLogsArgs, ) -> MultiRpcResult> { match CandidRpcClient::new(source, config) { @@ -55,8 +53,8 @@ pub async fn eth_get_logs( #[update(name = "eth_getBlockByNumber")] #[candid_method(rename = "eth_getBlockByNumber")] pub async fn eth_get_block_by_number( - source: RpcServices, - config: Option, + source: evm_rpc_types::RpcServices, + config: Option, block: evm_rpc_types::BlockTag, ) -> MultiRpcResult { match CandidRpcClient::new(source, config) { @@ -68,8 +66,8 @@ pub async fn eth_get_block_by_number( #[update(name = "eth_getTransactionReceipt")] #[candid_method(rename = "eth_getTransactionReceipt")] pub async fn eth_get_transaction_receipt( - source: RpcServices, - config: Option, + source: evm_rpc_types::RpcServices, + config: Option, tx_hash: Hex32, ) -> MultiRpcResult> { match CandidRpcClient::new(source, config) { @@ -81,8 +79,8 @@ pub async fn eth_get_transaction_receipt( #[update(name = "eth_getTransactionCount")] #[candid_method(rename = "eth_getTransactionCount")] pub async fn eth_get_transaction_count( - source: RpcServices, - config: Option, + source: evm_rpc_types::RpcServices, + config: Option, args: evm_rpc_types::GetTransactionCountArgs, ) -> MultiRpcResult { match CandidRpcClient::new(source, config) { @@ -94,8 +92,8 @@ pub async fn eth_get_transaction_count( #[update(name = "eth_feeHistory")] #[candid_method(rename = "eth_feeHistory")] pub async fn eth_fee_history( - source: RpcServices, - config: Option, + source: evm_rpc_types::RpcServices, + config: Option, args: evm_rpc_types::FeeHistoryArgs, ) -> MultiRpcResult { match CandidRpcClient::new(source, config) { @@ -107,8 +105,8 @@ pub async fn eth_fee_history( #[update(name = "eth_sendRawTransaction")] #[candid_method(rename = "eth_sendRawTransaction")] pub async fn eth_send_raw_transaction( - source: RpcServices, - config: Option, + source: evm_rpc_types::RpcServices, + config: Option, raw_signed_transaction_hex: evm_rpc_types::Hex, ) -> MultiRpcResult { match CandidRpcClient::new(source, config) { @@ -124,7 +122,7 @@ pub async fn eth_send_raw_transaction( #[update] #[candid_method] async fn request( - service: RpcService, + service: evm_rpc_types::RpcService, json_rpc_payload: String, max_response_bytes: u64, ) -> RpcResult { @@ -141,7 +139,7 @@ async fn request( #[query(name = "requestCost")] #[candid_method(query, rename = "requestCost")] fn request_cost( - _service: RpcService, + _service: evm_rpc_types::RpcService, json_rpc_payload: String, max_response_bytes: u64, ) -> RpcResult { @@ -163,7 +161,7 @@ fn get_providers() -> Vec { #[query(name = "getServiceProviderMap")] #[candid_method(query, rename = "getServiceProviderMap")] -fn get_service_provider_map() -> Vec<(RpcService, ProviderId)> { +fn get_service_provider_map() -> Vec<(evm_rpc_types::RpcService, ProviderId)> { SERVICE_PROVIDER_MAP.with(|map| map.iter().map(|(k, v)| (k.clone(), *v)).collect()) } diff --git a/src/providers.rs b/src/providers.rs index 663bbf8d..02b1473c 100644 --- a/src/providers.rs +++ b/src/providers.rs @@ -1,12 +1,7 @@ +use cketh_common::eth_rpc::ProviderError; +use evm_rpc_types::{EthMainnetService, EthSepoliaService, L2MainnetService, RpcApi, RpcService}; use std::collections::HashMap; -use cketh_common::{ - eth_rpc::ProviderError, - eth_rpc_client::providers::{ - EthMainnetService, EthSepoliaService, L2MainnetService, RpcApi, RpcService, - }, -}; - use crate::{ constants::{ ARBITRUM_ONE_CHAIN_ID, BASE_MAINNET_CHAIN_ID, ETH_MAINNET_CHAIN_ID, ETH_SEPOLIA_CHAIN_ID, diff --git a/src/types.rs b/src/types.rs index 3aa26921..4c4d7394 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,9 +1,5 @@ use candid::{CandidType, Principal}; use cketh_common::eth_rpc::RpcError; -use cketh_common::eth_rpc_client::providers::{ - EthMainnetService, EthSepoliaService, L2MainnetService, RpcApi, RpcService, -}; - use ic_cdk::api::management_canister::http_request::HttpHeader; use ic_stable_structures::{BoundedStorable, Storable}; use serde::{Deserialize, Serialize}; @@ -26,12 +22,12 @@ pub struct InitArgs { } pub enum ResolvedRpcService { - Api(RpcApi), + Api(evm_rpc_types::RpcApi), Provider(Provider), } impl ResolvedRpcService { - pub fn api(&self) -> RpcApi { + pub fn api(&self) -> evm_rpc_types::RpcApi { match self { Self::Api(api) => api.clone(), Self::Provider(provider) => provider.api(), @@ -268,27 +264,27 @@ pub struct Provider { #[serde(rename = "chainId")] pub chain_id: u64, pub access: RpcAccess, - pub alias: Option, + pub alias: Option, } impl Provider { - pub fn api(&self) -> RpcApi { + pub fn api(&self) -> evm_rpc_types::RpcApi { match &self.access { RpcAccess::Authenticated { auth, public_url } => match get_api_key(self.provider_id) { Some(api_key) => match auth { - RpcAuth::BearerToken { url } => RpcApi { + RpcAuth::BearerToken { url } => evm_rpc_types::RpcApi { url: url.to_string(), - headers: Some(vec![HttpHeader { + headers: Some(vec![evm_rpc_types::HttpHeader { name: "Authorization".to_string(), value: format!("Bearer {}", api_key.read()), }]), }, - RpcAuth::UrlParameter { url_pattern } => RpcApi { + RpcAuth::UrlParameter { url_pattern } => evm_rpc_types::RpcApi { url: url_pattern.replace(API_KEY_REPLACE_STRING, api_key.read()), headers: None, }, }, - None => RpcApi { + None => evm_rpc_types::RpcApi { url: public_url .unwrap_or_else(|| { panic!( @@ -300,7 +296,7 @@ impl Provider { headers: None, }, }, - RpcAccess::Unauthenticated { public_url } => RpcApi { + RpcAccess::Unauthenticated { public_url } => evm_rpc_types::RpcApi { url: public_url.to_string(), headers: None, }, @@ -357,7 +353,7 @@ pub type RpcResult = Result; #[derive(Clone, Debug, Eq, PartialEq, CandidType, Deserialize)] pub enum MultiRpcResult { Consistent(RpcResult), - Inconsistent(Vec<(RpcService, RpcResult)>), + Inconsistent(Vec<(evm_rpc_types::RpcService, RpcResult)>), } impl MultiRpcResult { @@ -388,7 +384,7 @@ impl MultiRpcResult { } } - pub fn inconsistent(self) -> Option)>> { + pub fn inconsistent(self) -> Option)>> { match self { MultiRpcResult::Consistent(_) => None, MultiRpcResult::Inconsistent(results) => Some(results), @@ -399,7 +395,7 @@ impl MultiRpcResult { self.consistent().expect("expected consistent results") } - pub fn expect_inconsistent(self) -> Vec<(RpcService, RpcResult)> { + pub fn expect_inconsistent(self) -> Vec<(evm_rpc_types::RpcService, RpcResult)> { self.inconsistent().expect("expected inconsistent results") } } @@ -410,27 +406,10 @@ impl From> for MultiRpcResult { } } -#[derive(Clone, CandidType, Deserialize)] -pub enum RpcServices { - Custom { - #[serde(rename = "chainId")] - chain_id: u64, - services: Vec, - }, - EthMainnet(Option>), - EthSepolia(Option>), - ArbitrumOne(Option>), - BaseMainnet(Option>), - OptimismMainnet(Option>), -} - #[cfg(test)] mod test { use candid::Principal; - use cketh_common::{ - eth_rpc::RpcError, - eth_rpc_client::providers::{EthMainnetService, RpcService}, - }; + use cketh_common::eth_rpc::RpcError; use ic_stable_structures::Storable; use crate::types::{ApiKey, BoolStorable, MultiRpcResult, PrincipalStorable}; @@ -450,45 +429,65 @@ mod test { ); assert_eq!( MultiRpcResult::Inconsistent(vec![( - RpcService::EthMainnet(EthMainnetService::Ankr), + evm_rpc_types::RpcService::EthMainnet(evm_rpc_types::EthMainnetService::Ankr), Ok(5) )]) .map(|n| n + 1), MultiRpcResult::Inconsistent(vec![( - RpcService::EthMainnet(EthMainnetService::Ankr), + evm_rpc_types::RpcService::EthMainnet(evm_rpc_types::EthMainnetService::Ankr), Ok(6) )]) ); assert_eq!( MultiRpcResult::Inconsistent(vec![ - (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)), ( - RpcService::EthMainnet(EthMainnetService::Cloudflare), + evm_rpc_types::RpcService::EthMainnet(evm_rpc_types::EthMainnetService::Ankr), + Ok(5) + ), + ( + evm_rpc_types::RpcService::EthMainnet( + evm_rpc_types::EthMainnetService::Cloudflare + ), Ok(10) ) ]) .map(|n| n + 1), MultiRpcResult::Inconsistent(vec![ - (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(6)), ( - RpcService::EthMainnet(EthMainnetService::Cloudflare), + evm_rpc_types::RpcService::EthMainnet(evm_rpc_types::EthMainnetService::Ankr), + Ok(6) + ), + ( + evm_rpc_types::RpcService::EthMainnet( + evm_rpc_types::EthMainnetService::Cloudflare + ), Ok(11) ) ]) ); assert_eq!( MultiRpcResult::Inconsistent(vec![ - (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(5)), ( - RpcService::EthMainnet(EthMainnetService::PublicNode), + evm_rpc_types::RpcService::EthMainnet(evm_rpc_types::EthMainnetService::Ankr), + Ok(5) + ), + ( + evm_rpc_types::RpcService::EthMainnet( + evm_rpc_types::EthMainnetService::PublicNode + ), Err(err.clone()) ) ]) .map(|n| n + 1), MultiRpcResult::Inconsistent(vec![ - (RpcService::EthMainnet(EthMainnetService::Ankr), Ok(6)), ( - RpcService::EthMainnet(EthMainnetService::PublicNode), + evm_rpc_types::RpcService::EthMainnet(evm_rpc_types::EthMainnetService::Ankr), + Ok(6) + ), + ( + evm_rpc_types::RpcService::EthMainnet( + evm_rpc_types::EthMainnetService::PublicNode + ), Err(err) ) ]) diff --git a/tests/tests.rs b/tests/tests.rs index ebfe2a39..8594977c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -5,12 +5,7 @@ use std::{marker::PhantomData, rc::Rc, str::FromStr, time::Duration}; use assert_matches::assert_matches; use candid::{CandidType, Decode, Encode, Nat}; use cketh_common::{ - address::Address, eth_rpc::{HttpOutcallError, JsonRpcError, ProviderError, RpcError}, - eth_rpc_client::{ - providers::{EthMainnetService, EthSepoliaService, RpcApi, RpcService}, - RpcConfig, - }, numeric::Wei, }; use ic_base_types::{CanisterId, PrincipalId}; @@ -30,11 +25,12 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use evm_rpc::{ constants::{CONTENT_TYPE_HEADER_LOWERCASE, CONTENT_TYPE_VALUE}, providers::PROVIDERS, - types::{ - InitArgs, Metrics, MultiRpcResult, ProviderId, RpcAccess, RpcMethod, RpcResult, RpcServices, - }, + types::{InitArgs, Metrics, MultiRpcResult, ProviderId, RpcAccess, RpcMethod, RpcResult}, +}; +use evm_rpc_types::{ + EthMainnetService, EthSepoliaService, Hex, Hex20, Hex32, Nat256, RpcApi, RpcService, + RpcServices, }; -use evm_rpc_types::{Hex, Hex20, Hex32, Nat256}; use mock::{MockOutcall, MockOutcallBuilder}; const DEFAULT_CALLER_TEST_ID: u64 = 10352385; @@ -226,7 +222,7 @@ impl EvmRpcSetup { pub fn eth_get_logs( &self, source: RpcServices, - config: Option, + config: Option, args: evm_rpc_types::GetLogsArgs, ) -> CallFlow>> { self.call_update("eth_getLogs", Encode!(&source, &config, &args).unwrap()) @@ -235,7 +231,7 @@ impl EvmRpcSetup { pub fn eth_get_block_by_number( &self, source: RpcServices, - config: Option, + config: Option, block: evm_rpc_types::BlockTag, ) -> CallFlow> { self.call_update( @@ -247,7 +243,7 @@ impl EvmRpcSetup { pub fn eth_get_transaction_receipt( &self, source: RpcServices, - config: Option, + config: Option, tx_hash: &str, ) -> CallFlow>> { self.call_update( @@ -259,7 +255,7 @@ impl EvmRpcSetup { pub fn eth_get_transaction_count( &self, source: RpcServices, - config: Option, + config: Option, args: evm_rpc_types::GetTransactionCountArgs, ) -> CallFlow> { self.call_update( @@ -271,7 +267,7 @@ impl EvmRpcSetup { pub fn eth_fee_history( &self, source: RpcServices, - config: Option, + config: Option, args: evm_rpc_types::FeeHistoryArgs, ) -> CallFlow>> { self.call_update("eth_feeHistory", Encode!(&source, &config, &args).unwrap()) @@ -280,7 +276,7 @@ impl EvmRpcSetup { pub fn eth_send_raw_transaction( &self, source: RpcServices, - config: Option, + config: Option, signed_raw_transaction_hex: &str, ) -> CallFlow> { let signed_raw_transaction_hex: Hex = signed_raw_transaction_hex.parse().unwrap(); @@ -606,8 +602,8 @@ fn should_decode_checked_amount() { #[test] fn should_decode_address() { - let value = Address::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(); - assert_eq!(Decode!(&Encode!(&value).unwrap(), Address).unwrap(), value); + let value = Hex20::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(); + assert_eq!(Decode!(&Encode!(&value).unwrap(), Hex20).unwrap(), value); } #[test] @@ -1329,7 +1325,7 @@ fn should_use_custom_response_size_estimate() { let response = setup .eth_get_logs( RpcServices::EthMainnet(Some(vec![EthMainnetService::Cloudflare])), - Some(RpcConfig { + Some(evm_rpc_types::RpcConfig { response_size_estimate: Some(max_response_bytes), }), evm_rpc_types::GetLogsArgs {