alloy_provider/layers/
batch.rs

1use crate::{
2    bindings::IMulticall3, Caller, Provider, ProviderCall, ProviderLayer, RootProvider,
3    MULTICALL3_ADDRESS,
4};
5use alloy_eips::BlockId;
6use alloy_network::{Ethereum, Network, TransactionBuilder};
7use alloy_primitives::{Address, Bytes, U256};
8use alloy_rpc_client::WeakClient;
9use alloy_sol_types::{SolCall, SolType, SolValue};
10use alloy_transport::{utils::Spawnable, TransportErrorKind, TransportResult};
11use std::{fmt, future::IntoFuture, marker::PhantomData, sync::Arc, time::Duration};
12use tokio::sync::{mpsc, oneshot};
13
14#[cfg(target_family = "wasm")]
15use wasmtimer::tokio::sleep;
16
17#[cfg(not(target_family = "wasm"))]
18use tokio::time::sleep;
19
20/// This is chosen somewhat arbitrarily. It should be short enough to not cause a noticeable
21/// delay on individual requests, but long enough to allow for batching requests issued together in
22/// a short period of time, such as when using `join!` macro or similar future combinators.
23const DEFAULT_WAIT: Duration = Duration::from_millis(1);
24
25/// Provider layer that aggregates contract calls (`eth_call`) over a time period into a single
26/// [Multicall3] contract call.
27///
28/// Some methods, such as `eth_getBlockNumber`, are first converted into contract calls to the
29/// [Multicall3] contract itself and then aggregated with other `eth_call`s.
30///
31/// Only calls that:
32/// - target the latest block ID,
33/// - have no state overrides,
34/// - have a target address and calldata,
35/// - have no other properties (nonce, gas, etc.)
36///
37/// can be sent with a multicall. This of course requires that the [Multicall3] contract is
38/// deployed on the network, by default at [`MULTICALL3_ADDRESS`].
39///
40/// This layer is useful for reducing the number of network requests made.
41/// However, this only works when requests are made in parallel, for example when using the
42/// [`tokio::join!`] macro or in multiple threads/tasks, as otherwise the requests will be sent one
43/// by one as normal, but with an added delay.
44///
45/// # Examples
46///
47/// ```no_run
48/// use alloy_provider::{layers::CallBatchLayer, Provider, ProviderBuilder};
49/// use std::time::Duration;
50///
51/// # async fn f(url: &str) -> Result<(), Box<dyn std::error::Error>> {
52/// // Build a provider with the default call batching configuration.
53/// let provider = ProviderBuilder::new().with_call_batching().connect(url).await?;
54///
55/// // Build a provider with a custom call batching configuration.
56/// let provider = ProviderBuilder::new()
57///     .layer(CallBatchLayer::new().wait(Duration::from_millis(10)))
58///     .connect(url)
59///     .await?;
60///
61/// // Both of these requests will be batched together and only 1 network request will be made.
62/// let (block_number_result, chain_id_result) =
63///     tokio::join!(provider.get_block_number(), provider.get_chain_id());
64/// let block_number = block_number_result?;
65/// let chain_id = chain_id_result?;
66/// println!("block number: {block_number}, chain id: {chain_id}");
67/// # Ok(())
68/// # }
69/// ```
70///
71/// [Multicall3]: https://github.com/mds1/multicall3
72#[derive(Debug)]
73pub struct CallBatchLayer {
74    m3a: Address,
75    wait: Duration,
76}
77
78impl Default for CallBatchLayer {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl CallBatchLayer {
85    /// Create a new `CallBatchLayer` with a default wait of 1ms.
86    pub const fn new() -> Self {
87        Self { m3a: MULTICALL3_ADDRESS, wait: DEFAULT_WAIT }
88    }
89
90    /// Set the amount of time to wait before sending the batch.
91    ///
92    /// This is the amount of time to wait after the first request is received before sending all
93    /// the requests received in that time period.
94    ///
95    /// This means that every request has a maximum delay of `wait` before being sent.
96    ///
97    /// The default is 1ms.
98    pub const fn wait(mut self, wait: Duration) -> Self {
99        self.wait = wait;
100        self
101    }
102
103    /// Set the multicall3 address.
104    ///
105    /// The default is [`MULTICALL3_ADDRESS`].
106    pub const fn multicall3_address(mut self, m3a: Address) -> Self {
107        self.m3a = m3a;
108        self
109    }
110}
111
112impl<P, N> ProviderLayer<P, N> for CallBatchLayer
113where
114    P: Provider<N> + 'static,
115    N: Network,
116{
117    type Provider = CallBatchProvider<P, N>;
118
119    fn layer(&self, inner: P) -> Self::Provider {
120        CallBatchProvider::new(inner, self)
121    }
122}
123
124type CallBatchMsgTx = TransportResult<IMulticall3::Result>;
125
126struct CallBatchMsg {
127    call: IMulticall3::Call3,
128    tx: oneshot::Sender<CallBatchMsgTx>,
129}
130
131impl fmt::Debug for CallBatchMsg {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.write_str("BatchProviderMessage(")?;
134        self.call.fmt(f)?;
135        f.write_str(")")
136    }
137}
138
139#[derive(Debug)]
140enum CallBatchMsgKind<N: Network = Ethereum> {
141    Call(N::TransactionRequest),
142    BlockNumber,
143    ChainId,
144    Balance(Address),
145}
146
147impl CallBatchMsg {
148    fn new<N: Network>(
149        kind: CallBatchMsgKind<N>,
150        m3a: Address,
151    ) -> (Self, oneshot::Receiver<CallBatchMsgTx>) {
152        let (tx, rx) = oneshot::channel();
153        (Self { call: kind.into_call3(m3a), tx }, rx)
154    }
155}
156
157impl<N: Network> CallBatchMsgKind<N> {
158    fn into_call3(self, m3a: Address) -> IMulticall3::Call3 {
159        let m3a_call = |data: Vec<u8>| IMulticall3::Call3 {
160            target: m3a,
161            allowFailure: true,
162            callData: data.into(),
163        };
164        match self {
165            Self::Call(tx) => IMulticall3::Call3 {
166                target: tx.to().unwrap_or_default(),
167                allowFailure: true,
168                callData: tx.input().cloned().unwrap_or_default(),
169            },
170            Self::BlockNumber => m3a_call(IMulticall3::getBlockNumberCall {}.abi_encode()),
171            Self::ChainId => m3a_call(IMulticall3::getChainIdCall {}.abi_encode()),
172            Self::Balance(addr) => m3a_call(IMulticall3::getEthBalanceCall { addr }.abi_encode()),
173        }
174    }
175}
176
177/// A provider that batches multiple requests into a single request.
178///
179/// See [`CallBatchLayer`] for more information.
180pub struct CallBatchProvider<P, N: Network = Ethereum> {
181    provider: Arc<P>,
182    inner: CallBatchProviderInner,
183    _pd: PhantomData<N>,
184}
185
186impl<P, N: Network> Clone for CallBatchProvider<P, N> {
187    fn clone(&self) -> Self {
188        Self { provider: self.provider.clone(), inner: self.inner.clone(), _pd: PhantomData }
189    }
190}
191
192impl<P: fmt::Debug, N: Network> fmt::Debug for CallBatchProvider<P, N> {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        f.write_str("BatchProvider(")?;
195        self.provider.fmt(f)?;
196        f.write_str(")")
197    }
198}
199
200impl<P: Provider<N> + 'static, N: Network> CallBatchProvider<P, N> {
201    fn new(inner: P, layer: &CallBatchLayer) -> Self {
202        let inner = Arc::new(inner);
203        let tx = CallBatchBackend::spawn(inner.clone(), layer);
204        Self {
205            provider: inner,
206            inner: CallBatchProviderInner { tx, m3a: layer.m3a },
207            _pd: PhantomData,
208        }
209    }
210}
211
212#[derive(Clone)]
213struct CallBatchProviderInner {
214    tx: mpsc::UnboundedSender<CallBatchMsg>,
215    m3a: Address,
216}
217
218impl CallBatchProviderInner {
219    /// We only want to perform a scheduled multicall if:
220    /// - The request has no block ID or state overrides,
221    /// - The request has a target address,
222    /// - The request has no other properties (`nonce`, `gas`, etc cannot be sent with a multicall).
223    ///
224    /// Ref: <https://github.com/wevm/viem/blob/ba8319f71503af8033fd3c77cfb64c7eb235c6a9/src/actions/public/call.ts#L295>
225    fn should_batch_call<N: Network>(&self, params: &crate::EthCallParams<N>) -> bool {
226        // TODO: block ID is not yet implemented
227        if params.block().is_some_and(|block| block != BlockId::latest()) {
228            return false;
229        }
230        if params.overrides.as_ref().is_some_and(|overrides| !overrides.is_empty()) {
231            return false;
232        }
233        let tx = params.data();
234        if tx.to().is_none() {
235            return false;
236        }
237        if let Ok(serde_json::Value::Object(obj)) = serde_json::to_value(tx) {
238            if obj.keys().any(|k| !matches!(k.as_str(), "to" | "data" | "input")) {
239                return false;
240            }
241        }
242        true
243    }
244
245    async fn schedule<N: Network>(self, msg: CallBatchMsgKind<N>) -> TransportResult<Bytes> {
246        let (msg, rx) = CallBatchMsg::new(msg, self.m3a);
247        self.tx.send(msg).map_err(|_| TransportErrorKind::backend_gone())?;
248
249        let IMulticall3::Result { success, returnData: data } =
250            rx.await.map_err(|_| TransportErrorKind::backend_gone())??;
251        if !success {
252            let revert_data = if data.is_empty() { "" } else { &format!(" with data: {data}") };
253            return Err(TransportErrorKind::custom_str(&format!(
254                "multicall batched call reverted{revert_data}"
255            )));
256        }
257        Ok(data)
258    }
259
260    async fn schedule_and_decode<N: Network, T>(
261        self,
262        msg: CallBatchMsgKind<N>,
263    ) -> TransportResult<T>
264    where
265        T: SolValue + From<<T::SolType as SolType>::RustType>,
266    {
267        let data = self.schedule(msg).await?;
268        T::abi_decode(&data).map_err(TransportErrorKind::custom)
269    }
270}
271
272struct CallBatchBackend<P, N: Network = Ethereum> {
273    inner: Arc<P>,
274    m3a: Address,
275    wait: Duration,
276    rx: mpsc::UnboundedReceiver<CallBatchMsg>,
277    pending: Vec<CallBatchMsg>,
278    _pd: PhantomData<N>,
279}
280
281impl<P: Provider<N> + 'static, N: Network> CallBatchBackend<P, N> {
282    fn spawn(inner: Arc<P>, layer: &CallBatchLayer) -> mpsc::UnboundedSender<CallBatchMsg> {
283        let CallBatchLayer { m3a, wait } = *layer;
284        let (tx, rx) = mpsc::unbounded_channel();
285        let this = Self { inner, m3a, wait, rx, pending: Vec::new(), _pd: PhantomData };
286        this.run().spawn_task();
287        tx
288    }
289
290    async fn run(mut self) {
291        'outer: loop {
292            // Wait for the first message.
293            debug_assert!(self.pending.is_empty());
294            match self.rx.recv().await {
295                Some(msg) => self.process_msg(msg),
296                None => break,
297            }
298
299            // Handle all remaining messages after waiting the duration.
300            debug_assert!(!self.pending.is_empty());
301            sleep(self.wait).await;
302            'inner: loop {
303                match self.rx.try_recv() {
304                    Ok(msg) => self.process_msg(msg),
305                    Err(mpsc::error::TryRecvError::Empty) => break 'inner,
306                    Err(mpsc::error::TryRecvError::Disconnected) => break 'outer,
307                }
308            }
309            // No more messages, send the batch.
310            self.send_batch().await;
311        }
312    }
313
314    fn process_msg(&mut self, msg: CallBatchMsg) {
315        self.pending.push(msg);
316    }
317
318    async fn send_batch(&mut self) {
319        let result = self.send_batch_inner().await;
320        let pending = std::mem::take(&mut self.pending);
321        match result {
322            Ok(results) => {
323                debug_assert_eq!(results.len(), pending.len());
324                for (result, msg) in results.into_iter().zip(pending) {
325                    let _ = msg.tx.send(Ok(result));
326                }
327            }
328            Err(e) => {
329                for msg in pending {
330                    let _ = msg.tx.send(Err(TransportErrorKind::custom_str(&e.to_string())));
331                }
332            }
333        }
334    }
335
336    async fn send_batch_inner(&mut self) -> TransportResult<Vec<IMulticall3::Result>> {
337        debug_assert!(!self.pending.is_empty());
338        debug!(len = self.pending.len(), "sending multicall");
339        let tx = N::TransactionRequest::default().with_to(self.m3a).with_input(self.make_payload());
340        let bytes = self.inner.call(tx).await?;
341        if bytes.is_empty() {
342            return Err(TransportErrorKind::custom_str(&format!(
343                "Multicall3 not deployed at {}",
344                self.m3a
345            )));
346        }
347        let ret = IMulticall3::aggregate3Call::abi_decode_returns(&bytes)
348            .map_err(TransportErrorKind::custom)?;
349        Ok(ret)
350    }
351
352    fn make_payload(&self) -> Vec<u8> {
353        IMulticall3::aggregate3Call {
354            calls: self.pending.iter().map(|msg| msg.call.clone()).collect(),
355        }
356        .abi_encode()
357    }
358}
359
360impl<P: Provider<N> + 'static, N: Network> Provider<N> for CallBatchProvider<P, N> {
361    fn root(&self) -> &RootProvider<N> {
362        self.provider.root()
363    }
364
365    fn call(&self, tx: <N as Network>::TransactionRequest) -> crate::EthCall<N, Bytes> {
366        crate::EthCall::call(CallBatchCaller::new(self), tx)
367    }
368
369    fn get_block_number(
370        &self,
371    ) -> crate::ProviderCall<
372        alloy_rpc_client::NoParams,
373        alloy_primitives::U64,
374        alloy_primitives::BlockNumber,
375    > {
376        crate::ProviderCall::BoxedFuture(Box::pin(
377            self.inner.clone().schedule_and_decode::<N, u64>(CallBatchMsgKind::BlockNumber),
378        ))
379    }
380
381    fn get_chain_id(
382        &self,
383    ) -> crate::ProviderCall<
384        alloy_rpc_client::NoParams,
385        alloy_primitives::U64,
386        alloy_primitives::ChainId,
387    > {
388        crate::ProviderCall::BoxedFuture(Box::pin(
389            self.inner.clone().schedule_and_decode::<N, u64>(CallBatchMsgKind::ChainId),
390        ))
391    }
392
393    fn get_balance(&self, address: Address) -> crate::RpcWithBlock<Address, U256, U256> {
394        let this = self.clone();
395        crate::RpcWithBlock::new_provider(move |block| {
396            if block != BlockId::latest() {
397                this.provider.get_balance(address).block_id(block).into_future()
398            } else {
399                ProviderCall::BoxedFuture(Box::pin(
400                    this.inner
401                        .clone()
402                        .schedule_and_decode::<N, U256>(CallBatchMsgKind::Balance(address)),
403                ))
404            }
405        })
406    }
407}
408
409struct CallBatchCaller {
410    inner: CallBatchProviderInner,
411    weak: WeakClient,
412}
413
414impl CallBatchCaller {
415    fn new<P: Provider<N>, N: Network>(provider: &CallBatchProvider<P, N>) -> Self {
416        Self { inner: provider.inner.clone(), weak: provider.provider.weak_client() }
417    }
418}
419
420impl<N: Network> Caller<N, Bytes> for CallBatchCaller {
421    fn call(
422        &self,
423        params: crate::EthCallParams<N>,
424    ) -> TransportResult<crate::ProviderCall<crate::EthCallParams<N>, Bytes>> {
425        if !self.inner.should_batch_call(&params) {
426            return Caller::<N, Bytes>::call(&self.weak, params);
427        }
428
429        Ok(crate::ProviderCall::BoxedFuture(Box::pin(
430            self.inner.clone().schedule::<N>(CallBatchMsgKind::Call(params.into_data())),
431        )))
432    }
433
434    fn estimate_gas(
435        &self,
436        params: crate::EthCallParams<N>,
437    ) -> TransportResult<crate::ProviderCall<crate::EthCallParams<N>, Bytes>> {
438        Caller::<N, Bytes>::estimate_gas(&self.weak, params)
439    }
440
441    fn call_many(
442        &self,
443        params: crate::EthCallManyParams<'_>,
444    ) -> TransportResult<crate::ProviderCall<crate::EthCallManyParams<'static>, Bytes>> {
445        Caller::<N, Bytes>::call_many(&self.weak, params)
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use crate::ProviderBuilder;
453    use alloy_primitives::{address, hex};
454    use alloy_rpc_types_eth::TransactionRequest;
455    use alloy_transport::mock::Asserter;
456
457    // https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11#code
458    const MULTICALL3_DEPLOYED_CODE: &[u8] = &hex!("0x6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033");
459    const COUNTER_ADDRESS: Address = address!("0x1234123412341234123412341234123412341234");
460    const COUNTER_DEPLOYED_CODE: &[u8] = &hex!("0x6080604052348015600e575f5ffd5b5060043610603a575f3560e01c80633fb5c1cb14603e5780638381f58a14604f578063d09de08a146068575b5f5ffd5b604d6049366004607d565b5f55565b005b60565f5481565b60405190815260200160405180910390f35b604d5f805490806076836093565b9190505550565b5f60208284031215608c575f5ffd5b5035919050565b5f6001820160af57634e487b7160e01b5f52601160045260245ffd5b506001019056fea2646970667358221220f423ff7a9a85bf49c3769164d3bd24403940510478df27a6b1deac980db69e5664736f6c634300081b0033");
461
462    fn push_m3_success(asserter: &Asserter, returns: &[(bool, Vec<u8>)]) {
463        asserter.push_success(
464            &returns
465                .iter()
466                .map(|&(success, ref data)| IMulticall3::Result {
467                    success,
468                    returnData: Bytes::copy_from_slice(data),
469                })
470                .collect::<Vec<_>>()
471                .abi_encode(),
472        )
473    }
474
475    #[tokio::test]
476    async fn basic_mocked() {
477        let asserter = Asserter::new();
478        let provider =
479            ProviderBuilder::new().with_call_batching().connect_mocked_client(asserter.clone());
480        push_m3_success(
481            &asserter,
482            &[
483                (true, 1.abi_encode()),  // IMulticall3::getBlockNumberCall
484                (true, 2.abi_encode()),  // IMulticall3::getChainIdCall
485                (false, 3.abi_encode()), // IMulticall3::getBlockNumberCall
486                (false, 4.abi_encode()), // IMulticall3::getChainIdCall
487            ],
488        );
489        let (block_number_ok, chain_id_ok, block_number_err, chain_id_err) = tokio::join!(
490            provider.get_block_number(),
491            provider.get_chain_id(),
492            provider.get_block_number(),
493            provider.get_chain_id(),
494        );
495        assert_eq!(block_number_ok.unwrap(), 1);
496        assert_eq!(chain_id_ok.unwrap(), 2);
497        assert!(block_number_err.unwrap_err().to_string().contains("reverted"));
498        assert!(chain_id_err.unwrap_err().to_string().contains("reverted"));
499        assert!(asserter.read_q().is_empty(), "only 1 request should've been made");
500    }
501
502    #[tokio::test]
503    #[cfg(feature = "anvil-api")]
504    async fn basic() {
505        use crate::ext::AnvilApi;
506        let provider = ProviderBuilder::new().with_call_batching().connect_anvil();
507        provider.anvil_set_code(COUNTER_ADDRESS, COUNTER_DEPLOYED_CODE.into()).await.unwrap();
508        provider.anvil_set_balance(COUNTER_ADDRESS, U256::from(123)).await.unwrap();
509
510        let do_calls = || async {
511            tokio::join!(
512                provider.call(
513                    TransactionRequest::default()
514                        .with_to(COUNTER_ADDRESS)
515                        .with_input(hex!("0x8381f58a")) // number()
516                ),
517                provider.call(
518                    TransactionRequest::default()
519                        .with_to(MULTICALL3_ADDRESS)
520                        .with_input(IMulticall3::getBlockNumberCall {}.abi_encode())
521                ),
522                provider.get_block_number(),
523                provider.get_chain_id(),
524                provider.get_balance(COUNTER_ADDRESS),
525            )
526        };
527
528        // Multicall3 has not yet been deployed.
529        let (a, b, c, d, e) = do_calls().await;
530        assert!(a.unwrap_err().to_string().contains("Multicall3 not deployed"));
531        assert!(b.unwrap_err().to_string().contains("Multicall3 not deployed"));
532        assert!(c.unwrap_err().to_string().contains("Multicall3 not deployed"));
533        assert!(d.unwrap_err().to_string().contains("Multicall3 not deployed"));
534        assert!(e.unwrap_err().to_string().contains("Multicall3 not deployed"));
535
536        provider.anvil_set_code(MULTICALL3_ADDRESS, MULTICALL3_DEPLOYED_CODE.into()).await.unwrap();
537
538        let (counter, block_number_raw, block_number, chain_id, balance) = do_calls().await;
539        assert_eq!(counter.unwrap(), 0u64.abi_encode());
540        assert_eq!(block_number_raw.unwrap(), 1u64.abi_encode());
541        assert_eq!(block_number.unwrap(), 1);
542        assert_eq!(chain_id.unwrap(), alloy_chains::NamedChain::AnvilHardhat as u64);
543        assert_eq!(balance.unwrap(), U256::from(123));
544    }
545}