Skip to main content

alloy_provider/provider/eth_call/
mod.rs

1use crate::ProviderCall;
2use alloy_eips::BlockId;
3use alloy_json_rpc::RpcRecv;
4use alloy_network::Network;
5use alloy_primitives::{Address, Bytes};
6use alloy_rpc_types_eth::{
7    state::{AccountOverride, StateOverride},
8    BlockOverrides,
9};
10use alloy_sol_types::SolCall;
11use alloy_transport::TransportResult;
12use futures::FutureExt;
13use std::{
14    future::{Future, IntoFuture},
15    marker::PhantomData,
16    sync::Arc,
17    task::Poll,
18    time::Duration,
19};
20
21#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
22use tokio::time::{timeout as timeout_future, Timeout};
23
24#[cfg(all(target_family = "wasm", target_os = "unknown"))]
25use wasmtimer::tokio::{timeout as timeout_future, Timeout};
26
27mod params;
28pub use params::{EthCallManyParams, EthCallParams};
29
30mod call_many;
31pub use call_many::EthCallMany;
32
33mod caller;
34pub use caller::Caller;
35
36/// The [`EthCallFut`] future is the future type for an `eth_call` RPC request.
37#[derive(Debug)]
38#[doc(hidden)] // Not public API.
39#[expect(unnameable_types)]
40#[pin_project::pin_project]
41pub struct EthCallFut<N, Resp, Output, Map>
42where
43    N: Network,
44    Resp: RpcRecv,
45    Output: 'static,
46    Map: Fn(Resp) -> Output,
47{
48    inner: EthCallFutInner<N, Resp, Output, Map>,
49}
50
51enum EthCallFutInner<N, Resp, Output, Map>
52where
53    N: Network,
54    Resp: RpcRecv,
55    Map: Fn(Resp) -> Output,
56{
57    Preparing {
58        caller: Arc<dyn Caller<N, Resp>>,
59        params: EthCallParams<N>,
60        method: &'static str,
61        map: Map,
62    },
63    Running {
64        map: Map,
65        fut: ProviderCall<EthCallParams<N>, Resp>,
66    },
67    Polling,
68}
69
70impl<N, Resp, Output, Map> core::fmt::Debug for EthCallFutInner<N, Resp, Output, Map>
71where
72    N: Network,
73    Resp: RpcRecv,
74    Output: 'static,
75    Map: Fn(Resp) -> Output,
76{
77    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78        match self {
79            Self::Preparing { caller: _, params, method, map: _ } => {
80                f.debug_struct("Preparing").field("params", params).field("method", method).finish()
81            }
82            Self::Running { .. } => f.debug_tuple("Running").finish(),
83            Self::Polling => f.debug_tuple("Polling").finish(),
84        }
85    }
86}
87
88impl<N, Resp, Output, Map> EthCallFut<N, Resp, Output, Map>
89where
90    N: Network,
91    Resp: RpcRecv,
92    Output: 'static,
93    Map: Fn(Resp) -> Output,
94{
95    /// Returns `true` if the future is in the preparing state.
96    const fn is_preparing(&self) -> bool {
97        matches!(self.inner, EthCallFutInner::Preparing { .. })
98    }
99
100    /// Returns `true` if the future is in the running state.
101    const fn is_running(&self) -> bool {
102        matches!(self.inner, EthCallFutInner::Running { .. })
103    }
104
105    fn poll_preparing(&mut self, cx: &mut std::task::Context<'_>) -> Poll<TransportResult<Output>> {
106        let EthCallFutInner::Preparing { caller, params, method, map } =
107            std::mem::replace(&mut self.inner, EthCallFutInner::Polling)
108        else {
109            unreachable!("bad state")
110        };
111
112        let fut =
113            if method.eq("eth_call") { caller.call(params) } else { caller.estimate_gas(params) }?;
114
115        self.inner = EthCallFutInner::Running { map, fut };
116
117        self.poll_running(cx)
118    }
119
120    fn poll_running(&mut self, cx: &mut std::task::Context<'_>) -> Poll<TransportResult<Output>> {
121        let EthCallFutInner::Running { ref map, ref mut fut } = self.inner else {
122            unreachable!("bad state")
123        };
124
125        fut.poll_unpin(cx).map(|res| res.map(map))
126    }
127}
128
129impl<N, Resp, Output, Map> Future for EthCallFut<N, Resp, Output, Map>
130where
131    N: Network,
132    Resp: RpcRecv,
133    Output: 'static,
134    Map: Fn(Resp) -> Output,
135{
136    type Output = TransportResult<Output>;
137
138    fn poll(
139        self: std::pin::Pin<&mut Self>,
140        cx: &mut std::task::Context<'_>,
141    ) -> std::task::Poll<Self::Output> {
142        let this = self.get_mut();
143        if this.is_preparing() {
144            this.poll_preparing(cx)
145        } else if this.is_running() {
146            this.poll_running(cx)
147        } else {
148            panic!("unexpected state")
149        }
150    }
151}
152
153/// A builder for an `"eth_call"` request. This type is returned by the
154/// [`Provider::call`] method.
155///
156/// Builders returned by the default [`Provider::call`] and estimation paths retain only a weak
157/// client handle. Keep the provider alive until such a call is awaited, or awaiting it returns a
158/// backend-gone transport error. Provider layers may supply a custom caller that retains additional
159/// state, as the batching layer does.
160///
161/// [`Provider::call`]: crate::Provider::call
162#[must_use = "EthCall must be awaited to execute the call"]
163#[derive(Clone)]
164pub struct EthCall<N, Resp, Output = Resp, Map = fn(Resp) -> Output>
165where
166    N: Network,
167    Resp: RpcRecv,
168    Map: Fn(Resp) -> Output,
169{
170    caller: Arc<dyn Caller<N, Resp>>,
171    params: EthCallParams<N>,
172    method: &'static str,
173    map: Map,
174    _pd: PhantomData<fn() -> (Resp, Output)>,
175}
176
177impl<N, Resp> core::fmt::Debug for EthCall<N, Resp>
178where
179    N: Network,
180    Resp: RpcRecv,
181{
182    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
183        f.debug_struct("EthCall")
184            .field("params", &self.params)
185            .field("method", &self.method)
186            .finish()
187    }
188}
189
190impl<N, Resp> EthCall<N, Resp>
191where
192    N: Network,
193    Resp: RpcRecv,
194{
195    /// Create a new [`EthCall`].
196    pub fn new(
197        caller: impl Caller<N, Resp> + 'static,
198        method: &'static str,
199        data: N::TransactionRequest,
200    ) -> Self {
201        Self {
202            caller: Arc::new(caller),
203            params: EthCallParams::new(data),
204            method,
205            map: std::convert::identity,
206            _pd: PhantomData,
207        }
208    }
209
210    /// Create a new [`EthCall`] with method set to `"eth_call"`.
211    pub fn call(caller: impl Caller<N, Resp> + 'static, data: N::TransactionRequest) -> Self {
212        Self::new(caller, "eth_call", data)
213    }
214
215    /// Create a new [`EthCall`] with method set to `"eth_estimateGas"`.
216    pub fn gas_estimate(
217        caller: impl Caller<N, Resp> + 'static,
218        data: N::TransactionRequest,
219    ) -> Self {
220        Self::new(caller, "eth_estimateGas", data)
221    }
222}
223
224impl<N, Resp, Output, Map> EthCall<N, Resp, Output, Map>
225where
226    N: Network,
227    Resp: RpcRecv,
228    Map: Fn(Resp) -> Output,
229{
230    /// Map the response to a different type. This is usable for converting
231    /// the response to a more usable type, e.g. changing `U64` to `u64`.
232    ///
233    /// ## Note
234    ///
235    /// Carefully review the rust documentation on [fn pointers] before passing
236    /// them to this function. Unless the pointer is specifically coerced to a
237    /// `fn(_) -> _`, the `NewMap` will be inferred as that function's unique
238    /// type. This can lead to confusing error messages.
239    ///
240    /// [fn pointers]: https://doc.rust-lang.org/std/primitive.fn.html#creating-function-pointers
241    pub fn map_resp<NewOutput, NewMap>(self, map: NewMap) -> EthCall<N, Resp, NewOutput, NewMap>
242    where
243        NewMap: Fn(Resp) -> NewOutput,
244    {
245        EthCall {
246            caller: self.caller,
247            params: self.params,
248            method: self.method,
249            map,
250            _pd: PhantomData,
251        }
252    }
253
254    /// Wraps this call in a client-side timeout that only stops waiting for the response.
255    ///
256    /// Awaiting the returned future produces a timeout result around the existing transport result,
257    /// so the two error cases can be handled separately.
258    ///
259    /// ```no_run
260    /// # async fn example<P: alloy_provider::Provider>(
261    /// #     provider: P,
262    /// #     tx: alloy_rpc_types_eth::TransactionRequest,
263    /// # ) -> Result<(), Box<dyn std::error::Error>> {
264    /// use alloy_provider::Provider as _;
265    /// use std::time::Duration;
266    ///
267    /// let output = provider.call(tx).timeout(Duration::from_secs(10)).await??;
268    /// # let _: alloy_primitives::Bytes = output;
269    /// # Ok(())
270    /// # }
271    /// ```
272    ///
273    /// # Panics
274    ///
275    /// On Tokio-backed targets, including WASI, the returned future panics when polled if there
276    /// is no current Tokio timer, for example when polled outside of a Tokio runtime.
277    pub fn timeout(self, duration: Duration) -> Timeout<<Self as IntoFuture>::IntoFuture>
278    where
279        Output: 'static,
280    {
281        timeout_future(duration, self.into_future())
282    }
283
284    /// Set the state overrides for this call.
285    pub fn overrides(mut self, overrides: impl Into<StateOverride>) -> Self {
286        self.params.overrides = Some(overrides.into());
287        self
288    }
289
290    /// Set the state overrides for this call, if any.
291    pub fn overrides_opt(mut self, overrides: Option<StateOverride>) -> Self {
292        self.params.overrides = overrides;
293        self
294    }
295
296    /// Appends a single [AccountOverride] to the state override.
297    ///
298    /// Creates a new [`StateOverride`] if none has been set yet.
299    pub fn account_override(mut self, address: Address, account_override: AccountOverride) -> Self {
300        let mut overrides = self.params.overrides.unwrap_or_default();
301        overrides.insert(address, account_override);
302        self.params.overrides = Some(overrides);
303
304        self
305    }
306
307    /// Extends the given [AccountOverride] to the state override.
308    ///
309    /// Creates a new [`StateOverride`] if none has been set yet.
310    pub fn account_overrides(
311        mut self,
312        overrides: impl IntoIterator<Item = (Address, AccountOverride)>,
313    ) -> Self {
314        for (addr, account_override) in overrides.into_iter() {
315            self = self.account_override(addr, account_override);
316        }
317        self
318    }
319
320    /// Sets the block overrides for this call.
321    pub fn with_block_overrides(mut self, overrides: BlockOverrides) -> Self {
322        self.params.block_overrides = Some(overrides);
323        self
324    }
325
326    /// Sets the block overrides for this call, if any.
327    pub fn with_block_overrides_opt(mut self, overrides: Option<BlockOverrides>) -> Self {
328        self.params.block_overrides = overrides;
329        self
330    }
331
332    /// Set the block to use for this call.
333    pub const fn block(mut self, block: BlockId) -> Self {
334        self.params.block = Some(block);
335        self
336    }
337
338    /// Set the block id to "pending".
339    pub const fn pending(self) -> Self {
340        self.block(BlockId::pending())
341    }
342
343    /// Set the block id to "latest".
344    pub const fn latest(self) -> Self {
345        self.block(BlockId::latest())
346    }
347
348    /// Set the block id to "earliest".
349    pub const fn earliest(self) -> Self {
350        self.block(BlockId::earliest())
351    }
352
353    /// Set the block id to "finalized".
354    pub const fn finalized(self) -> Self {
355        self.block(BlockId::finalized())
356    }
357
358    /// Set the block id to "safe".
359    pub const fn safe(self) -> Self {
360        self.block(BlockId::safe())
361    }
362
363    /// Set the block id to a specific height.
364    pub const fn number(self, number: u64) -> Self {
365        self.block(BlockId::number(number))
366    }
367
368    /// Set the block id to a specific hash, without requiring the hash be part
369    /// of the canonical chain.
370    pub const fn hash(self, hash: alloy_primitives::B256) -> Self {
371        self.block(BlockId::hash(hash))
372    }
373
374    /// Set the block id to a specific hash and require the hash be part of the
375    /// canonical chain.
376    pub const fn hash_canonical(self, hash: alloy_primitives::B256) -> Self {
377        self.block(BlockId::hash_canonical(hash))
378    }
379}
380
381impl<N> EthCall<N, Bytes>
382where
383    N: Network,
384{
385    /// Decode the [`Bytes`] returned by an `"eth_call"` into a [`SolCall::Return`] type.
386    ///
387    /// ## Note
388    ///
389    /// The result of the `eth_call` will be [`alloy_sol_types::Result`] with the Ok variant
390    /// containing the decoded [`SolCall::Return`] type.
391    ///
392    /// # Examples
393    ///
394    /// ```ignore
395    /// let call = EthCall::call(provider, data).decode_resp::<MySolCall>().await?.unwrap();
396    ///
397    /// assert!(matches!(call.return_value, MySolCall::MyStruct { .. }));
398    /// ```
399    pub fn decode_resp<S: SolCall>(self) -> EthCall<N, Bytes, alloy_sol_types::Result<S::Return>> {
400        self.map_resp(|data| S::abi_decode_returns(&data))
401    }
402}
403
404impl<N, Resp, Output, Map> std::future::IntoFuture for EthCall<N, Resp, Output, Map>
405where
406    N: Network,
407    Resp: RpcRecv,
408    Output: 'static,
409    Map: Fn(Resp) -> Output,
410{
411    type Output = TransportResult<Output>;
412
413    type IntoFuture = EthCallFut<N, Resp, Output, Map>;
414
415    fn into_future(self) -> Self::IntoFuture {
416        EthCallFut {
417            inner: EthCallFutInner::Preparing {
418                caller: self.caller,
419                params: self.params,
420                method: self.method,
421                map: self.map,
422            },
423        }
424    }
425}
426
427#[cfg(test)]
428mod test {
429    use super::*;
430    use alloy_eips::BlockNumberOrTag;
431    use alloy_network::{Ethereum, TransactionBuilder};
432    use alloy_primitives::{address, U256};
433    use alloy_rpc_types_eth::{state::StateOverride, TransactionRequest};
434
435    #[derive(Clone, Copy, Debug)]
436    struct PendingCaller;
437
438    impl Caller<Ethereum, U256> for PendingCaller {
439        fn call(
440            &self,
441            _params: EthCallParams<Ethereum>,
442        ) -> TransportResult<ProviderCall<EthCallParams<Ethereum>, U256>> {
443            Ok(ProviderCall::BoxedFuture(Box::pin(std::future::pending())))
444        }
445
446        fn estimate_gas(
447            &self,
448            _params: EthCallParams<Ethereum>,
449        ) -> TransportResult<ProviderCall<EthCallParams<Ethereum>, U256>> {
450            Ok(ProviderCall::BoxedFuture(Box::pin(std::future::pending())))
451        }
452
453        fn call_many(
454            &self,
455            _params: EthCallManyParams<'_>,
456        ) -> TransportResult<ProviderCall<EthCallManyParams<'static>, U256>> {
457            Ok(ProviderCall::BoxedFuture(Box::pin(std::future::pending())))
458        }
459    }
460
461    #[tokio::test]
462    async fn test_eth_call_with_timeout() {
463        let call = EthCall::call(PendingCaller, TransactionRequest::default());
464
465        assert!(call.timeout(Duration::ZERO).await.is_err());
466    }
467
468    #[test]
469    fn test_serialize_eth_call_params() {
470        let alice = address!("0000000000000000000000000000000000000001");
471        let bob = address!("0000000000000000000000000000000000000002");
472        let data = TransactionRequest::default()
473            .with_from(alice)
474            .with_to(bob)
475            .with_nonce(0)
476            .with_chain_id(1)
477            .value(U256::from(100))
478            .with_gas_limit(21_000)
479            .with_max_priority_fee_per_gas(1_000_000_000)
480            .with_max_fee_per_gas(20_000_000_000);
481
482        let block = BlockId::Number(BlockNumberOrTag::Number(1));
483        let overrides = StateOverride::default();
484
485        // Expected: [data]
486        let params: EthCallParams<Ethereum> = EthCallParams::new(data.clone());
487
488        assert_eq!(params.data(), &data);
489        assert_eq!(params.block(), None);
490        assert_eq!(params.overrides(), None);
491        assert_eq!(
492            serde_json::to_string(&params).unwrap(),
493            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"}]"#
494        );
495
496        // Expected: [data, block, overrides]
497        let params: EthCallParams<Ethereum> =
498            EthCallParams::new(data.clone()).with_block(block).with_overrides(overrides.clone());
499
500        assert_eq!(params.data(), &data);
501        assert_eq!(params.block(), Some(block));
502        assert_eq!(params.overrides(), Some(&overrides));
503        assert_eq!(
504            serde_json::to_string(&params).unwrap(),
505            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1",{}]"#
506        );
507
508        // Expected: [data, (default), overrides]
509        let params: EthCallParams<Ethereum> =
510            EthCallParams::new(data.clone()).with_overrides(overrides.clone());
511
512        assert_eq!(params.data(), &data);
513        assert_eq!(params.block(), None);
514        assert_eq!(params.overrides(), Some(&overrides));
515        assert_eq!(
516            serde_json::to_string(&params).unwrap(),
517            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"latest",{}]"#
518        );
519
520        // Expected: [data, block]
521        let params: EthCallParams<Ethereum> = EthCallParams::new(data.clone()).with_block(block);
522
523        assert_eq!(params.data(), &data);
524        assert_eq!(params.block(), Some(block));
525        assert_eq!(params.overrides(), None);
526        assert_eq!(
527            serde_json::to_string(&params).unwrap(),
528            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1"]"#
529        );
530    }
531}