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#[derive(Debug)]
23#[doc(hidden)] #[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 const fn is_preparing(&self) -> bool {
82 matches!(self.inner, EthCallFutInner::Preparing { .. })
83 }
84
85 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#[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 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 pub fn call(caller: impl Caller<N, Resp> + 'static, data: N::TransactionRequest) -> Self {
192 Self::new(caller, "eth_call", data)
193 }
194
195 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 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 pub fn overrides(mut self, overrides: impl Into<StateOverride>) -> Self {
236 self.params.overrides = Some(overrides.into());
237 self
238 }
239
240 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 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 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 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 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(¶ms).unwrap(),
350 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"}]"#
351 );
352
353 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(¶ms).unwrap(),
362 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1",{}]"#
363 );
364
365 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(¶ms).unwrap(),
374 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"latest",{}]"#
375 );
376
377 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(¶ms).unwrap(),
385 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1"]"#
386 );
387 }
388}