Repository: huff-language/huff-rs
Pull request #144 (2022-06-28 22:41:23)

[STAGE] -> [MAIN]
### `stage` -> `main`

### Changelog

- Artifact caching by @abigger87 in #134 
- Error Propagation by @abigger87 in #145 
- Loosened Keyword Requirements by @abigger87 in #145 
- Selector Builtins by @clabby in #148 
- Interactive Constructor Args by @abigger87 in #146 
- Solidity Interface Generation by @clabby in #153 
- Fixed Artifact Caching with Args by @abigger87 in #154 
- Uninstall NPM/Yarn huffc in huffup by @abigger87 in #155 


Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 3d1607e..9ef139a 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -111,6 +111,11 @@ jobs:
       - name: Checkout sources
         uses: actions/checkout@v2
 
+      - name: Checkout stage branch if Nightly
+        if: ${{ env.IS_NIGHTLY }}
+        run: |
+          git checkout stage
+
       - name: Install toolchain
         uses: actions-rs/toolchain@v1
         with:
@@ -216,6 +221,11 @@ jobs:
       - name: Checkout sources
         uses: actions/checkout@v2
 
+      - name: Checkout stage branch if Nightly
+        if: ${{ env.IS_NIGHTLY }}
+        run: |
+          git checkout stage
+
       # Moves the `nightly` tag to `HEAD`
       - name: Move nightly tag
         if: ${{ env.IS_NIGHTLY }}
diff --git a/.gitignore b/.gitignore
index 3239a15..38a5e6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,8 @@
 .idea
 
 # Compilation Artifacts
-artifacts
\ No newline at end of file
+artifacts
+
+# Foundry cache
+cache
+
diff --git a/Cargo.lock b/Cargo.lock
index cd2898a..8841e70 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -651,10 +651,11 @@ dependencies = [
 
 [[package]]
 name = "huff_cli"
-version = "0.1.0"
+version = "0.2.0"
 dependencies = [
  "clap 3.2.1",
  "ethers-core",
+ "huff_codegen",
  "huff_core",
  "huff_utils",
  "isatty",
@@ -673,6 +674,7 @@ dependencies = [
  "huff_utils",
  "proptest",
  "serde_json",
+ "tiny-keccak",
  "tracing",
  "uuid",
 ]
@@ -688,12 +690,14 @@ dependencies = [
  "huff_parser",
  "huff_utils",
  "proptest",
+ "rand",
  "rayon",
  "serde_json",
  "tracing",
  "tracing-subscriber",
  "tracing-test",
  "uuid",
+ "walkdir",
 ]
 
 [[package]]
diff --git a/Dockerfile b/Dockerfile
index 19079fb..3a53433 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,5 +18,5 @@ RUN apk add linux-headers gcompat
 RUN wget -q -O ${GLIBC_KEY_FILE} ${GLIBC_KEY} \
     && wget -O glibc.apk ${GLIBC_RELEASE} \
     && apk add glibc.apk --force
-COPY --from=build-environment /opt/huff-rs/target/release/huffc /usr/local/bin/hufc
+COPY --from=build-environment /opt/huff-rs/target/release/huffc /usr/local/bin/huffc
 ENTRYPOINT ["/bin/sh", "-c"]
diff --git a/README.md b/README.md
index 94c15a9..a05c800 100644
--- a/README.md
+++ b/README.md
@@ -4,14 +4,6 @@
 
 > `huff-rs` is a [Huff](https://github.com/huff-language) compiler built in rust.
 
-<br />
-<hr />
-
-> **Warning**
->
-> This repo contains highly experimental code. Expect rapid iteration. **Use at your own risk.**
-
-<hr />
 
 ## What is a Huff?
 
@@ -138,6 +130,21 @@ When the PR checklist isn't complete, it is **highly** recommended to make it a
 For breaking changes: make sure to edit the [excalidraw asset](https://excalidraw.com/#json=9YvTZp-rY9NOQnX9TC8Dz,sVM8vpgvQqGiXNXrBNshTg) and export the file to [./assets/huffc.excalidraw](./assets/huffc.excalidraw) along with an image to [./assets/huffc.png](./assets/huffc.png).
 
 
+## Safety
+
+> **Warning**
+>
+> This is **experimental software** and is provided on an "as is" and "as available" basis.
+> Expect rapid iteration and **use at your own risk**.
+>
+> This code is **not designed for safety**.
+> - There are untested invariants in the code that may break.
+> - **You can easily shoot yourself in the foot if you're not careful.**
+> - You should thoroughly read the documentation and examples.
+>
+> We **do not give any warranties** and **will not be liable for any loss** incurred through any use of this codebase.
+
+
 ## Acknowledgements
 
 The original [Huff Language](https://github.com/huff-language) compiler: [`huffc`](https://github.com/huff-language/huffc).
diff --git a/huff_cli/Cargo.toml b/huff_cli/Cargo.toml
index e62e6df..187d89c 100644
--- a/huff_cli/Cargo.toml
+++ b/huff_cli/Cargo.toml
@@ -1,19 +1,20 @@
 [package]
 name = "huff_cli"
-version = "0.1.0"
+version = "0.2.0"
 edition = "2021"
 authors = ["Andreas Bigger", "clabby", "exp.table"]
 readme = "README.md"
 repository = "https://github.com/huff-language/huff-rs/"
 license = "MIT OR Apache-2.0"
 description = """
-Efficient Huff compiler written in rust.
+Huff Language Compiler built in Pure Rust.
 """
 keywords = ["huff", "rust", "evm", "bytecode", "compiler"]
 
 [dependencies]
 clap = { version = "3.1.18", features = ["derive"] }
 huff_core = { path = "../huff_core", version = "0.x.x" }
+huff_codegen = { path = "../huff_codegen", version = "0.x.x" }
 huff_utils = { path = "../huff_utils", version = "0.x.x" }
 tracing = "0.1.34"
 ethers-core = "0.13.0"
diff --git a/huff_cli/README.md b/huff_cli/README.md
index 846e9bb..e8f5651 100644
--- a/huff_cli/README.md
+++ b/huff_cli/README.md
@@ -6,26 +6,29 @@ The `huffc` CLI is written using [clap's](https://docs.rs/clap) [derive feature]
 ## huffc
 
 ```
-huffc 0.1.0
-Efficient Huff compiler written in rust.
+huffc 0.2.0
+Huff Language Compiler built in Pure Rust.
 
 USAGE:
-    huffc [OPTIONS] [PATH]
+    huffc [OPTIONS] [--] [PATH]
 
 ARGS:
-    <PATH>
+    <PATH>    The contract(s) to compile
 
 OPTIONS:
+    -a, --artifacts                       Whether to generate artifacts or not
     -b, --bytecode                        Generate and log bytecode
     -d, --output-directory <OUTPUTDIR>    The output directory [default: ./artifacts]
+    -g, --interface                       Generate solidity interface for a Huff artifact
     -h, --help                            Print help information
     -i, --inputs <INPUTS>...              The input constructor arguments
+    -n, --interactive                     Interactively input the constructor args
     -o, --output <OUTPUT>                 The output file path
     -p, --print                           Prints out to the terminal
-    -s, --source-path <SOURCE>            The contracts source path [default: ./src]
+    -s, --source-path <SOURCE>            The contracts source path [default: ./contracts]
     -v, --verbose                         Verbose output
     -V, --version                         Print version information
-    -z, --optimize                        Optimize compilation
+    -z, --optimize                        Optimize compilation [WIP]
 ```
 
 _NOTE: To generate the above output, run: `huffc --help`_
@@ -33,19 +36,20 @@ _NOTE: To generate the above output, run: `huffc --help`_
 
 ## Usage
 
-To run `huffc` from the command line, you can use the following command:
-
+To run `huffc` from the command line, you can simply run:
 ```bash
-cargo run --bin huffc
+huffc --help
 ```
 
-To pass arguments into the `huffc` binary, simply pass them in after a `--` flag. For example, to get the `huffc` version (a `-V` flag), you can run:
+By default, huffc will attempt to compile all contracts in the `contracts` directory. If there is no `contracts` directory present, the following will spit out an error like so:
+```bash,color=red
+~ huffc
+
+Error: Invalid File Directory ./contracts
 
-```bash
-cargo run --bin huffc -- -V
 ```
 
-**Example: Using `huff-examples`**
+#### Examples using [`huff-examples`](https://github.com/huff-language/huff-examples)
 
 The [huff-examples](https://github.com/huff-language/huff-examples) github repository is added as a submodule to this repo for testing.
 
@@ -54,7 +58,7 @@ To run `huffc` against one of the examples, the path may simply be passed to `hu
 For example, to compile huff-example's [ERC20.huff](../huff-examples/erc20/contracts/ERC20.huff) contract, run:
 
 ```bash
-cargo run --bin huffc -- --bytecode ./huff-examples/erc20/contracts/ERC20.huff
+huffc --bytecode ./huff-examples/erc20/contracts/ERC20.huff
 ```
 
 _NOTE: The `--bytecode` flag will output the full deploy bytecode._
@@ -62,18 +66,87 @@ _NOTE: The `--bytecode` flag will output the full deploy bytecode._
 `huffc` also supports tracing using the [`tracing`](https://docs.rs/tracing/0.1.29/tracing/) crate. To produce a verbose output using tracing, append the `--verbose` or `-v` flag like so:
 
 ```bash
-cargo run --bin huffc -- --verbose --bytecode ./huff-examples/erc20/contracts/ERC20.huff
+huffc --verbose --bytecode ./huff-examples/erc20/contracts/ERC20.huff
 ```
 
+#### Specifying Artifact Outputs
+
 **By default**, `huffc` will export json build artifacts to a `./artifacts` directory. This can be overidden using the `--output-directory` flag or shorthand `-d` flag and specifying a string following. For example:
 
 ```bash
-cargo run --bin huffc -- -d ./output ./huff-examples/erc20/contracts/ERC20.huff
+huffc -d ./output ./huff-examples/erc20/contracts/ERC20.huff
 ```
 
 _NOTE: The huff cli will gracefully remove double and single quotes, so the following will also compile:_
 
 ```bash
-cargo run --bin huffc -- -d "./output" './huff-examples/erc20/contracts/ERC20.huff'
+huffc -d "./output" './huff-examples/erc20/contracts/ERC20.huff'
+```
+
+If a specific contract is specified for compiling (ie not a directory), a single `json` file may be specified as an output location for the contract artifact like so:
+
+```bash
+huffc -o ./artifact.json ./huff-examples/erc20/contracts/ERC20.huff
+```
+
+**NOTE**: The following will _not_ compile since multiple artifacts cannot be output to the same artifact json file.
+```bash
+huffc -o ./artifact.json ./contracts/
+```
+
+
+#### Entering Constructor Arguments
+
+`huffc` supports passing in constructor arguments to the contract. This is done by passing in the `--interactive` (shorthand: `-n`) flag or passing the `--inputs` (shorthand: `-i`) flag.
+
+ and passing in the arguments as a comma separated list.
+
+For example, to compile a contract (let's call it `example.huff`) with the following constructor definition:
+```huff
+#define macro CONSTRUCTOR(uint256, address) = takes(0) returns (0) {
+    0x04 calldataload
+    0x00 sstore
+    0x24 calldataload
+    0x01 sstore
+}
+```
+
+You can enter the arguments `(100, 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef)` interactively by passing in the `-n` or `--interactive` flag like so:
+
+```bash
+$ huffc -b -n ./contracts/example.huff
+[INTERACTIVE] Constructor Arguments for Contract: "./contracts/example.huff"
+[INTERACTIVE] Enter a uint256 for constructor param: 100
+[INTERACTIVE] Enter a address for constructor param: 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+
+33600.....f30000000000000000000000000000000000000000000000000000000000000064000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+```
+
+Alternatively, you can enter the arguments as a comma separated list by using the `-i` or `--inputs` flag like so:
+
+```bash
+$ huffc -b -i 100, 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef ./contracts/example.huff
+33600.....f30000000000000000000000000000000000000000000000000000000000000064000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+```
+
+#### Other Options
+
+- `-v` or `--verbose`: Outputs detailed logs to the terminal using the [tracing](https://crates.io/crates/tracing) crate.
+- `-V` or `--version`: Prints the version of `huffc`.
+- `-z` or `--optimize`: Optimizes the contract compilation - a work in progress.
+- `-g` or `--interface`: Generates a solidity interface for the contract.
+
+
+## Building huffc from source
+
+To run `huffc` from the command line, you can use the following command:
+```bash
+cargo run --bin huffc
+```
+
+To pass arguments into the `huffc` binary, simply pass them in after a `--` flag. For example, to get the `huffc` version (a `-V` flag), you can run:
+```bash
+cargo run --bin huffc -- -V
 ```
 
+All commands specified in [Usage](#usage) are also available from source by passing them in after the `--` flag.
diff --git a/huff_cli/src/huffc.rs b/huff_cli/src/huffc.rs
index 55b85c9..c20a643 100644
--- a/huff_cli/src/huffc.rs
+++ b/huff_cli/src/huffc.rs
@@ -8,24 +8,27 @@
 #![allow(deprecated)]
 
 use clap::Parser as ClapParser;
+use ethers_core::utils::hex;
+use huff_codegen::Codegen;
 use huff_core::Compiler;
 use huff_utils::prelude::{
-    unpack_files, AstSpan, CodegenError, CodegenErrorKind, CompilerError, FileSource, Span,
+    export_interfaces, gen_sol_interfaces, unpack_files, AstSpan, CodegenError, CodegenErrorKind,
+    CompilerError, FileSource, OutputLocation, Span,
 };
 use isatty::stdout_isatty;
 use spinners::{Spinner, Spinners};
-use std::{path::Path, sync::Arc};
+use std::{io::Write, path::Path, sync::Arc};
 use yansi::Paint;
 
 /// The Huff CLI Args
 #[derive(ClapParser, Debug, Clone)]
-#[clap(version, about, long_about = None)]
+#[clap(name = "huffc", version, about, long_about = None)]
 struct Huff {
-    /// The main path
+    /// The contract(s) to compile.
     pub path: Option<String>,
 
     /// The contracts source path.
-    #[clap(short = 's', long = "source-path", default_value = "./src")]
+    #[clap(short = 's', long = "source-path", default_value = "./contracts")]
     source: String,
 
     /// The output file path.
@@ -40,14 +43,22 @@ struct Huff {
     #[clap(short = 'i', long = "inputs", multiple_values = true)]
     inputs: Option<Vec<String>>,
 
+    /// Interactively input the constructor args
+    #[clap(short = 'n', long = "interactive")]
+    interactive: bool,
+
     /// Whether to generate artifacts or not
     #[clap(short = 'a', long = "artifacts")]
     artifacts: bool,
 
-    /// Optimize compilation.
+    /// Optimize compilation [WIP]
     #[clap(short = 'z', long = "optimize")]
     optimize: bool,
 
+    /// Generate solidity interface for a Huff artifact
+    #[clap(short = 'g', long = "interface")]
+    interface: bool,
+
     /// Generate and log bytecode.
     #[clap(short = 'b', long = "bytecode")]
     bytecode: bool,
@@ -61,9 +72,21 @@ struct Huff {
     verbose: bool,
 }
 
+/// Helper function to read an stdin input
+pub(crate) fn get_input(prompt: &str) -> String {
+    // let mut sp = Spinner::new(Spinners::Line, format!("{}{}",
+    // Paint::blue("[INTERACTIVE]".to_string()), prompt));
+    print!("{} {} ", Paint::blue("[INTERACTIVE]".to_string()), prompt);
+    let mut input = String::new();
+    let _ = std::io::stdout().flush();
+    let _ = std::io::stdin().read_line(&mut input);
+    // sp.stop();
+    input.trim().to_string()
+}
+
 fn main() {
     // Parse the command line arguments
-    let cli = Huff::parse();
+    let mut cli = Huff::parse();
 
     // Initiate Tracing if Verbose
     if cli.verbose {
@@ -78,20 +101,36 @@ fn main() {
             std::process::exit(1);
         }
     };
+
+    let mut use_cache = true;
+    if cli.interactive {
+        // Don't accept configured inputs
+        cli.inputs = None;
+        // Don't export artifacts are compile
+        // Have to first generate artifacts, prompt user for args,
+        // and finally save artifacts with the new constructor args.
+        cli.artifacts = false;
+        // Don't use cache if interactive since there's no way constructor arguments can match
+        use_cache = false;
+    }
+
+    let output = match (&cli.output, cli.artifacts) {
+        (Some(o), true) => Some(o.clone()),
+        (None, true) => Some(cli.outputdir.clone()),
+        _ => None,
+    };
+
     let compiler: Compiler = Compiler {
         sources: Arc::clone(&sources),
-        output: match (&cli.output, cli.artifacts) {
-            (Some(o), true) => Some(o.clone()),
-            (None, true) => Some(cli.outputdir.clone()),
-            _ => None,
-        },
+        output,
         construct_args: cli.inputs,
         optimize: cli.optimize,
         bytecode: cli.bytecode,
+        cached: use_cache,
     };
 
     // Create compiling spinner
-    tracing::debug!(target: "core", "[⠔] COMPILING");
+    tracing::debug!(target: "cli", "[⠔] COMPILING");
     let mut sp: Option<Spinner> = None;
     // If stdout is a TTY, create a spinner
     if stdout_isatty() {
@@ -99,13 +138,14 @@ fn main() {
     }
 
     let compile_res = compiler.execute();
+
     // Stop spinner animation if it exists
     if let Some(mut sp) = sp {
         sp.stop();
         println!(" ");
     }
     match compile_res {
-        Ok(artifacts) => {
+        Ok(mut artifacts) => {
             if artifacts.is_empty() {
                 let e = CompilerError::CodegenError(CodegenError {
                     kind: CodegenErrorKind::AbiGenerationFailure,
@@ -127,11 +167,94 @@ fn main() {
                     ),
                     token: None,
                 });
-                tracing::error!(target: "core", "COMPILER ERRORED: {:?}", e);
+                tracing::error!(target: "cli", "COMPILER ERRORED: {:?}", e);
                 eprintln!("{}", Paint::red(format!("{}", e)));
                 std::process::exit(1);
             }
+
+            if cli.interface {
+                tracing::info!(target: "cli", "GENERATING SOLIDITY INTERFACES FROM ARTIFACTS");
+                let interfaces = gen_sol_interfaces(&artifacts);
+                if export_interfaces(&interfaces).is_ok() {
+                    tracing::info!(target: "cli", "GENERATED SOLIDITY INTERFACES FROM ARTIFACTS SUCCESSFULLY");
+                    println!(
+                        "Exported Solidity Interfaces: {}",
+                        Paint::blue(
+                            interfaces
+                                .into_iter()
+                                .map(|(_, i, _)| format!("I{}.sol", i))
+                                .collect::<Vec<_>>()
+                                .join(", ")
+                        )
+                    );
+                } else {
+                    tracing::error!(target: "cli", "FAILED TO GENERATE SOLIDITY INTERFACES FROM ARTIFACTS");
+                    eprintln!(
+                        "{}",
+                        Paint::red("FAILED TO GENERATE SOLIDITY INTERFACES FROM ARTIFACTS")
+                    );
+                }
+            }
+
             if cli.bytecode {
+                if cli.interactive {
+                    tracing::info!(target: "cli", "ENTERING INTERACTIVE MODE");
+                    // let mut new_artifacts = vec![];
+                    for artifact in &mut artifacts {
+                        let mut appended_args = String::default();
+                        match artifact.abi {
+                            Some(ref abi) => match abi.constructor {
+                                Some(ref args) => {
+                                    println!(
+                                        "{} Constructor Arguments for Contract: \"{}\"",
+                                        Paint::blue("[INTERACTIVE]".to_string()),
+                                        artifact.file.path
+                                    );
+                                    for input in &args.inputs {
+                                        let arg_input = get_input(&format!(
+                                            "Enter a {:?} for constructor param{}:",
+                                            input.kind,
+                                            (!input.name.is_empty())
+                                                .then(|| format!(" \"{}\"", input.name))
+                                                .unwrap_or_default()
+                                        ));
+                                        let encoded =
+                                            Codegen::encode_constructor_args(vec![arg_input])
+                                                .iter()
+                                                .fold(String::default(), |acc, str| {
+                                                    let inner: Vec<u8> =
+                                                        ethers_core::abi::encode(&[str.clone()]);
+                                                    let hex_args: String =
+                                                        hex::encode(inner.as_slice());
+                                                    format!("{}{}", acc, hex_args)
+                                                });
+                                        appended_args.push_str(&encoded);
+                                    }
+                                }
+                                None => {
+                                    tracing::warn!(target: "cli", "NO CONSTRUCTOR FOR ABI: {:?}", abi)
+                                }
+                            },
+                            None => {
+                                tracing::warn!(target: "cli", "NO ABI FOR ARTIFACT: {:?}", artifact)
+                            }
+                        }
+                        match Arc::get_mut(artifact) {
+                            Some(art) => {
+                                art.bytecode = format!("{}{}", art.bytecode, appended_args);
+                            }
+                            None => {
+                                tracing::warn!(target: "cli", "FAILED TO ACQUIRE MUTABLE REF TO ARTIFACT")
+                            }
+                        }
+                    }
+                    tracing::debug!(target: "cli", "Re-exporting artifacts...");
+                    Compiler::export_artifacts(
+                        &artifacts,
+                        &OutputLocation(cli.output.unwrap_or_else(|| cli.outputdir.clone())),
+                    );
+                    tracing::info!(target: "cli", "RE-EXPORTED INTERACTIVE ARTIFACTS");
+                }
                 match sources.len() {
                     1 => print!("{}", artifacts[0].bytecode),
                     _ => artifacts
@@ -141,7 +264,7 @@ fn main() {
             }
         }
         Err(e) => {
-            tracing::error!(target: "core", "COMPILER ERRORED: {:?}", e);
+            tracing::error!(target: "cli", "COMPILER ERRORED: {:?}", e);
             eprintln!("{}", Paint::red(format!("{}", e)));
             std::process::exit(1);
         }
diff --git a/huff_codegen/Cargo.toml b/huff_codegen/Cargo.toml
index 0bc30f3..67dab54 100644
--- a/huff_codegen/Cargo.toml
+++ b/huff_codegen/Cargo.toml
@@ -19,3 +19,4 @@ ethers-core = "0.13.0"
 hex = "0.4.3"
 tracing = "0.1.34"
 uuid = { version = "1.1.1", features = ["v4"] }
+tiny-keccak = { version = "2.0.2", features = ["keccak"] }
diff --git a/huff_codegen/src/irgen/statements.rs b/huff_codegen/src/irgen/statements.rs
index 4c6e432..21a881b 100644
--- a/huff_codegen/src/irgen/statements.rs
+++ b/huff_codegen/src/irgen/statements.rs
@@ -1,4 +1,5 @@
 use huff_utils::prelude::*;
+use tiny_keccak::{Hasher, Keccak};
 
 use crate::Codegen;
 
@@ -100,7 +101,6 @@ pub fn statement_gen(
         StatementType::BuiltinFunctionCall(bf) => {
             // Generate code for a `BuiltinFunctionCall`
             // __codesize, __tablesize, or __tablestart
-            // TODO: Inline docs
             tracing::info!(target: "codegen", "RECURSE BYTECODE GOT BUILTIN FUNCTION CALL: {:?}", bf);
             match bf.kind {
                 BuiltinFunctionKind::Codesize => {
@@ -118,7 +118,7 @@ pub fn statement_gen(
                             kind: CodegenErrorKind::MissingMacroDefinition(
                                 bf.args[0].name.as_ref().unwrap().to_string(), /* yuck */
                             ),
-                            span: AstSpan(vec![Span { start: 0, end: 0, file: None }]),
+                            span: bf.span.clone(),
                             token: None,
                         })
                     };
@@ -165,7 +165,7 @@ pub fn statement_gen(
                             kind: CodegenErrorKind::MissingMacroDefinition(
                                 bf.args[0].name.as_ref().unwrap().to_string(), /* yuck */
                             ),
-                            span: AstSpan(vec![Span { start: 0, end: 0, file: None }]),
+                            span: bf.span.clone(),
                             token: None,
                         })
                     };
@@ -186,6 +186,112 @@ pub fn statement_gen(
                     bytes.push((*offset, Bytes(format!("{}xxxx", Opcode::Push2))));
                     *offset += 3;
                 }
+                BuiltinFunctionKind::FunctionSignature => {
+                    if bf.args.len() != 1 {
+                        tracing::error!(
+                            target: "codegen",
+                            "Incorrect number of arguments passed to __FUNC_SIG, should be 1: {}",
+                            bf.args.len()
+                        );
+                        return Err(CodegenError {
+                            kind: CodegenErrorKind::InvalidArguments(
+                                format!(
+                                    "Incorrect number of arguments passed to __FUNC_SIG, should be 1: {}",
+                                    bf.args.len()
+                                )
+                            ),
+                            span: bf.span.clone(),
+                            token: None,
+                        })
+                    }
+
+                    if let Some(func) = contract
+                        .functions
+                        .iter()
+                        .find(|f| bf.args[0].name.as_ref().unwrap().eq(&f.name))
+                    {
+                        let sig = hex::encode(func.signature);
+                        let push_bytes = format!("{:02x}{}", 95 + sig.len() / 2, sig);
+                        *offset += push_bytes.len() / 2;
+                        bytes.push((starting_offset, Bytes(push_bytes)));
+                    } else if let Some(s) = &bf.args[0].name {
+                        let mut signature = [0u8; 4]; // Only keep first 4 bytes
+                        let mut hasher = Keccak::v256();
+                        hasher.update(s.as_bytes());
+                        hasher.finalize(&mut signature);
+
+                        let sig = hex::encode(signature);
+                        let push_bytes = format!("{:02x}{}", 95 + sig.len() / 2, sig);
+                        *offset += push_bytes.len() / 2;
+                        bytes.push((starting_offset, Bytes(push_bytes)));
+                    } else {
+                        tracing::error!(
+                            target: "codegen",
+                            "MISSING FUNCTION INTERFACE PASSED TO __SIG: \"{}\"",
+                            bf.args[0].name.as_ref().unwrap()
+                        );
+                        return Err(CodegenError {
+                            kind: CodegenErrorKind::MissingFunctionInterface(
+                                bf.args[0].name.as_ref().unwrap().to_string(),
+                            ),
+                            span: bf.span.clone(),
+                            token: None,
+                        })
+                    }
+                }
+                BuiltinFunctionKind::EventHash => {
+                    if bf.args.len() != 1 {
+                        tracing::error!(
+                            target: "codegen",
+                            "Incorrect number of arguments passed to __EVENT_HASH, should be 1: {}",
+                            bf.args.len()
+                        );
+                        return Err(CodegenError {
+                            kind: CodegenErrorKind::InvalidArguments(
+                                format!(
+                                    "Incorrect number of arguments passed to __EVENT_HASH, should be 1: {}",
+                                    bf.args.len()
+                                )
+                            ),
+                            span: bf.span.clone(),
+                            token: None,
+                        })
+                    }
+
+                    if let Some(event) = contract
+                        .events
+                        .iter()
+                        .find(|e| bf.args[0].name.as_ref().unwrap().eq(&e.name))
+                    {
+                        let hash = bytes32_to_string(&event.hash, false);
+                        let push_bytes = format!("{:02x}{}", 95 + hash.len() / 2, hash);
+                        *offset += push_bytes.len() / 2;
+                        bytes.push((starting_offset, Bytes(push_bytes)));
+                    } else if let Some(s) = &bf.args[0].name {
+                        let mut hash = [0u8; 32];
+                        let mut hasher = Keccak::v256();
+                        hasher.update(s.as_bytes());
+                        hasher.finalize(&mut hash);
+
+                        let hash = hex::encode(hash);
+                        let push_bytes = format!("{:02x}{}", 95 + hash.len() / 2, hash);
+                        *offset += push_bytes.len() / 2;
+                        bytes.push((starting_offset, Bytes(push_bytes)));
+                    } else {
+                        tracing::error!(
+                            target: "codegen",
+                            "MISSING EVENT INTERFACE PASSED TO __EVENT_HASH: \"{}\"",
+                            bf.args[0].name.as_ref().unwrap()
+                        );
+                        return Err(CodegenError {
+                            kind: CodegenErrorKind::MissingEventInterface(
+                                bf.args[0].name.as_ref().unwrap().to_string(),
+                            ),
+                            span: bf.span.clone(),
+                            token: None,
+                        })
+                    }
+                }
             }
         }
         sty => {
diff --git a/huff_core/Cargo.toml b/huff_core/Cargo.toml
index c0336d0..c910371 100644
--- a/huff_core/Cargo.toml
+++ b/huff_core/Cargo.toml
@@ -24,9 +24,11 @@ rayon = { version = "1.5.3" }
 tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
 uuid = { version = "1.1.1", features = ["v4"] }
 tracing-test = "0.2.2"
+walkdir = "2"
 
 [dev-dependencies]
 criterion = "0.3.5"
+rand = "0.8.5"
 
 [[bench]]
 name = "huff_benchmark"
diff --git a/huff_core/README.md b/huff_core/README.md
index 2883ab2..880cbb3 100644
--- a/huff_core/README.md
+++ b/huff_core/README.md
@@ -21,7 +21,7 @@ use huff_utils::artifact::Artifact;
 use std::sync::Arc;
 
 // Instantiate the Compiler Instance
-let mut compiler = Compiler::new(Arc::new(vec!["../huff-examples/erc20/contracts/ERC20.huff".to_string()]), None, None, false);
+let mut compiler = Compiler::new(Arc::new(vec!["../huff-examples/erc20/contracts/ERC20.huff".to_string()]), None, None, false, false);
 
 // Execute the compiler
 let res: Result<Vec<Arc<Artifact>>, Arc<CompilerError<'_>>> = compiler.execute();
diff --git a/huff_core/src/cache.rs b/huff_core/src/cache.rs
new file mode 100644
index 0000000..3a5fef0
--- /dev/null
+++ b/huff_core/src/cache.rs
@@ -0,0 +1,93 @@
+use std::sync::Arc;
+
+use huff_utils::prelude::{Artifact, FileSource, OutputLocation};
+use walkdir::WalkDir;
+
+/// Parallelized Artifact Cachcing
+pub fn get_cached_artifacts(
+    files: &[Arc<FileSource>],
+    out: &OutputLocation,
+    constructor_args: String,
+) -> Option<Vec<Arc<Artifact>>> {
+    // Check if the file artifacts are already generated the the default "./artifacts/" directory or
+    // the specified output dir
+    let artifacts: Vec<(Arc<FileSource>, Artifact)> =
+        resolve_existing_artifacts(files, out, constructor_args)?;
+
+    // Return the artifacts if cached
+    Some(artifacts.into_iter().map(|(_, artifact)| Arc::new(artifact)).collect())
+}
+
+/// Attempt to grab the artifacts
+pub fn resolve_existing_artifacts(
+    files: &[Arc<FileSource>],
+    output: &OutputLocation,
+    constructor_args: String,
+) -> Option<Vec<(Arc<FileSource>, Artifact)>> {
+    let mut artifacts: Vec<(Arc<FileSource>, Artifact)> = Vec::new();
+
+    // Transform file sources into a hashmap of path to file source
+    let mut file_sources: std::collections::HashMap<String, Arc<FileSource>> =
+        files.iter().map(|f| (f.path.clone().to_lowercase(), Arc::clone(f))).collect();
+
+    // If outputdir is not specified, use the default "./artifacts/" directory
+    let output_dir = (!output.0.is_empty()).then(|| &*output.0).unwrap_or("./artifacts");
+
+    // For each file, check if the artifact file exists at the location
+    tracing::debug!(target: "core", "Traversing output directory {}", output_dir);
+    for entry in WalkDir::new(&output_dir)
+        .into_iter()
+        .filter_map(Result::ok)
+        .filter(|e| !e.file_type().is_dir())
+    {
+        // Are we expecting this file to be compiled
+        let formatted_path = entry
+            .path()
+            .display()
+            .to_string()
+            .replace(".json", "")
+            .replace(&output_dir, ".")
+            .to_lowercase();
+        let expected = file_sources.remove(&formatted_path);
+
+        // Try to read the file into an artifact
+        match serde_json::from_str::<Artifact>(&std::fs::read_to_string(entry.path()).unwrap()) {
+            Ok(artifact) => {
+                // If we expected compilation, the sources must match
+                match expected {
+                    Some(expected_fs) => {
+                        if !artifact.bytecode.ends_with(&constructor_args) {
+                            tracing::warn!(target: "core", "Mismatched Constructor Args for Cached Artifact \"{}\"", artifact.file.path);
+                            return None
+                        }
+                        if artifact.file.source != expected_fs.source {
+                            tracing::warn!(target: "core", "Cache Resolution Failed: \"{}\" Artifact Outdated", artifact.file.path);
+                            return None
+                        } else {
+                            artifacts.push((expected_fs, artifact));
+                        }
+                    }
+                    None => {
+                        tracing::warn!(target: "core", "Cache Resolution Found Unexpected Artifact: \"{}\"", artifact.file.path)
+                    }
+                }
+            }
+            Err(e) => {
+                // If the artifact is invalid, log the error and continue
+                tracing::error!(target: "core", "Invalid artifact file: {}", e);
+                if expected.is_some() {
+                    tracing::error!(target: "core", "Expected artifact file to be compiled: {}", entry.path().display());
+                    return None
+                }
+            }
+        }
+    }
+
+    match file_sources.is_empty() {
+        true => Some(artifacts),
+        false => {
+            tracing::warn!(target: "core", "Cache Resolution Failed: Missing Artifact Files");
+            None
+        }
+    }
+}
diff --git a/huff_core/src/lib.rs b/huff_core/src/lib.rs
index a6f974d..90a80f7 100644
--- a/huff_core/src/lib.rs
+++ b/huff_core/src/lib.rs
@@ -4,6 +4,7 @@
 #![forbid(unsafe_code)]
 #![forbid(where_clauses_object_safety)]
 
+use ethers_core::utils::hex;
 use huff_codegen::*;
 use huff_lexer::*;
 use huff_parser::*;
@@ -19,6 +20,8 @@ use std::{
 use tracing_subscriber::{filter::Directive, EnvFilter};
 use uuid::Uuid;
 
+pub(crate) mod cache;
+
 /// ## The Core Huff Compiler
 ///
 /// #### Usage
@@ -39,6 +42,7 @@ use uuid::Uuid;
 ///     Arc::new(vec!["../huff-examples/erc20/contracts/ERC20.huff".to_string()]),
 ///     Some("./artifacts".to_string()),
 ///     None,
+///     false,
 ///     false
 /// );
 /// ```
@@ -54,6 +58,8 @@ pub struct Compiler {
     pub optimize: bool,
     /// Generate and log bytecode
     pub bytecode: bool,
+    /// Whether to check cached artifacts
+    pub cached: bool,
 }
 
 impl<'a> Compiler {
@@ -63,11 +69,12 @@ impl<'a> Compiler {
         output: Option<String>,
         construct_args: Option<Vec<String>>,
         verbose: bool,
+        cached: bool,
     ) -> Self {
         if cfg!(feature = "verbose") || verbose {
             Compiler::init_tracing_subscriber(Some(vec![tracing::Level::INFO.into()]));
         }
-        Self { sources, output, construct_args, optimize: false, bytecode: false }
+        Self { sources, output, construct_args, optimize: false, bytecode: false, cached }
     }
 
     /// Tracing
@@ -120,54 +127,74 @@ impl<'a> Compiler {
             .filter_map(|fs| fs.as_ref().map(Arc::clone).ok())
             .collect::<Vec<Arc<FileSource>>>();
 
-        // Parallel Dependency Resolution
-        let recursed_file_sources: Vec<Result<Arc<FileSource>, Arc<CompilerError<'a>>>> =
-            files.into_par_iter().map(Compiler::recurse_deps).collect();
+        // Grab the output
+        let output = self.get_outputs();
 
-        // Collect Recurse Deps errors and try to resolve to the first one
-        let mut errors = recursed_file_sources
-            .iter()
-            .filter_map(|rfs| rfs.as_ref().err())
-            .collect::<Vec<&Arc<CompilerError>>>();
-        if !errors.is_empty() {
-            let error = errors.remove(0);
-            return Err(Arc::clone(error))
-        }
+        // TODO: Parallelize Artifact Caching
+        // rayon::spawn({
+        //     let cloned_files: Vec<Arc<FileSource>> = files.iter().map(Arc::clone).collect();
+        //     let ol: OutputLocation = output.clone();
+        //     || cache::get_cached_artifacts(cloned_files, ol)
+        // });
 
-        // Unpack recursed dependencies into FileSources
-        let files = recursed_file_sources
-            .iter()
-            .filter_map(|fs| fs.as_ref().map(Arc::clone).ok())
-            .collect::<Vec<Arc<FileSource>>>();
-        tracing::info!(target: "core", "COMPILER RECURSED {} FILE DEPENDENCIES", files.len());
+        let mut artifacts: Vec<Arc<Artifact>> = vec![];
 
-        // Parallel Compilation
-        let potential_artifacts: Vec<Result<Artifact, CompilerError<'a>>> =
-            files.into_par_iter().map(|f| self.gen_artifact(f)).collect();
+        // Get our constructor arguments as a hex encoded string to compare to the cache
+        let inputs = self.get_constructor_args();
+        let encoded_inputs = Codegen::encode_constructor_args(inputs);
+        let encoded: Vec<Vec<u8>> =
+            encoded_inputs.iter().map(|tok| ethers_core::abi::encode(&[tok.clone()])).collect();
+        let constructor_args = encoded.iter().map(|tok| hex::encode(tok.as_slice())).collect();
+
+        // Get Cached or Generate Artifacts
+        tracing::debug!(target: "core", "Output directory: {}", output.0);
+        match cache::get_cached_artifacts(&files, &output, constructor_args) {
+            Some(arts) => artifacts = arts,
+            None => {
+                // Parallel Dependency Resolution
+                let recursed_file_sources: Vec<Result<Arc<FileSource>, Arc<CompilerError<'a>>>> =
+                    files.into_par_iter().map(Compiler::recurse_deps).collect();
+
+                // Collect Recurse Deps errors and try to resolve to the first one
+                let mut errors = recursed_file_sources
+                    .iter()
+                    .filter_map(|rfs| rfs.as_ref().err())
+                    .collect::<Vec<&Arc<CompilerError>>>();
+                if !errors.is_empty() {
+                    let error = errors.remove(0);
+                    return Err(Arc::clone(error))
+                }
 
-        // Output errors + return OR print # of successfully compiled files
-        let mut errors: Vec<CompilerError<'a>> = vec![];
-        let mut artifacts: Vec<Arc<Artifact>> = vec![];
-        for r in potential_artifacts {
-            match r {
-                Ok(a) => artifacts.push(Arc::new(a)),
-                Err(ce) => errors.push(ce),
-            }
-        }
-        if !errors.is_empty() {
-            tracing::error!(target: "core", "{} FILES FAILED TO COMPILE", errors.len());
-            return Err(Arc::new(CompilerError::FailedCompiles(errors)))
-        }
-        match artifacts.len() {
-            0 => tracing::warn!(target: "core", "NO FILES COMPILED SUCCESSFULLY"),
-            num => tracing::info!(target: "core", "{} FILES COMPILED SUCCESSFULLY", num),
-        }
+                // Unpack recursed dependencies into FileSources
+                let files = recursed_file_sources
+                    .into_iter()
+                    .filter_map(|fs| fs.ok())
+                    .collect::<Vec<Arc<FileSource>>>();
+                tracing::info!(target: "core", "COMPILER RECURSED {} FILE DEPENDENCIES", files.len());
 
-        // Grab the output
-        let output = self.get_outputs();
+                // Parallel Compilation
+                let potential_artifacts: Vec<Result<Artifact, CompilerError<'a>>> =
+                    files.into_par_iter().map(|f| self.gen_artifact(f)).collect();
+
+                let mut gen_errors: Vec<CompilerError<'a>> = vec![];
 
-        // Export
-        Compiler::export_artifacts(&artifacts, &output);
+                // Output errors + return OR print # of successfully compiled files
+                for r in potential_artifacts {
+                    match r {
+                        Ok(a) => artifacts.push(Arc::new(a)),
+                        Err(ce) => gen_errors.push(ce),
+                    }
+                }
+
+                if !gen_errors.is_empty() {
+                    tracing::error!(target: "core", "{} FILES FAILED TO COMPILE", gen_errors.len());
+                    return Err(Arc::new(CompilerError::FailedCompiles(gen_errors)))
+                }
+
+                // Export
+                Compiler::export_artifacts(&artifacts, &output);
+            }
+        }
 
         Ok(artifacts)
     }
diff --git a/huff_lexer/README.md b/huff_lexer/README.md
index 59b9bc0..eb13af5 100644
--- a/huff_lexer/README.md
+++ b/huff_lexer/README.md
@@ -2,7 +2,10 @@
 
 Lexical analyzer for the Huff Language.
 
-The Huff Lexer is instantiable with a `FullFileSource` containing the source code, a `FileSource`, and "spans" (a `Vec<(FileSource, Span)>`).
+The Huff Lexer is instantiable with a `FullFileSource`. `FullFileSource` is composed of
+- the file source code
+- a `FileSource`
+- `spans` == (`Vec<(FileSource, Span)>`).
 
 Once instantiated, the lexer can be used to iterate over the tokens in the source code.
 It also exposes a number of practical methods for accessing information about the source code
diff --git a/huff_lexer/src/lib.rs b/huff_lexer/src/lib.rs
index f1c3a65..3c9216b 100644
--- a/huff_lexer/src/lib.rs
+++ b/huff_lexer/src/lib.rs
@@ -39,7 +39,7 @@ pub enum Context {
 /// The lexer encapsulated in a struct.
 pub struct Lexer<'a> {
     /// The source code as peekable chars.
-    /// SHOULD NOT BE MODIFIED EVER!
+    /// WARN: SHOULD NEVER BE MODIFIED!
     pub reference_chars: Peekable<Chars<'a>>,
     /// The source code as peekable chars.
     pub chars: Peekable<Chars<'a>>,
@@ -48,7 +48,7 @@ pub struct Lexer<'a> {
     /// The current lexing span.
     pub span: RefCell<Span>,
     /// The previous lexed Token.
-    /// Cannot be a whitespace.
+    /// NOTE: Cannot be a whitespace.
     pub lookback: Option<Token>,
     /// If the lexer has reached the end of file.
     pub eof: bool,
@@ -73,8 +73,8 @@ impl<'a> Lexer<'a> {
         }
     }
 
-    // `// #include "./Utils.huff"`
     /// Lex all imports
+    /// Example import: `// #include "./Utils.huff"`
     pub fn lex_imports(source: &str) -> Vec<String> {
         let mut imports = vec![];
         let mut peekable_source = source.chars().peekable();
@@ -118,24 +118,24 @@ impl<'a> Lexer<'a> {
                     }
 
                     // Then we should have an import path between quotes
-                    match peekable_source.peek() {
-                        Some(char) => match char {
+                    if let Some(char) = peekable_source.peek() {
+                        match char {
                             '"' | '\'' => {
                                 peekable_source.next();
                                 let mut import = String::new();
                                 while peekable_source.peek().is_some() {
-                                    match peekable_source.next().unwrap() {
-                                        '"' | '\'' => {
+                                    if let Some(c) = peekable_source.next() {
+                                        if matches!(c, '"' | '\'') {
                                             imports.push(import);
                                             break
+                                        } else {
+                                            import.push(c);
                                         }
-                                        c => import.push(c),
                                     }
                                 }
                             }
                             _ => { /* Ignore non-include tokens */ }
-                        },
-                        None => { /* EOF */ }
+                        }
                     }
                 } else if nc.ne(&include_chars_iterator.next().unwrap()) {
                     include_chars_iterator = "#include".chars().peekable();
@@ -185,6 +185,15 @@ impl<'a> Lexer<'a> {
         chars.iter().collect()
     }
 
+    /// Dynamically peeks until with last chec and checks
+    pub fn checked_lookforward(&mut self, ch: char) -> bool {
+        let mut current_pos = self.current_span().end;
+        while self.nth_peek(current_pos).map(|c| c.is_ascii_whitespace()).unwrap_or(false) {
+            current_pos += 1;
+        }
+        self.nth_peek(current_pos).map(|x| x == ch).unwrap_or(false)
+    }
+
     /// Try to peek at the nth character from the source
     pub fn nth_peek(&mut self, n: usize) -> Option<char> {
         self.reference_chars.clone().nth(n)
@@ -294,8 +303,8 @@ impl<'a> Lexer<'a> {
             Some(TokenKind::Takes) => self.checked_lookback(TokenKind::Assign),
             Some(TokenKind::Returns) => {
                 let cur_span_end = self.current_span().end;
-                // Allow for loose and tight syntax (e.g. `returns (0)` & `returns(0)`)
-                self.peek_n_chars_from(2, cur_span_end).trim().starts_with('(') &&
+                // Allow for loose and tight syntax (e.g. `returns   (0)`, `returns(0)`, ...)
+                self.checked_lookforward('(') &&
                     !self.checked_lookback(TokenKind::Function) &&
                     self.peek_n_chars_from(1, cur_span_end) != ":"
             }
@@ -543,7 +552,11 @@ impl<'a> Iterator for Lexer<'a> {
                         if self.context == Context::MacroBody &&
                             matches!(
                                 slice.as_ref(),
-                                "__codesize" | "__tablesize" | "__tablestart"
+                                "__codesize" |
+                                    "__tablesize" |
+                                    "__tablestart" |
+                                    "__FUNC_SIG" |
+                                    "__EVENT_HASH" /* TODO: Clean this process up */
                             )
                         {
                             TokenKind::BuiltinFunction(slice)
@@ -619,7 +632,7 @@ impl<'a> Iterator for Lexer<'a> {
                         Some('"') => {
                             self.consume();
                             let str = self.slice();
-                            break TokenKind::Str((&str[1..str.len() - 1]).to_string())
+                            break TokenKind::Str((str[1..str.len() - 1]).to_string())
                         }
                         Some('\\') if matches!(self.nth_peek(1), Some('\\') | Some('"')) => {
                             self.consume();
@@ -642,7 +655,7 @@ impl<'a> Iterator for Lexer<'a> {
                         Some('\'') => {
                             self.consume();
                             let str = self.slice();
-                            break TokenKind::Str((&str[1..str.len() - 1]).to_string())
+                            break TokenKind::Str((str[1..str.len() - 1]).to_string())
                         }
                         Some('\\') if matches!(self.nth_peek(1), Some('\\') | Some('\'')) => {
                             self.consume();
@@ -678,6 +691,7 @@ impl<'a> Iterator for Lexer<'a> {
                 Some(s) => s,
                 None => {
                     tracing::warn!(target: "lexer", "UNABLE TO RELATIVIZE SPAN FOR \"{}\"", kind);
+                    tracing::warn!(target: "lexer", "Current Span: {:?}", self.current_span());
                     self.current_span().clone()
                 }
             };
diff --git a/huff_parser/README.md b/huff_parser/README.md
index 76d5b3f..904c072 100644
--- a/huff_parser/README.md
+++ b/huff_parser/README.md
@@ -4,7 +4,7 @@ A parser for the Huff Language.
 
 The Huff Parser accepts a vector of Tokens during instantiation.
 
-Once instantiated, the par&ser will construct an AST from the Token Vector when the `parse`
+Once instantiated, the parser will construct an AST from the Token Vector when the `parse`
 method is called.
 
 It also exposes a number of practical methods for accessing information about the source code
@@ -32,6 +32,27 @@ let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
 let mut parser = Parser::new(tokens, None);
 
 // Parse into an AST
-parser.parse();
+let unwrapped_contract = parser.parse().unwrap();
 assert_eq!(parser.current_token.kind, TokenKind::Eof);
+
+// Validate the unwrapped contract
+let expected_contract = Contract {
+  macros: vec![
+    MacroDefinition {
+      name: "HELLO_WORLD".to_string(),
+      parameters: vec![],
+      statements: vec![],
+      takes: 0,
+      returns: 0,
+      span: AstSpan(vec![Span { start: 0, end: 7, file: None }, Span { start: 8, end: 13, file: None }, Span { start: 14, end: 25, file: None }, Span { start: 25, end: 26, file: None }, Span { start: 26, end: 27, file: None }, Span { start: 28, end: 29, file: None }, Span { start: 30, end: 35, file: None }, Span { start: 35, end: 36, file: None }, Span { start: 36, end: 37, file: None }, Span { start: 37, end: 38, file: None }, Span { start: 39, end: 46, file: None }, Span { start: 46, end: 47, file: None }, Span { start: 47, end: 48, file: None }, Span { start: 48, end: 49, file: None }, Span { start: 50, end: 51, file: None }, Span { start: 51, end: 52, file: None }]),
+    }
+  ],
+  invocations: vec![],
+  imports: vec![],
+  constants: vec![],
+  functions: vec![],
+  events: vec![],
+  tables: vec![],
+};
+assert_eq!(unwrapped_contract, expected_contract);
 ```
\ No newline at end of file
diff --git a/huff_parser/src/lib.rs b/huff_parser/src/lib.rs
index 4c25c56..96e4325 100644
--- a/huff_parser/src/lib.rs
+++ b/huff_parser/src/lib.rs
@@ -102,8 +102,9 @@ impl Parser {
                         self.current_token.kind
                     );
                     return Err(ParserError {
-                        kind: ParserErrorKind::InvalidDefinition,
-                        spans: AstSpan(self.spans.clone()),
+                        kind: ParserErrorKind::InvalidDefinition(self.current_token.kind.clone()),
+                        hint: Some("Definition must be one of: `function`, `event`, `constant`, or `macro`.".to_string()),
+                        spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
             };
@@ -127,7 +128,8 @@ impl Parser {
                 let new_spans = self.spans.clone();
                 self.spans = vec![];
                 return Err(ParserError {
-                    kind: ParserErrorKind::InvalidName(tok),
+                    kind: ParserErrorKind::InvalidName(tok.clone()),
+                    hint: Some(format!("Expected import string. Got: \"{}\"", tok)),
                     spans: AstSpan(new_spans),
                 })
             }
@@ -153,6 +155,7 @@ impl Parser {
             self.spans = vec![];
             return Err(ParserError {
                 kind: ParserErrorKind::InvalidImportPath(p),
+                hint: None,
                 spans: AstSpan(new_spans),
             })
         }
@@ -169,7 +172,8 @@ impl Parser {
         } else {
             tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED: {}, GOT: {}", kind, self.current_token.kind);
             Err(ParserError {
-                kind: ParserErrorKind::UnexpectedType(kind),
+                kind: ParserErrorKind::UnexpectedType(self.current_token.kind.clone()),
+                hint: Some(format!("Expected: \"{}\"", kind)),
                 spans: AstSpan(self.spans.clone()),
             })
         }
@@ -230,14 +234,15 @@ impl Parser {
             _ => {
                 tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED IDENT, GOT: {}", tok);
                 return Err(ParserError {
-                    kind: ParserErrorKind::InvalidName(tok),
+                    kind: ParserErrorKind::InvalidName(tok.clone()),
+                    hint: Some(format!("Expected function name, found: \"{}\"", tok)),
                     spans: AstSpan(self.spans.clone()),
                 })
             }
         };
 
         // function inputs should be next
-        let inputs: Vec<Argument> = self.parse_args(true, true, false)?;
+        let inputs: Vec<Argument> = self.parse_args(true, true, false, false)?;
         // function type should be next
         let fn_type = match self.current_token.kind.clone() {
             TokenKind::View => FunctionType::View,
@@ -247,7 +252,10 @@ impl Parser {
             tok => {
                 return Err(ParserError {
                     kind: ParserErrorKind::UnexpectedType(tok),
-                    spans: AstSpan(self.spans.clone()),
+                    hint: Some(
+                        "Expected one of: `view`, `pure`, `payable`, `nonpayable`.".to_string(),
+                    ),
+                    spans: AstSpan(vec![self.current_token.span.clone()]),
                 })
             }
         };
@@ -257,7 +265,7 @@ impl Parser {
         // next token should be of `TokenKind::Returns`
         self.match_kind(TokenKind::Returns)?;
         // function outputs should be next
-        let outputs: Vec<Argument> = self.parse_args(true, true, false)?;
+        let outputs: Vec<Argument> = self.parse_args(true, true, false, false)?;
 
         let mut signature = [0u8; 4]; // Only keep first 4 bytes
         let mut hasher = Keccak::v256();
@@ -290,16 +298,24 @@ impl Parser {
             _ => {
                 tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED IDENT, GOT: {}", tok);
                 return Err(ParserError {
-                    kind: ParserErrorKind::InvalidName(tok),
+                    kind: ParserErrorKind::InvalidName(tok.clone()),
+                    hint: Some(format!("Expected event name, found: \"{}\"", tok)),
                     spans: AstSpan(self.spans.clone()),
                 })
             }
         };
 
         // Parse the event's parameters
-        let parameters: Vec<Argument> = self.parse_args(true, true, true)?;
+        let parameters: Vec<Argument> = self.parse_args(true, true, true, false)?;
+
+        let mut hash = [0u8; 32];
+        let mut hasher = Keccak::v256();
+        let input_types =
+            parameters.iter().map(|i| i.arg_type.as_ref().unwrap().clone()).collect::<Vec<_>>();
+        hasher.update(format!("{}({})", name, input_types.join(",")).as_bytes());
+        hasher.finalize(&mut hash);
 
-        Ok(Event { name, parameters, span: AstSpan(self.spans.clone()) })
+        Ok(Event { name, parameters, span: AstSpan(self.spans.clone()), hash })
     }
 
     /// Parse a constant.
@@ -318,6 +334,7 @@ impl Parser {
                 self.spans = vec![];
                 return Err(ParserError {
                     kind: ParserErrorKind::UnexpectedType(tok),
+                    hint: Some("Expected constant name.".to_string()),
                     spans: AstSpan(new_spans),
                 })
             }
@@ -337,11 +354,13 @@ impl Parser {
             }
             kind => {
                 tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED FreeStoragePointer OR Literal, GOT: {}", self.current_token.kind);
-                let new_spans = self.spans.clone();
-                self.spans = vec![];
                 return Err(ParserError {
                     kind: ParserErrorKind::InvalidConstantValue(kind),
-                    spans: AstSpan(new_spans),
+                    hint: Some(
+                        "Expected constant value to be a literal or `FREE_STORAGE_POINTER()`"
+                            .to_string(),
+                    ),
+                    spans: AstSpan(vec![self.current_token.span.clone()]),
                 })
             }
         };
@@ -363,7 +382,7 @@ impl Parser {
             self.match_kind(TokenKind::Ident("MACRO_NAME".to_string()))?.to_string();
         tracing::info!(target: "parser", "PARSING MACRO: \"{}\"", macro_name);
 
-        let macro_arguments: Vec<Argument> = self.parse_args(true, false, false)?;
+        let macro_arguments: Vec<Argument> = self.parse_args(true, false, false, false)?;
         self.match_kind(TokenKind::Assign)?;
         self.match_kind(TokenKind::Takes)?;
         let macro_takes: usize = self.parse_single_arg()?;
@@ -473,7 +492,7 @@ impl Parser {
                 TokenKind::BuiltinFunction(f) => {
                     let mut curr_spans = vec![self.current_token.span.clone()];
                     self.match_kind(TokenKind::BuiltinFunction(String::default()))?;
-                    let args = self.parse_args(true, false, false)?;
+                    let args = self.parse_args(true, false, false, true)?;
                     args.iter().for_each(|a| curr_spans.extend_from_slice(&a.span.0));
                     tracing::info!(target: "parser", "PARSING MACRO BODY: [BUILTIN FN: {}({:?})]", f, args);
                     statements.push(Statement {
@@ -489,6 +508,7 @@ impl Parser {
                     tracing::error!(target: "parser", "TOKEN MISMATCH - MACRO BODY: {}", kind);
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidTokenInMacroBody(kind),
+                        hint: None,
                         spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
@@ -499,7 +519,6 @@ impl Parser {
         Ok(statements)
     }
 
-    // TODO: Better label scoping
     /// Parse the body of a label.
     ///
     /// ## Examples
@@ -584,11 +603,11 @@ impl Parser {
                     });
                 }
                 kind => {
-                    let curr_spans = vec![self.current_token.span.clone()];
                     tracing::error!(target: "parser", "TOKEN MISMATCH - LABEL BODY: {}", kind);
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidTokenInLabelDefinition(kind),
-                        spans: AstSpan(curr_spans),
+                        hint: None,
+                        spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
             };
@@ -617,10 +636,33 @@ impl Parser {
         select_name: bool,
         select_type: bool,
         has_indexed: bool,
+        is_builtin: bool,
     ) -> Result<Vec<Argument>, ParserError> {
         let mut args: Vec<Argument> = Vec::new();
         self.match_kind(TokenKind::OpenParen)?;
         while !self.check(TokenKind::CloseParen) {
+            // The builtin functions `__FUNC_SIG` and `__EVENT_HASH` can accept a single string as
+            // input. If the `is_builtin` flag was passed, check to see if a single
+            // string is present.
+            if let TokenKind::Str(s) = &self.current_token.kind {
+                if !is_builtin {
+                    return Err(ParserError {
+                        kind: ParserErrorKind::InvalidArgs(self.current_token.kind.clone()),
+                        hint: None,
+                        spans: AstSpan(vec![self.current_token.span.clone()]),
+                    })
+                }
+
+                args.push(Argument {
+                    name: Some(s.to_owned()), // Place the string in the "name" field
+                    arg_type: None,
+                    indexed: false,
+                    span: AstSpan(vec![self.current_token.span.clone()]),
+                });
+                self.consume();
+                continue
+            }
+
             let mut arg = Argument::default();
             let mut arg_spans = vec![];
 
@@ -665,6 +707,7 @@ impl Parser {
             _ => {
                 return Err(ParserError {
                     kind: ParserErrorKind::InvalidSingleArg(self.current_token.kind.clone()),
+                    hint: Some("Expected number representing stack item count.".to_string()),
                     spans: AstSpan(single_arg_span),
                 })
             }
@@ -712,6 +755,10 @@ impl Parser {
                     self.spans = vec![];
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidMacroArgs(arg),
+                        hint: Some(
+                            "Expected literal, identifier (string), or an argument call"
+                                .to_string(),
+                        ),
                         spans: AstSpan(new_spans),
                     })
                 }
@@ -794,6 +841,7 @@ impl Parser {
                     tracing::error!("Invalid Table Body Token: {:?}", self.current_token.kind);
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidTableBodyToken(kind.clone()),
+                        hint: Some("Expected an identifier string.".to_string()),
                         spans: AstSpan(new_spans),
                     })
                 }
@@ -820,6 +868,7 @@ impl Parser {
                 self.spans = vec![];
                 Err(ParserError {
                     kind: ParserErrorKind::InvalidConstant(kind),
+                    hint: None,
                     spans: AstSpan(new_spans),
                 })
             }
@@ -851,6 +900,7 @@ impl Parser {
                 self.spans = vec![];
                 Err(ParserError {
                     kind: ParserErrorKind::InvalidArgCallIdent(kind),
+                    hint: None,
                     spans: AstSpan(new_spans),
                 })
             }
@@ -879,6 +929,7 @@ impl Parser {
             }
             kind => Err(ParserError {
                 kind: ParserErrorKind::InvalidArgs(kind),
+                hint: None,
                 spans: AstSpan(vec![self.current_token.span.clone()]),
             }),
         }
@@ -895,6 +946,7 @@ impl Parser {
                 if !(8..=256).contains(&size) || size % 8 != 0 {
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidUint256(size),
+                        hint: None,
                         spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
@@ -904,6 +956,7 @@ impl Parser {
                 if !(1..=32).contains(&size) {
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidBytes(size),
+                        hint: None,
                         spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
@@ -917,6 +970,7 @@ impl Parser {
                 if !(8..=256).contains(&size) || size % 8 != 0 {
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidInt(size),
+                        hint: None,
                         spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
diff --git a/huff_utils/src/abi.rs b/huff_utils/src/abi.rs
index a8caf45..e38d76b 100644
--- a/huff_utils/src/abi.rs
+++ b/huff_utils/src/abi.rs
@@ -32,11 +32,10 @@
 //!
 //! // Create an ABI using that generate contract
 //! let abi: Abi = contract.into();
-//! println!("Abi instant: {:?}", abi);
 //! ```
 
 use serde::{Deserialize, Serialize};
-use std::collections::BTreeMap;
+use std::{collections::BTreeMap, fmt};
 
 use crate::ast::{self, FunctionType};
 
@@ -67,13 +66,46 @@ impl Abi {
 // Allows for simple ABI Generation by directly translating the AST
 impl From<ast::Contract> for Abi {
     fn from(contract: ast::Contract) -> Self {
-        let constructors = contract
-            .macros
+        // Try to get the constructor inputs from an overriden function
+        // Otherwise, use the CONSTRUCTOR macro if one exists
+        let constructor = contract
+            .functions
             .iter()
-            .filter(|m| m.name == "CONSTRUCTOR")
+            .filter(|m| m.name.to_lowercase() == "constructor")
             .cloned()
-            .collect::<Vec<ast::MacroDefinition>>();
-        let constructor: Option<&ast::MacroDefinition> = constructors.get(0);
+            .collect::<Vec<ast::Function>>()
+            .get(0)
+            .map(|func| Constructor {
+                inputs: func
+                    .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(),
+            })
+            .or_else(|| {
+                contract
+                    .macros
+                    .iter()
+                    .filter(|m| m.name == "CONSTRUCTOR")
+                    .cloned()
+                    .collect::<Vec<ast::MacroDefinition>>()
+                    .get(0)
+                    .map(|func| Constructor {
+                        inputs: func
+                            .parameters
+                            .iter()
+                            .map(|argument| FunctionParam {
+                                name: argument.name.clone().unwrap_or_default(),
+                                kind: argument.name.clone().unwrap_or_default().into(),
+                                internal_type: None,
+                            })
+                            .collect(),
+                    })
+            });
 
         // Instantiate functions and events
         let mut functions = BTreeMap::new();
@@ -132,7 +164,7 @@ impl From<ast::Contract> for Abi {
                             .map(|argument| EventParam {
                                 name: argument.name.clone().unwrap_or_default(),
                                 kind: argument.arg_type.clone().unwrap_or_default().into(),
-                                indexed: false, // TODO: This is not present in `argument`
+                                indexed: argument.indexed,
                             })
                             .collect(),
                         anonymous: false,
@@ -143,23 +175,7 @@ impl From<ast::Contract> for Abi {
                 let _ = events.insert(val.0, val.1);
             });
 
-        Self {
-            constructor: constructor.map(|c| Constructor {
-                inputs: c
-                    .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(),
-            }),
-            functions,
-            events,
-            receive: false,
-            fallback: false,
-        }
+        Self { constructor, functions, events, receive: false, fallback: false }
     }
 }
 
@@ -231,7 +247,7 @@ pub struct FunctionParam {
 /// #### FunctionParamType
 ///
 /// The type of a function parameter
-#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
 pub enum FunctionParamType {
     /// An address
     Address,
@@ -254,6 +270,62 @@ pub enum FunctionParamType {
 }
 
 impl FunctionParamType {
+    /// Checks if the param type should be designated as "memory" for solidity interface
+    /// generation.
+    pub fn is_memory_type(&self) -> bool {
+        matches!(
+            self,
+            FunctionParamType::Bytes |
+                FunctionParamType::String |
+                FunctionParamType::Tuple(_) |
+                FunctionParamType::Array(_, _)
+        )
+    }
+}
+
+impl fmt::Debug for FunctionParamType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.display(f)
+    }
+}
+
+impl fmt::Display for FunctionParamType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.display(f)
+    }
+}
+
+impl FunctionParamType {
+    /// Print a function parameter type to a formatter
+    pub fn display(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match &self {
+            FunctionParamType::Address => write!(f, "address"),
+            FunctionParamType::Bytes => write!(f, "bytes"),
+            FunctionParamType::Int(size) => write!(f, "int{}", size),
+            FunctionParamType::Uint(size) => write!(f, "uint{}", size),
+            FunctionParamType::Bool => write!(f, "bool"),
+            FunctionParamType::String => write!(f, "string"),
+            FunctionParamType::Array(fpt, sizes) => write!(
+                f,
+                "{}{}",
+                fpt,
+                sizes
+                    .iter()
+                    .map(|s| (!s.eq(&0))
+                        .then(|| format!("[{}]", s))
+                        .unwrap_or_else(|| "[]".to_string()))
+                    .collect::<Vec<_>>()
+                    .join("")
+            ),
+            FunctionParamType::FixedBytes(size) => write!(f, "bytes{}", size),
+            FunctionParamType::Tuple(inner) => write!(
+                f,
+                "({})",
+                inner.iter().map(|fpt| fpt.to_string()).collect::<Vec<_>>().join(", ")
+            ),
+        }
+    }
+
     /// Convert string to type
     pub fn convert_string_to_type(string: &str) -> Result<Self, Box<dyn std::error::Error>> {
         let input = string.to_string().to_lowercase();
diff --git a/huff_utils/src/ast.rs b/huff_utils/src/ast.rs
index de818e5..5a010ee 100644
--- a/huff_utils/src/ast.rs
+++ b/huff_utils/src/ast.rs
@@ -28,7 +28,7 @@ pub struct AstSpan(pub Vec<Span>);
 
 impl AstSpan {
     /// Coalesce Multiple Spans Into an error string
-    pub fn error(&self) -> String {
+    pub fn error(&self, hint: Option<&String>) -> String {
         let file_to_source_map =
             self.0.iter().fold(BTreeMap::<String, Vec<&Span>>::new(), |mut m, s| {
                 let file_name =
@@ -38,29 +38,38 @@ impl AstSpan {
                 m.insert(file_name, new_vec);
                 m
             });
-        file_to_source_map.iter().filter(|fs| !fs.0.is_empty()).fold("".to_string(), |s, fs| {
-            let start = fs.1.iter().map(|fs2| fs2.start).min().unwrap_or(0);
-            let end = fs.1.iter().map(|fs2| fs2.end).max().unwrap_or(0);
-            let newline_s = if s.is_empty() { "".to_string() } else { format!("{}\n", s) };
-            if start.eq(&0) && end.eq(&0) {
-                format!("{}-> {}:{}\n   > 0|", newline_s, fs.0, start)
-            } else {
-                format!(
-                    "{}-> {}:{}-{}{}",
-                    newline_s,
-                    fs.0,
-                    start,
-                    end,
-                    fs.1.iter()
-                        .map(|sp| sp.source_seg())
-                        .filter(|ss| !ss.is_empty())
-                        .collect::<Vec<String>>()
-                        .into_iter()
-                        .unique()
-                        .fold("".to_string(), |acc, ss| { format!("{}{}", acc, ss) })
-                )
-            }
-        })
+        let source_str = file_to_source_map.iter().filter(|fs| !fs.0.is_empty()).fold(
+            "".to_string(),
+            |s, fs| {
+                let start = fs.1.iter().map(|fs2| fs2.start).min().unwrap_or(0);
+                let end = fs.1.iter().map(|fs2| fs2.end).max().unwrap_or(0);
+                let newline_s = if s.is_empty() { "".to_string() } else { format!("{}\n", s) };
+                if start.eq(&0) && end.eq(&0) {
+                    format!("{}-> {}:{}\n   > 0|", newline_s, fs.0, start)
+                } else {
+                    format!(
+                        "{}-> {}:{}-{}{}",
+                        newline_s,
+                        fs.0,
+                        start,
+                        end,
+                        fs.1.iter()
+                            .map(|sp| sp.source_seg())
+                            .filter(|ss| !ss.is_empty())
+                            .collect::<Vec<String>>()
+                            .into_iter()
+                            .unique()
+                            .fold("".to_string(), |acc, ss| { format!("{}{}", acc, ss) })
+                    )
+                }
+            },
+        );
+        // Add in optional hint message
+        format!(
+            "{}{}",
+            hint.map(|msg| format!("{}\n", /* " ".repeat(7), */ msg)).unwrap_or_default(),
+            source_str
+        )
     }
 
     /// Print just the file for missing
@@ -327,6 +336,18 @@ pub enum FunctionType {
     Pure,
 }
 
+impl FunctionType {
+    /// Get the string representation of the function type for usage in Solidity interface
+    /// generation.
+    pub fn interface_mutability(&self) -> &str {
+        match self {
+            FunctionType::View => " view",
+            FunctionType::Pure => " pure",
+            _ => "", // payable / nonpayable types not valid in Solidity interfaces
+        }
+    }
+}
+
 /// An Event Signature
 #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Event {
@@ -336,6 +357,8 @@ pub struct Event {
     pub parameters: Vec<Argument>,
     /// The event span
     pub span: AstSpan,
+    /// The event hash
+    pub hash: Literal,
 }
 
 /// A Table Definition
@@ -588,6 +611,10 @@ pub enum BuiltinFunctionKind {
     Codesize,
     /// Table start function
     Tablestart,
+    /// Function signature function
+    FunctionSignature,
+    /// Event hash function
+    EventHash,
 }
 
 impl From<&str> for BuiltinFunctionKind {
@@ -596,6 +623,8 @@ impl From<&str> for BuiltinFunctionKind {
             "__tablesize" => BuiltinFunctionKind::Tablesize,
             "__codesize" => BuiltinFunctionKind::Codesize,
             "__tablestart" => BuiltinFunctionKind::Tablestart,
+            "__FUNC_SIG" => BuiltinFunctionKind::FunctionSignature,
+            "__EVENT_HASH" => BuiltinFunctionKind::EventHash,
             _ => panic!("Invalid Builtin Function Kind"), // TODO: Better error handling
         }
     }
diff --git a/huff_utils/src/error.rs b/huff_utils/src/error.rs
index 4bbbfb9..ded95ef 100644
--- a/huff_utils/src/error.rs
+++ b/huff_utils/src/error.rs
@@ -12,6 +12,8 @@ use std::{ffi::OsString, fmt, io::Write};
 pub struct ParserError {
     /// The type of Parser Error
     pub kind: ParserErrorKind,
+    /// Hints about the error
+    pub hint: Option<String>,
     /// A collection of spans the Parser Error crosses
     pub spans: AstSpan,
 }
@@ -19,12 +21,10 @@ pub struct ParserError {
 /// A Type of Parser Error
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
 pub enum ParserErrorKind {
-    /// A general syntax error that accepts a message
-    SyntaxError(String),
     /// Unexpected type
     UnexpectedType(TokenKind),
     /// Invalid definition
-    InvalidDefinition,
+    InvalidDefinition(TokenKind),
     /// Invalid constant value
     InvalidConstantValue(TokenKind),
     /// Unexpected token in macro body
@@ -135,6 +135,10 @@ pub enum CodegenErrorKind {
     InvalidMacroStatement,
     /// The Macro Definition is Missing
     MissingMacroDefinition(String),
+    /// The Function Interface is Missing
+    MissingFunctionInterface(String),
+    /// The Event Interface is Missing
+    MissingEventInterface(String),
     /// Missing Constant Definition
     MissingConstantDefinition(String),
     /// Abi Generation Failure
@@ -151,6 +155,8 @@ pub enum CodegenErrorKind {
     InvalidMacroInvocation(String),
     /// Conversion Error for usize
     UsizeConversion(String),
+    /// Invalid Arguments
+    InvalidArguments(String),
 }
 
 impl Spanned for CodegenError {
@@ -172,6 +178,12 @@ impl<W: Write> Report<W> for CodegenError {
             CodegenErrorKind::MissingMacroDefinition(str) => {
                 write!(f.out, "Missing Macro \"{}\" Definition!", str)
             }
+            CodegenErrorKind::MissingFunctionInterface(str) => {
+                write!(f.out, "Missing Function Interface for \"{}\"!", str)
+            }
+            CodegenErrorKind::MissingEventInterface(str) => {
+                write!(f.out, "Missing Event Interface for \"{}\"!", str)
+            }
             CodegenErrorKind::MissingConstantDefinition(cd) => {
                 write!(f.out, "Missing Constant Definition for \"{}\"!", cd)
             }
@@ -185,6 +197,9 @@ impl<W: Write> Report<W> for CodegenError {
             CodegenErrorKind::UsizeConversion(input) => {
                 write!(f.out, "Usize Conversion Failed for \"{}\"", input)
             }
+            CodegenErrorKind::InvalidArguments(msg) => {
+                write!(f.out, "Invalid arguments: \"{}\"", msg)
+            }
         }
     }
 }
@@ -263,21 +278,28 @@ impl<'a> fmt::Display for CompilerError<'a> {
                 }
             },
             CompilerError::ParserError(pe) => match &pe.kind {
-                ParserErrorKind::SyntaxError(se) => {
-                    write!(f, "\nError: Syntax Error: \"{}\" \n{}\n", se, pe.spans.error())
-                }
                 ParserErrorKind::UnexpectedType(ut) => {
-                    write!(f, "\nError: Unexpected Type: \"{}\" \n{}\n", ut, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Unexpected Type: \"{}\" \n{}\n",
+                        ut,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
-                ParserErrorKind::InvalidDefinition => {
-                    write!(f, "\nError: Invalid Defintiion\n{}\n", pe.spans.error())
+                ParserErrorKind::InvalidDefinition(k) => {
+                    write!(
+                        f,
+                        "\nError: Invalid Defintion \"{}\"\n{}\n",
+                        k,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidConstantValue(cv) => {
                     write!(
                         f,
                         "\nError: Invalid Constant Value: \"{}\" \n{}\n",
                         cv,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidTokenInMacroBody(tmb) => {
@@ -285,7 +307,7 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Invalid Token In Macro Body: \"{}\" \n{}\n",
                         tmb,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidTokenInLabelDefinition(tlb) => {
@@ -293,18 +315,23 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Invalid Token In Label Defintiion: \"{}\" \n{}\n",
                         tlb,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidSingleArg(sa) => {
-                    write!(f, "\nError: Invalid Argument: \"{}\" \n{}\n", sa, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Argument: \"{}\" \n{}\n",
+                        sa,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidTableBodyToken(tbt) => {
                     write!(
                         f,
                         "\nError: Invalid Token In Table Body: \"{}\" \n{}\n",
                         tbt,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidConstant(constant) => {
@@ -312,7 +339,7 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Invalid Constant: \"{}\" \n{}\n",
                         constant,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidArgCallIdent(aci) => {
@@ -320,37 +347,71 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Invalid Argument Call Identifier: \"{}\" \n{}\n",
                         aci,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidName(name) => {
-                    write!(f, "\nError: Invalid Name: \"{}\" \n{}\n", name, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Name: \"{}\" \n{}\n",
+                        name,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidArgs(args) => {
-                    write!(f, "\nError: Invalid Arguments: \"{}\" \n{}\n", args, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Argument Type: \"{}\" \n{}\n",
+                        args,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidUint256(v) => {
-                    write!(f, "\nError: Invalid Uint256 Value: \"{}\" \n{}\n", v, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Uint256 Value: \"{}\" \n{}\n",
+                        v,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidBytes(b) => {
-                    write!(f, "\nError: Invalid Bytes Value: \"{}\" \n{}\n", b, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Bytes Value: \"{}\" \n{}\n",
+                        b,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidInt(i) => {
-                    write!(f, "\nError: Invalid Int Value: \"{}\" \n{}\n", i, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Int Value: \"{}\" \n{}\n",
+                        i,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidMacroArgs(ma) => {
                     write!(
                         f,
                         "\nError: Invalid Macro Arguments: \"{}\" \n{}\n",
                         ma,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidReturnArgs => {
-                    write!(f, "\nError: Invalid Return Arguments\n{}\n", pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Return Arguments\n{}\n",
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidImportPath(ip) => {
-                    write!(f, "\nError: Invalid Import Path: \"{}\" \n{}\n", ip, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Import Path: \"{}\" \n{}\n",
+                        ip,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
             },
             CompilerError::PathBufRead(os_str) => {
@@ -362,10 +423,10 @@ impl<'a> fmt::Display for CompilerError<'a> {
             }
             CompilerError::CodegenError(ce) => match &ce.kind {
                 CodegenErrorKind::StoragePointersNotDerived => {
-                    write!(f, "\nError: Storage Pointers Not Derived\n{}\n", ce.span.error())
+                    write!(f, "\nError: Storage Pointers Not Derived\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::InvalidMacroStatement => {
-                    write!(f, "\nError: Invalid Macro Statement\n{}\n", ce.span.error())
+                    write!(f, "\nError: Invalid Macro Statement\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::MissingMacroDefinition(md) => {
                     write!(
@@ -380,34 +441,53 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Missing Macro Definition For Invocation: \"{}\"\n{}\n",
                         mmi,
-                        ce.span.error()
+                        ce.span.error(None)
+                    )
+                }
+                CodegenErrorKind::MissingFunctionInterface(func) => {
+                    write!(
+                        f,
+                        "\nError: Missing Function Interface: \"{}\"\n{}\n",
+                        func,
+                        ce.span.error(None)
+                    )
+                }
+                CodegenErrorKind::MissingEventInterface(event) => {
+                    write!(
+                        f,
+                        "\nError: Missing Event Interface: \"{}\"\n{}\n",
+                        event,
+                        ce.span.error(None)
                     )
                 }
                 CodegenErrorKind::MissingConstantDefinition(_) => {
-                    write!(f, "\nError: Missing Constant Definition\n{}\n", ce.span.error())
+                    write!(f, "\nError: Missing Constant Definition\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::AbiGenerationFailure => {
-                    write!(f, "\nError: ABI Generation Failed\n{}\n", ce.span.error())
+                    write!(f, "\nError: ABI Generation Failed\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::IOError(ioe) => {
                     write!(f, "\nError: IO Error: {}\n{}", ioe, ce.span.file())
                 }
                 CodegenErrorKind::UnkownArgcallType => {
-                    write!(f, "\nError: Unknown Arg Call Type\n{}\n", ce.span.error())
+                    write!(f, "\nError: Unknown Arg Call Type\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::MissingMacroInvocation(mmi) => {
                     write!(
                         f,
                         "\nError: Missing Macro Invocation: \"{}\"\n{}\n",
                         mmi,
-                        ce.span.error()
+                        ce.span.error(None)
                     )
                 }
                 CodegenErrorKind::UnmatchedJumpLabel => {
-                    write!(f, "\nError: Unmatched Jump Label\n{}\n", ce.span.error())
+                    write!(f, "\nError: Unmatched Jump Label\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::UsizeConversion(_) => {
-                    write!(f, "\nError: Usize Conversion\n{}\n", ce.span.error())
+                    write!(f, "\nError: Usize Conversion\n{}\n", ce.span.error(None))
+                }
+                CodegenErrorKind::InvalidArguments(_) => {
+                    write!(f, "\nError: Invalid Arguments\n{}\n", ce.span.error(None))
                 }
             },
             CompilerError::FailedCompiles(v) => {
diff --git a/huff_utils/src/files.rs b/huff_utils/src/files.rs
index 15ae0bc..ee7a09b 100644
--- a/huff_utils/src/files.rs
+++ b/huff_utils/src/files.rs
@@ -30,7 +30,8 @@ impl<'a> FullFileSource<'a> {
                 file: Some(s.0.clone()),
             })
             .collect::<Vec<Span>>()
-            .pop()
+            .into_iter()
+            .next()
     }
 }
 
@@ -193,7 +194,10 @@ impl Span {
                                 1;
                         let line_start = &s[0..self.start].rfind('\n').unwrap_or(0);
                         let line_end = self.end +
-                            s[self.end..s.len()].find('\n').unwrap_or(s.len()).to_owned();
+                            s[self.end..s.len()]
+                                .find('\n')
+                                .unwrap_or(s.len() - self.end)
+                                .to_owned();
                         let padding =
                             (0..line_num.to_string().len()).map(|_| " ").collect::<String>();
                         format!(
diff --git a/huff_utils/src/lib.rs b/huff_utils/src/lib.rs
index 1999bd2..fd236b1 100644
--- a/huff_utils/src/lib.rs
+++ b/huff_utils/src/lib.rs
@@ -42,10 +42,13 @@ pub mod types;
 /// Bytes Util Module
 pub mod bytes_util;
 
+/// Solidity Interface Generator
+pub mod sol_interface;
+
 /// Prelude wraps common utilities.
 pub mod prelude {
     pub use crate::{
         abi::*, artifact::*, ast::*, bytecode::*, bytes_util::*, error::*, evm::*, files::*, io::*,
-        report::*, token::*, types::*,
+        report::*, sol_interface::*, token::*, types::*,
     };
 }
diff --git a/huff_utils/src/sol_interface.rs b/huff_utils/src/sol_interface.rs
new file mode 100644
index 0000000..047d417
--- /dev/null
+++ b/huff_utils/src/sol_interface.rs
@@ -0,0 +1,98 @@
+use crate::prelude::Artifact;
+use std::{
+    fs,
+    path::{Path, PathBuf},
+    sync::Arc,
+};
+
+/// Generate solidity interfaces from a vector of artifacts.
+///
+/// @param artifacts The vector of artifacts to generate interfaces from.
+/// @return The vector of generated interfaces.
+pub fn gen_sol_interfaces(artifacts: &Vec<Arc<Artifact>>) -> Vec<(PathBuf, &str, String)> {
+    let mut interfaces = Vec::new();
+
+    for artifact in artifacts {
+        if let Some(a) = &artifact.abi {
+            let mut defs = Vec::new();
+            a.events.iter().for_each(|(_, f)| {
+                defs.push(format!(
+                    "{}event {}({});",
+                    "\t",
+                    f.name,
+                    f.inputs
+                        .iter()
+                        .map(|i| {
+                            format!(
+                                "{}{}",
+                                i.kind,
+                                if i.indexed {
+                                    String::from(" indexed")
+                                } else {
+                                    String::default()
+                                }
+                            )
+                        })
+                        .collect::<Vec<_>>()
+                        .join(", "),
+                ));
+            });
+            a.functions.iter().for_each(|(_, f)| {
+                defs.push(format!(
+                    "{}function {}({}) external{}{};",
+                    "\t",
+                    f.name,
+                    f.inputs
+                        .iter()
+                        .map(|i| format!(
+                            "{}{}",
+                            i.kind,
+                            if i.kind.is_memory_type() { " memory" } else { "" }
+                        ))
+                        .collect::<Vec<_>>()
+                        .join(", "),
+                    f.state_mutability.interface_mutability(),
+                    if f.outputs.is_empty() {
+                        String::default()
+                    } else {
+                        format!(
+                            " returns ({})",
+                            f.outputs
+                                .iter()
+                                .map(|o| format!(
+                                    "{}{}",
+                                    o.kind,
+                                    if o.kind.is_memory_type() { " memory" } else { "" }
+                                ))
+                                .collect::<Vec<_>>()
+                                .join(", ")
+                        )
+                    },
+                ));
+            });
+
+            let interface_name =
+                artifact.file.path.split('/').last().unwrap().split('.').next().unwrap();
+            interfaces.push((
+                Path::new(&artifact.file.path).parent().unwrap().to_path_buf(),
+                interface_name,
+                format!("interface I{} {{\n{}\n}}", interface_name, defs.join("\n"),),
+            ));
+        }
+    }
+
+    interfaces
+}
+
+/// Export generated solidity interfaces to a file.
+///
+/// @param interfaces The vector of generated interfaces.
+/// @return Unit type if success, error if failure.
+pub fn export_interfaces(interfaces: &Vec<(PathBuf, &str, String)>) -> Result<(), std::io::Error> {
+    for (path, name, interface) in interfaces {
+        let path_str = format!("{}/I{}.sol", path.to_str().unwrap_or(""), name);
+        let file_path = Path::new(&path_str);
+        fs::write(file_path, interface)?;
+    }
+    Ok(())
+}
diff --git a/huff_utils/src/types.rs b/huff_utils/src/types.rs
index 9d4b7dd..a5aa1d6 100644
--- a/huff_utils/src/types.rs
+++ b/huff_utils/src/types.rs
@@ -151,6 +151,15 @@ impl TryFrom<String> for EToken {
         }
         if input.chars().all(|x| x.is_alphanumeric()) {
             Ok(EToken(Token::String(input)))
+        } else if input.contains(',') {
+            // Try to unwrap something like "100,0x123,20" without brackets
+            let e_tokens: Result<Vec<EToken>, _> = input
+                .split(',')
+                .map(|x| x.replace(' ', "").replace('"', "").replace('\'', ""))
+                .map(EToken::try_from)
+                .collect();
+            let tokens: Vec<Token> = e_tokens?.into_iter().map(|x| x.0).collect();
+            Ok(EToken(Token::Array(tokens)))
         } else {
             Err(format!("Invalid input: {}", input))
         }
diff --git a/huffup/README.md b/huffup/README.md
index ed6c4e3..0edb59f 100644
--- a/huffup/README.md
+++ b/huffup/README.md
@@ -7,7 +7,9 @@ _Forked from [foundry](https://github.com/foundry-rs/foundry/tree/master/foundry
 
 ## Installing
 
-`curl -L https://get.huff.sh | bash`
+`curl -L get.huff.sh | bash` (installs the `huffup` installer)
+
+Run `huffup` to install the latest version of `huffc`.
 
 
 ## Usage
diff --git a/huffup/huffup b/huffup/huffup
index 6b9614a..845fd53 100755
--- a/huffup/huffup
+++ b/huffup/huffup
@@ -14,6 +14,21 @@ main() {
   need_cmd git
   need_cmd curl
 
+  # Uninstall npm typescript compiler if npm list doesn't contain empty for huffc
+  if ! [[ "$(npm list -g huffc)" =~ "empty" ]]; then
+    warn "It appears your system has an outdated installation of huffc via npm."
+    warn "Uninstalling huffc with npm to allow huffup to take precedence..."
+    npm uninstall -g huffc
+  fi
+
+  # Uninstall yarn typescript compiler if yarn list contains a huffc entry
+  if [[ "$(yarn global list)" =~ "huffc" ]]; then
+    warn "It appears your system has an outdated installation of huffc via yarn."
+    warn "Uninstalling huffc with yarn to allow huffup to take precedence..."
+    yarn global remove huffc
+  fi
+
+  # Otherwise, continue with huffup script
   while [[ $1 ]]; do
     case $1 in
       --)               shift; break;;
@@ -133,7 +148,7 @@ main() {
     say "done"
 
     if [[ $(which huffc) =~ "cargo" ]]; then
-      warn "it appears your system has already has huff-rs installed via cargo. you may need to run 'rm $(which huffc)' to allow huffup to take precedence!"
+      warn "it appears your system already has huff-rs installed via cargo. you may need to run 'rm $(which huffc)' to allow huffup to take precedence!"
     fi
   else
     need_cmd cargo


Test patch:
diff --git a/huff_codegen/tests/constructor_args.rs b/huff_codegen/tests/constructor_args.rs
index edbfcef..065c175 100644
--- a/huff_codegen/tests/constructor_args.rs
+++ b/huff_codegen/tests/constructor_args.rs
@@ -86,3 +86,54 @@ fn encode_array_constructor_args() {
     assert_eq!(results[4], expected_array);
     assert_eq!(results[5], expected_array);
 }
+
+#[test]
+fn encode_missing_brackets_array_constructor_args() {
+    let expected_address: [u8; 20] = [
+        100, 109, 184, 255, 194, 30, 125, 220, 43, 99, 39, 68, 141, 217, 250, 86, 13, 244, 16, 135,
+    ];
+    let _expected_bytes32: Vec<u8> =
+        str_to_vec("87674fa174add091f082eab424cc60625118fa4c553592a4e54a76fb9e8512f6").unwrap();
+    // Bogus constructors args
+    let args: Vec<String> = vec![
+        "  100,  200,  300   ",
+        " 0x646dB8ffC21e7ddc2B6327448dd9Fa560Df41087,    0x646dB8ffC21e7ddc2B6327448dd9Fa560Df41087",
+        "true,  false,   false",
+        "Hello, World, Yes",
+        "   \"Hello\", \"World\", \"Yes\"",
+        "'Hello',  'World', 'Yes'   ",
+    ]
+    .iter()
+    .map(|s| s.to_string())
+    .collect();
+
+    let results = Codegen::encode_constructor_args(args);
+
+    assert_eq!(
+        results[0],
+        Token::Array(vec![
+            Token::Uint(U256::from_dec_str("100").unwrap()),
+            Token::Uint(U256::from_dec_str("200").unwrap()),
+            Token::Uint(U256::from_dec_str("300").unwrap()),
+        ])
+    );
+    assert_eq!(
+        results[1],
+        Token::Array(vec![
+            Token::Address(H160::from(expected_address)),
+            Token::Address(H160::from(expected_address)),
+        ])
+    );
+    assert_eq!(
+        results[2],
+        Token::Array(vec![Token::Bool(true), Token::Bool(false), Token::Bool(false),])
+    );
+    let expected_array = Token::Array(vec![
+        Token::String("Hello".to_string()),
+        Token::String("World".to_string()),
+        Token::String("Yes".to_string()),
+    ]);
+    assert_eq!(results[3], expected_array);
+    assert_eq!(results[4], expected_array);
+    assert_eq!(results[5], expected_array);
+}
diff --git a/huff_core/tests/builtins.rs b/huff_core/tests/builtins.rs
index 49c3ab0..8851dd7 100644
--- a/huff_core/tests/builtins.rs
+++ b/huff_core/tests/builtins.rs
@@ -41,7 +41,6 @@ fn test_codesize_builtin() {
 
     // Have the Codegen create the constructor bytecode
     let cbytes = Codegen::generate_constructor_bytecode(&contract).unwrap();
-    println!("Constructor Bytecode Result: {:?}", cbytes);
     assert_eq!(cbytes, String::from("6004"));
 }
 
@@ -173,7 +172,6 @@ fn test_tablestart_builtin() {
 
     // Have the Codegen create the constructor bytecode
     let cbytes = Codegen::generate_constructor_bytecode(&contract).unwrap();
-    println!("Constructor Bytecode Result: {:?}", cbytes);
     assert_eq!(cbytes, String::from("61001e6100265b60006000f35b60006000f35b60006000f35b60006000f30006000c001200180000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018"));
 }
 
@@ -366,3 +364,106 @@ fn test_label_clashing() {
     let mbytes = Codegen::generate_main_bytecode(&contract).unwrap();
     assert_eq!(mbytes, String::from("600861004960003961012861005160003960003560e01c8063a9059cbb14610022575b60208703516202ffe016806020015b60206020015b60206020015b60206020015b602060200100310037003d004300000000000000000000000000000000000000000000000000000000000000310000000000000000000000000000000000000000000000000000000000000037000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000043"));
 }
+
+#[test]
+fn test_func_sig_builtin() {
+    let source: &str = r#"
+        #define function transfer(address,uint256) nonpayable returns ()
+
+        #define macro TRANSFER() = takes (0) returns (0) {
+            // ...
+        }
+
+        #define macro MAIN() = takes(0) returns (0) {
+            // Identify which function is being called.
+            0x00 calldataload 0xE0 shr
+            dup1 __FUNC_SIG("transfer(address,uint256)") eq transfer jumpi
+            dup1 __FUNC_SIG('transfer(address,uint256)') eq transfer jumpi
+            dup1 __FUNC_SIG(transfer) eq transfer jumpi
+
+            transfer:
+                TRANSFER()
+        }
+    "#;
+
+    // Parse tokens
+    let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(flattened_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, None);
+
+    // Parse the AST
+    let mut contract = parser.parse().unwrap();
+
+    // Derive storage pointers
+    contract.derive_storage_pointers();
+
+    // Instantiate Codegen
+    let cg = Codegen::new();
+
+    // The codegen instance should have no artifact
+    assert!(cg.artifact.is_none());
+
+    // Have the Codegen create the constructor bytecode
+    let cbytes = Codegen::generate_main_bytecode(&contract).unwrap();
+    // `transfer(address,uint256) signature = 0xa9059cbb
+    assert_eq!(&cbytes[16..24], "a9059cbb");
+    assert_eq!(&cbytes[38..46], "a9059cbb");
+    assert_eq!(&cbytes[60..68], "a9059cbb");
+    assert_eq!(
+        cbytes,
+        String::from(
+            "60003560e01c8063a9059cbb14610027578063a9059cbb14610027578063a9059cbb14610027575b"
+        )
+    );
+}
+
+#[test]
+fn test_event_hash_builtin() {
+    let source: &str = r#"
+        #define event transfer(address,address,uint256)
+
+        #define macro MAIN() = takes(0) returns (0) {
+            __EVENT_HASH("transfer(address,address,uint256)")
+            __EVENT_HASH('transfer(address,address,uint256)')
+            __EVENT_HASH(transfer)
+            0x00 sstore
+        }
+    "#;
+
+    // Parse tokens
+    let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(flattened_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, None);
+
+    // Parse the AST
+    let mut contract = parser.parse().unwrap();
+
+    // Derive storage pointers
+    contract.derive_storage_pointers();
+
+    // Instantiate Codegen
+    let cg = Codegen::new();
+
+    // The codegen instance should have no artifact
+    assert!(cg.artifact.is_none());
+
+    // Have the Codegen create the constructor bytecode
+    let cbytes = Codegen::generate_main_bytecode(&contract).unwrap();
+    // `transfer(address,address,uint256) signature =
+    // 0xbeabacc8ffedac16e9a60acdb2ca743d80c2ebb44977a93fa8e483c74d2b35a8
+    assert_eq!(&cbytes[2..66], "beabacc8ffedac16e9a60acdb2ca743d80c2ebb44977a93fa8e483c74d2b35a8");
+    assert_eq!(
+        &cbytes[68..132],
+        "beabacc8ffedac16e9a60acdb2ca743d80c2ebb44977a93fa8e483c74d2b35a8"
+    );
+    assert_eq!(
+        &cbytes[134..198],
+        "beabacc8ffedac16e9a60acdb2ca743d80c2ebb44977a93fa8e483c74d2b35a8"
+    );
+    assert_eq!(
+        cbytes,
+        String::from("7fbeabacc8ffedac16e9a60acdb2ca743d80c2ebb44977a93fa8e483c74d2b35a87fbeabacc8ffedac16e9a60acdb2ca743d80c2ebb44977a93fa8e483c74d2b35a87fbeabacc8ffedac16e9a60acdb2ca743d80c2ebb44977a93fa8e483c74d2b35a8600055")
+    );
+}
diff --git a/huff_core/tests/errors.rs b/huff_core/tests/codegen_errors.rs
similarity index 87%
rename from huff_core/tests/errors.rs
rename to huff_core/tests/codegen_errors.rs
index d647f47..d7364e8 100644
--- a/huff_core/tests/errors.rs
+++ b/huff_core/tests/codegen_errors.rs
@@ -116,51 +116,6 @@ fn test_invalid_constant_definition() {
     }
 }
 
-#[test]
-fn test_invalid_macro_statement() {
-    let source = r#"
-    #define macro CONSTRUCTOR() = takes(0) returns (0) {}
-
-    #define macro MINT() = takes(0) returns (0) {
-        0x04 calldataload   // [to]
-        0x00                // [from (0x00), to]
-        0x24 calldataload   // [value, from, to]
-
-        FREE_STORAGE_POINTER()
-    }
-
-    #define macro MAIN() = takes(0) returns (0) {
-        0x00 calldataload 0xE0 shr
-        dup1 0x40c10f19 eq mints jumpi
-
-        mints:
-            MINT()
-    }
-    "#;
-
-    let const_start = source.find("FREE_STORAGE_POINTER()").unwrap_or(0);
-    let const_end = const_start + "FREE_STORAGE_POINTER()".len();
-
-    let full_source = FullFileSource { source, file: None, spans: vec![] };
-    let lexer = Lexer::new(full_source);
-    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
-    let mut parser = Parser::new(tokens, Some("".to_string()));
-
-    // This should be caught before codegen invalid macro statement
-    match parser.parse() {
-        Ok(_) => panic!("moose"),
-        Err(e) => {
-            assert_eq!(
-                e,
-                ParserError {
-                    kind: ParserErrorKind::InvalidTokenInMacroBody(TokenKind::FreeStoragePointer),
-                    spans: AstSpan(vec![Span { start: const_start, end: const_end, file: None }]),
-                }
-            )
-        }
-    }
-}
-
 #[test]
 fn test_missing_constructor() {
     let source = r#"
diff --git a/huff_core/tests/erc20.rs b/huff_core/tests/erc20.rs
index 90a7279..94fd5cd 100644
--- a/huff_core/tests/erc20.rs
+++ b/huff_core/tests/erc20.rs
@@ -44,6 +44,5 @@ fn test_erc20_compile() {
     // Full expected bytecode output (generated from huffc)
     let expected_bytecode = "336000556101ac80600e3d393df360003560e01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3";
 
-    // TODO: Check the bytecode
     assert_eq!(artifact.bytecode.to_lowercase(), expected_bytecode.to_lowercase());
 }
diff --git a/huff_core/tests/erc721.rs b/huff_core/tests/erc721.rs
index 3a0fd64..2d022b5 100644
--- a/huff_core/tests/erc721.rs
+++ b/huff_core/tests/erc721.rs
@@ -43,9 +43,5 @@ fn test_erc721_compile() {
     // depth first)
     let expected_bytecode = "336000556103b180600e3d393df360003560e01c8063a9059cbb146100a057806342842e0e146101a3578063b88d4fde146101a9578063095ea7b31461027b578063a22cb46514610310578063081812fc146102f357806340c10f19146101af57806370a082311461025e5780636352211e1461039457806306fdde031461035e57806395d89b4114610364578063c87b56dd1461036a57806301ffc9a714610370578063e985e9c514610376575b6044356024356004358083600160005260006020015260406000205491146100c75761019d565b8033146101005733816000526000602001526040600020546101005782600260005260006020015260406000205433146101005761019d565b6001816003600052600060200152604060002054038160036000526000602001526040600020558160036000526000602001526040600020546001018260036000526000602001526040600020558183600160005260006020015260406000205560008360026000526000602001526040600020557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a4005b60006000fd5b60006000fd5b60006000fd5b60005433146101be5760006000fd5b6024356004356000826001600052600060200152604060002054156101e257610258565b8160036000526000602001526040600020546001018260036000526000602001526040600020558183600160005260006020015260406000205560008360026000526000602001526040600020557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60006000a4005b60006000fd5b600435600360005260006020015260406000205460005260206000f35b6024358060016000526000602001526040600020548033143382600052600060200152604060002054176102ae576102ed565b60043580836002600052600060200152604060002055907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560006000a4005b60006000fd5b600435600260005260006020015260406000205460005260206000f35b60243560043533600052600060200152604060002055600435336024356000527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160006000a4005b60006000fd5b60006000fd5b60006000fd5b60006000fd5b60006000fd5b60243560043560005260006020015260406000205460005260206000f35b600435600160005260006020015260406000205460005260206000f3";
 
-    println!("Expected bytecode: {}", expected_bytecode.to_lowercase());
-    println!("Current bytecode: {}", artifact.bytecode.to_lowercase());
-
-    // TODO: Check the bytecode
     assert_eq!(artifact.bytecode.to_lowercase(), expected_bytecode.to_lowercase());
 }
diff --git a/huff_core/tests/file_resolution.rs b/huff_core/tests/file_resolution.rs
index a04d882..507ed36 100644
--- a/huff_core/tests/file_resolution.rs
+++ b/huff_core/tests/file_resolution.rs
@@ -5,7 +5,7 @@ use huff_utils::prelude::{CompilerError, OutputLocation, UnpackError};
 
 #[test]
 fn test_get_outputs_no_output() {
-    let compiler: Compiler = Compiler::new(Arc::new(vec![]), None, None, false);
+    let compiler: Compiler = Compiler::new(Arc::new(vec![]), None, None, false, false);
     let ol: OutputLocation = compiler.get_outputs();
     assert_eq!(ol, OutputLocation::default());
 }
@@ -13,7 +13,7 @@ fn test_get_outputs_no_output() {
 #[test]
 fn test_get_outputs_with_output() {
     let compiler: Compiler =
-        Compiler::new(Arc::new(vec![]), Some("./test_out/".to_string()), None, false);
+        Compiler::new(Arc::new(vec![]), Some("./test_out/".to_string()), None, false, false);
     let ol: OutputLocation = compiler.get_outputs();
     assert_eq!(ol, OutputLocation("./test_out/".to_string()));
 }
@@ -21,7 +21,7 @@ fn test_get_outputs_with_output() {
 #[test]
 fn test_transform_paths() {
     let _compiler: Compiler =
-        Compiler::new(Arc::new(vec![]), Some("./test_out/".to_string()), None, false);
+        Compiler::new(Arc::new(vec![]), Some("./test_out/".to_string()), None, false, false);
     let path_bufs: Result<Vec<PathBuf>, CompilerError<'_>> = Compiler::transform_paths(&vec![
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
         "../huff-examples/erc20/contracts/utils/".to_string(),
@@ -55,7 +55,7 @@ fn test_transform_paths() {
 #[test]
 fn test_transform_paths_non_huff() {
     let _compiler: Compiler =
-        Compiler::new(Arc::new(vec![]), Some("./test_out/".to_string()), None, false);
+        Compiler::new(Arc::new(vec![]), Some("./test_out/".to_string()), None, false, false);
     let path_bufs: Result<Vec<PathBuf>, CompilerError<'_>> =
         Compiler::transform_paths(&vec!["./ERC20.txt".to_string()]);
     assert!(path_bufs.is_err());
@@ -72,7 +72,7 @@ fn test_transform_paths_non_huff() {
 #[test]
 fn test_transform_paths_no_dir() {
     let _compiler: Compiler =
-        Compiler::new(Arc::new(vec![]), Some("./test_out/".to_string()), None, false);
+        Compiler::new(Arc::new(vec![]), Some("./test_out/".to_string()), None, false, false);
     let path_bufs: Result<Vec<PathBuf>, CompilerError<'_>> =
         Compiler::transform_paths(&vec!["./examples/random_dir/".to_string()]);
     assert!(path_bufs.is_err());
diff --git a/huff_core/tests/gen_artifact.rs b/huff_core/tests/gen_artifact.rs
index 13aef34..a1d4e82 100644
--- a/huff_core/tests/gen_artifact.rs
+++ b/huff_core/tests/gen_artifact.rs
@@ -31,7 +31,7 @@ fn test_missing_constructor() {
     };
 
     // Instantiate a new compiler
-    let compiler = Compiler::new(Arc::new(vec![]), None, None, false);
+    let compiler = Compiler::new(Arc::new(vec![]), None, None, false, false);
 
     // Generate the compile artifact
     let arc_source = Arc::new(full_source);
@@ -76,7 +76,7 @@ fn test_missing_constructor_with_inputs() {
     };
 
     // Instantiate a new compiler
-    let compiler = Compiler::new(Arc::new(vec![]), None, Some(vec!["0".to_string()]), false);
+    let compiler = Compiler::new(Arc::new(vec![]), None, Some(vec!["0".to_string()]), false, false);
 
     // Generate the compile artifact
     let arc_source = Arc::new(full_source);
diff --git a/huff_core/tests/parser_errors.rs b/huff_core/tests/parser_errors.rs
new file mode 100644
index 0000000..dd8a150
--- /dev/null
+++ b/huff_core/tests/parser_errors.rs
@@ -0,0 +1,292 @@
+use huff_lexer::*;
+use huff_parser::*;
+use huff_utils::prelude::*;
+use rand::prelude::*;
+
+#[test]
+fn test_invalid_macro_statement() {
+    let source = r#"
+    #define macro CONSTRUCTOR() = takes(0) returns (0) {}
+
+    #define macro MINT() = takes(0) returns (0) {
+        0x04 calldataload   // [to]
+        0x00                // [from (0x00), to]
+        0x24 calldataload   // [value, from, to]
+
+        FREE_STORAGE_POINTER()
+    }
+
+    #define macro MAIN() = takes(0) returns (0) {
+        0x00 calldataload 0xE0 shr
+        dup1 0x40c10f19 eq mints jumpi
+
+        mints:
+            MINT()
+    }
+    "#;
+
+    let const_start = source.find("FREE_STORAGE_POINTER()").unwrap_or(0);
+    let const_end = const_start + "FREE_STORAGE_POINTER()".len();
+
+    let full_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(full_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, Some("".to_string()));
+
+    // This should be caught before codegen invalid macro statement
+    match parser.parse() {
+        Ok(_) => panic!("moose"),
+        Err(e) => {
+            assert_eq!(
+                e,
+                ParserError {
+                    kind: ParserErrorKind::InvalidTokenInMacroBody(TokenKind::FreeStoragePointer),
+                    hint: None,
+                    spans: AstSpan(vec![Span { start: const_start, end: const_end, file: None }]),
+                }
+            )
+        }
+    }
+}
+
+#[test]
+fn test_unexpected_type() {
+    let source = "#define function func() internal returns ()";
+
+    let full_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(full_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, Some("".to_string()));
+
+    match parser.parse() {
+        Ok(_) => panic!("moose"),
+        Err(e) => {
+            assert_eq!(
+                e,
+                ParserError {
+                    kind: ParserErrorKind::UnexpectedType(TokenKind::Ident("internal".to_string())),
+                    hint: Some(
+                        "Expected one of: `view`, `pure`, `payable`, `nonpayable`.".to_string(),
+                    ),
+                    spans: AstSpan(vec![Span {
+                        start: source.find("internal").unwrap_or(0),
+                        end: source.find("internal").unwrap_or(0) + "internal".len(),
+                        file: None
+                    }]),
+                }
+            )
+        }
+    }
+}
+
+#[test]
+fn test_invalid_definition() {
+    let source = "#define test func() returns ()";
+
+    let full_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(full_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, Some("".to_string()));
+
+    match parser.parse() {
+        Ok(_) => panic!("moose"),
+        Err(e) => {
+            assert_eq!(
+                e,
+                ParserError {
+                    kind: ParserErrorKind::InvalidDefinition(TokenKind::Ident("test".to_string())),
+                    hint: Some(
+                        "Definition must be one of: `function`, `event`, `constant`, or `macro`."
+                            .to_string()
+                    ),
+                    spans: AstSpan(vec![Span {
+                        start: source.find("test").unwrap_or(0),
+                        end: source.find("test").unwrap_or(0) + "test".len(),
+                        file: None
+                    }]),
+                }
+            )
+        }
+    }
+}
+
+#[test]
+fn test_invalid_constant_value() {
+    let invalid_constant_values = vec![
+        ("ident", TokenKind::Ident("ident".to_string())),
+        ("<", TokenKind::LeftAngle),
+        ("{", TokenKind::OpenBrace),
+        ("[", TokenKind::OpenBracket),
+        ("(", TokenKind::OpenParen),
+        (":", TokenKind::Colon),
+        (",", TokenKind::Comma),
+        ("+", TokenKind::Add),
+        ("-", TokenKind::Sub),
+    ];
+
+    for (value, kind) in invalid_constant_values {
+        let source = &format!("#define constant CONSTANT = {}", value);
+
+        let full_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(full_source);
+        let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+        let mut parser = Parser::new(tokens, Some("".to_string()));
+
+        match parser.parse() {
+            Ok(_) => panic!("moose"),
+            Err(e) => {
+                assert_eq!(
+                    e,
+                    ParserError {
+                        kind: ParserErrorKind::InvalidConstantValue(kind),
+                        hint: Some(
+                            "Expected constant value to be a literal or `FREE_STORAGE_POINTER()`"
+                                .to_string()
+                        ),
+                        spans: AstSpan(vec![Span {
+                            start: source.find(value).unwrap_or(0),
+                            end: source.find(value).unwrap_or(0) + value.len(),
+                            file: None
+                        }]),
+                    }
+                )
+            }
+        }
+    }
+}
+
+#[test]
+fn test_invalid_token_in_macro_body() {
+    let invalids = vec![
+        ("{", TokenKind::OpenBrace),
+        ("(", TokenKind::OpenParen),
+        (":", TokenKind::Colon),
+        (",", TokenKind::Comma),
+        ("+", TokenKind::Add),
+        ("-", TokenKind::Sub),
+        ("/", TokenKind::Div),
+    ];
+
+    for (value, kind) in invalids {
+        let source = &format!(
+            r#"#define macro CONSTANT() = takes (0) returns (0) {{
+            {}
+        }}"#,
+            value
+        );
+
+        let full_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(full_source);
+        let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+        let mut parser = Parser::new(tokens, Some("".to_string()));
+
+        match parser.parse() {
+            Ok(_) => panic!("moose"),
+            Err(e) => {
+                assert_eq!(
+                    e,
+                    ParserError {
+                        kind: ParserErrorKind::InvalidTokenInMacroBody(kind),
+                        hint: None,
+                        spans: AstSpan(vec![Span {
+                            start: source.rfind(value).unwrap_or(0),
+                            end: source.rfind(value).unwrap_or(0) + value.len(),
+                            file: None
+                        }]),
+                    }
+                )
+            }
+        }
+    }
+}
+
+#[test]
+fn test_invalid_token_in_label_definition() {
+    let invalids = vec![
+        ("{", TokenKind::OpenBrace),
+        ("(", TokenKind::OpenParen),
+        (":", TokenKind::Colon),
+        (",", TokenKind::Comma),
+        ("+", TokenKind::Add),
+        ("-", TokenKind::Sub),
+        ("/", TokenKind::Div),
+    ];
+
+    for (value, kind) in invalids {
+        let source = &format!(
+            r#"#define macro CONSTANT() = takes (0) returns (0) {{
+            lab:
+                {}
+        }}"#,
+            value
+        );
+
+        let full_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(full_source);
+        let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+        let mut parser = Parser::new(tokens, Some("".to_string()));
+
+        match parser.parse() {
+            Ok(_) => panic!("moose"),
+            Err(e) => {
+                assert_eq!(
+                    e,
+                    ParserError {
+                        kind: ParserErrorKind::InvalidTokenInLabelDefinition(kind),
+                        hint: None,
+                        spans: AstSpan(vec![Span {
+                            start: source.rfind(value).unwrap_or(0),
+                            end: source.rfind(value).unwrap_or(0) + value.len(),
+                            file: None
+                        }]),
+                    }
+                )
+            }
+        }
+    }
+}
+
+#[test]
+fn test_invalid_single_arg() {
+    for _ in [0..10_000] {
+        let random_char = rand::random::<u8>() as char;
+        if random_char.is_numeric() ||
+            !random_char.is_alphabetic() ||
+            random_char.to_string().as_bytes().len() > 1
+        {
+            continue
+        }
+        let source =
+            &format!("#define macro CONSTANT() = takes ({}) returns (0) {{}}", random_char);
+
+        let full_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(full_source);
+        let tokens = lexer
+            .into_iter()
+            .map(|x| match x {
+                Ok(t) => t,
+                Err(_) => {
+                    Token { kind: TokenKind::Add, span: Span { start: 0, end: 0, file: None } }
+                }
+            })
+            .collect::<Vec<Token>>();
+        let mut parser = Parser::new(tokens, Some("".to_string()));
+
+        match parser.parse() {
+            Ok(_) => panic!("moose"),
+            Err(e) => {
+                assert_eq!(
+                    e,
+                    ParserError {
+                        kind: ParserErrorKind::InvalidSingleArg(TokenKind::Ident(format!(
+                            "{}",
+                            random_char
+                        ))),
+                        hint: Some("Expected number representing stack item count.".to_string()),
+                        spans: AstSpan(vec![Span { start: 34, end: 35, file: None }]),
+                    }
+                )
+            }
+        }
+    }
+}
diff --git a/huff_lexer/tests/builtins.rs b/huff_lexer/tests/builtins.rs
index 1765147..cce87e8 100644
--- a/huff_lexer/tests/builtins.rs
+++ b/huff_lexer/tests/builtins.rs
@@ -4,7 +4,7 @@ use std::ops::Deref;
 
 #[test]
 fn parses_builtin_function_in_macro_body() {
-    let builtin_funcs = ["__codesize", "__tablesize", "__tablestart"];
+    let builtin_funcs = ["__codesize", "__tablesize", "__tablestart", "__FUNC_SIG", "__EVENT_HASH"];
 
     for builtin in builtin_funcs {
         let source = &format!(
@@ -69,7 +69,7 @@ fn parses_builtin_function_in_macro_body() {
 #[test]
 #[should_panic]
 fn fails_to_parse_builtin_outside_macro_body() {
-    let builtin_funcs = ["__codesize", "__tablesize", "__tablestart"];
+    let builtin_funcs = ["__codesize", "__tablesize", "__tablestart", "__FUNC_SIG", "__EVENT_HASH"];
 
     for builtin in builtin_funcs {
         let source = &format!("{}(MAIN)", builtin);
diff --git a/huff_lexer/tests/keywords.rs b/huff_lexer/tests/keywords.rs
index b2c4533..f235a66 100644
--- a/huff_lexer/tests/keywords.rs
+++ b/huff_lexer/tests/keywords.rs
@@ -138,7 +138,7 @@ fn parses_constant_keyword() {
 
 #[test]
 fn parses_takes_and_returns_keywords() {
-    let source = "#define macro TEST() = takes (0) returns (0)";
+    let source = "#define macro TEST() = takes   (0)   returns   (0)";
     let flattened_source = FullFileSource { source, file: None, spans: vec![] };
     let mut lexer = Lexer::new(flattened_source);
 
@@ -170,7 +170,7 @@ fn parses_takes_and_returns_keywords() {
     // Lex Returns
     let tok = lexer.next();
     let unwrapped = tok.unwrap().unwrap();
-    let returns_span = Span::new(33..40, None);
+    let returns_span = Span::new(37..44, None);
     assert_eq!(unwrapped, Token::new(TokenKind::Returns, returns_span.clone()));
     assert_eq!(lexer.current_span().deref(), &returns_span);
 
diff --git a/huff_parser/tests/event.rs b/huff_parser/tests/event.rs
index dda551b..b24750f 100644
--- a/huff_parser/tests/event.rs
+++ b/huff_parser/tests/event.rs
@@ -59,6 +59,10 @@ fn test_parse_event() {
                     // ")"
                     Span { start: 55, end: 56, file: None },
                 ]),
+                hash: [
+                    96, 60, 104, 14, 131, 197, 151, 167, 15, 107, 26, 61, 1, 186, 238, 67, 62, 152,
+                    177, 8, 184, 82, 242, 224, 18, 70, 210, 27, 27, 119, 23, 114,
+                ],
             },
         ),
         (
@@ -107,6 +111,10 @@ fn test_parse_event() {
                     // ")"
                     Span { start: 39, end: 40, file: None },
                 ]),
+                hash: [
+                    96, 60, 104, 14, 131, 197, 151, 167, 15, 107, 26, 61, 1, 186, 238, 67, 62, 152,
+                    177, 8, 184, 82, 242, 224, 18, 70, 210, 27, 27, 119, 23, 114,
+                ],
             },
         ),
         (
@@ -155,6 +163,10 @@ fn test_parse_event() {
                     // ")"
                     Span { start: 45, end: 46, file: None },
                 ]),
+                hash: [
+                    96, 60, 104, 14, 131, 197, 151, 167, 15, 107, 26, 61, 1, 186, 238, 67, 62, 152,
+                    177, 8, 184, 82, 242, 224, 18, 70, 210, 27, 27, 119, 23, 114,
+                ],
             },
         ),
     ];
diff --git a/huff_parser/tests/opcodes.rs b/huff_parser/tests/opcodes.rs
index eed0d88..e766021 100644
--- a/huff_parser/tests/opcodes.rs
+++ b/huff_parser/tests/opcodes.rs
@@ -1,6 +1,6 @@
 use huff_lexer::*;
 use huff_parser::*;
-use huff_utils::{evm::Opcode, prelude::*};
+use huff_utils::prelude::*;
 
 #[test]
 fn not_mistaken_as_opcode() {
@@ -29,7 +29,7 @@ fn not_mistaken_as_opcode() {
         let actual_label = tokens[tokens.len() - 5].kind.clone();
         let mut parser = Parser::new(tokens, None);
         // parsing to ensure tokens syntax is valid
-        let contract = parser.parse().unwrap();
+        let _contract = parser.parse().unwrap();
         assert_eq!(actual_label_arg, TokenKind::Ident(label.clone()));
         assert_eq!(actual_label, TokenKind::Label(label));
     }
diff --git a/huff_utils/tests/abi.rs b/huff_utils/tests/abi.rs
index ba6c03d..5ef6e00 100644
--- a/huff_utils/tests/abi.rs
+++ b/huff_utils/tests/abi.rs
@@ -32,3 +32,48 @@ fn convert_function_param_type() {
         assert_eq!(ac_func_type, *expected_fn_types.get(&index).unwrap());
     }
 }
+
+#[test]
+fn test_display_func_param_type() {
+    let func_types = [
+        "bool",
+        "string",
+        "address",
+        "uint198",
+        "uint",
+        "bytes32",
+        "bytes",
+        "uint256[][]",
+        "address[5][]",
+        "bool[][1][][2]",
+    ];
+
+    let expected_fn_types = HashMap::from([
+        (0, FunctionParamType::Bool),
+        (1, FunctionParamType::String),
+        (2, FunctionParamType::Address),
+        (3, FunctionParamType::Uint(198)),
+        (4, FunctionParamType::Uint(256)),
+        (5, FunctionParamType::FixedBytes(32)),
+        (6, FunctionParamType::Bytes),
+        (7, FunctionParamType::Array(Box::new(FunctionParamType::Uint(256)), vec![0, 0])),
+        (8, FunctionParamType::Array(Box::new(FunctionParamType::Address), vec![5, 0])),
+        (9, FunctionParamType::Array(Box::new(FunctionParamType::Bool), vec![0, 1, 0, 2])),
+    ]);
+
+    for (index, func_type) in func_types.into_iter().enumerate() {
+        let ac_func_type = FunctionParamType::convert_string_to_type(func_type).unwrap();
+        assert_eq!(ac_func_type, *expected_fn_types.get(&index).unwrap());
+        // manual check for uints since "uint" is coalesced to "uint256"
+        match ac_func_type {
+            FunctionParamType::Uint(s) => {
+                assert_eq!(ac_func_type.to_string(), format!("uint{}", s));
+                assert_eq!(format!("{:?}", ac_func_type), format!("uint{}", s));
+            }
+            _ => {
+                assert_eq!(ac_func_type.to_string(), func_types[index]);
+                assert_eq!(format!("{:?}", ac_func_type), func_types[index]);
+            }
+        }
+    }
+}
diff --git a/huff_utils/tests/files.rs b/huff_utils/tests/files.rs
index f5cffb2..edd3ef4 100644
--- a/huff_utils/tests/files.rs
+++ b/huff_utils/tests/files.rs
@@ -1,4 +1,40 @@
-use huff_utils::files::FileSource;
+use std::sync::Arc;
+
+use huff_utils::{files::FileSource, prelude::Span};
+
+#[test]
+fn test_source_seg() {
+    let span = Span {
+        start: 59,
+        end: 67,
+        file: Some(Arc::new(
+            FileSource {
+                id: uuid::Uuid::nil(),
+                path: "./huff-examples/errors/error.huff".to_string(),
+                source: Some("#include \"./import.huff\"\n\n#define function addressGetter() internal returns (address)".to_string()),
+                access: None,
+                dependencies: Some(vec![
+                    Arc::new(FileSource {
+                        id: uuid::Uuid::nil(),
+                        path: "./huff-examples/errors/import.huff".to_string(),
+                        source: Some("#define macro SOME_RANDOM_MACRO() = takes(2) returns (1) {\n    // Store the keys in memory\n    dup1 0x00 mstore\n    swap1 dup1 0x00 mstore\n\n    // Hash the data, generating a key.\n    0x40 sha3\n}\n".to_string()),
+                        access: None,
+                        dependencies: Some(vec![])
+                    })
+                ])
+            }
+        ))
+    };
+
+    let source_seg = span.source_seg();
+    assert_eq!(
+        source_seg,
+        format!(
+            "\n     {}|\n  > {} | {}\n     {}|",
+            " ", 3, "#define function addressGetter() internal returns (address)", " ",
+        )
+    );
+}
 
 #[test]
 fn test_derive_dir() {