use df_hyperliquid_core::client::evm::abi::{AbiError, Token};
use crate::U256;
use crate::client::read::one;
use crate::client::{Arg, Method, Read};
use crate::utils::Address;
pub const MULTICALL3: Address = match Address::parse_const("0xcA11bde05977b3631167028862bE2a173976CA11") {
Some(address) => address,
None => Address::ZERO,
};
pub const AGGREGATE3: Method =
Method::new("aggregate3((address,bool,bytes)[])", "(bool success, bytes returnData)[] returnData");
pub const GET_ETH_BALANCE: Method = Method::new("getEthBalance(address)", "uint256 balance");
pub const GET_BLOCK_NUMBER: Method = Method::new("getBlockNumber()", "uint256 blockNumber");
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Call3 {
pub target: Address,
pub allow_failure: bool,
pub data: Vec<u8>,
}
impl Call3 {
#[must_use]
pub fn of<T>(read: &Read<T>, allow_failure: bool) -> Call3 {
Call3 { target: read.call.to.unwrap_or(Address::ZERO), allow_failure, data: read.call.data.clone() }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Outcome {
pub success: bool,
pub data: Vec<u8>,
}
#[must_use]
pub fn aggregate3(calls: &[Call3]) -> Read<Vec<Outcome>> {
let mut read = Read::new(MULTICALL3, &AGGREGATE3, &[], |answer| {
let tokens = AGGREGATE3.answer(answer)?;
let bad = || AbiError::Type("aggregate3: not a list of (bool, bytes)".to_owned());
let [Token::Array(items)] = tokens.as_slice() else { return Err(bad()) };
items
.iter()
.map(|item| match item.as_items() {
Some([Token::Bool(success), Token::Bytes(data)]) => {
Ok(Outcome { success: *success, data: data.clone() })
}
_ => Err(bad()),
})
.collect()
});
read.call.data = aggregate3_calldata(calls);
read
}
fn aggregate3_calldata(calls: &[Call3]) -> Vec<u8> {
let word = |value: usize| U256::from_u64(value as u64).to_be_bytes();
let mut out = AGGREGATE3.selector().to_vec();
out.extend_from_slice(&word(32));
out.extend_from_slice(&word(calls.len()));
let mut tails = Vec::new();
let mut offsets = Vec::with_capacity(calls.len());
for call in calls {
offsets.push(calls.len() * 32 + tails.len());
let mut tuple = Vec::new();
tuple.extend_from_slice(&crate::B256::from_address(call.target).0);
tuple.extend_from_slice(&word(usize::from(call.allow_failure)));
tuple.extend_from_slice(&word(96));
tuple.extend_from_slice(&word(call.data.len()));
tuple.extend_from_slice(&call.data);
tuple.resize(tuple.len() + call.data.len().next_multiple_of(32) - call.data.len(), 0);
tails.extend(tuple);
}
for offset in offsets {
out.extend_from_slice(&word(offset));
}
out.extend(tails);
out
}
#[must_use]
pub fn eth_balance(owner: Address) -> Read<U256> {
Read::new(MULTICALL3, &GET_ETH_BALANCE, &[Arg::Address(owner)], |answer| {
one(&GET_ETH_BALANCE, answer, Token::as_u256)
})
}
#[must_use]
pub fn block_number() -> Read<U256> {
Read::new(MULTICALL3, &GET_BLOCK_NUMBER, &[], |answer| one(&GET_BLOCK_NUMBER, answer, Token::as_u256))
}
#[cfg(test)]
#[path = "tests/multicall.rs"]
mod tests;