From a862354ce13377ab4686434064e9e0a2028cf675 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 21 Nov 2023 20:32:27 -0500 Subject: [PATCH 01/30] Add skeleton of multicall middleware --- ethers-contract/src/base.rs | 3 +- ethers-contract/src/call.rs | 24 ++++++ ethers-contract/src/lib.rs | 2 +- ethers-middleware/README.md | 2 + ethers-middleware/src/lib.rs | 4 + ethers-middleware/src/multicall.rs | 124 +++++++++++++++++++++++++++++ 6 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 ethers-middleware/src/multicall.rs diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs index 1374b8b56..3fe572ff3 100644 --- a/ethers-contract/src/base.rs +++ b/ethers-contract/src/base.rs @@ -200,7 +200,8 @@ impl BaseContract { decode_function_data_raw(function, bytes, false) } - fn get_fn_from_input(&self, input: &[u8]) -> Result<&Function, AbiError> { + /// Returns the function from the input bytes (useful for going from tx.data to function) + pub fn get_fn_from_input(&self, input: &[u8]) -> Result<&Function, AbiError> { let sig: [u8; 4] = input .get(0..4) .ok_or(AbiError::WrongSelector)? diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs index 701207561..891e6abfe 100644 --- a/ethers-contract/src/call.rs +++ b/ethers-contract/src/call.rs @@ -176,6 +176,30 @@ pub struct FunctionCall { pub(crate) _m: PhantomData, } +impl FunctionCall +where + B: Borrow, + M: Middleware, + D: Detokenize, +{ + /// Creates a new function call object + pub fn new( + tx: TypedTransaction, + function: Function, + client: B, + block: Option, + ) -> Self { + Self { + tx, + function, + client, + block, + datatype: PhantomData, + _m: PhantomData, + } + } +} + impl Clone for FunctionCall where B: Clone, diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index 859b2102b..846b75335 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -30,7 +30,7 @@ pub mod stream; #[cfg(feature = "abigen")] #[cfg_attr(docsrs, doc(cfg(feature = "abigen")))] -mod multicall; +pub mod multicall; #[cfg(feature = "abigen")] #[cfg_attr(docsrs, doc(cfg(feature = "abigen")))] pub use multicall::{ diff --git a/ethers-middleware/README.md b/ethers-middleware/README.md index 19c415d5f..4105d386a 100644 --- a/ethers-middleware/README.md +++ b/ethers-middleware/README.md @@ -18,6 +18,8 @@ For more information, please refer to the [book](https://gakonst.com/ethers-rs). - [`Transformer`](./transformer/trait.Transformer.html): Allows intercepting and transforming a transaction to be broadcasted via a proxy wallet, e.g. [`DSProxy`](./transformer/struct.DsProxy.html). +- `MultiCall`: transparently batches multiple `eth_call` requests into a single + `eth_call` request, reducing the number of round trips to the RPC and saving API CU usage. ## Examples diff --git a/ethers-middleware/src/lib.rs b/ethers-middleware/src/lib.rs index 9c7518835..dc140e63b 100644 --- a/ethers-middleware/src/lib.rs +++ b/ethers-middleware/src/lib.rs @@ -41,6 +41,10 @@ pub use policy::{ pub mod timelag; pub use timelag::TimeLag; +/// The [MultiCall] middleware provides a way to batch multiple calls into a single call +pub mod multicall; +pub use multicall::MulticallMiddleware; + /// [MiddlewareBuilder] provides a way to compose many [`Middleware`]s in a concise way. pub mod builder; pub use builder::MiddlewareBuilder; diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs new file mode 100644 index 000000000..02c467a19 --- /dev/null +++ b/ethers-middleware/src/multicall.rs @@ -0,0 +1,124 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use ethers_contract::{multicall::Multicall, BaseContract, ContractCall, FunctionCall}; +use ethers_core::{ + abi::{Bytes, Tokenizable}, + types::{transaction::eip2718::TypedTransaction, BlockId}, +}; +use ethers_providers::{Middleware, MiddlewareError}; +use instant::Duration; +use thiserror::Error; + +use tokio::sync::mpsc; +use tokio::sync::oneshot; + +type MulticallTx = (ContractCall, oneshot::Sender>>); + +/// Middleware used for transparently leveraging multicall functionality +pub struct MulticallMiddleware { + inner: Arc, + contract: BaseContract, + multicall: Multicall, + rx: mpsc::UnboundedReceiver>, + tx: mpsc::UnboundedSender>, + checkpoint: instant::Instant, + frequency: Duration +} + +impl MulticallMiddleware +where + M: Middleware, +{ + /// Instantiates the nonce manager with a 0 nonce. The `address` should be the + /// address which you'll be sending transactions from + pub async fn new(inner: M, contract: BaseContract, frequency: Duration) -> Result> { + // TODO: support custom multicall address + let multicall = Multicall::new(inner, None).await?; + + let (tx, rx) = mpsc::unbounded_channel(); + + let timestamp = instant::now(); + + Ok(Self { inner: Arc::new(inner), multicall, contract, tx, rx, checkpoint: timestamp, frequency }) + } + + pub async fn run(&mut self) { + while let Some((call, callback)) = self.rx.recv().await { + self.multicall.add_call(call, false); + + let timestamp = instant::now(); + if timestamp.duration_since(self.checkpoint) > self.frequency { + self.checkpoint = timestamp; + + let results = self.multicall.call_raw().await?; + self.multicall.clear_calls(); + + callback.send(results.pop()); + } + } + } + + fn call_from_tx(&self, tx: &TypedTransaction, block: Option) -> Option> { + if let Some(data) = tx.data() { + if let Ok(function) = self.contract.get_fn_from_input(data) { + return Some(FunctionCall::new(*tx, *function, self.inner, block)); + } + } + + None + } +} + +#[derive(Error, Debug)] +/// Thrown when an error happens at the Multicall middleware +pub enum MulticallError { + /// Thrown when the internal middleware errors + #[error("{0}")] + MiddlewareError(M::Error), +} + +impl MiddlewareError for MulticallError { + type Inner = M::Error; + + fn from_err(src: M::Error) -> Self { + MulticallError::MiddlewareError(src) + } + + fn as_inner(&self) -> Option<&Self::Inner> { + match self { + MulticallError::MiddlewareError(e) => Some(e), + } + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl Middleware for MulticallMiddleware +where + M: Middleware, +{ + type Error = MulticallError; + type Provider = M::Provider; + type Inner = M; + + fn inner(&self) -> &M { + &self.inner + } + + async fn call( + &self, + tx: &TypedTransaction, + block: Option, + ) -> Result { + if let Some(call) = self.call_from_tx(tx, block) { + let (tx, rx) = oneshot::channel(); + + self.tx.send((call, tx)); + + return rx.await; + } + + return self.inner.call(tx, block).await; + } +} From 5a350094f2d474190ec3938c044a95613cb6337a Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 21 Nov 2023 20:39:37 -0500 Subject: [PATCH 02/30] Add vector of callbacks --- ethers-middleware/src/multicall.rs | 36 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 02c467a19..4acbb767a 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -13,17 +13,19 @@ use thiserror::Error; use tokio::sync::mpsc; use tokio::sync::oneshot; -type MulticallTx = (ContractCall, oneshot::Sender>>); +type MulticallTx = + (ContractCall, oneshot::Sender>>); /// Middleware used for transparently leveraging multicall functionality pub struct MulticallMiddleware { inner: Arc, contract: BaseContract, multicall: Multicall, + callbacks: Vec>>>, rx: mpsc::UnboundedReceiver>, tx: mpsc::UnboundedSender>, checkpoint: instant::Instant, - frequency: Duration + frequency: Duration, } impl MulticallMiddleware @@ -32,20 +34,36 @@ where { /// Instantiates the nonce manager with a 0 nonce. The `address` should be the /// address which you'll be sending transactions from - pub async fn new(inner: M, contract: BaseContract, frequency: Duration) -> Result> { + pub async fn new( + inner: M, + contract: BaseContract, + frequency: Duration, + ) -> Result> { // TODO: support custom multicall address let multicall = Multicall::new(inner, None).await?; + let callbacks = Vec::new(); let (tx, rx) = mpsc::unbounded_channel(); let timestamp = instant::now(); - Ok(Self { inner: Arc::new(inner), multicall, contract, tx, rx, checkpoint: timestamp, frequency }) + Ok(Self { + inner: Arc::new(inner), + multicall, + callbacks, + contract, + tx, + rx, + checkpoint: timestamp, + frequency, + }) } pub async fn run(&mut self) { + // TODO: prevent deadlock while let Some((call, callback)) = self.rx.recv().await { self.multicall.add_call(call, false); + self.callbacks.push(callback); let timestamp = instant::now(); if timestamp.duration_since(self.checkpoint) > self.frequency { @@ -54,12 +72,18 @@ where let results = self.multicall.call_raw().await?; self.multicall.clear_calls(); - callback.send(results.pop()); + for (result, callback) in results.into_iter().zip(self.callbacks.drain(..)) { + callback.send(result); + } } } } - fn call_from_tx(&self, tx: &TypedTransaction, block: Option) -> Option> { + fn call_from_tx( + &self, + tx: &TypedTransaction, + block: Option, + ) -> Option> { if let Some(data) = tx.data() { if let Ok(function) = self.contract.get_fn_from_input(data) { return Some(FunctionCall::new(*tx, *function, self.inner, block)); From 10fc677c1bb0ff5fde357a19f4182ed949abb3b1 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 21 Nov 2023 20:41:56 -0500 Subject: [PATCH 03/30] Add another TODO for ABI support --- ethers-middleware/src/multicall.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 4acbb767a..e96ec41ec 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -34,6 +34,7 @@ where { /// Instantiates the nonce manager with a 0 nonce. The `address` should be the /// address which you'll be sending transactions from + /// TODO: support multiple contract ABIs // 4byte DB pub async fn new( inner: M, contract: BaseContract, From d93a521817cae9d39c1f34ffb9c4abd5a2e805e7 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 22 Nov 2023 14:52:03 -0500 Subject: [PATCH 04/30] Use non-blocking mpsc try_recv --- ethers-middleware/src/multicall.rs | 105 +++++++++++++++++++---------- 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index e96ec41ec..74c827934 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; +use std::{cmp::Ordering, sync::Arc, time::Instant}; use async_trait::async_trait; -use ethers_contract::{multicall::Multicall, BaseContract, ContractCall, FunctionCall}; +use ethers_contract::{multicall::Multicall, BaseContract, ContractCall, MulticallError}; use ethers_core::{ - abi::{Bytes, Tokenizable}, + abi::{Bytes, Token, Tokenizable}, types::{transaction::eip2718::TypedTransaction, BlockId}, }; use ethers_providers::{Middleware, MiddlewareError}; @@ -13,17 +13,18 @@ use thiserror::Error; use tokio::sync::mpsc; use tokio::sync::oneshot; -type MulticallTx = - (ContractCall, oneshot::Sender>>); +type MulticallResult = Result; +type MulticallRequest = (ContractCall, oneshot::Sender); +#[derive(Debug)] /// Middleware used for transparently leveraging multicall functionality pub struct MulticallMiddleware { inner: Arc, - contract: BaseContract, + contracts: Vec, multicall: Multicall, - callbacks: Vec>>>, - rx: mpsc::UnboundedReceiver>, - tx: mpsc::UnboundedSender>, + callbacks: Vec>, + rx: mpsc::UnboundedReceiver>, + tx: mpsc::UnboundedSender>, checkpoint: instant::Instant, frequency: Duration, } @@ -37,8 +38,8 @@ where /// TODO: support multiple contract ABIs // 4byte DB pub async fn new( inner: M, - contract: BaseContract, - frequency: Duration, + contracts: Vec, + batch_frequency: Duration, ) -> Result> { // TODO: support custom multicall address let multicall = Multicall::new(inner, None).await?; @@ -46,35 +47,63 @@ where let (tx, rx) = mpsc::unbounded_channel(); - let timestamp = instant::now(); + let timestamp = Instant::now(); Ok(Self { - inner: Arc::new(inner), + inner, multicall, callbacks, - contract, + contracts, tx, rx, checkpoint: timestamp, - frequency, + frequency: batch_frequency, }) } - pub async fn run(&mut self) { - // TODO: prevent deadlock - while let Some((call, callback)) = self.rx.recv().await { - self.multicall.add_call(call, false); - self.callbacks.push(callback); - - let timestamp = instant::now(); - if timestamp.duration_since(self.checkpoint) > self.frequency { - self.checkpoint = timestamp; - - let results = self.multicall.call_raw().await?; - self.multicall.clear_calls(); + pub async fn run(&mut self) -> Result<(), MulticallMiddlewareError> { + loop { + let maybe_request = self.rx.try_recv(); + match maybe_request { + Ok((call, callback)) => { + self.multicall.add_call(call, true); + self.callbacks.push(callback); + // keep filling batch until channel is empty (or closed) + continue; + } + Err(mpsc::error::TryRecvError::Disconnected) => { + // TODO: exit? + } + Err(mpsc::error::TryRecvError::Empty) => { + // TODO: consider sleeping here? + } + } - for (result, callback) in results.into_iter().zip(self.callbacks.drain(..)) { - callback.send(result); + // check if batch is non-empty and frequency has elapsed since last batch was sent + if self.callbacks.len() > 0 + && self.checkpoint.elapsed().cmp(&self.frequency) == Ordering::Greater + { + let maybe_results = self.multicall.call_raw().await; + match maybe_results { + Ok(results) => { + self.multicall.clear_calls(); + + for (result, callback) in results.into_iter().zip(self.callbacks.drain(..)) + { + callback.send(result); + } + + self.checkpoint = Instant::now(); + } + Err(MulticallError::ContractError(ce)) => { + // TODO: bubble up to callback? + } + Err(MulticallError::InvalidChainId(id)) => { + // TODO: exit? + } + Err(MulticallError::IllegalRevert) => { + // TODO: idk + } } } } @@ -86,8 +115,10 @@ where block: Option, ) -> Option> { if let Some(data) = tx.data() { - if let Ok(function) = self.contract.get_fn_from_input(data) { - return Some(FunctionCall::new(*tx, *function, self.inner, block)); + for contract in self.contracts.iter() { + if let Ok(function) = contract.get_fn_from_input(data) { + return Some(ContractCall::new(*tx, *function, self.inner, block)); + } } } @@ -97,22 +128,22 @@ where #[derive(Error, Debug)] /// Thrown when an error happens at the Multicall middleware -pub enum MulticallError { +pub enum MulticallMiddlewareError { /// Thrown when the internal middleware errors #[error("{0}")] MiddlewareError(M::Error), } -impl MiddlewareError for MulticallError { +impl MiddlewareError for MulticallMiddlewareError { type Inner = M::Error; fn from_err(src: M::Error) -> Self { - MulticallError::MiddlewareError(src) + MulticallMiddlewareError::MiddlewareError(src) } fn as_inner(&self) -> Option<&Self::Inner> { match self { - MulticallError::MiddlewareError(e) => Some(e), + MulticallMiddlewareError::MiddlewareError(e) => Some(e), } } } @@ -123,7 +154,7 @@ impl Middleware for MulticallMiddleware where M: Middleware, { - type Error = MulticallError; + type Error = MulticallMiddlewareError; type Provider = M::Provider; type Inner = M; @@ -139,7 +170,7 @@ where if let Some(call) = self.call_from_tx(tx, block) { let (tx, rx) = oneshot::channel(); - self.tx.send((call, tx)); + self.tx.send((call, tx))?; return rx.await; } From 73735316d7b3cb099a5f0dade3f85d57baaf8754 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 22 Nov 2023 23:22:11 -0500 Subject: [PATCH 05/30] Fix build --- ethers-middleware/src/multicall.rs | 163 ++++++++++++++--------------- 1 file changed, 81 insertions(+), 82 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 74c827934..b49fa2106 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,9 +1,11 @@ use std::{cmp::Ordering, sync::Arc, time::Instant}; use async_trait::async_trait; -use ethers_contract::{multicall::Multicall, BaseContract, ContractCall, MulticallError}; +use ethers_contract::{ + multicall::Multicall, BaseContract, ContractCall, ContractError, MulticallError, +}; use ethers_core::{ - abi::{Bytes, Token, Tokenizable}, + abi::{encode, Token, Tokenizable}, types::{transaction::eip2718::TypedTransaction, BlockId}, }; use ethers_providers::{Middleware, MiddlewareError}; @@ -13,98 +15,105 @@ use thiserror::Error; use tokio::sync::mpsc; use tokio::sync::oneshot; -type MulticallResult = Result; -type MulticallRequest = (ContractCall, oneshot::Sender); +type MulticallResult = Result>; +type MulticallRequest = (ContractCall, oneshot::Sender>); #[derive(Debug)] /// Middleware used for transparently leveraging multicall functionality pub struct MulticallMiddleware { inner: Arc, contracts: Vec, - multicall: Multicall, - callbacks: Vec>, rx: mpsc::UnboundedReceiver>, tx: mpsc::UnboundedSender>, - checkpoint: instant::Instant, frequency: Duration, } +#[derive(Error, Debug)] +/// Thrown when an error happens at the Multicall middleware +pub enum MulticallMiddlewareError { + /// Thrown when the internal middleware errors + #[error("{0}")] + MiddlewareError(M::Error), + /// Thrown when the internal multicall errors + #[error("{0}")] + MulticallError(MulticallError), +} + +impl MiddlewareError for MulticallMiddlewareError { + type Inner = M::Error; + + fn from_err(src: M::Error) -> Self { + MulticallMiddlewareError::MiddlewareError(src) + } + + fn as_inner(&self) -> Option<&Self::Inner> { + match self { + MulticallMiddlewareError::MiddlewareError(e) => Some(e), + MulticallMiddlewareError::MulticallError(e) => e.as_middleware_error(), + } + } +} + +impl From> for MulticallMiddlewareError { + fn from(value: MulticallError) -> Self { + MulticallMiddlewareError::MulticallError(value) + } +} + impl MulticallMiddleware where M: Middleware, { - /// Instantiates the nonce manager with a 0 nonce. The `address` should be the - /// address which you'll be sending transactions from - /// TODO: support multiple contract ABIs // 4byte DB - pub async fn new( + /// Instantiates the multicall middleware to recognize the given `contracts` selectors + /// and batch calls in a single inner call every `frequency` interval + pub fn new( inner: M, contracts: Vec, - batch_frequency: Duration, + frequency: Duration, ) -> Result> { - // TODO: support custom multicall address - let multicall = Multicall::new(inner, None).await?; - let callbacks = Vec::new(); - let (tx, rx) = mpsc::unbounded_channel(); - let timestamp = Instant::now(); - - Ok(Self { - inner, - multicall, - callbacks, - contracts, - tx, - rx, - checkpoint: timestamp, - frequency: batch_frequency, - }) + Ok(Self { inner: Arc::new(inner), contracts, tx, rx, frequency }) } pub async fn run(&mut self) -> Result<(), MulticallMiddlewareError> { + // TODO: support custom multicall address + let mut multicall = Multicall::new(self.inner.clone(), None).await?; + let mut callbacks = Vec::new(); + let mut checkpoint = Instant::now(); + loop { let maybe_request = self.rx.try_recv(); match maybe_request { Ok((call, callback)) => { - self.multicall.add_call(call, true); - self.callbacks.push(callback); - // keep filling batch until channel is empty (or closed) + multicall.add_call(call, true); + callbacks.push(callback); + // keep filling batch until channel is empty (or disconnected) continue; } Err(mpsc::error::TryRecvError::Disconnected) => { - // TODO: exit? + panic!("multicall channel disconnected"); } Err(mpsc::error::TryRecvError::Empty) => { - // TODO: consider sleeping here? + // TODO: consider sleeping here } } // check if batch is non-empty and frequency has elapsed since last batch was sent - if self.callbacks.len() > 0 - && self.checkpoint.elapsed().cmp(&self.frequency) == Ordering::Greater + if callbacks.len() > 0 && checkpoint.elapsed().cmp(&self.frequency) == Ordering::Greater { - let maybe_results = self.multicall.call_raw().await; - match maybe_results { - Ok(results) => { - self.multicall.clear_calls(); - - for (result, callback) in results.into_iter().zip(self.callbacks.drain(..)) - { - callback.send(result); - } - - self.checkpoint = Instant::now(); - } - Err(MulticallError::ContractError(ce)) => { - // TODO: bubble up to callback? - } - Err(MulticallError::InvalidChainId(id)) => { - // TODO: exit? - } - Err(MulticallError::IllegalRevert) => { - // TODO: idk + let results = multicall.call_raw().await?; + multicall.clear_calls(); + + for (result, callback) in results.into_iter().zip(callbacks.drain(..)) { + let response = + result.map_err(|e| MulticallError::ContractError(ContractError::Revert(e))); + if let Err(e) = callback.send(response) { + panic!("oneshot channel closed: {:?}", e); } } + + checkpoint = Instant::now(); } } } @@ -117,7 +126,12 @@ where if let Some(data) = tx.data() { for contract in self.contracts.iter() { if let Ok(function) = contract.get_fn_from_input(data) { - return Some(ContractCall::new(*tx, *function, self.inner, block)); + return Some(ContractCall::new( + tx.clone(), + function.clone(), + self.inner.clone(), + block, + )); } } } @@ -126,28 +140,6 @@ where } } -#[derive(Error, Debug)] -/// Thrown when an error happens at the Multicall middleware -pub enum MulticallMiddlewareError { - /// Thrown when the internal middleware errors - #[error("{0}")] - MiddlewareError(M::Error), -} - -impl MiddlewareError for MulticallMiddlewareError { - type Inner = M::Error; - - fn from_err(src: M::Error) -> Self { - MulticallMiddlewareError::MiddlewareError(src) - } - - fn as_inner(&self) -> Option<&Self::Inner> { - match self { - MulticallMiddlewareError::MiddlewareError(e) => Some(e), - } - } -} - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Middleware for MulticallMiddleware @@ -166,15 +158,22 @@ where &self, tx: &TypedTransaction, block: Option, - ) -> Result { + ) -> Result { if let Some(call) = self.call_from_tx(tx, block) { let (tx, rx) = oneshot::channel(); - self.tx.send((call, tx))?; + if let Err(e) = self.tx.send((call, tx)) { + panic!("multicall channel disconnected: {:?}", e); + }; - return rx.await; + match rx.await { + Err(e) => panic!("multicall channel disconnected: {:?}", e), + Ok(response) => { + return response.map(|token| encode(&[token]).into()).map_err(|e| e.into()); + } + } } - return self.inner.call(tx, block).await; + return self.inner.call(tx, block).await.map_err(MulticallMiddlewareError::from_err); } } From f044444881d03291350933050e8b3f900ff2b13b Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 22 Nov 2023 23:53:12 -0500 Subject: [PATCH 06/30] Add skeleton of test --- ethers-contract/src/multicall/constants.rs | 3 +++ ethers-middleware/src/multicall.rs | 9 ++++--- ethers-middleware/tests/it/main.rs | 2 ++ ethers-middleware/tests/it/multicall.rs | 30 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 ethers-middleware/tests/it/multicall.rs diff --git a/ethers-contract/src/multicall/constants.rs b/ethers-contract/src/multicall/constants.rs index ac0fdbbd6..32a7a7d85 100644 --- a/ethers-contract/src/multicall/constants.rs +++ b/ethers-contract/src/multicall/constants.rs @@ -1,5 +1,8 @@ use ethers_core::types::{Chain, H160}; +/// From https://github.com/mds1/multicall#new-deployments +pub const DEPLOY_MULTICALL_TX: &[u8; 7854] = b"0xf90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086"; + /// The Multicall3 contract address that is deployed in [`MULTICALL_SUPPORTED_CHAIN_IDS`]: /// [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11) pub const MULTICALL_ADDRESS: H160 = H160([ diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index b49fa2106..dc1353f6c 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -6,7 +6,7 @@ use ethers_contract::{ }; use ethers_core::{ abi::{encode, Token, Tokenizable}, - types::{transaction::eip2718::TypedTransaction, BlockId}, + types::{transaction::eip2718::TypedTransaction, BlockId, Address}, }; use ethers_providers::{Middleware, MiddlewareError}; use instant::Duration; @@ -22,6 +22,7 @@ type MulticallRequest = (ContractCall, oneshot::Sender { inner: Arc, + multicall_address: Option
, contracts: Vec, rx: mpsc::UnboundedReceiver>, tx: mpsc::UnboundedSender>, @@ -70,15 +71,15 @@ where inner: M, contracts: Vec, frequency: Duration, + multicall_address: Option
, ) -> Result> { let (tx, rx) = mpsc::unbounded_channel(); - Ok(Self { inner: Arc::new(inner), contracts, tx, rx, frequency }) + Ok(Self { inner: Arc::new(inner), contracts, tx, rx, frequency, multicall_address }) } pub async fn run(&mut self) -> Result<(), MulticallMiddlewareError> { - // TODO: support custom multicall address - let mut multicall = Multicall::new(self.inner.clone(), None).await?; + let mut multicall = Multicall::new(self.inner.clone(), self.multicall_address).await?; let mut callbacks = Vec::new(); let mut checkpoint = Instant::now(); diff --git a/ethers-middleware/tests/it/main.rs b/ethers-middleware/tests/it/main.rs index bf2dc441d..02e01a203 100644 --- a/ethers-middleware/tests/it/main.rs +++ b/ethers-middleware/tests/it/main.rs @@ -15,6 +15,8 @@ mod gas_oracle; #[cfg(not(feature = "celo"))] mod signer; +mod multicall; + #[cfg(not(feature = "celo"))] mod nonce_manager; diff --git a/ethers-middleware/tests/it/multicall.rs b/ethers-middleware/tests/it/multicall.rs new file mode 100644 index 000000000..3569cd035 --- /dev/null +++ b/ethers-middleware/tests/it/multicall.rs @@ -0,0 +1,30 @@ +use crate::spawn_anvil; +use ethers_core::types::*; +use ethers_middleware::{MiddlewareBuilder, MulticallMiddleware}; +use ethers_providers::Middleware; + +use ethers_contract::multicall::constants::{DEPLOY_MULTICALL_TX, MULTICALL_ADDRESS}; +use instant::Duration; + +#[tokio::test] +async fn multicall() { + let (provider, anvil) = spawn_anvil(); + + let tx_bytes: Bytes = DEPLOY_MULTICALL_TX.into(); + + provider.send_raw_transaction(tx_bytes).await.unwrap(); + + let contracts = Vec::new(); + + let mut multicall_provider = MulticallMiddleware::new( + provider, + contracts, + Duration::from_secs(1), + Some(MULTICALL_ADDRESS), + ).unwrap(); + + // spawn the multicall middleware + tokio::spawn(async move { + multicall_provider.run().await; + }); +} From eba790b874bb09ec4d3099b313cf7627c22a27b4 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 23 Nov 2023 00:35:59 -0500 Subject: [PATCH 07/30] Succesfully deploy multicall to anvil in tests --- ethers-contract/src/multicall/constants.rs | 6 +++- ethers-contract/src/multicall/middleware.rs | 1 + ethers-middleware/tests/it/multicall.rs | 34 ++++++++++++++++----- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/ethers-contract/src/multicall/constants.rs b/ethers-contract/src/multicall/constants.rs index 32a7a7d85..ea70debc8 100644 --- a/ethers-contract/src/multicall/constants.rs +++ b/ethers-contract/src/multicall/constants.rs @@ -1,7 +1,11 @@ use ethers_core::types::{Chain, H160}; /// From https://github.com/mds1/multicall#new-deployments -pub const DEPLOY_MULTICALL_TX: &[u8; 7854] = b"0xf90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086"; +pub const SIGNED_DEPLOY_MULTICALL_TX: &'static str = "0xf90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086"; +pub const DEPLOYER_ADDRESS: H160 = H160([ + 0x05, 0xf3, 0x2B, 0x3c, 0xC3, 0x88, 0x84, 0x53, 0xff, 0x71, 0xB0, 0x11, 0x35, 0xB3, 0x4F, 0xF8, + 0xe4, 0x12, 0x63, 0xF2, +]); /// The Multicall3 contract address that is deployed in [`MULTICALL_SUPPORTED_CHAIN_IDS`]: /// [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11) diff --git a/ethers-contract/src/multicall/middleware.rs b/ethers-contract/src/multicall/middleware.rs index 98474755d..36c0711e6 100644 --- a/ethers-contract/src/multicall/middleware.rs +++ b/ethers-contract/src/multicall/middleware.rs @@ -202,6 +202,7 @@ impl Multicall { let address: Address = match address { Some(addr) => addr, None => { + // TODO: replace with eth_getCode? let chain_id = client .get_chainid() .await diff --git a/ethers-middleware/tests/it/multicall.rs b/ethers-middleware/tests/it/multicall.rs index 3569cd035..e210efb23 100644 --- a/ethers-middleware/tests/it/multicall.rs +++ b/ethers-middleware/tests/it/multicall.rs @@ -1,30 +1,50 @@ +use std::sync::Arc; + use crate::spawn_anvil; use ethers_core::types::*; use ethers_middleware::{MiddlewareBuilder, MulticallMiddleware}; use ethers_providers::Middleware; -use ethers_contract::multicall::constants::{DEPLOY_MULTICALL_TX, MULTICALL_ADDRESS}; +use ethers_contract::{ + multicall::constants::{DEPLOYER_ADDRESS, MULTICALL_ADDRESS, SIGNED_DEPLOY_MULTICALL_TX}, + multicall::contract::Multicall3, + BaseContract, +}; use instant::Duration; #[tokio::test] async fn multicall() { let (provider, anvil) = spawn_anvil(); - let tx_bytes: Bytes = DEPLOY_MULTICALL_TX.into(); - - provider.send_raw_transaction(tx_bytes).await.unwrap(); - - let contracts = Vec::new(); + provider + .request::<(H160, U256), ()>( + "anvil_setBalance", + (DEPLOYER_ADDRESS, U256::from(1_000_000_000_000_000_000u64)), + ) + .await + .unwrap(); + provider + .request::<[serde_json::Value; 1], H256>( + "eth_sendRawTransaction", + [SIGNED_DEPLOY_MULTICALL_TX.into()], + ) + .await + .unwrap(); + // TODO: inject some contract ABIs to call + let contracts = vec![]; let mut multicall_provider = MulticallMiddleware::new( provider, contracts, Duration::from_secs(1), Some(MULTICALL_ADDRESS), - ).unwrap(); + ) + .unwrap(); // spawn the multicall middleware tokio::spawn(async move { multicall_provider.run().await; }); + + // TODO: make some async calls and verify that only 1 RPC is made } From 12ec70c447c866af80c8dd9cc81fe0ad57159ca8 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 23 Nov 2023 12:52:47 -0500 Subject: [PATCH 08/30] Add some working tests --- ethers-middleware/src/multicall.rs | 93 +++++++++++++++---------- ethers-middleware/tests/it/multicall.rs | 63 +++++++++++++---- 2 files changed, 104 insertions(+), 52 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index dc1353f6c..0099fdd48 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -2,31 +2,37 @@ use std::{cmp::Ordering, sync::Arc, time::Instant}; use async_trait::async_trait; use ethers_contract::{ - multicall::Multicall, BaseContract, ContractCall, ContractError, MulticallError, + multicall::Multicall, BaseContract, ContractCall, ContractError, ContractRevert, EthError, + MulticallError, }; use ethers_core::{ abi::{encode, Token, Tokenizable}, - types::{transaction::eip2718::TypedTransaction, BlockId, Address}, + types::{transaction::eip2718::TypedTransaction, Address, BlockId, Bytes}, }; use ethers_providers::{Middleware, MiddlewareError}; use instant::Duration; use thiserror::Error; -use tokio::sync::mpsc; use tokio::sync::oneshot; +use tokio::{sync::mpsc, time::sleep}; type MulticallResult = Result>; type MulticallRequest = (ContractCall, oneshot::Sender>); #[derive(Debug)] /// Middleware used for transparently leveraging multicall functionality -pub struct MulticallMiddleware { +pub struct MulticallProcessor { inner: Arc, multicall_address: Option
, - contracts: Vec, + frequency: Duration, rx: mpsc::UnboundedReceiver>, +} + +#[derive(Debug, Clone)] +pub struct MulticallMiddleware { + inner: Arc, + contracts: Vec, tx: mpsc::UnboundedSender>, - frequency: Duration, } #[derive(Error, Debug)] @@ -36,8 +42,11 @@ pub enum MulticallMiddlewareError { #[error("{0}")] MiddlewareError(M::Error), /// Thrown when the internal multicall errors + #[error(transparent)] + MulticallError(#[from] MulticallError), + /// Thrown when a revert reason is decoded from the contract #[error("{0}")] - MulticallError(MulticallError), + RevertReason(String), } impl MiddlewareError for MulticallMiddlewareError { @@ -51,52 +60,33 @@ impl MiddlewareError for MulticallMiddlewareError { match self { MulticallMiddlewareError::MiddlewareError(e) => Some(e), MulticallMiddlewareError::MulticallError(e) => e.as_middleware_error(), + MulticallMiddlewareError::RevertReason(_) => None, } } } -impl From> for MulticallMiddlewareError { - fn from(value: MulticallError) -> Self { - MulticallMiddlewareError::MulticallError(value) - } -} - -impl MulticallMiddleware +impl MulticallProcessor where M: Middleware, { - /// Instantiates the multicall middleware to recognize the given `contracts` selectors - /// and batch calls in a single inner call every `frequency` interval - pub fn new( - inner: M, - contracts: Vec, - frequency: Duration, - multicall_address: Option
, - ) -> Result> { - let (tx, rx) = mpsc::unbounded_channel(); - - Ok(Self { inner: Arc::new(inner), contracts, tx, rx, frequency, multicall_address }) - } - - pub async fn run(&mut self) -> Result<(), MulticallMiddlewareError> { - let mut multicall = Multicall::new(self.inner.clone(), self.multicall_address).await?; + pub async fn run(mut self) -> Result<(), MulticallMiddlewareError> { + let mut multicall = Multicall::new(self.inner, self.multicall_address).await?; let mut callbacks = Vec::new(); let mut checkpoint = Instant::now(); loop { let maybe_request = self.rx.try_recv(); match maybe_request { - Ok((call, callback)) => { - multicall.add_call(call, true); - callbacks.push(callback); - // keep filling batch until channel is empty (or disconnected) + Err(mpsc::error::TryRecvError::Empty) => { + sleep(self.frequency).await; continue; } Err(mpsc::error::TryRecvError::Disconnected) => { panic!("multicall channel disconnected"); } - Err(mpsc::error::TryRecvError::Empty) => { - // TODO: consider sleeping here + Ok((call, callback)) => { + multicall.add_call(call, true); + callbacks.push(callback); } } @@ -118,6 +108,28 @@ where } } } +} + +impl MulticallMiddleware +where + M: Middleware, +{ + /// Instantiates the multicall middleware to recognize the given `contracts` selectors + /// and batch calls in a single inner call every `frequency` interval + pub fn new( + inner: M, + contracts: Vec, + frequency: Duration, + multicall_address: Option
, + ) -> (Self, MulticallProcessor) { + let (tx, rx) = mpsc::unbounded_channel(); + let client = Arc::new(inner); + + ( + Self { inner: client.clone(), tx, contracts }, + MulticallProcessor { inner: client, rx, frequency, multicall_address }, + ) + } fn call_from_tx( &self, @@ -136,7 +148,6 @@ where } } } - None } } @@ -170,11 +181,19 @@ where match rx.await { Err(e) => panic!("multicall channel disconnected: {:?}", e), Ok(response) => { - return response.map(|token| encode(&[token]).into()).map_err(|e| e.into()); + return response.map(|token| encode(&[token]).into()).map_err(|e| { + if let Some(reason) = e.decode_revert::() { + MulticallMiddlewareError::RevertReason(reason) + } else { + MulticallMiddlewareError::MulticallError(e) + } + }); } } } return self.inner.call(tx, block).await.map_err(MulticallMiddlewareError::from_err); } + + // TODO: support other Multicall methods (blocknumber, balance, etc) } diff --git a/ethers-middleware/tests/it/multicall.rs b/ethers-middleware/tests/it/multicall.rs index e210efb23..b4faf0b41 100644 --- a/ethers-middleware/tests/it/multicall.rs +++ b/ethers-middleware/tests/it/multicall.rs @@ -1,21 +1,27 @@ use std::sync::Arc; use crate::spawn_anvil; -use ethers_core::types::*; -use ethers_middleware::{MiddlewareBuilder, MulticallMiddleware}; -use ethers_providers::Middleware; +use ethers_core::{types::*, abi::AbiEncode}; +use ethers_middleware::{MulticallMiddleware, SignerMiddleware, multicall::MulticallMiddlewareError}; use ethers_contract::{ - multicall::constants::{DEPLOYER_ADDRESS, MULTICALL_ADDRESS, SIGNED_DEPLOY_MULTICALL_TX}, - multicall::contract::Multicall3, - BaseContract, + abigen, + multicall::constants::{DEPLOYER_ADDRESS, MULTICALL_ADDRESS, SIGNED_DEPLOY_MULTICALL_TX}, ContractError, EthError, }; +use ethers_signers::{LocalWallet, Signer}; use instant::Duration; +abigen!( + SimpleRevertingStorage, + "../ethers-contract/tests/solidity-contracts/SimpleRevertingStorage.json" +); +abigen!(SimpleStorage, "../ethers-contract/tests/solidity-contracts/SimpleStorage.json"); + #[tokio::test] async fn multicall() { let (provider, anvil) = spawn_anvil(); + // 1. deploy multicall contract (if not already) provider .request::<(H160, U256), ()>( "anvil_setBalance", @@ -31,20 +37,47 @@ async fn multicall() { .await .unwrap(); - // TODO: inject some contract ABIs to call - let contracts = vec![]; - let mut multicall_provider = MulticallMiddleware::new( - provider, + // 2. deploy some contracts to interact with + let wallet: LocalWallet = anvil.keys()[0].clone().into(); + let client = Arc::new(SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id()))); + + let value = "multicall!".to_string(); + let simple = + SimpleStorage::deploy(client.clone(), value.clone()).unwrap().send().await.unwrap(); + let simple_reverting = + SimpleRevertingStorage::deploy(client.clone(), value.clone()).unwrap().send().await.unwrap(); + + // 3. instantiate the multicall middleware + // TODO: get BaseContracts before deploying? + let contracts = vec![simple.abi().clone().into(), simple_reverting.abi().to_owned().into()]; + let (multicall_provider, multicall_processor) = MulticallMiddleware::new( + client, contracts, Duration::from_secs(1), Some(MULTICALL_ADDRESS), - ) - .unwrap(); + ); + + let multicall_client = Arc::new(multicall_provider); + + // 4. reconnect contracts to the multicall provider + let simple = SimpleStorage::new(simple.address(), multicall_client.clone()); + let simple_reverting = + SimpleRevertingStorage::new(simple_reverting.address(), multicall_client); + + // 5. spawn the multicall processor + tokio::spawn(async move { + let _ = multicall_processor.run().await; + }); - // spawn the multicall middleware + // 6. perform some calls in parallel tokio::spawn(async move { - multicall_provider.run().await; + let simple_result = simple.get_value().call().await.unwrap(); + assert_eq!(simple_result, value); }); - // TODO: make some async calls and verify that only 1 RPC is made + let reverting_result = simple_reverting.get_value(true).call().await.unwrap_err().to_string(); + assert_eq!( + reverting_result, + "getValue revert" + ); } From 7aac46fa558b120075f69144496362ea72828b05 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 23 Nov 2023 12:58:38 -0500 Subject: [PATCH 09/30] Some cleanup --- ethers-contract/src/multicall/constants.rs | 6 +++++- ethers-middleware/src/multicall.rs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ethers-contract/src/multicall/constants.rs b/ethers-contract/src/multicall/constants.rs index ea70debc8..4309fd8cd 100644 --- a/ethers-contract/src/multicall/constants.rs +++ b/ethers-contract/src/multicall/constants.rs @@ -1,7 +1,11 @@ use ethers_core::types::{Chain, H160}; -/// From https://github.com/mds1/multicall#new-deployments +// from https://github.com/mds1/multicall#new-deployments + +/// presigned tx that will deploy multicall3 pub const SIGNED_DEPLOY_MULTICALL_TX: &'static str = "0xf90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086"; + +/// address to be funded for `SIGNED_DEPLOY_MULTICALL_TX` to succeed pub const DEPLOYER_ADDRESS: H160 = H160([ 0x05, 0xf3, 0x2B, 0x3c, 0xC3, 0x88, 0x84, 0x53, 0xff, 0x71, 0xB0, 0x11, 0x35, 0xB3, 0x4F, 0xF8, 0xe4, 0x12, 0x63, 0xF2, diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 0099fdd48..a7b7a9019 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -2,12 +2,12 @@ use std::{cmp::Ordering, sync::Arc, time::Instant}; use async_trait::async_trait; use ethers_contract::{ - multicall::Multicall, BaseContract, ContractCall, ContractError, ContractRevert, EthError, + multicall::Multicall, BaseContract, ContractCall, ContractError, MulticallError, }; use ethers_core::{ abi::{encode, Token, Tokenizable}, - types::{transaction::eip2718::TypedTransaction, Address, BlockId, Bytes}, + types::{transaction::eip2718::TypedTransaction, Address, BlockId}, }; use ethers_providers::{Middleware, MiddlewareError}; use instant::Duration; From 4b5c39b27a0b9a8b247b824feb0797199fd8181d Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 23 Nov 2023 13:09:05 -0500 Subject: [PATCH 10/30] Improve error handling --- ethers-middleware/src/multicall.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index a7b7a9019..89fc0e59e 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -82,7 +82,7 @@ where continue; } Err(mpsc::error::TryRecvError::Disconnected) => { - panic!("multicall channel disconnected"); + return Ok(()); } Ok((call, callback)) => { multicall.add_call(call, true); @@ -93,18 +93,18 @@ where // check if batch is non-empty and frequency has elapsed since last batch was sent if callbacks.len() > 0 && checkpoint.elapsed().cmp(&self.frequency) == Ordering::Greater { + checkpoint = Instant::now(); + let results = multicall.call_raw().await?; multicall.clear_calls(); for (result, callback) in results.into_iter().zip(callbacks.drain(..)) { let response = result.map_err(|e| MulticallError::ContractError(ContractError::Revert(e))); - if let Err(e) = callback.send(response) { - panic!("oneshot channel closed: {:?}", e); - } - } - checkpoint = Instant::now(); + // ignore send errors, as the receiver may have dropped + let _ = callback.send(response); + } } } } From 51a86722d19af3b66b4d67effc70d1181e799e69 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 24 Nov 2023 15:33:34 -0500 Subject: [PATCH 11/30] Add get_block_number and get_balance --- ethers-contract/src/base.rs | 15 ++-- ethers-middleware/src/multicall.rs | 133 +++++++++++++++++++++-------- 2 files changed, 107 insertions(+), 41 deletions(-) diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs index 3fe572ff3..b547bda31 100644 --- a/ethers-contract/src/base.rs +++ b/ethers-contract/src/base.rs @@ -53,7 +53,7 @@ impl BaseContract { signature: Selector, args: T, ) -> Result { - let function = self.get_from_signature(signature)?; + let function = self.get_fn_from_selector(signature)?; encode_function_data(function, args) } @@ -147,7 +147,7 @@ impl BaseContract { signature: Selector, bytes: T, ) -> Result, AbiError> { - let function = self.get_from_signature(signature)?; + let function = self.get_fn_from_selector(signature)?; decode_function_data_raw(function, bytes, true) } @@ -157,7 +157,7 @@ impl BaseContract { signature: Selector, bytes: T, ) -> Result { - let function = self.get_from_signature(signature)?; + let function = self.get_fn_from_selector(signature)?; decode_function_data(function, bytes, true) } @@ -183,7 +183,7 @@ impl BaseContract { signature: Selector, bytes: T, ) -> Result { - let function = self.get_from_signature(signature)?; + let function = self.get_fn_from_selector(signature)?; decode_function_data(function, bytes, false) } @@ -196,7 +196,7 @@ impl BaseContract { signature: Selector, bytes: T, ) -> Result, AbiError> { - let function = self.get_from_signature(signature)?; + let function = self.get_fn_from_selector(signature)?; decode_function_data_raw(function, bytes, false) } @@ -207,10 +207,11 @@ impl BaseContract { .ok_or(AbiError::WrongSelector)? .try_into() .map_err(|_e| AbiError::WrongSelector)?; - self.get_from_signature(sig) + self.get_fn_from_selector(sig) } - fn get_from_signature(&self, signature: Selector) -> Result<&Function, AbiError> { + /// Returns the function from the selector + pub fn get_fn_from_selector(&self, signature: Selector) -> Result<&Function, AbiError> { Ok(self .methods .get(&signature) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 89fc0e59e..e277ecd92 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,20 +1,31 @@ -use std::{cmp::Ordering, sync::Arc, time::Instant}; +use std::{cmp::Ordering, ops::Deref, sync::Arc, time::Instant}; use async_trait::async_trait; use ethers_contract::{ - multicall::Multicall, BaseContract, ContractCall, ContractError, - MulticallError, + multicall::{ + contract::{ + GetBlockNumberCall, GetBlockNumberReturn, GetEthBalanceCall, GetEthBalanceReturn, + MULTICALL3_ABI, + }, + Multicall, + }, + BaseContract, ContractCall, ContractError, EthCall, MulticallError, }; use ethers_core::{ - abi::{encode, Token, Tokenizable}, - types::{transaction::eip2718::TypedTransaction, Address, BlockId}, + abi::{encode, Abi, AbiDecode, Token, Tokenizable}, + types::{ + transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, NameOrAddress, + TransactionRequest, + }, }; use ethers_providers::{Middleware, MiddlewareError}; use instant::Duration; use thiserror::Error; -use tokio::sync::oneshot; -use tokio::{sync::mpsc, time::sleep}; +use tokio::{ + sync::{mpsc, oneshot}, + time::sleep, +}; type MulticallResult = Result>; type MulticallRequest = (ContractCall, oneshot::Sender>); @@ -31,6 +42,7 @@ pub struct MulticallProcessor { #[derive(Debug, Clone)] pub struct MulticallMiddleware { inner: Arc, + multicall: BaseContract, contracts: Vec, tx: mpsc::UnboundedSender>, } @@ -77,16 +89,23 @@ where loop { let maybe_request = self.rx.try_recv(); match maybe_request { - Err(mpsc::error::TryRecvError::Empty) => { - sleep(self.frequency).await; - continue; - } + Err(mpsc::error::TryRecvError::Empty) => {} Err(mpsc::error::TryRecvError::Disconnected) => { return Ok(()); } - Ok((call, callback)) => { - multicall.add_call(call, true); + Ok((mut call, callback)) => { + // use to: None as sentinel for system calls to get block number, etc + if call.tx.to().is_none() { + call.tx.set_to(multicall.contract.address()); + multicall.add_call(call, false); + } else { + multicall.add_call(call, true); + } + callbacks.push(callback); + + // keep filling up the batch until channel is empty + continue; } } @@ -114,19 +133,23 @@ impl MulticallMiddleware where M: Middleware, { - /// Instantiates the multicall middleware to recognize the given `contracts` selectors + /// Instantiates the multicall middleware to recognize the given `match_abis` /// and batch calls in a single inner call every `frequency` interval pub fn new( inner: M, - contracts: Vec, + match_abis: Vec, frequency: Duration, multicall_address: Option
, ) -> (Self, MulticallProcessor) { let (tx, rx) = mpsc::unbounded_channel(); let client = Arc::new(inner); + let contracts = match_abis.iter().map(|abi| abi.clone().into()).collect(); + + let multicall: BaseContract = MULTICALL3_ABI.clone().into(); + ( - Self { inner: client.clone(), tx, contracts }, + Self { inner: client.clone(), tx, contracts, multicall }, MulticallProcessor { inner: client, rx, frequency, multicall_address }, ) } @@ -150,6 +173,19 @@ where } None } + + async fn batch_call(&self, call: ContractCall) -> Result> { + let (tx, rx) = oneshot::channel(); + + if let Err(e) = self.tx.send((call, tx)) { + panic!("multicall processor disconnected: {:?}", e); + }; + + match rx.await { + Err(e) => panic!("multicall processor disconnected: {:?}", e), + Ok(response) => response.map(|token| encode(&[token]).into()), + } + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -172,28 +208,57 @@ where block: Option, ) -> Result { if let Some(call) = self.call_from_tx(tx, block) { - let (tx, rx) = oneshot::channel(); - - if let Err(e) = self.tx.send((call, tx)) { - panic!("multicall channel disconnected: {:?}", e); - }; - - match rx.await { - Err(e) => panic!("multicall channel disconnected: {:?}", e), - Ok(response) => { - return response.map(|token| encode(&[token]).into()).map_err(|e| { - if let Some(reason) = e.decode_revert::() { - MulticallMiddlewareError::RevertReason(reason) - } else { - MulticallMiddlewareError::MulticallError(e) - } - }); + return self.batch_call(call).await.map_err(|e| { + if let Some(reason) = e.decode_revert::() { + MulticallMiddlewareError::RevertReason(reason) + } else { + MulticallMiddlewareError::MulticallError(e) } - } + }); } return self.inner.call(tx, block).await.map_err(MulticallMiddlewareError::from_err); } - // TODO: support other Multicall methods (blocknumber, balance, etc) + async fn get_block_number(&self) -> Result { + let get_block_fn = + self.multicall.get_fn_from_selector(GetBlockNumberCall::selector()).unwrap(); + let data = get_block_fn.encode_input(&vec![]).unwrap(); + let call = + ContractCall::new( + TypedTransaction::Legacy(TransactionRequest::new().data(data)), + get_block_fn.to_owned(), + self.inner.clone(), + None, + ); + return self + .batch_call(call) + .await + .map(|b| GetBlockNumberReturn::decode(b.deref()).unwrap().block_number.as_u64().into()) + .map_err(MulticallMiddlewareError::MulticallError); + } + + async fn get_balance + Send + Sync>( + &self, + address_or_name: T, + block: Option, + ) -> Result { + let address = *address_or_name.into().as_address().unwrap(); + let get_balance_fn = + self.multicall.get_fn_from_selector(GetEthBalanceCall::selector()).unwrap(); + let data = get_balance_fn.encode_input(&vec![Token::Address(address)]).unwrap(); + let call = ContractCall::new( + TypedTransaction::Legacy(TransactionRequest::new().data(data)), + get_balance_fn.to_owned(), + self.inner.clone(), + block, + ); + return self + .batch_call(call) + .await + .map(|b| GetEthBalanceReturn::decode(b.deref()).unwrap().balance) + .map_err(MulticallMiddlewareError::MulticallError); + } + + // TODO: implement more middleware functions? } From a23aaed217c5e1f56ba3b2a653456cf1aa047fe7 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 24 Nov 2023 17:27:48 -0500 Subject: [PATCH 12/30] Improve blocking logic of processor --- ethers-middleware/src/multicall.rs | 101 ++++++++++++++++------------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index e277ecd92..8480fa842 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, ops::Deref, sync::Arc, time::Instant}; +use std::{ops::Deref, sync::Arc}; use async_trait::async_trait; use ethers_contract::{ @@ -19,13 +19,9 @@ use ethers_core::{ }, }; use ethers_providers::{Middleware, MiddlewareError}; -use instant::Duration; use thiserror::Error; -use tokio::{ - sync::{mpsc, oneshot}, - time::sleep, -}; +use tokio::sync::{mpsc, oneshot}; type MulticallResult = Result>; type MulticallRequest = (ContractCall, oneshot::Sender>); @@ -35,7 +31,7 @@ type MulticallRequest = (ContractCall, oneshot::Sender { inner: Arc, multicall_address: Option
, - frequency: Duration, + max_batch_size: usize, rx: mpsc::UnboundedReceiver>, } @@ -81,50 +77,54 @@ impl MulticallProcessor where M: Middleware, { - pub async fn run(mut self) -> Result<(), MulticallMiddlewareError> { - let mut multicall = Multicall::new(self.inner, self.multicall_address).await?; - let mut callbacks = Vec::new(); - let mut checkpoint = Instant::now(); + pub async fn run(mut self) -> () { + let mut multicall: Multicall = + Multicall::new(self.inner, self.multicall_address).await.unwrap(); + let mut requests: Vec> = Vec::with_capacity(self.max_batch_size); loop { - let maybe_request = self.rx.try_recv(); - match maybe_request { - Err(mpsc::error::TryRecvError::Empty) => {} - Err(mpsc::error::TryRecvError::Disconnected) => { - return Ok(()); - } - Ok((mut call, callback)) => { - // use to: None as sentinel for system calls to get block number, etc - if call.tx.to().is_none() { - call.tx.set_to(multicall.contract.address()); - multicall.add_call(call, false); - } else { - multicall.add_call(call, true); - } - - callbacks.push(callback); - - // keep filling up the batch until channel is empty - continue; - } + match self.rx.recv().await { + Some(request) => requests.push(request), + None => break, } - // check if batch is non-empty and frequency has elapsed since last batch was sent - if callbacks.len() > 0 && checkpoint.elapsed().cmp(&self.frequency) == Ordering::Greater - { - checkpoint = Instant::now(); + while requests.len() < self.max_batch_size { + match self.rx.try_recv() { + Ok(request) => requests.push(request), - let results = multicall.call_raw().await?; - multicall.clear_calls(); + // For both errors (Disconnected and Empty), the correct action + // is to process the items. If the error was Disconnected, on + // the next iteration rx.recv().await will be None and we'll + // break from the outer loop anyway. + Err(_) => break, + } + } - for (result, callback) in results.into_iter().zip(callbacks.drain(..)) { - let response = - result.map_err(|e| MulticallError::ContractError(ContractError::Revert(e))); + for (call, _) in &requests { + let mut call = call.to_owned(); - // ignore send errors, as the receiver may have dropped - let _ = callback.send(response); + // use `to: None` as sentinel for system calls to get block number, etc + if call.tx.to().is_none() { + call.tx.set_to(multicall.contract.address()); + multicall.add_call(call, false); + } else { + multicall.add_call(call, true); } } + + println!("sending batch of {} calls", requests.len()); + + let results = multicall.call_raw().await.unwrap(); + for (result, (_, callback)) in results.into_iter().zip(requests.drain(..)) { + let response = + result.map_err(|e| MulticallError::ContractError(ContractError::Revert(e))); + + // ignore send errors, as the receiver may have dropped + let _ = callback.send(response); + } + + multicall.clear_calls(); + requests.clear(); // just to be safe } } } @@ -138,7 +138,7 @@ where pub fn new( inner: M, match_abis: Vec, - frequency: Duration, + max_batch_size: usize, multicall_address: Option
, ) -> (Self, MulticallProcessor) { let (tx, rx) = mpsc::unbounded_channel(); @@ -150,7 +150,7 @@ where ( Self { inner: client.clone(), tx, contracts, multicall }, - MulticallProcessor { inner: client, rx, frequency, multicall_address }, + MulticallProcessor { inner: client, rx, multicall_address, max_batch_size }, ) } @@ -240,10 +240,19 @@ where async fn get_balance + Send + Sync>( &self, - address_or_name: T, + address: T, block: Option, ) -> Result { - let address = *address_or_name.into().as_address().unwrap(); + let address_or_name = address.into(); + if address_or_name.as_name().is_some() { + return self + .inner + .get_balance(address_or_name, block) + .await + .map_err(MulticallMiddlewareError::from_err); + } + + let address = *address_or_name.as_address().unwrap(); let get_balance_fn = self.multicall.get_fn_from_selector(GetEthBalanceCall::selector()).unwrap(); let data = get_balance_fn.encode_input(&vec![Token::Address(address)]).unwrap(); From a8aa1b09a153807d51ba532a941f8b001a2edc03 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 24 Nov 2023 17:28:03 -0500 Subject: [PATCH 13/30] Test new endpoints --- ethers-middleware/tests/it/multicall.rs | 72 ++++++++++++------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/ethers-middleware/tests/it/multicall.rs b/ethers-middleware/tests/it/multicall.rs index b4faf0b41..1281a4b1f 100644 --- a/ethers-middleware/tests/it/multicall.rs +++ b/ethers-middleware/tests/it/multicall.rs @@ -1,25 +1,30 @@ use std::sync::Arc; use crate::spawn_anvil; -use ethers_core::{types::*, abi::AbiEncode}; -use ethers_middleware::{MulticallMiddleware, SignerMiddleware, multicall::MulticallMiddlewareError}; +use ethers_core::types::*; +use ethers_middleware::{MulticallMiddleware, SignerMiddleware}; use ethers_contract::{ abigen, - multicall::constants::{DEPLOYER_ADDRESS, MULTICALL_ADDRESS, SIGNED_DEPLOY_MULTICALL_TX}, ContractError, EthError, + multicall::constants::{DEPLOYER_ADDRESS, MULTICALL_ADDRESS, SIGNED_DEPLOY_MULTICALL_TX}, }; +use ethers_providers::Middleware; use ethers_signers::{LocalWallet, Signer}; -use instant::Duration; abigen!( SimpleRevertingStorage, "../ethers-contract/tests/solidity-contracts/SimpleRevertingStorage.json" ); -abigen!(SimpleStorage, "../ethers-contract/tests/solidity-contracts/SimpleStorage.json"); +abigen!( + SimpleStorage, + "../ethers-contract/tests/solidity-contracts/SimpleStorage.json" +); #[tokio::test] async fn multicall() { let (provider, anvil) = spawn_anvil(); + let wallet: LocalWallet = anvil.keys()[0].clone().into(); + let client = Arc::new(SignerMiddleware::new(provider.clone(), wallet.with_chain_id(anvil.chain_id()))); // 1. deploy multicall contract (if not already) provider @@ -37,47 +42,42 @@ async fn multicall() { .await .unwrap(); - // 2. deploy some contracts to interact with - let wallet: LocalWallet = anvil.keys()[0].clone().into(); - let client = Arc::new(SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id()))); - - let value = "multicall!".to_string(); - let simple = - SimpleStorage::deploy(client.clone(), value.clone()).unwrap().send().await.unwrap(); - let simple_reverting = - SimpleRevertingStorage::deploy(client.clone(), value.clone()).unwrap().send().await.unwrap(); - - // 3. instantiate the multicall middleware - // TODO: get BaseContracts before deploying? - let contracts = vec![simple.abi().clone().into(), simple_reverting.abi().to_owned().into()]; + // 2. instantiate the multicall middleware let (multicall_provider, multicall_processor) = MulticallMiddleware::new( client, - contracts, - Duration::from_secs(1), + vec![SIMPLEREVERTINGSTORAGE_ABI.to_owned(), SIMPLESTORAGE_ABI.to_owned()], + 10, Some(MULTICALL_ADDRESS), ); - let multicall_client = Arc::new(multicall_provider); - // 4. reconnect contracts to the multicall provider - let simple = SimpleStorage::new(simple.address(), multicall_client.clone()); - let simple_reverting = - SimpleRevertingStorage::new(simple_reverting.address(), multicall_client); + // 3. deploy a contract to interact with + let value = "multicall!".to_string(); + let simple = SimpleStorage::deploy(multicall_client.clone(), value.clone()).unwrap().send().await.unwrap(); + let simple_reverting = SimpleRevertingStorage::deploy(multicall_client.clone(), value.clone()).unwrap().send().await.unwrap(); - // 5. spawn the multicall processor + // 4. spawn the multicall processor tokio::spawn(async move { let _ = multicall_processor.run().await; }); - // 6. perform some calls in parallel - tokio::spawn(async move { - let simple_result = simple.get_value().call().await.unwrap(); - assert_eq!(simple_result, value); - }); - - let reverting_result = simple_reverting.get_value(true).call().await.unwrap_err().to_string(); - assert_eq!( - reverting_result, - "getValue revert" + // 5. make some calls in parallel + tokio::join!( + async { + let val: String = simple.get_value().call().await.unwrap(); + assert_eq!(val, value); + }, + async { + let val = simple_reverting.get_value(true).call().await.unwrap_err().to_string(); + assert_eq!(val, "getValue revert"); + }, + async { + let bal = multicall_client.get_balance(DEPLOYER_ADDRESS, None).await.unwrap(); + assert!(bal > U256::zero()); + }, + async { + let block = multicall_client.get_block_number().await.unwrap(); + assert!(block > U64::zero()); + } ); } From ad69c2ad018dcbae5cfd14c0aefa32a1e9e74dce Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 24 Nov 2023 17:33:42 -0500 Subject: [PATCH 14/30] Reuse call_from_tx helper --- ethers-middleware/src/multicall.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 8480fa842..1c3fc5b00 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -224,13 +224,8 @@ where let get_block_fn = self.multicall.get_fn_from_selector(GetBlockNumberCall::selector()).unwrap(); let data = get_block_fn.encode_input(&vec![]).unwrap(); - let call = - ContractCall::new( - TypedTransaction::Legacy(TransactionRequest::new().data(data)), - get_block_fn.to_owned(), - self.inner.clone(), - None, - ); + let tx = TypedTransaction::Legacy(TransactionRequest::new().data(data.clone())); + let call = self.call_from_tx(&tx, None).unwrap(); return self .batch_call(call) .await @@ -256,12 +251,8 @@ where let get_balance_fn = self.multicall.get_fn_from_selector(GetEthBalanceCall::selector()).unwrap(); let data = get_balance_fn.encode_input(&vec![Token::Address(address)]).unwrap(); - let call = ContractCall::new( - TypedTransaction::Legacy(TransactionRequest::new().data(data)), - get_balance_fn.to_owned(), - self.inner.clone(), - block, - ); + let tx = TypedTransaction::Legacy(TransactionRequest::new().data(data.clone())); + let call = self.call_from_tx(&tx, block).unwrap(); return self .batch_call(call) .await From a2cd44518d4a8c157f7204a39eb0625368981e0e Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 24 Nov 2023 17:48:45 -0500 Subject: [PATCH 15/30] Reuse multicall ABI matching --- ethers-middleware/src/multicall.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 1c3fc5b00..fcd1147f9 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -12,7 +12,7 @@ use ethers_contract::{ BaseContract, ContractCall, ContractError, EthCall, MulticallError, }; use ethers_core::{ - abi::{encode, Abi, AbiDecode, Token, Tokenizable}, + abi::{encode, Abi, AbiDecode, AbiEncode, Token, Tokenizable}, types::{ transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, NameOrAddress, TransactionRequest, @@ -38,7 +38,6 @@ pub struct MulticallProcessor { #[derive(Debug, Clone)] pub struct MulticallMiddleware { inner: Arc, - multicall: BaseContract, contracts: Vec, tx: mpsc::UnboundedSender>, } @@ -144,12 +143,14 @@ where let (tx, rx) = mpsc::unbounded_channel(); let client = Arc::new(inner); - let contracts = match_abis.iter().map(|abi| abi.clone().into()).collect(); - - let multicall: BaseContract = MULTICALL3_ABI.clone().into(); + let contracts = match_abis + .iter() + .map(|abi| abi.clone().into()) + .chain(vec![MULTICALL3_ABI.to_owned().into()]) + .collect(); ( - Self { inner: client.clone(), tx, contracts, multicall }, + Self { inner: client.clone(), tx, contracts }, MulticallProcessor { inner: client, rx, multicall_address, max_batch_size }, ) } @@ -221,9 +222,7 @@ where } async fn get_block_number(&self) -> Result { - let get_block_fn = - self.multicall.get_fn_from_selector(GetBlockNumberCall::selector()).unwrap(); - let data = get_block_fn.encode_input(&vec![]).unwrap(); + let data = (GetBlockNumberCall {}).encode(); let tx = TypedTransaction::Legacy(TransactionRequest::new().data(data.clone())); let call = self.call_from_tx(&tx, None).unwrap(); return self @@ -248,9 +247,7 @@ where } let address = *address_or_name.as_address().unwrap(); - let get_balance_fn = - self.multicall.get_fn_from_selector(GetEthBalanceCall::selector()).unwrap(); - let data = get_balance_fn.encode_input(&vec![Token::Address(address)]).unwrap(); + let data = (GetEthBalanceCall { addr: address }).encode(); let tx = TypedTransaction::Legacy(TransactionRequest::new().data(data.clone())); let call = self.call_from_tx(&tx, block).unwrap(); return self From b2ba459f390359c7590ee52ee170c064cb8843bf Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 24 Nov 2023 17:54:48 -0500 Subject: [PATCH 16/30] Reuse call in block and balance middlewares --- ethers-middleware/src/multicall.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index fcd1147f9..4f9b7d6f7 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -224,12 +224,9 @@ where async fn get_block_number(&self) -> Result { let data = (GetBlockNumberCall {}).encode(); let tx = TypedTransaction::Legacy(TransactionRequest::new().data(data.clone())); - let call = self.call_from_tx(&tx, None).unwrap(); - return self - .batch_call(call) + self.call(&tx, None) .await .map(|b| GetBlockNumberReturn::decode(b.deref()).unwrap().block_number.as_u64().into()) - .map_err(MulticallMiddlewareError::MulticallError); } async fn get_balance + Send + Sync>( @@ -249,12 +246,7 @@ where let address = *address_or_name.as_address().unwrap(); let data = (GetEthBalanceCall { addr: address }).encode(); let tx = TypedTransaction::Legacy(TransactionRequest::new().data(data.clone())); - let call = self.call_from_tx(&tx, block).unwrap(); - return self - .batch_call(call) - .await - .map(|b| GetEthBalanceReturn::decode(b.deref()).unwrap().balance) - .map_err(MulticallMiddlewareError::MulticallError); + self.call(&tx, block).await.map(|b| GetEthBalanceReturn::decode(b.deref()).unwrap().balance) } // TODO: implement more middleware functions? From 35117d2fd4aaf39e84e0e4c5cf57f96ac30e87f9 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Sat, 25 Nov 2023 16:39:33 -0500 Subject: [PATCH 17/30] Restore changes to base contract --- ethers-contract/src/base.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs index b547bda31..3fe572ff3 100644 --- a/ethers-contract/src/base.rs +++ b/ethers-contract/src/base.rs @@ -53,7 +53,7 @@ impl BaseContract { signature: Selector, args: T, ) -> Result { - let function = self.get_fn_from_selector(signature)?; + let function = self.get_from_signature(signature)?; encode_function_data(function, args) } @@ -147,7 +147,7 @@ impl BaseContract { signature: Selector, bytes: T, ) -> Result, AbiError> { - let function = self.get_fn_from_selector(signature)?; + let function = self.get_from_signature(signature)?; decode_function_data_raw(function, bytes, true) } @@ -157,7 +157,7 @@ impl BaseContract { signature: Selector, bytes: T, ) -> Result { - let function = self.get_fn_from_selector(signature)?; + let function = self.get_from_signature(signature)?; decode_function_data(function, bytes, true) } @@ -183,7 +183,7 @@ impl BaseContract { signature: Selector, bytes: T, ) -> Result { - let function = self.get_fn_from_selector(signature)?; + let function = self.get_from_signature(signature)?; decode_function_data(function, bytes, false) } @@ -196,7 +196,7 @@ impl BaseContract { signature: Selector, bytes: T, ) -> Result, AbiError> { - let function = self.get_fn_from_selector(signature)?; + let function = self.get_from_signature(signature)?; decode_function_data_raw(function, bytes, false) } @@ -207,11 +207,10 @@ impl BaseContract { .ok_or(AbiError::WrongSelector)? .try_into() .map_err(|_e| AbiError::WrongSelector)?; - self.get_fn_from_selector(sig) + self.get_from_signature(sig) } - /// Returns the function from the selector - pub fn get_fn_from_selector(&self, signature: Selector) -> Result<&Function, AbiError> { + fn get_from_signature(&self, signature: Selector) -> Result<&Function, AbiError> { Ok(self .methods .get(&signature) From d80349bc3dfd2fc87f03fe5cd63b4ad70d5077e6 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Sun, 26 Nov 2023 16:29:44 -0500 Subject: [PATCH 18/30] Improve error handling and unit tests --- ethers-middleware/src/multicall.rs | 210 ++++++++++++++++---- ethers-middleware/tests/it/multicall.rs | 4 +- ethers-providers/src/rpc/transports/mock.rs | 9 +- 3 files changed, 179 insertions(+), 44 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 4f9b7d6f7..922f559a2 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,4 +1,4 @@ -use std::{ops::Deref, sync::Arc}; +use std::{iter::repeat, ops::Deref, rc::Rc, sync::Arc}; use async_trait::async_trait; use ethers_contract::{ @@ -9,7 +9,7 @@ use ethers_contract::{ }, Multicall, }, - BaseContract, ContractCall, ContractError, EthCall, MulticallError, + BaseContract, ContractCall, ContractError, EthCall, EthError, MulticallError, }; use ethers_core::{ abi::{encode, Abi, AbiDecode, AbiEncode, Token, Tokenizable}, @@ -23,7 +23,7 @@ use thiserror::Error; use tokio::sync::{mpsc, oneshot}; -type MulticallResult = Result>; +type MulticallResult = Result>>; type MulticallRequest = (ContractCall, oneshot::Sender>); #[derive(Debug)] @@ -32,14 +32,14 @@ pub struct MulticallProcessor { inner: Arc, multicall_address: Option
, max_batch_size: usize, - rx: mpsc::UnboundedReceiver>, + rx: mpsc::Receiver>, } #[derive(Debug, Clone)] pub struct MulticallMiddleware { inner: Arc, contracts: Vec, - tx: mpsc::UnboundedSender>, + tx: mpsc::Sender>, } #[derive(Error, Debug)] @@ -50,10 +50,10 @@ pub enum MulticallMiddlewareError { MiddlewareError(M::Error), /// Thrown when the internal multicall errors #[error(transparent)] - MulticallError(#[from] MulticallError), - /// Thrown when a revert reason is decoded from the contract - #[error("{0}")] - RevertReason(String), + MulticallError(#[from] Arc>), + /// Thrown when the processor isn't running + #[error("Processor is not running")] + ProcessorNotRunning, } impl MiddlewareError for MulticallMiddlewareError { @@ -67,7 +67,7 @@ impl MiddlewareError for MulticallMiddlewareError { match self { MulticallMiddlewareError::MiddlewareError(e) => Some(e), MulticallMiddlewareError::MulticallError(e) => e.as_middleware_error(), - MulticallMiddlewareError::RevertReason(_) => None, + MulticallMiddlewareError::ProcessorNotRunning => None, } } } @@ -79,14 +79,17 @@ where pub async fn run(mut self) -> () { let mut multicall: Multicall = Multicall::new(self.inner, self.multicall_address).await.unwrap(); - let mut requests: Vec> = Vec::with_capacity(self.max_batch_size); loop { + let mut requests = Vec::new(); + + // wait for the first request match self.rx.recv().await { Some(request) => requests.push(request), None => break, } + // attempt to batch more requests, up to the max batch size while requests.len() < self.max_batch_size { match self.rx.try_recv() { Ok(request) => requests.push(request), @@ -99,31 +102,38 @@ where } } - for (call, _) in &requests { - let mut call = call.to_owned(); + let (calls, callbacks): (Vec<_>, Vec<_>) = requests.into_iter().unzip(); + multicall.clear_calls(); + for mut call in calls.into_iter() { // use `to: None` as sentinel for system calls to get block number, etc if call.tx.to().is_none() { call.tx.set_to(multicall.contract.address()); + // do not allow reverts for system calls multicall.add_call(call, false); } else { + // allow reverts for user calls multicall.add_call(call, true); } } - - println!("sending batch of {} calls", requests.len()); - - let results = multicall.call_raw().await.unwrap(); - for (result, (_, callback)) in results.into_iter().zip(requests.drain(..)) { - let response = - result.map_err(|e| MulticallError::ContractError(ContractError::Revert(e))); - - // ignore send errors, as the receiver may have dropped + let results = multicall.call_raw().await; + + let responses = match results { + Ok(results) => results + .into_iter() + .map(|result| { + result.map_err( + |e| Arc::new(MulticallError::ContractError(ContractError::Revert(e))) + ) + }) + .collect(), + Err(e) => vec![Err(Arc::new(e)); callbacks.len()], + }; + + for (callback, response) in callbacks.into_iter().zip(responses) { + // ignore errors, the receiver may have dropped let _ = callback.send(response); } - - multicall.clear_calls(); - requests.clear(); // just to be safe } } } @@ -133,19 +143,22 @@ where M: Middleware, { /// Instantiates the multicall middleware to recognize the given `match_abis` - /// and batch calls in a single inner call every `frequency` interval pub fn new( inner: M, match_abis: Vec, max_batch_size: usize, multicall_address: Option
, ) -> (Self, MulticallProcessor) { - let (tx, rx) = mpsc::unbounded_channel(); + if max_batch_size < 2 { + panic!("batches must be at least 2 calls to justify the overhead of multicall"); + } + + let (tx, rx) = mpsc::channel(max_batch_size); let client = Arc::new(inner); let contracts = match_abis .iter() - .map(|abi| abi.clone().into()) + .map(|abi| abi.to_owned().into()) .chain(vec![MULTICALL3_ABI.to_owned().into()]) .collect(); @@ -175,16 +188,21 @@ where None } - async fn batch_call(&self, call: ContractCall) -> Result> { + async fn batch_call( + &self, + call: ContractCall, + ) -> Result> { let (tx, rx) = oneshot::channel(); - if let Err(e) = self.tx.send((call, tx)) { - panic!("multicall processor disconnected: {:?}", e); + if let Err(_) = self.tx.send((call, tx)).await { + return Err(MulticallMiddlewareError::ProcessorNotRunning); }; match rx.await { - Err(e) => panic!("multicall processor disconnected: {:?}", e), - Ok(response) => response.map(|token| encode(&[token]).into()), + Err(_) => Err(MulticallMiddlewareError::ProcessorNotRunning), + Ok(response) => response + .map(|token| encode(&[token]).into()) + .map_err(MulticallMiddlewareError::MulticallError), } } } @@ -209,13 +227,7 @@ where block: Option, ) -> Result { if let Some(call) = self.call_from_tx(tx, block) { - return self.batch_call(call).await.map_err(|e| { - if let Some(reason) = e.decode_revert::() { - MulticallMiddlewareError::RevertReason(reason) - } else { - MulticallMiddlewareError::MulticallError(e) - } - }); + return self.batch_call(call).await; } return self.inner.call(tx, block).await.map_err(MulticallMiddlewareError::from_err); @@ -251,3 +263,121 @@ where // TODO: implement more middleware functions? } + +#[cfg(test)] +mod tests { + use super::*; + use ethers_contract::abigen; + use ethers_providers::{MockProvider, Provider}; + + abigen!(Test, r#"[read(string) view returns (bytes4)]"#); + + #[tokio::test] + #[should_panic( + expected = "batches must be at least 2 calls to justify the overhead of multicall" + )] + async fn needs_min_batch() { + // will panic if batch size is less than 2 + let _ = MulticallMiddleware::new(Provider::new(MockProvider::new()), vec![], 1, None); + } + + #[tokio::test] + async fn needs_processor() { + let (provider, _) = + MulticallMiddleware::new(Provider::new(MockProvider::new()), vec![], 2, None); + let e = provider.get_block_number().await.unwrap_err(); + assert!(matches!(e, MulticallMiddlewareError::ProcessorNotRunning)); + } + + #[tokio::test] + async fn matches_multicall_signatures() { + let (provider1, mock1) = Provider::mocked(); + let (provider2, mock2) = Provider::mocked(); + let mock_multicall = Address::random(); + + let (provider, processor) = + MulticallMiddleware::new(provider1.clone(), vec![], 2, Some(mock_multicall)); + + let mut multicall = Multicall::new(provider2.clone(), Some(mock_multicall)).await.unwrap(); + + tokio::spawn(async move { + let _ = processor.run().await; + }); + + let address = Address::zero(); + + let _ = tokio::join!(provider.get_block_number(), provider.get_balance(address, None)); + + let _ = + multicall.add_get_block_number().add_get_eth_balance(address, false).call_raw().await; + + assert!(mock1.requests_match(&mock2)); + } + + #[tokio::test] + async fn uses_batch_size() { + let (provider1, mock1) = Provider::mocked(); + let (provider2, mock2) = Provider::mocked(); + let mock_multicall = Address::random(); + + let (provider, processor) = + MulticallMiddleware::new(provider1.clone(), vec![], 2, Some(mock_multicall)); + + let mut multicall = Multicall::new(provider2.clone(), Some(mock_multicall)).await.unwrap(); + + tokio::spawn(async move { + let _ = processor.run().await; + }); + + let _ = tokio::join!( + provider.get_block_number(), + provider.get_block_number(), + provider.get_block_number() + ); + + let _ = multicall.add_get_block_number().add_get_block_number().call_raw().await; + multicall.clear_calls(); + + let _ = multicall.add_get_block_number().call_raw().await; + + assert!(mock1.requests_match(&mock2)); + } + + #[tokio::test] + async fn matches_provided_signatures() { + let (provider1, mock1) = Provider::mocked(); + let (provider2, mock2) = Provider::mocked(); + let mock_multicall = Address::random(); + + let (provider, processor) = MulticallMiddleware::new( + provider1.clone(), + vec![TEST_ABI.clone()], + 2, + Some(mock_multicall), + ); + + let mut multicall = Multicall::new(provider2.clone(), Some(mock_multicall)).await.unwrap(); + + let mock_test = Address::random(); + let test1 = Test::new(mock_test, Arc::new(provider.clone())); + let test2 = Test::new(mock_test, Arc::new(provider2.clone())); + + tokio::spawn(async move { + let _ = processor.run().await; + }); + + let call1 = test1.read("call1".to_string()); + let call2 = test1.read("call2".to_string()); + + let _ = tokio::join!(call1.call(), call2.call()); + + let _ = + multicall + .add_call(test2.read("call1".to_string()), true) + .add_call(test2.read("call2".to_string()), true) + .call_raw() + .await; + + assert!(mock1.requests_match(&mock2)); + } +} diff --git a/ethers-middleware/tests/it/multicall.rs b/ethers-middleware/tests/it/multicall.rs index 1281a4b1f..6a7d426da 100644 --- a/ethers-middleware/tests/it/multicall.rs +++ b/ethers-middleware/tests/it/multicall.rs @@ -68,8 +68,8 @@ async fn multicall() { assert_eq!(val, value); }, async { - let val = simple_reverting.get_value(true).call().await.unwrap_err().to_string(); - assert_eq!(val, "getValue revert"); + let e = simple_reverting.get_value(true).call().await.unwrap_err(); + assert!(e.to_string().contains("call reverted")); }, async { let bal = multicall_client.get_balance(DEPLOYER_ADDRESS, None).await.unwrap(); diff --git a/ethers-providers/src/rpc/transports/mock.rs b/ethers-providers/src/rpc/transports/mock.rs index c00d1455d..46ea09545 100644 --- a/ethers-providers/src/rpc/transports/mock.rs +++ b/ethers-providers/src/rpc/transports/mock.rs @@ -5,14 +5,14 @@ use serde_json::Value; use std::{ borrow::Borrow, collections::VecDeque, - sync::{Arc, Mutex}, + sync::{Arc, Mutex}, ops::Deref, }; use thiserror::Error; /// Helper type that can be used to pass through the `params` value. /// This is necessary because the wrapper provider is supposed to skip the `params` if it's of /// size 0, see `crate::transports::common::Request` -#[derive(Debug)] +#[derive(Debug, PartialEq)] enum MockParams { Value(Value), Zst, @@ -73,6 +73,11 @@ impl JsonRpcClient for MockProvider { } impl MockProvider { + /// Checks that the provided requests match + pub fn requests_match(&self, other: &Self) -> bool { + self.requests.lock().unwrap().deref() == other.requests.lock().unwrap().deref() + } + /// Checks that the provided request was submitted by the client pub fn assert_request( &self, From 52390ae8e0c24face284a73cf8c6e002a6cbf1ad Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Sun, 26 Nov 2023 16:34:59 -0500 Subject: [PATCH 19/30] Fix compiler warnings --- ethers-contract/src/multicall/constants.rs | 2 -- ethers-contract/src/multicall/mod.rs | 4 ++++ ethers-middleware/src/multicall.rs | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ethers-contract/src/multicall/constants.rs b/ethers-contract/src/multicall/constants.rs index 4309fd8cd..dcff4f302 100644 --- a/ethers-contract/src/multicall/constants.rs +++ b/ethers-contract/src/multicall/constants.rs @@ -1,7 +1,5 @@ use ethers_core::types::{Chain, H160}; -// from https://github.com/mds1/multicall#new-deployments - /// presigned tx that will deploy multicall3 pub const SIGNED_DEPLOY_MULTICALL_TX: &'static str = "0xf90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086"; diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs index 681890073..e4388e46c 100644 --- a/ethers-contract/src/multicall/mod.rs +++ b/ethers-contract/src/multicall/mod.rs @@ -1,14 +1,18 @@ +//! Contains the `Multicall` contract which enables batching `eth_call` RPC requests + use std::result::Result as StdResult; /// The Multicall contract bindings. Auto-generated with `abigen`. pub mod contract; +/// vendored constants from Multicall https://github.com/mds1/multicall#new-deployments pub mod constants; if_providers! { mod middleware; pub use middleware::{Call, Multicall, MulticallContract}; + /// The Multicall error types. pub mod error; } diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 922f559a2..3cc807fed 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,4 +1,4 @@ -use std::{iter::repeat, ops::Deref, rc::Rc, sync::Arc}; +use std::{ops::Deref, sync::Arc}; use async_trait::async_trait; use ethers_contract::{ @@ -9,7 +9,7 @@ use ethers_contract::{ }, Multicall, }, - BaseContract, ContractCall, ContractError, EthCall, EthError, MulticallError, + BaseContract, ContractCall, ContractError, MulticallError, }; use ethers_core::{ abi::{encode, Abi, AbiDecode, AbiEncode, Token, Tokenizable}, From 23e2f53fdb77aeafb78229c008300268b40e1387 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 27 Nov 2023 11:23:59 -0500 Subject: [PATCH 20/30] Fix clippy --- ethers-contract/src/multicall/constants.rs | 2 +- ethers-middleware/src/multicall.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ethers-contract/src/multicall/constants.rs b/ethers-contract/src/multicall/constants.rs index dcff4f302..9c175704c 100644 --- a/ethers-contract/src/multicall/constants.rs +++ b/ethers-contract/src/multicall/constants.rs @@ -1,7 +1,7 @@ use ethers_core::types::{Chain, H160}; /// presigned tx that will deploy multicall3 -pub const SIGNED_DEPLOY_MULTICALL_TX: &'static str = "0xf90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086"; +pub const SIGNED_DEPLOY_MULTICALL_TX: &str = "0xf90f538085174876e800830f42408080b90f00608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c00331ca0edce47092c0f398cebf3ffc267f05c8e7076e3b89445e0fe50f6332273d4569ba01b0b9d000e19b24c5869b0fc3b22b0d6fa47cd63316875cbbd577d76e6fde086"; /// address to be funded for `SIGNED_DEPLOY_MULTICALL_TX` to succeed pub const DEPLOYER_ADDRESS: H160 = H160([ diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 3cc807fed..98460d31c 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -76,7 +76,7 @@ impl MulticallProcessor where M: Middleware, { - pub async fn run(mut self) -> () { + pub async fn run(mut self) { let mut multicall: Multicall = Multicall::new(self.inner, self.multicall_address).await.unwrap(); @@ -194,7 +194,7 @@ where ) -> Result> { let (tx, rx) = oneshot::channel(); - if let Err(_) = self.tx.send((call, tx)).await { + if self.tx.send((call, tx)).await.is_err() { return Err(MulticallMiddlewareError::ProcessorNotRunning); }; From e57fe64337ff353de63e3c04aae4a33c0bbc158d Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 27 Nov 2023 11:37:07 -0500 Subject: [PATCH 21/30] Fix doc --- ethers-contract/src/multicall/mod.rs | 2 +- ethers-middleware/README.md | 2 +- ethers-middleware/src/lib.rs | 2 +- ethers-middleware/src/multicall.rs | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs index e4388e46c..69b5e9cba 100644 --- a/ethers-contract/src/multicall/mod.rs +++ b/ethers-contract/src/multicall/mod.rs @@ -5,7 +5,7 @@ use std::result::Result as StdResult; /// The Multicall contract bindings. Auto-generated with `abigen`. pub mod contract; -/// vendored constants from Multicall https://github.com/mds1/multicall#new-deployments +/// vendored constants from [Multicall repo](https://github.com/mds1/multicall#new-deployments) pub mod constants; if_providers! { diff --git a/ethers-middleware/README.md b/ethers-middleware/README.md index 4105d386a..ddd3e0787 100644 --- a/ethers-middleware/README.md +++ b/ethers-middleware/README.md @@ -18,7 +18,7 @@ For more information, please refer to the [book](https://gakonst.com/ethers-rs). - [`Transformer`](./transformer/trait.Transformer.html): Allows intercepting and transforming a transaction to be broadcasted via a proxy wallet, e.g. [`DSProxy`](./transformer/struct.DsProxy.html). -- `MultiCall`: transparently batches multiple `eth_call` requests into a single +- [`MultiCall`](./multicall/struct.MulticallMiddleware.html): transparently batches multiple `eth_call` requests into a single `eth_call` request, reducing the number of round trips to the RPC and saving API CU usage. ## Examples diff --git a/ethers-middleware/src/lib.rs b/ethers-middleware/src/lib.rs index dc140e63b..23093f755 100644 --- a/ethers-middleware/src/lib.rs +++ b/ethers-middleware/src/lib.rs @@ -41,7 +41,7 @@ pub use policy::{ pub mod timelag; pub use timelag::TimeLag; -/// The [MultiCall] middleware provides a way to batch multiple calls into a single call +/// The [MulticallMiddleware] provides a way to batch multiple calls into a single call pub mod multicall; pub use multicall::MulticallMiddleware; diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 98460d31c..fac1a57f0 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -27,7 +27,7 @@ type MulticallResult = Result>>; type MulticallRequest = (ContractCall, oneshot::Sender>); #[derive(Debug)] -/// Middleware used for transparently leveraging multicall functionality +/// Processor for multicall middleware requests pub struct MulticallProcessor { inner: Arc, multicall_address: Option
, @@ -36,6 +36,7 @@ pub struct MulticallProcessor { } #[derive(Debug, Clone)] +/// Middleware used for transparently leveraging multicall functionality pub struct MulticallMiddleware { inner: Arc, contracts: Vec, @@ -76,6 +77,7 @@ impl MulticallProcessor where M: Middleware, { + /// Should be run in a separate task to process requests pub async fn run(mut self) { let mut multicall: Multicall = Multicall::new(self.inner, self.multicall_address).await.unwrap(); From 21cd2cb3497076c2bcf3f598fb902537465ddac7 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 27 Nov 2023 11:38:56 -0500 Subject: [PATCH 22/30] Fix cargo fmt --- ethers-middleware/src/multicall.rs | 7 +++--- ethers-middleware/tests/it/multicall.rs | 33 +++++++++++++++---------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index fac1a57f0..1f29e1497 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -373,12 +373,11 @@ mod tests { let _ = tokio::join!(call1.call(), call2.call()); - let _ = - multicall + let _ = multicall .add_call(test2.read("call1".to_string()), true) .add_call(test2.read("call2".to_string()), true) - .call_raw() - .await; + .call_raw() + .await; assert!(mock1.requests_match(&mock2)); } diff --git a/ethers-middleware/tests/it/multicall.rs b/ethers-middleware/tests/it/multicall.rs index 6a7d426da..6e4d86cfa 100644 --- a/ethers-middleware/tests/it/multicall.rs +++ b/ethers-middleware/tests/it/multicall.rs @@ -15,16 +15,14 @@ abigen!( SimpleRevertingStorage, "../ethers-contract/tests/solidity-contracts/SimpleRevertingStorage.json" ); -abigen!( - SimpleStorage, - "../ethers-contract/tests/solidity-contracts/SimpleStorage.json" -); +abigen!(SimpleStorage, "../ethers-contract/tests/solidity-contracts/SimpleStorage.json"); #[tokio::test] async fn multicall() { let (provider, anvil) = spawn_anvil(); let wallet: LocalWallet = anvil.keys()[0].clone().into(); - let client = Arc::new(SignerMiddleware::new(provider.clone(), wallet.with_chain_id(anvil.chain_id()))); + let client = + Arc::new(SignerMiddleware::new(provider.clone(), wallet.with_chain_id(anvil.chain_id()))); // 1. deploy multicall contract (if not already) provider @@ -43,18 +41,27 @@ async fn multicall() { .unwrap(); // 2. instantiate the multicall middleware - let (multicall_provider, multicall_processor) = MulticallMiddleware::new( - client, - vec![SIMPLEREVERTINGSTORAGE_ABI.to_owned(), SIMPLESTORAGE_ABI.to_owned()], - 10, - Some(MULTICALL_ADDRESS), - ); + let (multicall_provider, multicall_processor) = + MulticallMiddleware::new( + client, + vec![SIMPLEREVERTINGSTORAGE_ABI.to_owned(), SIMPLESTORAGE_ABI.to_owned()], + 10, + Some(MULTICALL_ADDRESS), + ); let multicall_client = Arc::new(multicall_provider); // 3. deploy a contract to interact with let value = "multicall!".to_string(); - let simple = SimpleStorage::deploy(multicall_client.clone(), value.clone()).unwrap().send().await.unwrap(); - let simple_reverting = SimpleRevertingStorage::deploy(multicall_client.clone(), value.clone()).unwrap().send().await.unwrap(); + let simple = SimpleStorage::deploy(multicall_client.clone(), value.clone()) + .unwrap() + .send() + .await + .unwrap(); + let simple_reverting = SimpleRevertingStorage::deploy(multicall_client.clone(), value.clone()) + .unwrap() + .send() + .await + .unwrap(); // 4. spawn the multicall processor tokio::spawn(async move { From 0e57f5e571beeffa7442e8a9ea65904c54d1c9de Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 27 Nov 2023 11:46:50 -0500 Subject: [PATCH 23/30] Fix wasm target compilation --- ethers-middleware/src/lib.rs | 1 + ethers-middleware/src/multicall.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ethers-middleware/src/lib.rs b/ethers-middleware/src/lib.rs index 23093f755..8490394b0 100644 --- a/ethers-middleware/src/lib.rs +++ b/ethers-middleware/src/lib.rs @@ -43,6 +43,7 @@ pub use timelag::TimeLag; /// The [MulticallMiddleware] provides a way to batch multiple calls into a single call pub mod multicall; +#[cfg(not(target_arch = "wasm32"))] pub use multicall::MulticallMiddleware; /// [MiddlewareBuilder] provides a way to compose many [`Middleware`]s in a concise way. diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 1f29e1497..1b984e78d 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_arch = "wasm32"))] + use std::{ops::Deref, sync::Arc}; use async_trait::async_trait; From 207682f3facf9a9a1ce04c232a23ea116177cc67 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 27 Nov 2023 12:24:09 -0500 Subject: [PATCH 24/30] Fix cargo fmt with nightly --- ethers-middleware/src/multicall.rs | 6 +++--- ethers-middleware/tests/it/multicall.rs | 13 ++++++------- ethers-providers/src/rpc/transports/mock.rs | 3 ++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 1b984e78d..7836741f2 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -126,9 +126,9 @@ where Ok(results) => results .into_iter() .map(|result| { - result.map_err( - |e| Arc::new(MulticallError::ContractError(ContractError::Revert(e))) - ) + result.map_err(|e| { + Arc::new(MulticallError::ContractError(ContractError::Revert(e))) + }) }) .collect(), Err(e) => vec![Err(Arc::new(e)); callbacks.len()], diff --git a/ethers-middleware/tests/it/multicall.rs b/ethers-middleware/tests/it/multicall.rs index 6e4d86cfa..e2824f8c5 100644 --- a/ethers-middleware/tests/it/multicall.rs +++ b/ethers-middleware/tests/it/multicall.rs @@ -41,13 +41,12 @@ async fn multicall() { .unwrap(); // 2. instantiate the multicall middleware - let (multicall_provider, multicall_processor) = - MulticallMiddleware::new( - client, - vec![SIMPLEREVERTINGSTORAGE_ABI.to_owned(), SIMPLESTORAGE_ABI.to_owned()], - 10, - Some(MULTICALL_ADDRESS), - ); + let (multicall_provider, multicall_processor) = MulticallMiddleware::new( + client, + vec![SIMPLEREVERTINGSTORAGE_ABI.to_owned(), SIMPLESTORAGE_ABI.to_owned()], + 10, + Some(MULTICALL_ADDRESS), + ); let multicall_client = Arc::new(multicall_provider); // 3. deploy a contract to interact with diff --git a/ethers-providers/src/rpc/transports/mock.rs b/ethers-providers/src/rpc/transports/mock.rs index 46ea09545..425b214b9 100644 --- a/ethers-providers/src/rpc/transports/mock.rs +++ b/ethers-providers/src/rpc/transports/mock.rs @@ -5,7 +5,8 @@ use serde_json::Value; use std::{ borrow::Borrow, collections::VecDeque, - sync::{Arc, Mutex}, ops::Deref, + ops::Deref, + sync::{Arc, Mutex}, }; use thiserror::Error; From 4c1e2a7cd60cf7f520f25357b30a4b2dbf8e1662 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 29 Nov 2023 15:27:11 -0500 Subject: [PATCH 25/30] Address pr comments --- ethers-middleware/src/multicall.rs | 62 ++++++++++++++++-------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 7836741f2..cad14c167 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -28,35 +28,21 @@ use tokio::sync::{mpsc, oneshot}; type MulticallResult = Result>>; type MulticallRequest = (ContractCall, oneshot::Sender>); -#[derive(Debug)] /// Processor for multicall middleware requests +#[derive(Debug)] pub struct MulticallProcessor { inner: Arc, multicall_address: Option
, max_batch_size: usize, - rx: mpsc::Receiver>, + rx: mpsc::UnboundedReceiver>, } -#[derive(Debug, Clone)] /// Middleware used for transparently leveraging multicall functionality +#[derive(Debug, Clone)] pub struct MulticallMiddleware { inner: Arc, - contracts: Vec, - tx: mpsc::Sender>, -} - -#[derive(Error, Debug)] -/// Thrown when an error happens at the Multicall middleware -pub enum MulticallMiddlewareError { - /// Thrown when the internal middleware errors - #[error("{0}")] - MiddlewareError(M::Error), - /// Thrown when the internal multicall errors - #[error(transparent)] - MulticallError(#[from] Arc>), - /// Thrown when the processor isn't running - #[error("Processor is not running")] - ProcessorNotRunning, + contracts: Arc>, + tx: mpsc::UnboundedSender>, } impl MiddlewareError for MulticallMiddlewareError { @@ -75,14 +61,28 @@ impl MiddlewareError for MulticallMiddlewareError { } } +/// Thrown when an error happens at the Multicall middleware +#[derive(Error, Debug)] +pub enum MulticallMiddlewareError { + /// Thrown when the internal middleware errors + #[error("{0}")] + MiddlewareError(M::Error), + /// Thrown when the internal multicall errors + #[error(transparent)] + MulticallError(#[from] Arc>), + /// Thrown when the processor isn't running + #[error("Processor is not running")] + ProcessorNotRunning, +} + impl MulticallProcessor where M: Middleware, { /// Should be run in a separate task to process requests - pub async fn run(mut self) { + pub async fn run(mut self) -> Result<(), MulticallError> { let mut multicall: Multicall = - Multicall::new(self.inner, self.multicall_address).await.unwrap(); + Multicall::new(self.inner.clone(), self.multicall_address).await?; loop { let mut requests = Vec::new(); @@ -139,6 +139,8 @@ where let _ = callback.send(response); } } + + Ok(()) } } @@ -147,6 +149,8 @@ where M: Middleware, { /// Instantiates the multicall middleware to recognize the given `match_abis` + /// # Panics + /// Panics if `max_batch_size` is less than 2 pub fn new( inner: M, match_abis: Vec, @@ -157,14 +161,16 @@ where panic!("batches must be at least 2 calls to justify the overhead of multicall"); } - let (tx, rx) = mpsc::channel(max_batch_size); + let (tx, rx) = mpsc::unbounded_channel(); let client = Arc::new(inner); - let contracts = match_abis - .iter() - .map(|abi| abi.to_owned().into()) - .chain(vec![MULTICALL3_ABI.to_owned().into()]) - .collect(); + let contracts = Arc::new( + match_abis + .iter() + .map(|abi| abi.to_owned().into()) + .chain(vec![MULTICALL3_ABI.to_owned().into()]) + .collect(), + ); ( Self { inner: client.clone(), tx, contracts }, @@ -198,7 +204,7 @@ where ) -> Result> { let (tx, rx) = oneshot::channel(); - if self.tx.send((call, tx)).await.is_err() { + if self.tx.send((call, tx)).is_err() { return Err(MulticallMiddlewareError::ProcessorNotRunning); }; From 58b6e256bf19b34b8a16a2c71dd76cd948811aa1 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 30 Nov 2023 10:49:13 -0500 Subject: [PATCH 26/30] Disable celo feature for multicall it tests --- ethers-middleware/tests/it/multicall.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethers-middleware/tests/it/multicall.rs b/ethers-middleware/tests/it/multicall.rs index e2824f8c5..862e7ee23 100644 --- a/ethers-middleware/tests/it/multicall.rs +++ b/ethers-middleware/tests/it/multicall.rs @@ -17,6 +17,7 @@ abigen!( ); abigen!(SimpleStorage, "../ethers-contract/tests/solidity-contracts/SimpleStorage.json"); +#[cfg(not(feature = "celo"))] #[tokio::test] async fn multicall() { let (provider, anvil) = spawn_anvil(); From 9bffd32c9e696865f36dc3531f0b28ee064d2cc1 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 30 Nov 2023 10:51:04 -0500 Subject: [PATCH 27/30] Derive Eq for MockParams --- ethers-providers/src/rpc/transports/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-providers/src/rpc/transports/mock.rs b/ethers-providers/src/rpc/transports/mock.rs index 425b214b9..8637dc88e 100644 --- a/ethers-providers/src/rpc/transports/mock.rs +++ b/ethers-providers/src/rpc/transports/mock.rs @@ -13,7 +13,7 @@ use thiserror::Error; /// Helper type that can be used to pass through the `params` value. /// This is necessary because the wrapper provider is supposed to skip the `params` if it's of /// size 0, see `crate::transports::common::Request` -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] enum MockParams { Value(Value), Zst, From 8196ce27a115da28f03e7a6a7401ce117be9ecfd Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 4 Dec 2023 11:56:35 -0500 Subject: [PATCH 28/30] Update multicall.rs Co-authored-by: Matthias Seitz --- ethers-middleware/src/multicall.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index cad14c167..5affb3022 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -22,7 +22,6 @@ use ethers_core::{ }; use ethers_providers::{Middleware, MiddlewareError}; use thiserror::Error; - use tokio::sync::{mpsc, oneshot}; type MulticallResult = Result>>; From 59270a65a759425e61824209533a56dba93e748a Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 4 Dec 2023 11:56:39 -0500 Subject: [PATCH 29/30] Update multicall.rs Co-authored-by: Matthias Seitz --- ethers-middleware/src/multicall.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index 5affb3022..d430435ef 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,7 +1,6 @@ #![cfg(not(target_arch = "wasm32"))] use std::{ops::Deref, sync::Arc}; - use async_trait::async_trait; use ethers_contract::{ multicall::{ From e461b53a69b340e0c92d5c49b84a050e78be5f88 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 5 Dec 2023 21:48:54 +0100 Subject: [PATCH 30/30] rustfmt --- ethers-middleware/src/multicall.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethers-middleware/src/multicall.rs b/ethers-middleware/src/multicall.rs index d430435ef..c480683fb 100644 --- a/ethers-middleware/src/multicall.rs +++ b/ethers-middleware/src/multicall.rs @@ -1,6 +1,5 @@ #![cfg(not(target_arch = "wasm32"))] -use std::{ops::Deref, sync::Arc}; use async_trait::async_trait; use ethers_contract::{ multicall::{ @@ -20,6 +19,7 @@ use ethers_core::{ }, }; use ethers_providers::{Middleware, MiddlewareError}; +use std::{ops::Deref, sync::Arc}; use thiserror::Error; use tokio::sync::{mpsc, oneshot};