1use crate::{
2 bindings::{ArbSys, IMulticall3},
3 Caller, Provider, ProviderCall, ProviderLayer, RootProvider, ARB_SYS_ADDRESS,
4 MULTICALL3_ADDRESS,
5};
6use alloy_eips::BlockId;
7use alloy_network::{Ethereum, Network, TransactionBuilder};
8use alloy_primitives::{Address, Bytes, U256};
9use alloy_rpc_client::WeakClient;
10use alloy_sol_types::{SolCall, SolType, SolValue};
11use alloy_transport::{utils::Spawnable, TransportErrorKind, TransportResult};
12use std::{fmt, future::IntoFuture, marker::PhantomData, sync::Arc, time::Duration};
13use tokio::sync::{mpsc, oneshot};
14
15#[cfg(all(target_family = "wasm", target_os = "unknown"))]
16use wasmtimer::tokio::sleep;
17
18#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
19use tokio::time::sleep;
20
21const DEFAULT_WAIT: Duration = Duration::from_millis(1);
25
26#[derive(Debug)]
74pub struct CallBatchLayer {
75 m3a: Address,
76 wait: Duration,
77 arbsys: bool,
78}
79
80impl Default for CallBatchLayer {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl CallBatchLayer {
87 pub const fn new() -> Self {
89 Self { m3a: MULTICALL3_ADDRESS, wait: DEFAULT_WAIT, arbsys: false }
90 }
91
92 pub const fn wait(mut self, wait: Duration) -> Self {
101 self.wait = wait;
102 self
103 }
104
105 pub const fn multicall3_address(mut self, m3a: Address) -> Self {
109 self.m3a = m3a;
110 self
111 }
112
113 pub const fn arbitrum_compat(mut self) -> Self {
123 self.arbsys = true;
124 self
125 }
126}
127
128impl<P, N> ProviderLayer<P, N> for CallBatchLayer
129where
130 P: Provider<N> + 'static,
131 N: Network,
132{
133 type Provider = CallBatchProvider<P, N>;
134
135 fn layer(&self, inner: P) -> Self::Provider {
136 CallBatchProvider::new(inner, self)
137 }
138}
139
140type CallBatchMsgTx = TransportResult<IMulticall3::Result>;
141
142struct CallBatchMsg<N: Network> {
143 kind: CallBatchMsgKind<N>,
144 tx: oneshot::Sender<CallBatchMsgTx>,
145}
146
147impl<N: Network> Clone for CallBatchMsgKind<N>
148where
149 N::TransactionRequest: Clone,
150{
151 fn clone(&self) -> Self {
152 match self {
153 Self::Call(tx) => Self::Call(tx.clone()),
154 Self::BlockNumber => Self::BlockNumber,
155 Self::ChainId => Self::ChainId,
156 Self::Balance(addr) => Self::Balance(*addr),
157 }
158 }
159}
160
161impl<N: Network> fmt::Debug for CallBatchMsg<N> {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 f.write_str("BatchProviderMessage(")?;
164 self.kind.fmt(f)?;
165 f.write_str(")")
166 }
167}
168
169#[derive(Debug)]
170enum CallBatchMsgKind<N: Network = Ethereum> {
171 Call(N::TransactionRequest),
172 BlockNumber,
173 ChainId,
174 Balance(Address),
175}
176
177impl<N: Network> CallBatchMsg<N> {
178 fn new(kind: CallBatchMsgKind<N>) -> (Self, oneshot::Receiver<CallBatchMsgTx>) {
179 let (tx, rx) = oneshot::channel();
180 (Self { kind, tx }, rx)
181 }
182}
183
184impl<N: Network> CallBatchMsgKind<N> {
185 fn to_call3(&self, m3a: Address, arbsys: bool) -> IMulticall3::Call3 {
186 let m3a_call = |data: Vec<u8>| IMulticall3::Call3 {
187 target: m3a,
188 allowFailure: true,
189 callData: data.into(),
190 };
191 match self {
192 Self::Call(tx) => IMulticall3::Call3 {
193 target: tx.to().unwrap_or_default(),
194 allowFailure: true,
195 callData: tx.input().cloned().unwrap_or_default(),
196 },
197 Self::BlockNumber => {
198 if arbsys {
199 return IMulticall3::Call3 {
200 target: ARB_SYS_ADDRESS,
201 allowFailure: false,
202 callData: ArbSys::arbBlockNumberCall {}.abi_encode().into(),
203 };
204 }
205 m3a_call(IMulticall3::getBlockNumberCall {}.abi_encode())
206 }
207 Self::ChainId => m3a_call(IMulticall3::getChainIdCall {}.abi_encode()),
208 &Self::Balance(addr) => m3a_call(IMulticall3::getEthBalanceCall { addr }.abi_encode()),
209 }
210 }
211}
212
213pub struct CallBatchProvider<P, N: Network = Ethereum> {
217 provider: Arc<P>,
218 inner: CallBatchProviderInner<N>,
219 _pd: PhantomData<N>,
220}
221
222impl<P, N: Network> Clone for CallBatchProvider<P, N> {
223 fn clone(&self) -> Self {
224 Self { provider: self.provider.clone(), inner: self.inner.clone(), _pd: PhantomData }
225 }
226}
227
228impl<P: fmt::Debug, N: Network> fmt::Debug for CallBatchProvider<P, N> {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 f.write_str("BatchProvider(")?;
231 self.provider.fmt(f)?;
232 f.write_str(")")
233 }
234}
235
236impl<P: Provider<N> + 'static, N: Network> CallBatchProvider<P, N> {
237 fn new(inner: P, layer: &CallBatchLayer) -> Self {
238 let inner = Arc::new(inner);
239 let tx = CallBatchBackend::spawn(inner.clone(), layer);
240 Self { provider: inner, inner: CallBatchProviderInner { tx }, _pd: PhantomData }
241 }
242}
243
244#[derive(Clone)]
245struct CallBatchProviderInner<N: Network> {
246 tx: mpsc::UnboundedSender<CallBatchMsg<N>>,
247}
248
249impl<N: Network> CallBatchProviderInner<N> {
250 fn should_batch_call(&self, params: &crate::EthCallParams<N>) -> bool {
257 if params.block().is_some_and(|block| block != BlockId::latest()) {
259 return false;
260 }
261 if params.overrides.as_ref().is_some_and(|overrides| !overrides.is_empty()) {
262 return false;
263 }
264 if params.block_overrides().is_some_and(|overrides| !overrides.is_empty()) {
265 return false;
266 }
267 let tx = params.data();
268 if tx.to().is_none() {
269 return false;
270 }
271 if let Ok(serde_json::Value::Object(obj)) = serde_json::to_value(tx) {
272 if obj.keys().any(|k| !matches!(k.as_str(), "to" | "data" | "input")) {
273 return false;
274 }
275 }
276 true
277 }
278
279 async fn schedule(self, msg: CallBatchMsgKind<N>) -> TransportResult<Bytes> {
280 let (msg, rx) = CallBatchMsg::new(msg);
281 self.tx.send(msg).map_err(|_| TransportErrorKind::backend_gone())?;
282
283 let IMulticall3::Result { success, returnData } =
284 rx.await.map_err(|_| TransportErrorKind::backend_gone())??;
285
286 if !success {
287 let revert_data = if returnData.is_empty() {
288 "".to_string()
289 } else {
290 format!(" with data: {returnData}")
291 };
292 Err(TransportErrorKind::custom_str(&format!(
293 "multicall batched call reverted{revert_data}"
294 )))
295 } else {
296 Ok(returnData)
297 }
298 }
299
300 async fn schedule_and_decode<T>(self, msg: CallBatchMsgKind<N>) -> TransportResult<T>
301 where
302 T: SolValue + From<<T::SolType as SolType>::RustType>,
303 {
304 let data = self.schedule(msg).await?;
305 T::abi_decode(&data).map_err(TransportErrorKind::custom)
306 }
307}
308
309struct CallBatchBackend<P, N: Network = Ethereum> {
310 inner: Arc<P>,
311 m3a: Address,
312 wait: Duration,
313 arbsys: bool,
314 rx: mpsc::UnboundedReceiver<CallBatchMsg<N>>,
315 pending: Vec<CallBatchMsg<N>>,
316 _pd: PhantomData<N>,
317}
318
319impl<P: Provider<N> + 'static, N: Network> CallBatchBackend<P, N> {
320 fn spawn(inner: Arc<P>, layer: &CallBatchLayer) -> mpsc::UnboundedSender<CallBatchMsg<N>> {
321 let CallBatchLayer { m3a, wait, arbsys } = *layer;
322 let (tx, rx) = mpsc::unbounded_channel();
323 let this = Self { inner, m3a, wait, arbsys, rx, pending: Vec::new(), _pd: PhantomData };
324 this.run().spawn_task();
325 tx
326 }
327
328 async fn run(mut self) {
329 'outer: loop {
330 debug_assert!(self.pending.is_empty());
332 match self.rx.recv().await {
333 Some(msg) => self.process_msg(msg),
334 None => break,
335 }
336
337 debug_assert!(!self.pending.is_empty());
339 sleep(self.wait).await;
340 'inner: loop {
341 match self.rx.try_recv() {
342 Ok(msg) => self.process_msg(msg),
343 Err(mpsc::error::TryRecvError::Empty) => break 'inner,
344 Err(mpsc::error::TryRecvError::Disconnected) => break 'outer,
345 }
346 }
347 self.send_batch().await;
349 }
350 }
351
352 fn process_msg(&mut self, msg: CallBatchMsg<N>) {
353 self.pending.push(msg);
354 }
355
356 async fn send_batch(&mut self) {
357 let mut pending = std::mem::take(&mut self.pending);
358
359 pending.retain(|msg| !msg.tx.is_closed());
361
362 if pending.is_empty() {
364 return;
365 }
366
367 if pending.len() == 1 {
369 let msg = pending.into_iter().next().unwrap();
370 let result = self.call_one(msg.kind).await;
371 let _ = msg.tx.send(result);
372 return;
373 }
374
375 let result = self.send_batch_inner(&pending).await;
376 match result {
377 Ok(results) => {
378 if results.len() != pending.len() {
379 let err = format!(
380 "multicall batch response count mismatch: expected {}, got {}",
381 pending.len(),
382 results.len()
383 );
384 for msg in pending {
385 let _ = msg.tx.send(Err(TransportErrorKind::custom_str(&err)));
386 }
387 return;
388 }
389
390 for (result, msg) in results.into_iter().zip(pending) {
391 let _ = msg.tx.send(Ok(result));
392 }
393 }
394 Err(e) => {
395 for msg in pending {
396 let _ = msg.tx.send(Err(TransportErrorKind::custom_str(&e.to_string())));
397 }
398 }
399 }
400 }
401
402 async fn call_one(&mut self, msg: CallBatchMsgKind<N>) -> TransportResult<IMulticall3::Result> {
403 let m3_res =
404 |success, return_data| IMulticall3::Result { success, returnData: return_data };
405 match msg {
406 CallBatchMsgKind::Call(tx) => self.inner.call(tx).await.map(|res| m3_res(true, res)),
407 CallBatchMsgKind::BlockNumber => {
408 self.inner.get_block_number().await.map(|res| m3_res(true, res.abi_encode().into()))
409 }
410 CallBatchMsgKind::ChainId => {
411 self.inner.get_chain_id().await.map(|res| m3_res(true, res.abi_encode().into()))
412 }
413 CallBatchMsgKind::Balance(addr) => {
414 self.inner.get_balance(addr).await.map(|res| m3_res(true, res.abi_encode().into()))
415 }
416 }
417 }
418
419 async fn send_batch_inner(
420 &self,
421 pending: &[CallBatchMsg<N>],
422 ) -> TransportResult<Vec<IMulticall3::Result>> {
423 let calls: Vec<_> =
424 pending.iter().map(|msg| msg.kind.to_call3(self.m3a, self.arbsys)).collect();
425
426 let tx = N::TransactionRequest::default()
427 .with_to(self.m3a)
428 .with_input(IMulticall3::aggregate3Call { calls }.abi_encode());
429
430 let bytes = self.inner.call(tx).await?;
431 if bytes.is_empty() {
432 return Err(TransportErrorKind::custom_str(&format!(
433 "Multicall3 not deployed at {}",
434 self.m3a
435 )));
436 }
437
438 let ret = IMulticall3::aggregate3Call::abi_decode_returns(&bytes)
439 .map_err(TransportErrorKind::custom)?;
440 Ok(ret)
441 }
442}
443
444impl<P: Provider<N> + 'static, N: Network> Provider<N> for CallBatchProvider<P, N> {
445 fn root(&self) -> &RootProvider<N> {
446 self.provider.root()
447 }
448
449 fn call(&self, tx: <N as Network>::TransactionRequest) -> crate::EthCall<N, Bytes> {
450 crate::EthCall::call(CallBatchCaller::new(self), tx)
451 }
452
453 fn get_block_number(
454 &self,
455 ) -> crate::ProviderCall<
456 alloy_rpc_client::NoParams,
457 alloy_primitives::U64,
458 alloy_primitives::BlockNumber,
459 > {
460 crate::ProviderCall::BoxedFuture(Box::pin(
461 self.inner.clone().schedule_and_decode::<u64>(CallBatchMsgKind::BlockNumber),
462 ))
463 }
464
465 fn get_chain_id(
466 &self,
467 ) -> crate::ProviderCall<
468 alloy_rpc_client::NoParams,
469 alloy_primitives::U64,
470 alloy_primitives::ChainId,
471 > {
472 crate::ProviderCall::BoxedFuture(Box::pin(
473 self.inner.clone().schedule_and_decode::<u64>(CallBatchMsgKind::ChainId),
474 ))
475 }
476
477 fn get_balance(&self, address: Address) -> crate::RpcWithBlock<Address, U256, U256> {
478 let this = self.clone();
479 crate::RpcWithBlock::new_provider(move |block| {
480 if block != BlockId::latest() {
481 this.provider.get_balance(address).block_id(block).into_future()
482 } else {
483 ProviderCall::BoxedFuture(Box::pin(
484 this.inner
485 .clone()
486 .schedule_and_decode::<U256>(CallBatchMsgKind::Balance(address)),
487 ))
488 }
489 })
490 }
491}
492
493struct CallBatchCaller<N: Network> {
494 inner: CallBatchProviderInner<N>,
495 weak: WeakClient,
496}
497
498impl<N: Network> CallBatchCaller<N> {
499 fn new<P: Provider<N>>(provider: &CallBatchProvider<P, N>) -> Self {
500 Self { inner: provider.inner.clone(), weak: provider.provider.weak_client() }
501 }
502}
503
504impl<N: Network> Caller<N, Bytes> for CallBatchCaller<N> {
505 fn call(
506 &self,
507 params: crate::EthCallParams<N>,
508 ) -> TransportResult<crate::ProviderCall<crate::EthCallParams<N>, Bytes>> {
509 if !self.inner.should_batch_call(¶ms) {
510 return Caller::<N, Bytes>::call(&self.weak, params);
511 }
512
513 Ok(crate::ProviderCall::BoxedFuture(Box::pin(
514 self.inner.clone().schedule(CallBatchMsgKind::Call(params.into_data())),
515 )))
516 }
517
518 fn estimate_gas(
519 &self,
520 params: crate::EthCallParams<N>,
521 ) -> TransportResult<crate::ProviderCall<crate::EthCallParams<N>, Bytes>> {
522 Caller::<N, Bytes>::estimate_gas(&self.weak, params)
523 }
524
525 fn call_many(
526 &self,
527 params: crate::EthCallManyParams<'_>,
528 ) -> TransportResult<crate::ProviderCall<crate::EthCallManyParams<'static>, Bytes>> {
529 Caller::<N, Bytes>::call_many(&self.weak, params)
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536 use crate::ProviderBuilder;
537 use alloy_network::{Ethereum, TransactionBuilder};
538 use alloy_primitives::{address, hex};
539 use alloy_rpc_types_eth::{BlockOverrides, TransactionRequest};
540 use alloy_transport::mock::Asserter;
541
542 const MULTICALL3_DEPLOYED_CODE: &[u8] = &hex!("0x6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033");
544 const COUNTER_ADDRESS: Address = address!("0x1234123412341234123412341234123412341234");
545 const COUNTER_DEPLOYED_CODE: &[u8] = &hex!("0x6080604052348015600e575f5ffd5b5060043610603a575f3560e01c80633fb5c1cb14603e5780638381f58a14604f578063d09de08a146068575b5f5ffd5b604d6049366004607d565b5f55565b005b60565f5481565b60405190815260200160405180910390f35b604d5f805490806076836093565b9190505550565b5f60208284031215608c575f5ffd5b5035919050565b5f6001820160af57634e487b7160e01b5f52601160045260245ffd5b506001019056fea2646970667358221220f423ff7a9a85bf49c3769164d3bd24403940510478df27a6b1deac980db69e5664736f6c634300081b0033");
546
547 fn push_m3_success(asserter: &Asserter, returns: &[(bool, Vec<u8>)]) {
548 asserter.push_success(
549 &returns
550 .iter()
551 .map(|&(success, ref data)| IMulticall3::Result {
552 success,
553 returnData: Bytes::copy_from_slice(data),
554 })
555 .collect::<Vec<_>>()
556 .abi_encode(),
557 )
558 }
559
560 #[tokio::test]
561 async fn basic_mocked() {
562 let asserter = Asserter::new();
563 let provider =
564 ProviderBuilder::new().with_call_batching().connect_mocked_client(asserter.clone());
565 push_m3_success(
566 &asserter,
567 &[
568 (true, 1.abi_encode()), (true, 2.abi_encode()), (false, 3.abi_encode()), (false, 4.abi_encode()), ],
573 );
574 let (block_number_ok, chain_id_ok, block_number_err, chain_id_err) = tokio::join!(
575 provider.get_block_number(),
576 provider.get_chain_id(),
577 provider.get_block_number(),
578 provider.get_chain_id(),
579 );
580 assert_eq!(block_number_ok.unwrap(), 1);
581 assert_eq!(chain_id_ok.unwrap(), 2);
582 assert!(block_number_err.unwrap_err().to_string().contains("reverted"));
583 assert!(chain_id_err.unwrap_err().to_string().contains("reverted"));
584 assert!(asserter.read_q().is_empty(), "only 1 request should've been made");
585 }
586
587 #[tokio::test]
588 async fn batch_response_len_mismatch_errors_all_callers() {
589 let asserter = Asserter::new();
590 let provider =
591 ProviderBuilder::new().with_call_batching().connect_mocked_client(asserter.clone());
592
593 push_m3_success(&asserter, &[(true, 1.abi_encode())]);
594
595 let (block_number, chain_id) =
596 tokio::join!(provider.get_block_number(), provider.get_chain_id());
597
598 let block_number_err = block_number.unwrap_err().to_string();
599 let chain_id_err = chain_id.unwrap_err().to_string();
600 assert!(block_number_err.contains("response count mismatch"), "{block_number_err}");
601 assert!(chain_id_err.contains("response count mismatch"), "{chain_id_err}");
602 assert!(asserter.read_q().is_empty(), "only 1 request should've been made");
603 }
604
605 #[test]
606 fn should_not_batch_calls_with_block_overrides() {
607 let (tx, _) = tokio::sync::mpsc::unbounded_channel();
608 let inner = CallBatchProviderInner::<Ethereum> { tx };
609 let params =
610 crate::EthCallParams::new(TransactionRequest::default().with_to(COUNTER_ADDRESS))
611 .with_block_overrides(BlockOverrides::default().with_number(U256::from(1)));
612
613 assert!(!inner.should_batch_call(¶ms));
614 }
615
616 #[tokio::test]
617 #[cfg(feature = "anvil-api")]
618 async fn basic() {
619 use crate::ext::AnvilApi;
620 let provider = ProviderBuilder::new().with_call_batching().connect_anvil();
621 provider.anvil_set_code(COUNTER_ADDRESS, COUNTER_DEPLOYED_CODE.into()).await.unwrap();
622 provider.anvil_set_balance(COUNTER_ADDRESS, U256::from(123)).await.unwrap();
623
624 let do_calls = || async {
625 tokio::join!(
626 provider.call(
627 TransactionRequest::default()
628 .with_to(COUNTER_ADDRESS)
629 .with_input(hex!("0x8381f58a")) ),
631 provider.call(
632 TransactionRequest::default()
633 .with_to(MULTICALL3_ADDRESS)
634 .with_input(IMulticall3::getBlockNumberCall {}.abi_encode())
635 ),
636 provider.get_block_number(),
637 provider.get_chain_id(),
638 provider.get_balance(COUNTER_ADDRESS),
639 )
640 };
641
642 let (a, b, c, d, e) = do_calls().await;
644 assert!(a.unwrap_err().to_string().contains("Multicall3 not deployed"));
645 assert!(b.unwrap_err().to_string().contains("Multicall3 not deployed"));
646 assert!(c.unwrap_err().to_string().contains("Multicall3 not deployed"));
647 assert!(d.unwrap_err().to_string().contains("Multicall3 not deployed"));
648 assert!(e.unwrap_err().to_string().contains("Multicall3 not deployed"));
649
650 provider.anvil_set_code(MULTICALL3_ADDRESS, MULTICALL3_DEPLOYED_CODE.into()).await.unwrap();
651
652 let (counter, block_number_raw, block_number, chain_id, balance) = do_calls().await;
653 assert_eq!(counter.unwrap(), 0u64.abi_encode());
654 assert_eq!(block_number_raw.unwrap(), 1u64.abi_encode());
655 assert_eq!(block_number.unwrap(), 1);
656 assert_eq!(chain_id.unwrap(), alloy_chains::NamedChain::AnvilHardhat as u64);
657 assert_eq!(balance.unwrap(), U256::from(123));
658 }
659
660 #[tokio::test]
661 #[ignore]
662 async fn arbitrum() {
663 let url = "https://arbitrum.rpc.subquery.network/public";
664
665 let batched = ProviderBuilder::new().with_call_batching().connect(url).await.unwrap();
666
667 let batch_layer = CallBatchLayer::new().arbitrum_compat();
668 let batched_compat = ProviderBuilder::new().layer(batch_layer).connect(url).await.unwrap();
669
670 let block = batched.get_block_number().await.unwrap();
672
673 let (b, _) = tokio::join!(batched.get_block_number(), batched.get_chain_id());
675 let block_wrong = b.unwrap();
677
678 let (b, _) = tokio::join!(batched_compat.get_block_number(), batched.get_chain_id());
680 let block_compat = b.unwrap();
682
683 dbg!(block, block_wrong, block_compat);
684
685 assert!(block.abs_diff(block_compat) < 10);
687 assert!(block.abs_diff(block_wrong) > 100_000);
688 }
689}