Repository: huff-language/huff-rs
Pull request #183 (2022-07-29 17:14:55)

feat(huff_cli): Add custom errors to solidity interface gen
## Overview

Adds custom `error` definitions to the solidity interface generation util.


Patch:
diff --git a/huff_parser/src/lib.rs b/huff_parser/src/lib.rs
index b617489..0331edf 100644
--- a/huff_parser/src/lib.rs
+++ b/huff_parser/src/lib.rs
@@ -405,18 +405,18 @@ impl Parser {
         };
 
         // Get arguments for signature
-        let inputs = self.parse_args(true, true, false, false)?;
+        let parameters = self.parse_args(true, true, false, false)?;
 
         let mut selector = [0u8; 4]; // Only keep first 4 bytes
         let input_types =
-            inputs.iter().map(|i| i.arg_type.as_ref().unwrap().clone()).collect::<Vec<_>>();
+            parameters.iter().map(|i| i.arg_type.as_ref().unwrap().clone()).collect::<Vec<_>>();
         hash_bytes(&mut selector, &format!("{}({})", name, input_types.join(",")));
 
         // Clone spans and set to nothing
         let new_spans = self.spans.clone();
         self.spans = vec![];
 
-        Ok(ErrorDefinition { name, selector, span: AstSpan(new_spans) })
+        Ok(ErrorDefinition { name, selector, parameters, span: AstSpan(new_spans) })
     }
 
     /// Parses a macro.
diff --git a/huff_utils/src/abi.rs b/huff_utils/src/abi.rs
index bc820d9..27d8fbb 100644
--- a/huff_utils/src/abi.rs
+++ b/huff_utils/src/abi.rs
@@ -53,6 +53,8 @@ pub struct Abi {
     pub functions: BTreeMap<String, Function>,
     /// A list of events and their definitions
     pub events: BTreeMap<String, Event>,
+    /// A list of errors and their definitions
+    pub errors: BTreeMap<String, Error>,
     /// If the contract defines receive logic
     pub receive: bool,
     /// If the contract defines fallback logic
@@ -113,72 +115,85 @@ impl From<ast::Contract> for Abi {
         // Instantiate functions and events
         let mut functions = BTreeMap::new();
         let mut events = BTreeMap::new();
+        let mut errors = BTreeMap::new();
 
         // Translate contract functions
         // Excluding constructor
-        contract
-            .functions
-            .iter()
-            .filter(|function: &&ast::Function| function.name != "CONSTRUCTOR")
-            .map(|function| {
-                (
-                    function.name.to_string(),
-                    Function {
-                        name: function.name.to_string(),
-                        inputs: function
-                            .inputs
-                            .iter()
-                            .map(|argument| FunctionParam {
-                                name: argument.name.clone().unwrap_or_default(),
-                                kind: argument.arg_type.clone().unwrap_or_default().into(),
-                                internal_type: None,
-                            })
-                            .collect(),
-                        outputs: function
-                            .outputs
-                            .iter()
-                            .map(|argument| FunctionParam {
-                                name: argument.name.clone().unwrap_or_default(),
-                                kind: argument.arg_type.clone().unwrap_or_default().into(),
-                                internal_type: None,
-                            })
-                            .collect(),
-                        constant: false,
-                        state_mutability: function.fn_type.clone(),
-                    },
-                )
-            })
-            .for_each(|val| {
-                let _ = functions.insert(val.0, val.1);
-            });
+        functions.extend(
+            contract
+                .functions
+                .iter()
+                .filter(|function: &&ast::Function| function.name != "CONSTRUCTOR")
+                .map(|function| {
+                    (
+                        function.name.to_string(),
+                        Function {
+                            name: function.name.to_string(),
+                            inputs: function
+                                .inputs
+                                .iter()
+                                .map(|argument| FunctionParam {
+                                    name: argument.name.clone().unwrap_or_default(),
+                                    kind: argument.arg_type.clone().unwrap_or_default().into(),
+                                    internal_type: None,
+                                })
+                                .collect(),
+                            outputs: function
+                                .outputs
+                                .iter()
+                                .map(|argument| FunctionParam {
+                                    name: argument.name.clone().unwrap_or_default(),
+                                    kind: argument.arg_type.clone().unwrap_or_default().into(),
+                                    internal_type: None,
+                                })
+                                .collect(),
+                            constant: false,
+                            state_mutability: function.fn_type.clone(),
+                        },
+                    )
+                }),
+        );
 
         // Translate contract events
-        contract
-            .events
-            .iter()
-            .map(|event| {
-                (
-                    event.name.to_string(),
-                    Event {
-                        name: event.name.to_string(),
-                        inputs: event
-                            .parameters
-                            .iter()
-                            .map(|argument| EventParam {
-                                name: argument.name.clone().unwrap_or_default(),
-                                kind: argument.arg_type.clone().unwrap_or_default().into(),
-                                indexed: argument.indexed,
-                            })
-                            .collect(),
-                        anonymous: false,
-                    },
-                )
-            })
-            .for_each(|val| {
-                let _ = events.insert(val.0, val.1);
-            });
+        events.extend(contract.events.iter().map(|event| {
+            (
+                event.name.to_string(),
+                Event {
+                    name: event.name.to_string(),
+                    inputs: event
+                        .parameters
+                        .iter()
+                        .map(|argument| EventParam {
+                            name: argument.name.clone().unwrap_or_default(),
+                            kind: argument.arg_type.clone().unwrap_or_default().into(),
+                            indexed: argument.indexed,
+                        })
+                        .collect(),
+                    anonymous: false,
+                },
+            )
+        }));
 
-        Self { constructor, functions, events, receive: false, fallback: false }
+        // Translate contract errors
+        errors.extend(contract.errors.iter().map(|error| {
+            (
+                error.name.to_string(),
+                Error {
+                    name: error.name.to_string(),
+                    inputs: error
+                        .parameters
+                        .iter()
+                        .map(|argument| FunctionParam {
+                            name: argument.name.clone().unwrap_or_default(),
+                            kind: argument.arg_type.clone().unwrap_or_default().into(),
+                            internal_type: None,
+                        })
+                        .collect(),
+                },
+            )
+        }));
+
+        Self { constructor, functions, events, errors, receive: false, fallback: false }
     }
 }
 
@@ -225,6 +240,17 @@ pub struct EventParam {
     pub indexed: bool,
 }
 
+/// #### Error
+///
+/// An Error definition.
+#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub struct Error {
+    /// The error name
+    pub name: String,
+    /// The error inputs
+    pub inputs: Vec<FunctionParam>,
+}
+
 /// #### Constructor
 ///
 /// The contract constructor
diff --git a/huff_utils/src/ast.rs b/huff_utils/src/ast.rs
index 53c676d..34e9d6d 100644
--- a/huff_utils/src/ast.rs
+++ b/huff_utils/src/ast.rs
@@ -631,6 +631,8 @@ pub struct ErrorDefinition {
     pub name: String,
     /// The Error's selector
     pub selector: [u8; 4],
+    /// The parameters of the error
+    pub parameters: Vec<Argument>,
     /// The Span of the Constant Definition
     pub span: AstSpan,
 }
diff --git a/huff_utils/src/sol_interface.rs b/huff_utils/src/sol_interface.rs
index eaaaeab..6a68929 100644
--- a/huff_utils/src/sol_interface.rs
+++ b/huff_utils/src/sol_interface.rs
@@ -40,6 +40,22 @@ pub fn gen_sol_interfaces(
                         .join(", "),
                 ));
             });
+            a.errors.iter().for_each(|(_, e)| {
+                defs.push(format!(
+                    "{}error {}({});",
+                    "\t",
+                    e.name,
+                    e.inputs
+                        .iter()
+                        .map(|i| format!(
+                            "{}{}",
+                            i.kind,
+                            if i.kind.is_memory_type() { " memory" } else { "" }
+                        ))
+                        .collect::<Vec<_>>()
+                        .join(", "),
+                ));
+            });
             a.functions.iter().for_each(|(_, f)| {
                 defs.push(format!(
                     "{}function {}({}) external{}{};",
@@ -80,8 +96,7 @@ pub fn gen_sol_interfaces(
                     artifact.file.path.split('/').last().unwrap().split('.').next().unwrap()
                 )
             });
-            let formatted_str =
-                format!("interface I{} {{\n{}\n}}", interface_name, defs.join("\n"));
+            let formatted_str = format!("interface {} {{\n{}\n}}", interface_name, defs.join("\n"));
             interfaces.push((
                 Path::new(&artifact.file.path).parent().unwrap().to_path_buf(),
                 interface_name,


Test patch:
diff --git a/huff_codegen/tests/abigen.rs b/huff_codegen/tests/abigen.rs
index 1761e30..94b91bc 100644
--- a/huff_codegen/tests/abigen.rs
+++ b/huff_codegen/tests/abigen.rs
@@ -34,6 +34,7 @@ fn constructs_valid_abi() {
             constructor: Some(Constructor { inputs: vec![] }),
             functions: BTreeMap::new(),
             events: BTreeMap::new(),
+            errors: BTreeMap::new(),
             receive: false,
             fallback: false
         }
diff --git a/huff_parser/tests/error.rs b/huff_parser/tests/error.rs
index 8ffcc6a..7395864 100644
--- a/huff_parser/tests/error.rs
+++ b/huff_parser/tests/error.rs
@@ -18,6 +18,12 @@ fn test_parses_custom_error() {
         ErrorDefinition {
             name: String::from("TestError"),
             selector: [124, 104, 44, 83],
+            parameters: vec![Argument {
+                arg_type: Some(String::from("uint256")),
+                name: None,
+                indexed: false,
+                span: AstSpan(vec![Span { start: 24, end: 31, file: None }])
+            }],
             span: AstSpan(vec![
                 Span { start: 0, end: 7, file: None },
                 Span { start: 8, end: 13, file: None },