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#[derive(Debug)]
38#[doc(hidden)] #[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 const fn is_preparing(&self) -> bool {
97 matches!(self.inner, EthCallFutInner::Preparing { .. })
98 }
99
100 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#[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 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 pub fn call(caller: impl Caller<N, Resp> + 'static, data: N::TransactionRequest) -> Self {
212 Self::new(caller, "eth_call", data)
213 }
214
215 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 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 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 pub fn overrides(mut self, overrides: impl Into<StateOverride>) -> Self {
286 self.params.overrides = Some(overrides.into());
287 self
288 }
289
290 pub fn overrides_opt(mut self, overrides: Option<StateOverride>) -> Self {
292 self.params.overrides = overrides;
293 self
294 }
295
296 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 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 pub fn with_block_overrides(mut self, overrides: BlockOverrides) -> Self {
322 self.params.block_overrides = Some(overrides);
323 self
324 }
325
326 pub fn with_block_overrides_opt(mut self, overrides: Option<BlockOverrides>) -> Self {
328 self.params.block_overrides = overrides;
329 self
330 }
331
332 pub const fn block(mut self, block: BlockId) -> Self {
334 self.params.block = Some(block);
335 self
336 }
337
338 pub const fn pending(self) -> Self {
340 self.block(BlockId::pending())
341 }
342
343 pub const fn latest(self) -> Self {
345 self.block(BlockId::latest())
346 }
347
348 pub const fn earliest(self) -> Self {
350 self.block(BlockId::earliest())
351 }
352
353 pub const fn finalized(self) -> Self {
355 self.block(BlockId::finalized())
356 }
357
358 pub const fn safe(self) -> Self {
360 self.block(BlockId::safe())
361 }
362
363 pub const fn number(self, number: u64) -> Self {
365 self.block(BlockId::number(number))
366 }
367
368 pub const fn hash(self, hash: alloy_primitives::B256) -> Self {
371 self.block(BlockId::hash(hash))
372 }
373
374 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 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 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(¶ms).unwrap(),
493 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"}]"#
494 );
495
496 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(¶ms).unwrap(),
505 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1",{}]"#
506 );
507
508 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(¶ms).unwrap(),
517 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"latest",{}]"#
518 );
519
520 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(¶ms).unwrap(),
528 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1"]"#
529 );
530 }
531}