alloy_provider/provider/multicall/
tuple.rs

1use super::{
2    bindings::IMulticall3::Result as MulticallResult,
3    inner_types::{Dynamic, Failure, MulticallError, Result},
4};
5use alloy_primitives::Bytes;
6use alloy_sol_types::SolCall;
7
8/// Sealed trait to prevent external implementations
9mod private {
10    #[allow(unnameable_types)]
11    pub trait Sealed {}
12}
13use private::Sealed;
14
15/// A trait for tuples that can have types pushed to them
16#[doc(hidden)]
17#[allow(unnameable_types)]
18pub trait TuplePush<T> {
19    /// The resulting type after pushing T
20    type Pushed;
21}
22
23/// A trait for tuples of SolCalls that can be decoded
24#[doc(hidden)]
25pub trait CallTuple: Sealed {
26    /// Flattened tuple consisting of the return values of each call.
27    ///
28    /// Each return value is wrapped in a [`Result`] in order to account for failures in calls when
29    /// others succeed.
30    ///
31    /// - [`Result::Ok`] contains the decoded return value of the call.
32    /// - [`Result::Err`] contains a [`Failure`] struct with the index of the call that failed and
33    ///   the raw bytes returned on failure.
34    ///
35    /// For example,
36    ///
37    /// ```ignore
38    /// use alloy_sol_types::sol;
39    /// use alloy_primitives::Address;
40    /// use alloy_provider::{CallItem, Provider, ProviderBuilder, Result, Failure};
41    /// use crate::SomeContract::failureCall;
42    /// sol! {
43    ///     #[derive(Debug)]
44    ///     #[sol(rpc)]
45    ///     contract SomeContract {
46    ///       function success() external;
47    ///       function failure() external;
48    ///     }
49    /// }
50    ///
51    /// #[tokio::main]
52    /// async fn main() {
53    ///     let target = Address::random();
54    ///     let provider = ProviderBuilder::new().connect("https://..").await.unwrap();    
55    ///     let some_contract = SomeContract::new(target, &provider);
56    ///     let allow_failure_call = CallItem::<failureCall>::new(target, some_contract.failure().input()).allow_failure(true); // This calls is allowed to fail so that the batch doesn't revert.
57    ///
58    ///     let multicall = provider.multicall().add(some_contract.success()).add_call(allow_failure_call);
59    ///
60    ///     let (success_result, failure_result) = multicall.aggregate3().await.unwrap();
61    ///     match success_result {
62    ///       Ok(success) => { println!("Success: {:?}", success) },
63    ///       Err(failure) => { /* handle failure */ },
64    ///     }
65    ///
66    ///     match failure_result {
67    ///       Ok(success) => { /* handle success */ },
68    ///       Err(failure) => { assert!(matches!(failure, Failure { idx: 1, return_data: _ })) },
69    ///     }
70    /// }
71    /// ```
72    type Returns;
73
74    /// Flattened tuple consisting of the decoded return values of each call.
75    type SuccessReturns;
76
77    /// Decode the returns from a sequence of bytes
78    ///
79    /// To be used for calls where success is ensured i.e `allow_failure` for all calls is false.
80    fn decode_returns(data: &[Bytes]) -> Result<Self::SuccessReturns>;
81
82    /// Converts Returns to SuccessReturns if all results are Ok
83    fn decode_return_results(results: &[MulticallResult]) -> Result<Self::Returns>;
84
85    /// Converts Returns to SuccessReturns if all results are Ok
86    fn try_into_success(results: Self::Returns) -> Result<Self::SuccessReturns>;
87}
88
89/// Type indicating that the [`MulticallBuilder`](crate::MulticallBuilder) is empty.
90#[derive(Debug, Clone)]
91pub struct Empty;
92
93impl Sealed for Empty {}
94
95impl<T: SolCall> TuplePush<T> for Empty {
96    type Pushed = (T,);
97}
98
99impl CallTuple for Empty {
100    type Returns = ();
101    type SuccessReturns = ();
102    fn decode_returns(_: &[Bytes]) -> Result<Self::SuccessReturns> {
103        Ok(())
104    }
105    fn decode_return_results(_results: &[MulticallResult]) -> Result<Self::Returns> {
106        Ok(())
107    }
108    fn try_into_success(_: Self::Returns) -> Result<Self::SuccessReturns> {
109        Ok(())
110    }
111}
112
113impl<D: SolCall> Sealed for Dynamic<D> {}
114
115// Macro to implement for tuples of different sizes
116macro_rules! impl_tuple {
117    ($($idx:tt => $ty:ident),+) => {
118        impl<$($ty: SolCall,)+> Sealed for ($($ty,)+) {}
119
120        // Implement pushing a new type onto the tuple
121        impl<T: SolCall, $($ty: SolCall,)+> TuplePush<T> for ($($ty,)+) {
122            type Pushed = ($($ty,)+ T,);
123        }
124
125        // Implement decoding for the tuple
126        impl<$($ty: SolCall,)+> CallTuple for ($($ty,)+) {
127            // The Returns associated type is a tuple of each SolCall's Return type
128            type Returns = ($(Result<$ty::Return, Failure>,)+);
129
130            type SuccessReturns = ($($ty::Return,)+);
131
132            fn decode_returns(data: &[Bytes]) -> Result<Self::SuccessReturns> {
133                if data.len() != count!($($ty),+) {
134                    return Err(MulticallError::NoReturnData);
135                }
136
137                // Decode each return value in order
138                Ok(($($ty::abi_decode_returns(&data[$idx], false).map_err(MulticallError::DecodeError)?,)+))
139            }
140
141            fn decode_return_results(results: &[MulticallResult]) -> Result<Self::Returns> {
142                if results.len() != count!($($ty),+) {
143                    return Err(MulticallError::NoReturnData);
144                }
145
146                Ok(($(
147                    match &results[$idx].success {
148                        true => Ok($ty::abi_decode_returns(&results[$idx].returnData, false).map_err(MulticallError::DecodeError)?),
149                        false => Err(Failure { idx: $idx, return_data: results[$idx].returnData.clone() }),
150                    },
151                )+))
152            }
153
154            fn try_into_success(results: Self::Returns) -> Result<Self::SuccessReturns> {
155                Ok(($(
156                    match results.$idx {
157                        Ok(value) => value,
158                        Err(failure) => return Err(MulticallError::CallFailed(failure.return_data)),
159                    },
160                )+))
161            }
162        }
163    };
164}
165
166// Helper macro to count number of types
167macro_rules! count {
168    () => (0);
169    ($x:tt $(,$xs:tt)*) => (1 + count!($($xs),*));
170}
171
172// Max CALL_LIMIT is 16
173impl_tuple!(0 => T1);
174impl_tuple!(0 => T1, 1 => T2);
175impl_tuple!(0 => T1, 1 => T2, 2 => T3);
176impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4);
177impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5);
178impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6);
179impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7);
180impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8);
181impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9);
182impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10);
183impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10, 10 => T11);
184impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10, 10 => T11, 11 => T12);
185impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10, 10 => T11, 11 => T12, 12 => T13);
186impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10, 10 => T11, 11 => T12, 12 => T13, 13 => T14);
187impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10, 10 => T11, 11 => T12, 12 => T13, 13 => T14, 14 => T15);
188impl_tuple!(0 => T1, 1 => T2, 2 => T3, 3 => T4, 4 => T5, 5 => T6, 6 => T7, 7 => T8, 8 => T9, 9 => T10, 10 => T11, 11 => T12, 12 => T13, 13 => T14, 14 => T15, 15 => T16);