alloy_provider/provider/multicall/
inner_types.rs1use std::{fmt::Debug, marker::PhantomData};
2
3use super::{
4 bindings::IMulticall3::{Call, Call3, Call3Value},
5 CallTuple,
6};
7use alloy_primitives::{Address, Bytes, U256};
8use alloy_sol_types::SolCall;
9use thiserror::Error;
10
11pub type Result<T, E = MulticallError> = core::result::Result<T, E>;
13
14#[derive(Debug, Clone, PartialEq, Eq, Error)]
16#[error("Call failed at index {idx} with return data: {return_data:?}")]
17pub struct Failure {
18 pub idx: usize,
20 pub return_data: Bytes,
22}
23
24pub trait MulticallItem {
27 type Decoder: SolCall;
29
30 fn target(&self) -> Address;
32 fn input(&self) -> Bytes;
34}
35
36#[derive(Debug)]
38pub struct CallItemBuilder;
39
40impl CallItemBuilder {
41 #[expect(clippy::new_ret_no_self)]
43 pub fn new<Item: MulticallItem>(item: Item) -> CallItem<Item::Decoder> {
44 CallItem::new(item.target(), item.input())
45 }
46}
47
48#[derive(Clone)]
51pub struct CallItem<D: SolCall> {
52 target: Address,
53 input: Bytes,
54 allow_failure: bool,
55 value: U256,
56 decoder: PhantomData<D>,
57}
58
59impl<D: SolCall> Debug for CallItem<D> {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("CallItem")
62 .field("target", &self.target)
63 .field("allow_failure", &self.allow_failure)
64 .field("value", &self.value)
65 .field("input", &self.input)
66 .finish()
67 }
68}
69
70impl<D: SolCall> CallItem<D> {
71 pub const fn new(target: Address, input: Bytes) -> Self {
73 Self { target, input, allow_failure: false, value: U256::ZERO, decoder: PhantomData }
74 }
75
76 pub const fn allow_failure(mut self, allow_failure: bool) -> Self {
78 self.allow_failure = allow_failure;
79 self
80 }
81
82 pub const fn value(mut self, value: U256) -> Self {
84 self.value = value;
85 self
86 }
87}
88impl<D: SolCall> CallInfoTrait for CallItem<D> {
89 fn to_call(&self) -> Call {
90 Call { target: self.target, callData: self.input.clone() }
91 }
92
93 fn to_call3(&self) -> Call3 {
94 Call3 {
95 target: self.target,
96 allowFailure: self.allow_failure,
97 callData: self.input.clone(),
98 }
99 }
100
101 fn to_call3_value(&self) -> Call3Value {
102 Call3Value {
103 target: self.target,
104 allowFailure: self.allow_failure,
105 callData: self.input.clone(),
106 value: self.value,
107 }
108 }
109}
110pub trait CallInfoTrait: std::fmt::Debug {
112 fn to_call(&self) -> Call;
114 fn to_call3(&self) -> Call3;
116 fn to_call3_value(&self) -> Call3Value;
118}
119
120#[derive(Debug)]
123pub struct Dynamic<D: SolCall>(PhantomData<fn(D) -> D>);
124
125impl<D: SolCall> CallTuple for Dynamic<D> {
126 type Returns = Vec<Result<D::Return, Failure>>;
127 type SuccessReturns = Vec<D::Return>;
128
129 fn decode_returns(data: &[Bytes]) -> Result<Self::SuccessReturns> {
130 data.iter().map(|d| D::abi_decode_returns(d).map_err(MulticallError::DecodeError)).collect()
131 }
132
133 fn decode_return_results(
134 results: &[super::bindings::IMulticall3::Result],
135 ) -> Result<Self::Returns> {
136 let mut ret = vec![];
137 for (idx, res) in results.iter().enumerate() {
138 if res.success {
139 ret.push(Ok(
140 D::abi_decode_returns(&res.returnData).map_err(MulticallError::DecodeError)?
141 ));
142 } else {
143 ret.push(Err(Failure { idx, return_data: res.returnData.clone() }));
144 }
145 }
146
147 Ok(ret)
148 }
149
150 fn try_into_success(results: Self::Returns) -> Result<Self::SuccessReturns> {
151 let mut ret = vec![];
152 for res in results {
153 ret.push(res.map_err(|e| MulticallError::CallFailed(e.return_data))?);
154 }
155 Ok(ret)
156 }
157}
158
159#[derive(Debug, Error)]
161pub enum MulticallError {
162 #[error("batch contains a tx with a value, try using .send() instead")]
164 ValueTx,
165 #[error("could not decode")]
167 DecodeError(alloy_sol_types::Error),
168 #[error("no return data")]
170 NoReturnData,
171 #[error("call failed when success was assured, this occurs when try_into_success is called on a failed call")]
173 CallFailed(Bytes),
174 #[error("Transport error: {0}")]
176 TransportError(#[from] alloy_transport::TransportError),
177}