use crate::{ContractInstance, Error, Result};
use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt};
use alloy_json_abi::{Function, JsonAbi};
use alloy_primitives::{
map::{FbHashMap, SelectorHashMap},
Address, FixedBytes, Selector,
};
use std::collections::BTreeMap;
#[derive(Clone, Debug)]
pub struct Interface {
abi: JsonAbi,
functions: SelectorHashMap<(String, usize)>,
}
impl Interface {
pub fn new(abi: JsonAbi) -> Self {
let functions = create_mapping(&abi.functions, Function::selector);
Self { abi, functions }
}
pub fn encode_input(&self, name: &str, args: &[DynSolValue]) -> Result<Vec<u8>> {
self.get_from_name(name)?.abi_encode_input(args).map_err(Into::into)
}
pub fn encode_input_with_selector(
&self,
selector: &Selector,
args: &[DynSolValue],
) -> Result<Vec<u8>> {
self.get_from_selector(selector)?.abi_encode_input(args).map_err(Into::into)
}
pub fn decode_input(&self, name: &str, data: &[u8]) -> Result<Vec<DynSolValue>> {
self.get_from_name(name)?.abi_decode_input(data).map_err(Into::into)
}
pub fn decode_input_with_selector(
&self,
selector: &Selector,
data: &[u8],
) -> Result<Vec<DynSolValue>> {
self.get_from_selector(selector)?.abi_decode_input(data).map_err(Into::into)
}
pub fn decode_output(&self, name: &str, data: &[u8]) -> Result<Vec<DynSolValue>> {
self.get_from_name(name)?.abi_decode_output(data).map_err(Into::into)
}
pub fn decode_output_with_selector(
&self,
selector: &Selector,
data: &[u8],
) -> Result<Vec<DynSolValue>> {
self.get_from_selector(selector)?.abi_decode_output(data).map_err(Into::into)
}
pub const fn abi(&self) -> &JsonAbi {
&self.abi
}
pub fn into_abi(self) -> JsonAbi {
self.abi
}
pub(crate) fn get_from_name(&self, name: &str) -> Result<&Function> {
self.abi
.function(name)
.and_then(|r| r.first())
.ok_or_else(|| Error::UnknownFunction(name.to_string()))
}
pub(crate) fn get_from_selector(&self, selector: &Selector) -> Result<&Function> {
self.functions
.get(selector)
.map(|(name, index)| &self.abi.functions[name][*index])
.ok_or_else(|| Error::UnknownSelector(*selector))
}
pub const fn connect<P, N>(self, address: Address, provider: P) -> ContractInstance<P, N> {
ContractInstance::new(address, provider, self)
}
}
fn create_mapping<const N: usize, T, F>(
elements: &BTreeMap<String, Vec<T>>,
signature: F,
) -> FbHashMap<N, (String, usize)>
where
F: Fn(&T) -> FixedBytes<N> + Copy,
{
elements
.iter()
.flat_map(|(name, sub_elements)| {
sub_elements
.iter()
.enumerate()
.map(move |(index, element)| (signature(element), (name.to_owned(), index)))
})
.collect()
}