alloy_provider/provider/eth_call/
call_many.rs

1use std::{marker::PhantomData, sync::Arc, task::Poll};
2
3use alloy_eips::BlockId;
4use alloy_json_rpc::RpcRecv;
5use alloy_network::Network;
6use alloy_rpc_types_eth::{state::StateOverride, Bundle, StateContext, TransactionIndex};
7use alloy_transport::TransportResult;
8use futures::{future, FutureExt};
9
10use crate::ProviderCall;
11
12use super::{Caller, EthCallManyParams};
13
14/// A builder for an `"eth_callMany"` RPC request.
15#[derive(Clone)]
16pub struct EthCallMany<'req, N, Resp: RpcRecv, Output = Resp, Map = fn(Resp) -> Output>
17where
18    N: Network,
19    Resp: RpcRecv,
20    Map: Fn(Resp) -> Output,
21{
22    caller: Arc<dyn Caller<N, Resp>>,
23    params: EthCallManyParams<'req>,
24    map: Map,
25    _pd: PhantomData<fn() -> (Resp, Output)>,
26}
27
28impl<N, Resp, Output, Map> std::fmt::Debug for EthCallMany<'_, N, Resp, Output, Map>
29where
30    N: Network,
31    Resp: RpcRecv,
32    Map: Fn(Resp) -> Output,
33{
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.debug_struct("EthCallMany")
36            .field("params", &self.params)
37            .field("method", &"eth_callMany")
38            .finish()
39    }
40}
41
42impl<'req, N, Resp> EthCallMany<'req, N, Resp>
43where
44    N: Network,
45    Resp: RpcRecv,
46{
47    /// Instantiates a new `EthCallMany` with the given parameters.
48    pub fn new(caller: impl Caller<N, Resp> + 'static, bundles: &'req [Bundle]) -> Self {
49        Self {
50            caller: Arc::new(caller),
51            params: EthCallManyParams::new(bundles),
52            map: std::convert::identity,
53            _pd: PhantomData,
54        }
55    }
56}
57
58impl<'req, N, Resp, Output, Map> EthCallMany<'req, N, Resp, Output, Map>
59where
60    N: Network,
61    Resp: RpcRecv,
62    Map: Fn(Resp) -> Output,
63{
64    /// Set a mapping function to transform the response.
65    pub fn map<NewOutput, NewMap>(
66        self,
67        map: NewMap,
68    ) -> EthCallMany<'req, N, Resp, NewOutput, NewMap>
69    where
70        NewMap: Fn(Resp) -> NewOutput,
71    {
72        EthCallMany { caller: self.caller, params: self.params, map, _pd: PhantomData }
73    }
74
75    /// Set the [`BlockId`] in the [`StateContext`].
76    pub fn block(mut self, block: BlockId) -> Self {
77        self.params = self.params.with_block(block);
78        self
79    }
80
81    /// Set the block id to "pending".
82    pub fn pending(self) -> Self {
83        self.block(BlockId::pending())
84    }
85
86    /// Set the block id to "latest".
87    pub fn latest(self) -> Self {
88        self.block(BlockId::latest())
89    }
90
91    /// Set the block id to "earliest".
92    pub fn earliest(self) -> Self {
93        self.block(BlockId::earliest())
94    }
95
96    /// Set the block id to "finalized".
97    pub fn finalized(self) -> Self {
98        self.block(BlockId::finalized())
99    }
100
101    /// Set the block id to "safe".
102    pub fn safe(self) -> Self {
103        self.block(BlockId::safe())
104    }
105
106    /// Set the block id to a specific height.
107    pub fn number(self, number: u64) -> Self {
108        self.block(BlockId::number(number))
109    }
110
111    /// Set the block id to a specific hash, without requiring the hash be part
112    /// of the canonical chain.
113    pub fn hash(self, hash: alloy_primitives::B256) -> Self {
114        self.block(BlockId::hash(hash))
115    }
116
117    /// Set the block id to a specific hash and require the hash be part of the
118    /// canonical chain.
119    pub fn hash_canonical(self, hash: alloy_primitives::B256) -> Self {
120        self.block(BlockId::hash_canonical(hash))
121    }
122
123    /// Set the [`TransactionIndex`] in the [`StateContext`].
124    pub fn transaction_index(mut self, tx_index: TransactionIndex) -> Self {
125        self.params = self.params.with_transaction_index(tx_index);
126        self
127    }
128
129    /// Set the [`StateContext`] for the call.
130    pub fn context(mut self, context: &'req StateContext) -> Self {
131        self.params = self.params.with_context(*context);
132        self
133    }
134
135    /// Set the [`StateOverride`] for the call.
136    pub fn overrides(mut self, overrides: &'req StateOverride) -> Self {
137        self.params = self.params.with_overrides(overrides);
138        self
139    }
140
141    /// Extend the bundles for the call.
142    pub fn extend_bundles(mut self, bundles: &'req [Bundle]) -> Self {
143        self.params.bundles_mut().extend_from_slice(bundles);
144        self
145    }
146}
147
148impl<'req, N, Resp, Output, Map> std::future::IntoFuture for EthCallMany<'req, N, Resp, Output, Map>
149where
150    N: Network,
151    Resp: RpcRecv,
152    Map: Fn(Resp) -> Output,
153{
154    type Output = TransportResult<Output>;
155
156    type IntoFuture = CallManyFut<'req, N, Resp, Output, Map>;
157
158    fn into_future(self) -> Self::IntoFuture {
159        CallManyFut {
160            inner: CallManyInnerFut::Preparing {
161                caller: self.caller,
162                params: self.params,
163                map: self.map,
164            },
165        }
166    }
167}
168
169/// Intermediate future for `"eth_callMany"` requests.
170#[derive(Debug)]
171#[doc(hidden)] // Not public API.
172#[expect(unnameable_types)]
173#[pin_project::pin_project]
174pub struct CallManyFut<'req, N: Network, Resp: RpcRecv, Output, Map: Fn(Resp) -> Output> {
175    inner: CallManyInnerFut<'req, N, Resp, Output, Map>,
176}
177
178impl<N, Resp, Output, Map> CallManyFut<'_, N, Resp, Output, Map>
179where
180    N: Network,
181    Resp: RpcRecv,
182    Map: Fn(Resp) -> Output,
183{
184    const fn is_preparing(&self) -> bool {
185        matches!(self.inner, CallManyInnerFut::Preparing { .. })
186    }
187
188    const fn is_running(&self) -> bool {
189        matches!(self.inner, CallManyInnerFut::Running { .. })
190    }
191
192    fn poll_preparing(&mut self, cx: &mut std::task::Context<'_>) -> Poll<TransportResult<Output>> {
193        let CallManyInnerFut::Preparing { caller, params, map } =
194            std::mem::replace(&mut self.inner, CallManyInnerFut::Polling)
195        else {
196            unreachable!("bad state");
197        };
198
199        let fut = caller.call_many(params)?;
200        self.inner = CallManyInnerFut::Running { fut, map };
201        self.poll_running(cx)
202    }
203
204    fn poll_running(&mut self, cx: &mut std::task::Context<'_>) -> Poll<TransportResult<Output>> {
205        let CallManyInnerFut::Running { ref mut fut, ref map } = self.inner else {
206            unreachable!("bad state");
207        };
208
209        fut.poll_unpin(cx).map(|res| res.map(map))
210    }
211}
212
213impl<N, Resp, Output, Map> future::Future for CallManyFut<'_, N, Resp, Output, Map>
214where
215    N: Network,
216    Resp: RpcRecv,
217    Map: Fn(Resp) -> Output,
218{
219    type Output = TransportResult<Output>;
220
221    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
222        let this = self.get_mut();
223
224        if this.is_preparing() {
225            this.poll_preparing(cx)
226        } else if this.is_running() {
227            this.poll_running(cx)
228        } else {
229            panic!("bad state");
230        }
231    }
232}
233
234enum CallManyInnerFut<'req, N: Network, Resp: RpcRecv, Output, Map: Fn(Resp) -> Output> {
235    Preparing { caller: Arc<dyn Caller<N, Resp>>, params: EthCallManyParams<'req>, map: Map },
236    Running { fut: ProviderCall<EthCallManyParams<'static>, Resp>, map: Map },
237    Polling,
238}
239
240impl<N, Resp, Output, Map> std::fmt::Debug for CallManyInnerFut<'_, N, Resp, Output, Map>
241where
242    N: Network,
243    Resp: RpcRecv,
244    Map: Fn(Resp) -> Output,
245{
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        match self {
248            CallManyInnerFut::Preparing { params, .. } => {
249                f.debug_tuple("Preparing").field(&params).finish()
250            }
251            CallManyInnerFut::Running { .. } => f.debug_tuple("Running").finish(),
252            CallManyInnerFut::Polling => f.debug_tuple("Polling").finish(),
253        }
254    }
255}