alloy_contract/
interface.rs

1use crate::{ContractInstance, Error, Result};
2use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt};
3use alloy_json_abi::{Function, JsonAbi};
4use alloy_primitives::{
5    map::{FbHashMap, SelectorHashMap},
6    Address, FixedBytes, Selector,
7};
8use std::collections::BTreeMap;
9
10/// A smart contract interface.
11#[derive(Clone, Debug)]
12pub struct Interface {
13    abi: JsonAbi,
14    functions: SelectorHashMap<(String, usize)>,
15}
16
17// TODO: events/errors
18impl Interface {
19    /// Creates a new contract interface from the provided ABI.
20    pub fn new(abi: JsonAbi) -> Self {
21        let functions = create_mapping(&abi.functions, Function::selector);
22        Self { abi, functions }
23    }
24
25    /// Returns the ABI encoded data (including the selector) for the provided function and
26    /// arguments.
27    ///
28    /// # Note
29    ///
30    /// If the function exists multiple times and you want to use one of the overloaded versions,
31    /// consider using [`Self::encode_input_with_selector`].
32    pub fn encode_input(&self, name: &str, args: &[DynSolValue]) -> Result<Vec<u8>> {
33        self.get_from_name(name)?.abi_encode_input(args).map_err(Into::into)
34    }
35
36    /// Returns the ABI encoded data (including the selector) for the function with the provided
37    /// selector and arguments.
38    pub fn encode_input_with_selector(
39        &self,
40        selector: &Selector,
41        args: &[DynSolValue],
42    ) -> Result<Vec<u8>> {
43        self.get_from_selector(selector)?.abi_encode_input(args).map_err(Into::into)
44    }
45
46    /// ABI-decodes the given data according to the function's types.
47    ///
48    /// # Note
49    ///
50    /// If the function exists multiple times and you want to use one of the overloaded versions,
51    /// consider using [`Self::decode_input_with_selector`].
52    pub fn decode_input(&self, name: &str, data: &[u8]) -> Result<Vec<DynSolValue>> {
53        self.get_from_name(name)?.abi_decode_input(data).map_err(Into::into)
54    }
55
56    /// Decode the provided ABI encoded bytes as the input of the provided function selector.
57    pub fn decode_input_with_selector(
58        &self,
59        selector: &Selector,
60        data: &[u8],
61    ) -> Result<Vec<DynSolValue>> {
62        self.get_from_selector(selector)?.abi_decode_input(data).map_err(Into::into)
63    }
64
65    /// Decode the provided ABI encoded bytes as the output of the first function with the given
66    /// name.
67    ///
68    /// # Note
69    ///
70    /// If there are multiple functions with the same name, consider using
71    /// [`Self::decode_output_with_selector`]
72    pub fn decode_output(&self, name: &str, data: &[u8]) -> Result<Vec<DynSolValue>> {
73        self.get_from_name(name)?.abi_decode_output(data).map_err(Into::into)
74    }
75
76    /// Decode the provided ABI encoded bytes as the output of the provided function selector.
77    pub fn decode_output_with_selector(
78        &self,
79        selector: &Selector,
80        data: &[u8],
81    ) -> Result<Vec<DynSolValue>> {
82        self.get_from_selector(selector)?.abi_decode_output(data).map_err(Into::into)
83    }
84
85    /// Returns a reference to the contract's ABI.
86    pub const fn abi(&self) -> &JsonAbi {
87        &self.abi
88    }
89
90    /// Consumes the interface, returning the inner ABI.
91    pub fn into_abi(self) -> JsonAbi {
92        self.abi
93    }
94
95    pub(crate) fn get_from_name(&self, name: &str) -> Result<&Function> {
96        self.abi
97            .function(name)
98            .and_then(|r| r.first())
99            .ok_or_else(|| Error::UnknownFunction(name.to_string()))
100    }
101
102    pub(crate) fn get_from_selector(&self, selector: &Selector) -> Result<&Function> {
103        self.functions
104            .get(selector)
105            .map(|(name, index)| &self.abi.functions[name][*index])
106            .ok_or_else(|| Error::UnknownSelector(*selector))
107    }
108
109    /// Create a [`ContractInstance`] from this ABI for a contract at the given address.
110    pub const fn connect<P, N>(self, address: Address, provider: P) -> ContractInstance<P, N> {
111        ContractInstance::new(address, provider, self)
112    }
113}
114
115/// Utility function for creating a mapping between a unique signature and a
116/// name-index pair for accessing contract ABI items.
117fn create_mapping<const N: usize, T, F>(
118    elements: &BTreeMap<String, Vec<T>>,
119    signature: F,
120) -> FbHashMap<N, (String, usize)>
121where
122    F: Fn(&T) -> FixedBytes<N> + Copy,
123{
124    elements
125        .iter()
126        .flat_map(|(name, sub_elements)| {
127            sub_elements
128                .iter()
129                .enumerate()
130                .map(move |(index, element)| (signature(element), (name.to_owned(), index)))
131        })
132        .collect()
133}