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#[derive(Debug)]
26#[doc(hidden)] #[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 const fn is_preparing(&self) -> bool {
85 matches!(self.inner, EthCallFutInner::Preparing { .. })
86 }
87
88 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#[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 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 pub fn call(caller: impl Caller<N, Resp> + 'static, data: N::TransactionRequest) -> Self {
195 Self::new(caller, "eth_call", data)
196 }
197
198 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 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 pub fn overrides(mut self, overrides: impl Into<StateOverride>) -> Self {
239 self.params.overrides = Some(overrides.into());
240 self
241 }
242
243 pub fn overrides_opt(mut self, overrides: Option<StateOverride>) -> Self {
245 self.params.overrides = overrides;
246 self
247 }
248
249 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 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 pub fn with_block_overrides(mut self, overrides: BlockOverrides) -> Self {
275 self.params.block_overrides = Some(overrides);
276 self
277 }
278
279 pub fn with_block_overrides_opt(mut self, overrides: Option<BlockOverrides>) -> Self {
281 self.params.block_overrides = overrides;
282 self
283 }
284
285 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 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 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(¶ms).unwrap(),
371 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"}]"#
372 );
373
374 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(¶ms).unwrap(),
383 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1",{}]"#
384 );
385
386 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(¶ms).unwrap(),
395 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"latest",{}]"#
396 );
397
398 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(¶ms).unwrap(),
406 r#"[{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","maxFeePerGas":"0x4a817c800","maxPriorityFeePerGas":"0x3b9aca00","gas":"0x5208","value":"0x64","nonce":"0x0","chainId":"0x1"},"0x1"]"#
407 );
408 }
409}