diff --git a/ethers-core/src/types/trace/erigon.rs b/ethers-core/src/types/trace/erigon.rs new file mode 100644 index 000000000..8a2bd183d --- /dev/null +++ b/ethers-core/src/types/trace/erigon.rs @@ -0,0 +1,39 @@ +use ethabi::ethereum_types::U256; +use serde::{Deserialize, Serialize}; + +use crate::types::{transaction::eip2718::TypedTransaction, BlockNumber, Bytes}; + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct EthCallManyBlockOverride { + pub block_number: U256, +} + +#[derive(Serialize, Debug, Clone)] +pub struct EthCallManyBundle { + pub transactions: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_override: Option, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct EthCallManyStateContext { + pub block_number: BlockNumber, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub transaction_index: Option, +} + +#[derive(Serialize, Debug, Clone)] +pub struct EthCallManyBalanceDiff { + pub balance: U256, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct EthCallManyOutputEmpty {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct EthCallManyOutput { + pub value: Option, + pub error: Option, +} diff --git a/ethers-core/src/types/trace/mod.rs b/ethers-core/src/types/trace/mod.rs index 4098c65f4..85c63e086 100644 --- a/ethers-core/src/types/trace/mod.rs +++ b/ethers-core/src/types/trace/mod.rs @@ -12,6 +12,9 @@ pub use filter::*; mod geth; pub use geth::*; +mod erigon; +pub use erigon::*; + #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] /// Description of the type of trace to make pub enum TraceType { diff --git a/ethers-providers/src/middleware.rs b/ethers-providers/src/middleware.rs index a6afb6b81..28cb61907 100644 --- a/ethers-providers/src/middleware.rs +++ b/ethers-providers/src/middleware.rs @@ -6,7 +6,7 @@ use ethers_core::types::{ }; use futures_util::future::join_all; use serde::{de::DeserializeOwned, Serialize}; -use std::fmt::Debug; +use std::{collections::HashMap, fmt::Debug}; use url::Url; use crate::{ @@ -727,6 +727,19 @@ pub trait Middleware: Sync + Send + Debug { self.inner().txpool_status().await.map_err(MiddlewareError::from_err) } + /// Simulates a set of bundles + /// Implementation: + /// Ref: + /// [Here](https://github.com/ledgerwatch/erigon/blob/513fd50fa501ab6385dc3f58b18079d806d6ff5a/turbo/jsonrpc/eth_callMany.go#L72-L72) + async fn eth_call_many( + &self, + bundles: Vec, + state_context: EthCallManyStateContext, + state_override: Option>>, + ) -> Result>, Self::Error> { + self.inner().eth_call_many(bundles, state_context, state_override).await.map_err(MiddlewareError::from_err) + } + // Geth `trace` support /// After replaying any previous transactions in the same block, diff --git a/ethers-providers/src/rpc/provider.rs b/ethers-providers/src/rpc/provider.rs index 181e1047d..04da19358 100644 --- a/ethers-providers/src/rpc/provider.rs +++ b/ethers-providers/src/rpc/provider.rs @@ -1,5 +1,3 @@ -use ethers_core::types::SyncingStatus; - use crate::{ call_raw::CallBuilder, errors::ProviderError, @@ -13,7 +11,7 @@ use crate::{ #[cfg(not(target_arch = "wasm32"))] use crate::{HttpRateLimitRetryPolicy, RetryClient}; -use std::net::Ipv4Addr; +use std::{collections::HashMap, net::Ipv4Addr}; pub use crate::Middleware; @@ -24,10 +22,11 @@ use ethers_core::{ types::{ transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed}, Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, Chain, EIP1186ProofResponse, + EthCallManyBalanceDiff, EthCallManyBundle, EthCallManyStateContext, EthCallManyOutput, FeeHistory, Filter, FilterBlockOption, GethDebugTracingCallOptions, - GethDebugTracingOptions, GethTrace, Log, NameOrAddress, Selector, Signature, Trace, - TraceFilter, TraceType, Transaction, TransactionReceipt, TransactionRequest, TxHash, - TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64, + GethDebugTracingOptions, GethTrace, Log, NameOrAddress, Selector, Signature, SyncingStatus, + Trace, TraceFilter, TraceType, Transaction, TransactionReceipt, TransactionRequest, TxHash, + TxpoolContent, TxpoolInspect, TxpoolStatus, H160, H256, U256, U64, }, utils, }; @@ -927,6 +926,25 @@ impl Middleware for Provider

{ self.request("txpool_status", ()).await } + async fn eth_call_many( + &self, + bundles: Vec, + state_context: EthCallManyStateContext, + state_override: Option>>, + ) -> Result>, ProviderError> { + let bundles: Vec = bundles + .into_iter() + .map(|tx_bundle| EthCallManyBundle { + transactions: tx_bundle.transactions.into_iter().map(|tx| tx.into()).collect(), + block_override: tx_bundle.block_override, + }) + .collect(); + let bundles = utils::serialize(&bundles); + let options_state_context = utils::serialize(&state_context); + let options_state_override = utils::serialize(&state_override); + self.request("eth_callMany", [bundles, options_state_context, options_state_override]).await + } + async fn debug_trace_transaction( &self, tx_hash: TxHash, diff --git a/examples/transactions/examples/eth_call_many.rs b/examples/transactions/examples/eth_call_many.rs new file mode 100644 index 000000000..4ef1d6e09 --- /dev/null +++ b/examples/transactions/examples/eth_call_many.rs @@ -0,0 +1,74 @@ +use ethers::{ + providers::{Http, Middleware, Provider}, + types::{ + transaction::eip2718::TypedTransaction, Address, BlockNumber, Bytes, + EthCallManyBalanceDiff, EthCallManyBundle, EthCallManyStateContext, TransactionRequest, + H160, U256, + }, +}; +use eyre::Result; +use std::{collections::HashMap, str::FromStr}; + +/// use `eth_callMany` to simulate output +/// requires, a valid endpoint in `RPC_URL` env var that supports `eth_callMany` (Erigon) +/// Example 1: of approving SHIBA INU (token) with Uniswap V2 as spender returns bool +/// Expected output: 0x0000000000000000000000000000000000000000000000000000000000000001 +/// Example 2: transferring tokens while not having enough balance +/// Expected output: Empty error object +#[tokio::main] +async fn main() -> Result<()> { + if let Ok(url) = std::env::var("RPC_URL") { + let client = Provider::::try_from(url)?; + let block_number = client.get_block_number().await.unwrap(); + let gas_fees = client.get_gas_price().await.unwrap(); + { + let tx = TransactionRequest::new().from(Address::from_str("0xdeadbeef29292929192939494959594933929292").unwrap()).to(Address::from_str("0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce").unwrap()).gas_price(gas_fees).gas("0x7a120").data(Bytes::from_str("0x095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d0000000000000000000000000000000000000000b3827a7d5189a7b76dac0000").unwrap()); + let req = vec![EthCallManyBundle { + transactions: vec![TypedTransaction::Legacy(tx)], + block_override: None, + }]; + let mut hashmap: HashMap> = HashMap::new(); + hashmap.insert( + "0xDeaDbEEF29292929192939494959594933929292".parse::().unwrap(), + Some(EthCallManyBalanceDiff { + balance: U256::from( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + ), + }), + ); + + let state_context = EthCallManyStateContext { + block_number: BlockNumber::Number(block_number), + transaction_index: None, + }; + let traces = client.eth_call_many(req, state_context, Some(hashmap)).await?; + println!("{traces:?}"); + } + + { + let tx = TransactionRequest::new().from(Address::from_str("0xdeadbeef29292929192939494959594933929292").unwrap()).to(Address::from_str("0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce").unwrap()).gas_price(gas_fees).gas("0x7a120").data(Bytes::from_str("0xa9059cbb000000000000000000000000deadbeef292929291929394949595949339292920000000000000000000000000000000000000000000000000000000000000005").unwrap()); + let req = vec![EthCallManyBundle { + transactions: vec![TypedTransaction::Legacy(tx)], + block_override: None, + }]; + let mut hashmap: HashMap> = HashMap::new(); + hashmap.insert( + "0xDeaDbEEF29292929192939494959594933929292".parse::().unwrap(), + Some(EthCallManyBalanceDiff { + balance: U256::from( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + ), + }), + ); + + let state_context = EthCallManyStateContext { + block_number: BlockNumber::Number(block_number), + transaction_index: None, + }; + let traces = client.eth_call_many(req, state_context, Some(hashmap)).await?; + println!("{traces:?}"); + } + } + + Ok(()) +}