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 to use for this call, if any.
339    pub const fn block_opt(mut self, block: Option<BlockId>) -> Self {
340        self.params.block = block;
341        self
342    }
343
344    /// Set the block id to "pending".
345    pub const fn pending(self) -> Self {
346        self.block(BlockId::pending())
347    }
348
349    /// Set the block id to "latest".
350    pub const fn latest(self) -> Self {
351        self.block(BlockId::latest())
352    }
353
354    /// Set the block id to "earliest".
355    pub const fn earliest(self) -> Self {
356        self.block(BlockId::earliest())
357    }
358
359    /// Set the block id to "finalized".
360    pub const fn finalized(self) -> Self {
361        self.block(BlockId::finalized())
362    }
363
364    /// Set the block id to "safe".
365    pub const fn safe(self) -> Self {
366        self.block(BlockId::safe())
367    }
368
369    /// Set the block id to a specific height.
370    pub const fn number(self, number: u64) -> Self {
371        self.block(BlockId::number(number))
372    }
373
374    /// Set the block id to a specific hash, without requiring the hash be part
375    /// of the canonical chain.
376    pub const fn hash(self, hash: alloy_primitives::B256) -> Self {
377        self.block(BlockId::hash(hash))
378    }
379
380    /// Set the block id to a specific hash and require the hash be part of the
381    /// canonical chain.
382    pub const fn hash_canonical(self, hash: alloy_primitives::B256) -> Self {
383        self.block(BlockId::hash_canonical(hash))
384    }
385}
386
387impl<N> EthCall<N, Bytes>
388where
389    N: Network,
390{
391    /// Decode the [`Bytes`] returned by an `"eth_call"` into a [`SolCall::Return`] type.
392    ///
393    /// ## Note
394    ///
395    /// The result of the `eth_call` will be [`alloy_sol_types::Result`] with the Ok variant
396    /// containing the decoded [`SolCall::Return`] type.
397    ///
398    /// # Examples
399    ///
400    /// ```ignore
401    /// let call = EthCall::call(provider, data).decode_resp::<MySolCall>().await?.unwrap();
402    ///
403    /// assert!(matches!(call.return_value, MySolCall::MyStruct { .. }));
404    /// ```
405    pub fn decode_resp<S: SolCall>(self) -> EthCall<N, Bytes, alloy_sol_types::Result<S::Return>> {
406        self.map_resp(|data| S::abi_decode_returns(&data))
407    }
408}
409
410impl<N, Resp, Output, Map> std::future::IntoFuture for EthCall<N, Resp, Output, Map>
411where
412    N: Network,
413    Resp: RpcRecv,
414    Output: 'static,
415    Map: Fn(Resp) -> Output,
416{
417    type Output = TransportResult<Output>;
418
419    type IntoFuture = EthCallFut<N, Resp, Output, Map>;
420
421    fn into_future(self) -> Self::IntoFuture {
422        EthCallFut {
423            inner: EthCallFutInner::Preparing {
424                caller: self.caller,
425                params: self.params,
426                method: self.method,
427                map: self.map,
428            },
429        }
430    }
431}
432
433#[cfg(test)]
434mod test {
435    use super::*;
436    use alloy_eips::BlockNumberOrTag;
437    use alloy_network::{Ethereum, TransactionBuilder};
438    use alloy_primitives::{address, U256};
439    use alloy_rpc_types_eth::{state::StateOverride, TransactionRequest};
440
441    #[derive(Clone, Copy, Debug)]
442    struct PendingCaller;
443
444    impl Caller<Ethereum, U256> for PendingCaller {
445        fn call(
446            &self,
447            _params: EthCallParams<Ethereum>,
448        ) -> TransportResult<ProviderCall<EthCallParams<Ethereum>, U256>> {
449            Ok(ProviderCall::BoxedFuture(Box::pin(std::future::pending())))
450        }
451
452        fn estimate_gas(
453            &self,
454            _params: EthCallParams<Ethereum>,
455        ) -> TransportResult<ProviderCall<EthCallParams<Ethereum>, U256>> {
456            Ok(ProviderCall::BoxedFuture(Box::pin(std::future::pending())))
457        }
458
459        fn call_many(
460            &self,
461            _params: EthCallManyParams<'_>,
462        ) -> TransportResult<ProviderCall<EthCallManyParams<'static>, U256>> {
463            Ok(ProviderCall::BoxedFuture(Box::pin(std::future::pending())))
464        }
465    }
466
467    #[tokio::test]
468    async fn test_eth_call_with_timeout() {
469        let call = EthCall::call(PendingCaller, TransactionRequest::default());
470
471        assert!(call.timeout(Duration::ZERO).await.is_err());
472    }
473
474    #[test]
475    fn test_serialize_eth_call_params() {
476        let alice = address!("0000000000000000000000000000000000000001");
477        let bob = address!("0000000000000000000000000000000000000002");
478        let data = TransactionRequest::default()
479            .with_from(alice)
480            .with_to(bob)
481            .with_nonce(0)
482            .with_chain_id(1)
483            .value(U256::from(100))
484            .with_gas_limit(21_000)
485            .with_max_priority_fee_per_gas(1_000_000_000)
486            .with_max_fee_per_gas(20_000_000_000);
487
488        let block = BlockId::Number(BlockNumberOrTag::Number(1));
489        let overrides = StateOverride::default();
490
491        // Expected: [data]
492        let params: EthCallParams<Ethereum> = EthCallParams::new(data.clone());
493
494        assert_eq!(params.data(), &data);
495        assert_eq!(params.block(), None);
496        assert_eq!(params.overrides(), None);
497        assert_eq!(
498            serde_json::to_string(&params).unwrap(),
499            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"}]"#
500        );
501
502        // Expected: [data, block, overrides]
503        let params: EthCallParams<Ethereum> =
504            EthCallParams::new(data.clone()).with_block(block).with_overrides(overrides.clone());
505
506        assert_eq!(params.data(), &data);
507        assert_eq!(params.block(), Some(block));
508        assert_eq!(params.overrides(), Some(&overrides));
509        assert_eq!(
510            serde_json::to_string(&params).unwrap(),
511            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1",{}]"#
512        );
513
514        // Expected: [data, (default), overrides]
515        let params: EthCallParams<Ethereum> =
516            EthCallParams::new(data.clone()).with_overrides(overrides.clone());
517
518        assert_eq!(params.data(), &data);
519        assert_eq!(params.block(), None);
520        assert_eq!(params.overrides(), Some(&overrides));
521        assert_eq!(
522            serde_json::to_string(&params).unwrap(),
523            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"latest",{}]"#
524        );
525
526        // Expected: [data, block]
527        let params: EthCallParams<Ethereum> = EthCallParams::new(data.clone()).with_block(block);
528
529        assert_eq!(params.data(), &data);
530        assert_eq!(params.block(), Some(block));
531        assert_eq!(params.overrides(), None);
532        assert_eq!(
533            serde_json::to_string(&params).unwrap(),
534            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1"]"#
535        );
536    }
537}