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