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::{future::Future, marker::PhantomData, sync::Arc, task::Poll};
14
15mod params;
16pub use params::{EthCallManyParams, EthCallParams};
17
18mod call_many;
19pub use call_many::EthCallMany;
20
21mod caller;
22pub use caller::Caller;
23
24/// The [`EthCallFut`] future is the future type for an `eth_call` RPC request.
25#[derive(Debug)]
26#[doc(hidden)] // Not public API.
27#[expect(unnameable_types)]
28#[pin_project::pin_project]
29pub struct EthCallFut<N, Resp, Output, Map>
30where
31    N: Network,
32    Resp: RpcRecv,
33    Output: 'static,
34    Map: Fn(Resp) -> Output,
35{
36    inner: EthCallFutInner<N, Resp, Output, Map>,
37}
38
39enum EthCallFutInner<N, Resp, Output, Map>
40where
41    N: Network,
42    Resp: RpcRecv,
43    Map: Fn(Resp) -> Output,
44{
45    Preparing {
46        caller: Arc<dyn Caller<N, Resp>>,
47        params: EthCallParams<N>,
48        method: &'static str,
49        map: Map,
50    },
51    Running {
52        map: Map,
53        fut: ProviderCall<EthCallParams<N>, Resp>,
54    },
55    Polling,
56}
57
58impl<N, Resp, Output, Map> core::fmt::Debug for EthCallFutInner<N, Resp, Output, Map>
59where
60    N: Network,
61    Resp: RpcRecv,
62    Output: 'static,
63    Map: Fn(Resp) -> Output,
64{
65    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
66        match self {
67            Self::Preparing { caller: _, params, method, map: _ } => {
68                f.debug_struct("Preparing").field("params", params).field("method", method).finish()
69            }
70            Self::Running { .. } => f.debug_tuple("Running").finish(),
71            Self::Polling => f.debug_tuple("Polling").finish(),
72        }
73    }
74}
75
76impl<N, Resp, Output, Map> EthCallFut<N, Resp, Output, Map>
77where
78    N: Network,
79    Resp: RpcRecv,
80    Output: 'static,
81    Map: Fn(Resp) -> Output,
82{
83    /// Returns `true` if the future is in the preparing state.
84    const fn is_preparing(&self) -> bool {
85        matches!(self.inner, EthCallFutInner::Preparing { .. })
86    }
87
88    /// Returns `true` if the future is in the running state.
89    const fn is_running(&self) -> bool {
90        matches!(self.inner, EthCallFutInner::Running { .. })
91    }
92
93    fn poll_preparing(&mut self, cx: &mut std::task::Context<'_>) -> Poll<TransportResult<Output>> {
94        let EthCallFutInner::Preparing { caller, params, method, map } =
95            std::mem::replace(&mut self.inner, EthCallFutInner::Polling)
96        else {
97            unreachable!("bad state")
98        };
99
100        let fut =
101            if method.eq("eth_call") { caller.call(params) } else { caller.estimate_gas(params) }?;
102
103        self.inner = EthCallFutInner::Running { map, fut };
104
105        self.poll_running(cx)
106    }
107
108    fn poll_running(&mut self, cx: &mut std::task::Context<'_>) -> Poll<TransportResult<Output>> {
109        let EthCallFutInner::Running { ref map, ref mut fut } = self.inner else {
110            unreachable!("bad state")
111        };
112
113        fut.poll_unpin(cx).map(|res| res.map(map))
114    }
115}
116
117impl<N, Resp, Output, Map> Future for EthCallFut<N, Resp, Output, Map>
118where
119    N: Network,
120    Resp: RpcRecv,
121    Output: 'static,
122    Map: Fn(Resp) -> Output,
123{
124    type Output = TransportResult<Output>;
125
126    fn poll(
127        self: std::pin::Pin<&mut Self>,
128        cx: &mut std::task::Context<'_>,
129    ) -> std::task::Poll<Self::Output> {
130        let this = self.get_mut();
131        if this.is_preparing() {
132            this.poll_preparing(cx)
133        } else if this.is_running() {
134            this.poll_running(cx)
135        } else {
136            panic!("unexpected state")
137        }
138    }
139}
140
141/// A builder for an `"eth_call"` request. This type is returned by the
142/// [`Provider::call`] method.
143///
144/// [`Provider::call`]: crate::Provider::call
145#[must_use = "EthCall must be awaited to execute the call"]
146#[derive(Clone)]
147pub struct EthCall<N, Resp, Output = Resp, Map = fn(Resp) -> Output>
148where
149    N: Network,
150    Resp: RpcRecv,
151    Map: Fn(Resp) -> Output,
152{
153    caller: Arc<dyn Caller<N, Resp>>,
154    params: EthCallParams<N>,
155    method: &'static str,
156    map: Map,
157    _pd: PhantomData<fn() -> (Resp, Output)>,
158}
159
160impl<N, Resp> core::fmt::Debug for EthCall<N, Resp>
161where
162    N: Network,
163    Resp: RpcRecv,
164{
165    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166        f.debug_struct("EthCall")
167            .field("params", &self.params)
168            .field("method", &self.method)
169            .finish()
170    }
171}
172
173impl<N, Resp> EthCall<N, Resp>
174where
175    N: Network,
176    Resp: RpcRecv,
177{
178    /// Create a new [`EthCall`].
179    pub fn new(
180        caller: impl Caller<N, Resp> + 'static,
181        method: &'static str,
182        data: N::TransactionRequest,
183    ) -> Self {
184        Self {
185            caller: Arc::new(caller),
186            params: EthCallParams::new(data),
187            method,
188            map: std::convert::identity,
189            _pd: PhantomData,
190        }
191    }
192
193    /// Create a new [`EthCall`] with method set to `"eth_call"`.
194    pub fn call(caller: impl Caller<N, Resp> + 'static, data: N::TransactionRequest) -> Self {
195        Self::new(caller, "eth_call", data)
196    }
197
198    /// Create a new [`EthCall`] with method set to `"eth_estimateGas"`.
199    pub fn gas_estimate(
200        caller: impl Caller<N, Resp> + 'static,
201        data: N::TransactionRequest,
202    ) -> Self {
203        Self::new(caller, "eth_estimateGas", data)
204    }
205}
206
207impl<N, Resp, Output, Map> EthCall<N, Resp, Output, Map>
208where
209    N: Network,
210    Resp: RpcRecv,
211    Map: Fn(Resp) -> Output,
212{
213    /// Map the response to a different type. This is usable for converting
214    /// the response to a more usable type, e.g. changing `U64` to `u64`.
215    ///
216    /// ## Note
217    ///
218    /// Carefully review the rust documentation on [fn pointers] before passing
219    /// them to this function. Unless the pointer is specifically coerced to a
220    /// `fn(_) -> _`, the `NewMap` will be inferred as that function's unique
221    /// type. This can lead to confusing error messages.
222    ///
223    /// [fn pointers]: https://doc.rust-lang.org/std/primitive.fn.html#creating-function-pointers
224    pub fn map_resp<NewOutput, NewMap>(self, map: NewMap) -> EthCall<N, Resp, NewOutput, NewMap>
225    where
226        NewMap: Fn(Resp) -> NewOutput,
227    {
228        EthCall {
229            caller: self.caller,
230            params: self.params,
231            method: self.method,
232            map,
233            _pd: PhantomData,
234        }
235    }
236
237    /// Set the state overrides for this call.
238    pub fn overrides(mut self, overrides: impl Into<StateOverride>) -> Self {
239        self.params.overrides = Some(overrides.into());
240        self
241    }
242
243    /// Set the state overrides for this call, if any.
244    pub fn overrides_opt(mut self, overrides: Option<StateOverride>) -> Self {
245        self.params.overrides = overrides;
246        self
247    }
248
249    /// Appends a single [AccountOverride] to the state override.
250    ///
251    /// Creates a new [`StateOverride`] if none has been set yet.
252    pub fn account_override(mut self, address: Address, account_override: AccountOverride) -> Self {
253        let mut overrides = self.params.overrides.unwrap_or_default();
254        overrides.insert(address, account_override);
255        self.params.overrides = Some(overrides);
256
257        self
258    }
259
260    /// Extends the given [AccountOverride] to the state override.
261    ///
262    /// Creates a new [`StateOverride`] if none has been set yet.
263    pub fn account_overrides(
264        mut self,
265        overrides: impl IntoIterator<Item = (Address, AccountOverride)>,
266    ) -> Self {
267        for (addr, account_override) in overrides.into_iter() {
268            self = self.account_override(addr, account_override);
269        }
270        self
271    }
272
273    /// Sets the block overrides for this call.
274    pub fn with_block_overrides(mut self, overrides: BlockOverrides) -> Self {
275        self.params.block_overrides = Some(overrides);
276        self
277    }
278
279    /// Sets the block overrides for this call, if any.
280    pub fn with_block_overrides_opt(mut self, overrides: Option<BlockOverrides>) -> Self {
281        self.params.block_overrides = overrides;
282        self
283    }
284
285    /// Set the block to use for this call.
286    pub const fn block(mut self, block: BlockId) -> Self {
287        self.params.block = Some(block);
288        self
289    }
290}
291
292impl<N> EthCall<N, Bytes>
293where
294    N: Network,
295{
296    /// Decode the [`Bytes`] returned by an `"eth_call"` into a [`SolCall::Return`] type.
297    ///
298    /// ## Note
299    ///
300    /// The result of the `eth_call` will be [`alloy_sol_types::Result`] with the Ok variant
301    /// containing the decoded [`SolCall::Return`] type.
302    ///
303    /// # Examples
304    ///
305    /// ```ignore
306    /// let call = EthCall::call(provider, data).decode_resp::<MySolCall>().await?.unwrap();
307    ///
308    /// assert!(matches!(call.return_value, MySolCall::MyStruct { .. }));
309    /// ```
310    pub fn decode_resp<S: SolCall>(self) -> EthCall<N, Bytes, alloy_sol_types::Result<S::Return>> {
311        self.map_resp(|data| S::abi_decode_returns(&data))
312    }
313}
314
315impl<N, Resp, Output, Map> std::future::IntoFuture for EthCall<N, Resp, Output, Map>
316where
317    N: Network,
318    Resp: RpcRecv,
319    Output: 'static,
320    Map: Fn(Resp) -> Output,
321{
322    type Output = TransportResult<Output>;
323
324    type IntoFuture = EthCallFut<N, Resp, Output, Map>;
325
326    fn into_future(self) -> Self::IntoFuture {
327        EthCallFut {
328            inner: EthCallFutInner::Preparing {
329                caller: self.caller,
330                params: self.params,
331                method: self.method,
332                map: self.map,
333            },
334        }
335    }
336}
337
338#[cfg(test)]
339mod test {
340    use super::*;
341    use alloy_eips::BlockNumberOrTag;
342    use alloy_network::{Ethereum, TransactionBuilder};
343    use alloy_primitives::{address, U256};
344    use alloy_rpc_types_eth::{state::StateOverride, TransactionRequest};
345
346    #[test]
347    fn test_serialize_eth_call_params() {
348        let alice = address!("0000000000000000000000000000000000000001");
349        let bob = address!("0000000000000000000000000000000000000002");
350        let data = TransactionRequest::default()
351            .with_from(alice)
352            .with_to(bob)
353            .with_nonce(0)
354            .with_chain_id(1)
355            .value(U256::from(100))
356            .with_gas_limit(21_000)
357            .with_max_priority_fee_per_gas(1_000_000_000)
358            .with_max_fee_per_gas(20_000_000_000);
359
360        let block = BlockId::Number(BlockNumberOrTag::Number(1));
361        let overrides = StateOverride::default();
362
363        // Expected: [data]
364        let params: EthCallParams<Ethereum> = EthCallParams::new(data.clone());
365
366        assert_eq!(params.data(), &data);
367        assert_eq!(params.block(), None);
368        assert_eq!(params.overrides(), None);
369        assert_eq!(
370            serde_json::to_string(&params).unwrap(),
371            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"}]"#
372        );
373
374        // Expected: [data, block, overrides]
375        let params: EthCallParams<Ethereum> =
376            EthCallParams::new(data.clone()).with_block(block).with_overrides(overrides.clone());
377
378        assert_eq!(params.data(), &data);
379        assert_eq!(params.block(), Some(block));
380        assert_eq!(params.overrides(), Some(&overrides));
381        assert_eq!(
382            serde_json::to_string(&params).unwrap(),
383            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1",{}]"#
384        );
385
386        // Expected: [data, (default), overrides]
387        let params: EthCallParams<Ethereum> =
388            EthCallParams::new(data.clone()).with_overrides(overrides.clone());
389
390        assert_eq!(params.data(), &data);
391        assert_eq!(params.block(), None);
392        assert_eq!(params.overrides(), Some(&overrides));
393        assert_eq!(
394            serde_json::to_string(&params).unwrap(),
395            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"latest",{}]"#
396        );
397
398        // Expected: [data, block]
399        let params: EthCallParams<Ethereum> = EthCallParams::new(data.clone()).with_block(block);
400
401        assert_eq!(params.data(), &data);
402        assert_eq!(params.block(), Some(block));
403        assert_eq!(params.overrides(), None);
404        assert_eq!(
405            serde_json::to_string(&params).unwrap(),
406            r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1"]"#
407        );
408    }
409}