Skip to main content

huff_neo_utils/
abi.rs

1//! ## Abi
2//!
3//! A module containing ABI type definitions for ethereum contracts.
4//!
5//! #### Usage
6//!
7//! Let's say you have a `Contract<'a>` generated from the [huff_parser](./huff_parser),
8//! representing an AST. This crate lets you easily convert the `Contract` into an `Abi`
9//! instance like so:
10//!
11//! ```rust
12//! use std::sync::{Arc, Mutex};
13//! use indexmap::IndexMap;
14//! use huff_neo_utils::ast::abi::FunctionType;
15//! use huff_neo_utils::ast::span::AstSpan;
16//! use huff_neo_utils::prelude::*;
17//!
18//! // Generate a default contract for demonstrative purposes.
19//! // Realistically, contract generation would be done as shown in [huff_parser](./huff_parser)
20//! let contract = Contract {
21//!     macros: IndexMap::new(),
22//!     invocations: vec![],
23//!     imports: vec![],
24//!     constants: Arc::new(Mutex::new(vec![])),
25//!     errors: vec![],
26//!     functions: vec![huff_neo_utils::ast::abi::FunctionDefinition {
27//!         name: "CONSTRUCTOR".to_string(),
28//!         signature: [0u8, 0u8, 0u8, 0u8],
29//!         inputs: vec![],
30//!         fn_type: FunctionType::NonPayable,
31//!         outputs: vec![],
32//!         span: AstSpan(vec![]),
33//!     }],
34//!     events: vec![],
35//!     tables: vec![],
36//!     labels: Default::default(),
37//!     flattened_source: None,
38//!     source_files: vec![],
39//!     source_map: vec![],
40//!     runtime_size: None,
41//! };
42//!
43//! // Create an ABI using that generate contract
44//! let abi: Abi = contract.into();
45//! ```
46
47use crate::ast;
48use crate::ast::abi::FunctionType;
49use crate::ast::huff::{self};
50use serde::{Deserialize, Serialize};
51use std::{collections::BTreeMap, fmt};
52
53/// #### Abi
54///
55/// The ABI of the generated code.
56#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
57#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
58#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
59pub struct Abi {
60    /// The constructor
61    pub constructor: Option<Constructor>,
62    /// A list of functions and their definitions
63    pub functions: BTreeMap<String, Function>,
64    /// A list of events and their definitions
65    pub events: BTreeMap<String, Event>,
66    /// A list of errors and their definitions
67    pub errors: BTreeMap<String, Error>,
68    /// If the contract defines receive logic
69    pub receive: bool,
70    /// If the contract defines fallback logic
71    pub fallback: bool,
72}
73
74impl Abi {
75    /// Public associated function to instantiate a new Abi.
76    pub fn new() -> Self {
77        Self::default()
78    }
79}
80
81// Allows for simple ABI Generation by directly translating the AST
82impl From<huff::Contract> for Abi {
83    fn from(contract: huff::Contract) -> Self {
84        // The inputs of `#define function constructor(...)`. A CONSTRUCTOR macro without that
85        // declaration takes no arguments; its macro parameters are not ABI inputs.
86        let constructor = contract
87            .functions
88            .iter()
89            .find(|function| function.name.eq_ignore_ascii_case("constructor"))
90            .map(|func| Constructor {
91                inputs: func
92                    .inputs
93                    .iter()
94                    .map(|argument| FunctionParam {
95                        name: argument.name.clone().unwrap_or_default(),
96                        kind: argument.arg_type.clone().unwrap_or_default().into(),
97                        internal_type: None,
98                    })
99                    .collect(),
100            })
101            .or_else(|| contract.macros.contains_key("CONSTRUCTOR").then(|| Constructor { inputs: vec![] }));
102
103        // Instantiate functions and events
104        let mut functions = BTreeMap::new();
105        let mut events = BTreeMap::new();
106        let mut errors = BTreeMap::new();
107
108        // Translate contract functions
109        // Excluding constructor
110        functions.extend(
111            contract
112                .functions
113                .iter()
114                .filter(|function: &&ast::abi::FunctionDefinition| !function.name.eq_ignore_ascii_case("constructor"))
115                .map(|function| {
116                    (
117                        function.name.to_string(),
118                        Function {
119                            name: function.name.to_string(),
120                            inputs: function
121                                .inputs
122                                .iter()
123                                .map(|argument| FunctionParam {
124                                    name: argument.name.clone().unwrap_or_default(),
125                                    kind: argument.arg_type.clone().unwrap_or_default().into(),
126                                    internal_type: None,
127                                })
128                                .collect(),
129                            outputs: function
130                                .outputs
131                                .iter()
132                                .map(|argument| FunctionParam {
133                                    name: argument.name.clone().unwrap_or_default(),
134                                    kind: argument.arg_type.clone().unwrap_or_default().into(),
135                                    internal_type: None,
136                                })
137                                .collect(),
138                            constant: false,
139                            state_mutability: function.fn_type.clone(),
140                        },
141                    )
142                }),
143        );
144
145        // Translate contract events
146        events.extend(contract.events.iter().map(|event| {
147            (
148                event.name.to_string(),
149                Event {
150                    name: event.name.to_string(),
151                    inputs: event
152                        .parameters
153                        .iter()
154                        .map(|argument| EventParam {
155                            name: argument.name.clone().unwrap_or_default(),
156                            kind: argument.arg_type.clone().unwrap_or_default().into(),
157                            indexed: argument.indexed,
158                        })
159                        .collect(),
160                    anonymous: false,
161                },
162            )
163        }));
164
165        // Translate contract errors
166        errors.extend(contract.errors.iter().map(|error| {
167            (
168                error.name.to_string(),
169                Error {
170                    name: error.name.to_string(),
171                    inputs: error
172                        .parameters
173                        .iter()
174                        .map(|argument| FunctionParam {
175                            name: argument.name.clone().unwrap_or_default(),
176                            kind: argument.arg_type.clone().unwrap_or_default().into(),
177                            internal_type: None,
178                        })
179                        .collect(),
180                },
181            )
182        }));
183
184        Self { constructor, functions, events, errors, receive: false, fallback: false }
185    }
186}
187
188/// #### Function
189///
190/// A function definition.
191#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
192#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
193#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
194pub struct Function {
195    /// The function name
196    pub name: String,
197    /// The function inputs
198    pub inputs: Vec<FunctionParam>,
199    /// The function outputs
200    pub outputs: Vec<FunctionParam>,
201    /// Constant
202    pub constant: bool,
203    /// The state mutability
204    pub state_mutability: FunctionType,
205}
206
207/// #### Event
208///
209/// An Event definition.
210#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
211#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
212#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
213pub struct Event {
214    /// The event name
215    pub name: String,
216    /// The event inputs
217    pub inputs: Vec<EventParam>,
218    /// Anonymity
219    pub anonymous: bool,
220}
221
222/// #### EventParam
223///
224/// Event parameters.
225#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
226#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
227#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
228pub struct EventParam {
229    /// The parameter name
230    pub name: String,
231    /// The parameter type
232    pub kind: FunctionParamType,
233    /// If the parameter is indexed
234    pub indexed: bool,
235}
236
237/// #### Error
238///
239/// An Error definition.
240#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
241#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
242#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
243pub struct Error {
244    /// The error name
245    pub name: String,
246    /// The error inputs
247    pub inputs: Vec<FunctionParam>,
248}
249
250/// #### Constructor
251///
252/// The contract constructor
253#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
254#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
255#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
256pub struct Constructor {
257    /// Constructor inputs
258    pub inputs: Vec<FunctionParam>,
259}
260
261/// #### FunctionParam
262///
263/// A generic function parameter
264#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
265#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
266#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
267pub struct FunctionParam {
268    /// The function parameter name
269    pub name: String,
270    /// The function parameter type
271    pub kind: FunctionParamType,
272    /// The internal type of the parameter
273    pub internal_type: Option<String>,
274}
275
276/// #### FunctionParamType
277///
278/// The type of function parameter
279#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
280#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
281#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
282pub enum FunctionParamType {
283    /// An address
284    Address,
285    /// Bytes
286    Bytes,
287    /// A signed integer
288    Int(usize),
289    /// An unsigned integer
290    Uint(usize),
291    /// A boolean
292    Bool,
293    /// A String
294    String,
295    /// Array ; `uint256[2][]` => `Array(Uint(256), [2, 0])`
296    Array(Box<FunctionParamType>, Vec<usize>),
297    /// Fixed number of bytes
298    FixedBytes(usize),
299    /// A tuple of parameters
300    Tuple(Vec<FunctionParamType>),
301}
302
303impl FunctionParamType {
304    /// Checks if the param type should be designated as "memory" for solidity interface
305    /// generation.
306    pub fn is_memory_type(&self) -> bool {
307        matches!(self, FunctionParamType::Bytes | FunctionParamType::String | FunctionParamType::Tuple(_) | FunctionParamType::Array(_, _))
308    }
309}
310
311impl fmt::Debug for FunctionParamType {
312    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
313        self.display(f)
314    }
315}
316
317impl fmt::Display for FunctionParamType {
318    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
319        self.display(f)
320    }
321}
322
323impl FunctionParamType {
324    /// Print a function parameter type to a formatter
325    pub fn display(&self, f: &mut fmt::Formatter) -> fmt::Result {
326        match &self {
327            FunctionParamType::Address => write!(f, "address"),
328            FunctionParamType::Bytes => write!(f, "bytes"),
329            FunctionParamType::Int(size) => write!(f, "int{size}"),
330            FunctionParamType::Uint(size) => write!(f, "uint{size}"),
331            FunctionParamType::Bool => write!(f, "bool"),
332            FunctionParamType::String => write!(f, "string"),
333            FunctionParamType::Array(fpt, sizes) => write!(
334                f,
335                "{}{}",
336                fpt,
337                sizes.iter().map(|s| { if !s.eq(&0) { format!("[{s}]") } else { "[]".to_string() } }).collect::<Vec<_>>().join("")
338            ),
339            FunctionParamType::FixedBytes(size) => write!(f, "bytes{size}"),
340            FunctionParamType::Tuple(inner) => write!(f, "({})", inner.iter().map(|fpt| fpt.to_string()).collect::<Vec<_>>().join(",")),
341        }
342    }
343
344    /// Convert string to type
345    pub fn convert_string_to_type(string: &str) -> Result<Self, Box<dyn std::error::Error>> {
346        let input = string.trim().to_lowercase();
347        // Array sizes are the trailing `[n]` or `[]` groups, so brackets inside a tuple stay with it
348        if input.ends_with(']') {
349            let mut base = input.as_str();
350            let mut sizes = Vec::new();
351            while let Some(rest) = base.strip_suffix(']') {
352                let open = rest.rfind('[').ok_or_else(|| format!("Failed to create FunctionParamType from string: {string}"))?;
353                let size = &rest[open + 1..];
354                sizes.push(if size.is_empty() { 0 } else { size.parse::<usize>()? });
355                base = &rest[..open];
356            }
357            sizes.reverse();
358            return Ok(Self::Array(Box::new(FunctionParamType::convert_string_to_type(base)?), sizes));
359        }
360        if let Some(inner) = input.strip_prefix('(').and_then(|rest| rest.strip_suffix(')')) {
361            return Ok(Self::Tuple(split_tuple_components(inner).into_iter().map(Self::convert_string_to_type).collect::<Result<_, _>>()?));
362        }
363        if input.starts_with("uint") {
364            // Default to 256 if no size
365            let size = match input.get(4..input.len()) {
366                Some(s) => match s.is_empty() {
367                    false => s.parse::<usize>().unwrap(),
368                    true => 256,
369                },
370                None => 256,
371            };
372            return Ok(Self::Uint(size));
373        }
374        if input.starts_with("int") {
375            // Default to 256 if no size
376            let size = match input.get(3..input.len()) {
377                Some(s) => match s.is_empty() {
378                    false => s.parse::<usize>().unwrap(),
379                    true => 256,
380                },
381                None => 256,
382            };
383            return Ok(Self::Int(size));
384        }
385        if input.starts_with("bytes") && input.len() != 5 {
386            let size = input.get(5..input.len()).unwrap().parse::<usize>().unwrap();
387            return Ok(Self::FixedBytes(size));
388        }
389        if input.starts_with("bool") {
390            return Ok(Self::Bool);
391        }
392        if input.starts_with("address") {
393            return Ok(Self::Address);
394        }
395        if input.starts_with("string") {
396            return Ok(Self::String);
397        }
398        if input == "bytes" {
399            Ok(Self::Bytes)
400        } else {
401            tracing::error!("Failed to create FunctionParamType from string: {}", string);
402            Err(format!("Failed to create FunctionParamType from string: {string}"))?
403        }
404    }
405}
406
407/// Splits the components of a tuple type at its top-level commas
408fn split_tuple_components(inner: &str) -> Vec<&str> {
409    let mut components = Vec::new();
410    let (mut depth, mut start) = (0usize, 0);
411    for (i, c) in inner.char_indices() {
412        match c {
413            '(' => depth += 1,
414            ')' => depth = depth.saturating_sub(1),
415            ',' if depth == 0 => {
416                components.push(&inner[start..i]);
417                start = i + 1;
418            }
419            _ => {}
420        }
421    }
422    components.push(&inner[start..]);
423    components
424}
425
426impl From<&str> for FunctionParamType {
427    fn from(string: &str) -> Self {
428        FunctionParamType::convert_string_to_type(string).unwrap()
429    }
430}
431
432impl From<String> for FunctionParamType {
433    fn from(string: String) -> Self {
434        FunctionParamType::convert_string_to_type(&string).unwrap()
435    }
436}