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