Repository: huff-language/huff-rs
Pull request #124 (2022-06-24 16:19:16)

Stage
### `stage` -> `main`

### Changelog

- `true` and `false` literal syntactic sugar by @clabby in #122 
- Fix Array Support by @exp-table in #119 
- Bubble up Missing File Compile Errors by @abigger87 in #121 
- Cleaner Verbose Output by @clabby in #125
- Fix Missing Constructor Error by @abigger87 in #129 
- Only Export Artifacts with CLI Flag by @abigger87 in #131
- Optimized Constructor Bytecode by @clabby in #136 
- Fix Lexer Keyword Labels by @exp-table in #130 
- Prevent Non-tty Spinner Output by @clabby in #138  



Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index b41d8df..3d1607e 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -27,6 +27,11 @@ jobs:
         with:
           fetch-depth: 0
 
+      - name: Checkout stage branch if Nightly
+        if: ${{ env.IS_NIGHTLY }}
+        run: |
+          git checkout stage
+
       - name: Compute release name and tag
         id: release_info
         run: |
@@ -225,4 +230,4 @@ jobs:
         with:
           script: |
             const prunePrereleases = require('./.github/scripts/prune-prereleases.js')
-            await prunePrereleases({github, context})
\ No newline at end of file
+            await prunePrereleases({github, context})
diff --git a/Cargo.lock b/Cargo.lock
index 8fc7494..cd2898a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -161,6 +161,12 @@ dependencies = [
  "rustc_version",
 ]
 
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -292,7 +298,7 @@ version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "crossbeam-utils",
 ]
 
@@ -302,7 +308,7 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "crossbeam-epoch",
  "crossbeam-utils",
 ]
@@ -314,7 +320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
 dependencies = [
  "autocfg",
- "cfg-if",
+ "cfg-if 1.0.0",
  "crossbeam-utils",
  "lazy_static",
  "memoffset",
@@ -327,7 +333,7 @@ version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "lazy_static",
 ]
 
@@ -584,7 +590,7 @@ version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
  "wasi",
 ]
@@ -651,6 +657,7 @@ dependencies = [
  "ethers-core",
  "huff_core",
  "huff_utils",
+ "isatty",
  "spinners",
  "tracing",
  "uuid",
@@ -784,7 +791,19 @@ version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "isatty"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31a8281fc93ec9693494da65fbf28c0c2aa60a2eaec25dc58e2f31952e95edc"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "redox_syscall 0.1.57",
+ "winapi",
 ]
 
 [[package]]
@@ -823,7 +842,7 @@ version = "0.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "ecdsa",
  "elliptic-curve",
  "sec1",
@@ -854,7 +873,7 @@ version = "0.4.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -1239,6 +1258,12 @@ dependencies = [
  "num_cpus",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.13"
@@ -1577,10 +1602,10 @@ version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "fastrand",
  "libc",
- "redox_syscall",
+ "redox_syscall 0.2.13",
  "remove_dir_all",
  "winapi",
 ]
@@ -1672,7 +1697,7 @@ version = "0.1.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "pin-project-lite",
  "tracing-attributes",
  "tracing-core",
@@ -1840,7 +1865,7 @@ version = "0.2.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
  "wasm-bindgen-macro",
 ]
 
diff --git a/README.md b/README.md
index 98daaef..94c15a9 100644
--- a/README.md
+++ b/README.md
@@ -28,12 +28,12 @@ To dive deeper into [Huff](https://github.com/huff-language), visit the [Officia
 
 ## Installation
 
-_Something not working? Send a message in [discord](https://discord.gg/2uuJyatE6M)._
+_Something not working? Send a message in [discord](https://discord.huff.sh)._
 
 First run the command below to get `huffup`, the Huff installer:
 
 ```bash
-curl -L https://get.huff.sh | bash
+curl -L get.huff.sh | bash
 ```
 
 To avoid redirecting the script directly into bash, download and run the [huffup installation script](https://raw.githubusercontent.com/huff-language/huff-rs/main/huffup/install).
diff --git a/huff_cli/Cargo.toml b/huff_cli/Cargo.toml
index 80a1fbb..e62e6df 100644
--- a/huff_cli/Cargo.toml
+++ b/huff_cli/Cargo.toml
@@ -20,6 +20,7 @@ ethers-core = "0.13.0"
 yansi = "0.5.1"
 spinners = "4.1.0"
 uuid = { version = "1.1.1", features = ["v4"] }
+isatty = "0.1.9"
 
 [[bin]]
 name = "huffc"
diff --git a/huff_cli/src/huffc.rs b/huff_cli/src/huffc.rs
index ec38da4..55b85c9 100644
--- a/huff_cli/src/huffc.rs
+++ b/huff_cli/src/huffc.rs
@@ -12,6 +12,7 @@ use huff_core::Compiler;
 use huff_utils::prelude::{
     unpack_files, AstSpan, CodegenError, CodegenErrorKind, CompilerError, FileSource, Span,
 };
+use isatty::stdout_isatty;
 use spinners::{Spinner, Spinners};
 use std::{path::Path, sync::Arc};
 use yansi::Paint;
@@ -39,6 +40,10 @@ struct Huff {
     #[clap(short = 'i', long = "inputs", multiple_values = true)]
     inputs: Option<Vec<String>>,
 
+    /// Whether to generate artifacts or not
+    #[clap(short = 'a', long = "artifacts")]
+    artifacts: bool,
+
     /// Optimize compilation.
     #[clap(short = 'z', long = "optimize")]
     optimize: bool,
@@ -75,9 +80,10 @@ fn main() {
     };
     let compiler: Compiler = Compiler {
         sources: Arc::clone(&sources),
-        output: match &cli.output {
-            Some(o) => Some(o.clone()),
-            None => Some(cli.outputdir.clone()),
+        output: match (&cli.output, cli.artifacts) {
+            (Some(o), true) => Some(o.clone()),
+            (None, true) => Some(cli.outputdir.clone()),
+            _ => None,
         },
         construct_args: cli.inputs,
         optimize: cli.optimize,
@@ -86,11 +92,18 @@ fn main() {
 
     // Create compiling spinner
     tracing::debug!(target: "core", "[⠔] COMPILING");
-    let mut sp = Spinner::new(Spinners::Dots, "Compiling...".into());
+    let mut sp: Option<Spinner> = None;
+    // If stdout is a TTY, create a spinner
+    if stdout_isatty() {
+        sp = Some(Spinner::new(Spinners::Dots, "Compiling...".into()));
+    }
 
     let compile_res = compiler.execute();
-    sp.stop();
-    println!(" ");
+    // Stop spinner animation if it exists
+    if let Some(mut sp) = sp {
+        sp.stop();
+        println!(" ");
+    }
     match compile_res {
         Ok(artifacts) => {
             if artifacts.is_empty() {
@@ -120,7 +133,7 @@ fn main() {
             }
             if cli.bytecode {
                 match sources.len() {
-                    1 => println!("{}", artifacts[0].bytecode),
+                    1 => print!("{}", artifacts[0].bytecode),
                     _ => artifacts
                         .iter()
                         .for_each(|a| println!("\"{}\" bytecode: {}", a.file.path, a.bytecode)),
diff --git a/huff_codegen/README.md b/huff_codegen/README.md
index 05089c2..cc9ad30 100644
--- a/huff_codegen/README.md
+++ b/huff_codegen/README.md
@@ -40,7 +40,7 @@ let inputs = vec![];
 let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode);
 
 // Validate the output bytecode
-assert_eq!(churn_res.unwrap().bytecode, "336000556101ac806100116000396000f360003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3".to_lowercase());
+assert_eq!(churn_res.unwrap().bytecode, "336000556101ac80600e3d393df360003560e01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3".to_lowercase());
 
 // Write the compile artifact out to a file
 // cg.export("./output.json");
diff --git a/huff_codegen/src/irgen/statements.rs b/huff_codegen/src/irgen/statements.rs
index 1d61512..4c6e432 100644
--- a/huff_codegen/src/irgen/statements.rs
+++ b/huff_codegen/src/irgen/statements.rs
@@ -18,7 +18,7 @@ pub fn statement_gen(
 ) -> Result<Vec<(usize, Bytes)>, CodegenError> {
     let mut bytes = vec![];
 
-    tracing::debug!(target: "codegen", "Got Statement: {:?}", s);
+    tracing::debug!(target: "codegen", "Got Statement: {}", s.ty);
 
     match &s.ty {
         StatementType::MacroInvocation(mi) => {
@@ -58,7 +58,7 @@ pub fn statement_gen(
                 };
 
             // Set jump table values
-            tracing::debug!(target: "codegen", "Unmatched jumps: {:?}", res.unmatched_jumps);
+            tracing::debug!(target: "codegen", "Unmatched jumps: {:?}", res.unmatched_jumps.iter().map(|uj| uj.label.clone()).collect::<Vec<String>>());
             for j in res.unmatched_jumps.iter_mut() {
                 let new_index = j.bytecode_index;
                 j.bytecode_index = 0;
@@ -80,7 +80,7 @@ pub fn statement_gen(
         }
         StatementType::Label(label) => {
             // Add JUMPDEST opcode to final result and add to label_indices
-            tracing::info!(target: "codegen", "RECURSE BYTECODE GOT LABEL: {:?}", label);
+            tracing::info!(target: "codegen", "RECURSE BYTECODE GOT LABEL: {:?}", label.name);
             label_indices.insert(label.name.clone(), *offset);
             bytes.push((*offset, Bytes(Opcode::Jumpdest.to_string())));
             *offset += 1;
diff --git a/huff_codegen/src/lib.rs b/huff_codegen/src/lib.rs
index 3358f99..e633fe8 100644
--- a/huff_codegen/src/lib.rs
+++ b/huff_codegen/src/lib.rs
@@ -397,16 +397,36 @@ impl Codegen {
         let contract_length = main_bytecode.len() / 2;
         let constructor_length = constructor_bytecode.len() / 2;
 
-        let contract_size = format!("{:04x}", contract_length);
-        let contract_code_offset = format!("{:04x}", 13 + constructor_length);
-
         let encoded: Vec<Vec<u8>> =
             args.iter().map(|tok| ethers_core::abi::encode(&[tok.clone()])).collect();
         let hex_args: Vec<String> = encoded.iter().map(|tok| hex::encode(tok.as_slice())).collect();
         let constructor_args = hex_args.join("");
 
+        // Constructor size optimizations
+        let mut bootstrap_code_size = 9;
+        let contract_size = if contract_length < 256 {
+            format!("60{}", pad_n_bytes(format!("{:x}", contract_length).as_str(), 1))
+        } else {
+            bootstrap_code_size += 1;
+
+            format!("61{}", pad_n_bytes(format!("{:x}", contract_length).as_str(), 2))
+        };
+        let contract_code_offset = if (bootstrap_code_size + constructor_length) < 256 {
+            format!(
+                "60{}",
+                pad_n_bytes(format!("{:x}", bootstrap_code_size + constructor_length).as_str(), 1)
+            )
+        } else {
+            bootstrap_code_size += 1;
+
+            format!(
+                "61{}",
+                pad_n_bytes(format!("{:x}", bootstrap_code_size + constructor_length).as_str(), 2)
+            )
+        };
+
         // Generate the final bytecode
-        let bootstrap_code = format!("61{}8061{}6000396000f3", contract_size, contract_code_offset);
+        let bootstrap_code = format!("{}80{}3d393df3", contract_size, contract_code_offset);
         let constructor_code = format!("{}{}", constructor_bytecode, bootstrap_code);
         artifact.bytecode =
             format!("{}{}{}", constructor_code, main_bytecode, constructor_args).to_lowercase();
@@ -430,7 +450,7 @@ impl Codegen {
     ///
     /// * `out` - Output location to write the serialized json artifact to.
     pub fn export(output: String, art: &Artifact) -> Result<(), CodegenError> {
-        let serialized_artifact = serde_json::to_string(art).unwrap();
+        let serialized_artifact = serde_json::to_string_pretty(art).unwrap();
         // Try to create the parent directory
         let file_path = Path::new(&output);
         if let Some(p) = file_path.parent() {
diff --git a/huff_core/benches/huff_benchmark.rs b/huff_core/benches/huff_benchmark.rs
index 0e08bf5..3f4c212 100644
--- a/huff_core/benches/huff_benchmark.rs
+++ b/huff_core/benches/huff_benchmark.rs
@@ -7,9 +7,12 @@ use huff_utils::prelude::*;
 use std::{path::PathBuf, sync::Arc};
 
 fn lex_erc20_from_source_benchmark(c: &mut Criterion) {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .into_iter()
+    .map(|p| p.unwrap())
+    .collect();
 
     // Recurse file deps + generate flattened source
     let file_source = file_sources.get(0).unwrap();
@@ -31,9 +34,12 @@ fn lex_erc20_from_source_benchmark(c: &mut Criterion) {
 }
 
 fn parse_erc20_benchmark(c: &mut Criterion) {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .into_iter()
+    .map(|p| p.unwrap())
+    .collect();
 
     // Recurse file deps + generate flattened source
     let file_source = file_sources.get(0).unwrap();
@@ -60,9 +66,12 @@ fn parse_erc20_benchmark(c: &mut Criterion) {
 }
 
 fn codegen_erc20_benchmark(c: &mut Criterion) {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .into_iter()
+    .map(|p| p.unwrap())
+    .collect();
 
     // Recurse file deps + generate flattened source
     let file_source = file_sources.get(0).unwrap();
@@ -101,9 +110,12 @@ fn codegen_erc20_benchmark(c: &mut Criterion) {
 
 fn erc20_compilation_benchmark(c: &mut Criterion) {
     c.bench_function("Full ERC-20 compilation", |b| b.iter(|| {
-        let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+        let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
             "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-        )]);
+        )])
+            .into_iter()
+            .map(|p| p.unwrap())
+            .collect();
 
         // Recurse file deps + generate flattened source
         let file_source = file_sources.get(0).unwrap();
@@ -138,9 +150,12 @@ fn erc20_compilation_benchmark(c: &mut Criterion) {
 
 fn erc721_compilation_benchmark(c: &mut Criterion) {
     c.bench_function("Full ERC-721 compilation", |b| b.iter(|| {
-        let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+        let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
             "../huff-examples/erc721/contracts/ERC721.huff".to_string(),
-        )]);
+        )])
+            .into_iter()
+            .map(|p| p.unwrap())
+            .collect();
 
         // Recurse file deps + generate flattened source
         let file_source = file_sources.get(0).unwrap();
diff --git a/huff_core/src/lib.rs b/huff_core/src/lib.rs
index 33dd2bf..a6f974d 100644
--- a/huff_core/src/lib.rs
+++ b/huff_core/src/lib.rs
@@ -103,7 +103,22 @@ impl<'a> Compiler {
         let file_paths: Vec<PathBuf> = Compiler::transform_paths(&self.sources)?;
 
         // Parallel file fetching
-        let files: Vec<Arc<FileSource>> = Compiler::fetch_sources(&file_paths);
+        let files: Vec<Result<Arc<FileSource>, CompilerError>> =
+            Compiler::fetch_sources(file_paths);
+
+        // Unwrap errors
+        let mut errors =
+            files.iter().filter_map(|rfs| rfs.as_ref().err()).collect::<Vec<&CompilerError>>();
+        if !errors.is_empty() {
+            let error = errors.remove(0);
+            return Err(Arc::new(error.clone()))
+        }
+
+        // Unpack files into their file sources
+        let files = files
+            .iter()
+            .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>>>> =
@@ -170,7 +185,7 @@ impl<'a> Compiler {
             file: Some(Arc::clone(&file)),
             spans: flattened.1,
         };
-        tracing::debug!(target: "core", "GOT FULL SOURCE: \"{:?}\"", full_source);
+        tracing::debug!(target: "core", "GOT FULL SOURCE FOR PATH: {:?}", file.path);
 
         // Perform Lexical Analysis
         // Create a new lexer from the FileSource, flattening dependencies
@@ -200,11 +215,10 @@ impl<'a> Compiler {
                 e.span = AstSpan(
                     e.span
                         .0
-                        .iter()
-                        .map(|s| {
-                            let mut n_s = s.clone();
-                            n_s.file = Some(Arc::clone(&file));
-                            n_s
+                        .into_iter()
+                        .map(|mut s| {
+                            s.file = Some(Arc::clone(&file));
+                            s
                         })
                         .collect::<Vec<Span>>(),
                 );
@@ -213,29 +227,32 @@ impl<'a> Compiler {
             }
         };
         tracing::info!(target: "core", "MAIN BYTECODE GENERATED [{}]", main_bytecode);
+        let inputs = self.get_constructor_args();
         let constructor_bytecode = match Codegen::generate_constructor_bytecode(&contract) {
             Ok(mb) => mb,
             Err(mut e) => {
-                // Add File Source to Span
-                e.span = AstSpan(
-                    e.span
-                        .0
-                        .iter()
-                        .map(|s| {
-                            let mut n_s = s.clone();
-                            n_s.file = Some(Arc::clone(&file));
-                            n_s
-                        })
-                        .collect::<Vec<Span>>(),
-                );
-                tracing::error!(target: "codegen", "Construct Failed with CodegenError: {:?}", e);
-                return Err(CompilerError::CodegenError(e))
+                if !inputs.is_empty() {
+                    // Add File Source to Span
+                    e.span = AstSpan(
+                        e.span
+                            .0
+                            .into_iter()
+                            .map(|mut s| {
+                                s.file = Some(Arc::clone(&file));
+                                s
+                            })
+                            .collect::<Vec<Span>>(),
+                    );
+                    tracing::error!(target: "codegen", "Constructor inputs provided, but contract missing \"CONSTRUCTOR\" macro!");
+                    return Err(CompilerError::CodegenError(e))
+                }
+                tracing::warn!(target: "codegen", "Contract has no \"CONSTRUCTOR\" macro definition!");
+                "".to_string()
             }
         };
 
         // Encode Constructor Arguments
         tracing::info!(target: "core", "CONSTRUCTOR BYTECODE GENERATED [{}]", constructor_bytecode);
-        let inputs = self.get_constructor_args();
         tracing::info!(target: "core", "ENCODING {} INPUTS", inputs.len());
         let encoded_inputs = Codegen::encode_constructor_args(inputs);
         tracing::info!(target: "core", "ENCODED {} INPUTS", encoded_inputs.len());
@@ -265,13 +282,13 @@ impl<'a> Compiler {
     }
 
     /// Get the file sources for a vec of PathBufs
-    pub fn fetch_sources(paths: &Vec<PathBuf>) -> Vec<Arc<FileSource>> {
-        let files: Vec<Arc<FileSource>> = paths
+    pub fn fetch_sources(paths: Vec<PathBuf>) -> Vec<Result<Arc<FileSource>, CompilerError<'a>>> {
+        paths
             .into_par_iter()
             .map(|pb| {
                 let file_loc = String::from(pb.to_string_lossy());
                 match std::fs::read_to_string(&file_loc) {
-                    Ok(source) => Some(Arc::new(FileSource {
+                    Ok(source) => Ok(Arc::new(FileSource {
                         id: Uuid::new_v4(),
                         path: file_loc,
                         source: Some(source),
@@ -280,15 +297,11 @@ impl<'a> Compiler {
                     })),
                     Err(_) => {
                         tracing::error!(target: "core", "FILE READ FAILED: \"{}\"!", file_loc);
-                        None
+                        Err(CompilerError::FileUnpackError(UnpackError::MissingFile(file_loc)))
                     }
                 }
             })
-            .filter(|f| f.is_some())
-            .map(|f| f.unwrap_or_default())
-            .collect();
-        tracing::info!(target: "core", "COMPILER FETCHED {} FILE SOURCES", files.len());
-        files
+            .collect()
     }
 
     /// Recurses file dependencies
@@ -327,7 +340,12 @@ impl<'a> Compiler {
             tracing::info!(target: "core", "LOCALIZED IMPORTS {:?}", localized_imports);
         }
         let import_bufs: Vec<PathBuf> = Compiler::transform_paths(&localized_imports)?;
-        let mut file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&import_bufs);
+        let potentials: Result<Vec<Arc<FileSource>>, CompilerError> =
+            Compiler::fetch_sources(import_bufs).into_iter().collect();
+        let mut file_sources = match potentials {
+            Ok(p) => p,
+            Err(e) => return Err(Arc::new(e)),
+        };
         if !file_sources.is_empty() {
             tracing::info!(target: "core", "FETCHED {} FILE SOURCES", file_sources.len());
         }
@@ -355,17 +373,32 @@ impl<'a> Compiler {
     /// 1. Cleans any previous artifacts in the output directory.
     /// 2. Exports artifacts in parallel as serialized json `Artifact` objects.
     pub fn export_artifacts(artifacts: &Vec<Arc<Artifact>>, output: &OutputLocation) {
+        // Exit if empty output location
+        if output.0.is_empty() {
+            tracing::warn!(target: "core", "Exiting artifact export with empty output location!");
+            return
+        }
+
         // Clean the Output Directory
         tracing::warn!(target: "core", "REMOVING DIRECTORY: \"{}\"", output.0);
         if !output.0.is_empty() && fs::remove_dir_all(&output.0).is_ok() {
             tracing::info!(target: "core", "OUTPUT DIRECTORY DELETED!");
         }
 
+        // Is the output a directory or a file?
+        let is_file = std::path::PathBuf::from(&output.0).extension().is_some();
+
         // Export the artifacts with parallelized io
         artifacts.into_par_iter().for_each(|a| {
-            let json_out =
-                format!("{}/{}.json", output.0, a.file.path.to_uppercase().replacen("./", "", 1));
-            tracing::debug!(target: "core", "JSON OUTPUT: {:?}", json_out);
+            // If it's a file type, we just export to `output.0`
+            let json_out = match is_file {
+                true => output.0.clone(),
+                false => format!(
+                    "{}/{}.json",
+                    output.0,
+                    a.file.path.to_uppercase().replacen("./", "", 1)
+                ),
+            };
 
             if let Err(e) = a.export(&json_out) {
                 tracing::error!(target: "core", "ARTIFACT EXPORT FAILED!\nError: {:?}", e);
diff --git a/huff_lexer/src/lib.rs b/huff_lexer/src/lib.rs
index e474da4..f1c3a65 100644
--- a/huff_lexer/src/lib.rs
+++ b/huff_lexer/src/lib.rs
@@ -24,6 +24,8 @@ pub enum Context {
     MacroDefinition,
     /// Macro's body context
     MacroBody,
+    /// Macro's argument context (definition or being called)
+    MacroArgs,
     /// ABI context
     Abi,
     /// Lexing args of functions inputs/outputs and events
@@ -444,9 +446,18 @@ impl<'a> Iterator for Lexer<'a> {
                     }
 
                     let pot_op = self.dyn_peek(|c| c.is_alphanumeric());
+
+                    // Syntax sugar: true evaluates to 0x01, false evaluates to 0x00
+                    if matches!(pot_op.as_str(), "true" | "false") {
+                        found_kind = Some(TokenKind::Literal(str_to_bytes32(
+                            if pot_op.as_str() == "true" { "1" } else { "0" },
+                        )));
+                        self.dyn_consume(|c| c.is_alphabetic());
+                    }
+
                     // goes over all opcodes
                     for opcode in OPCODES {
-                        if self.context != Context::MacroBody {
+                        if self.context != Context::MacroBody || found_kind != None {
                             break
                         }
                         if opcode == pot_op {
@@ -477,7 +488,6 @@ impl<'a> Iterator for Lexer<'a> {
                                     .split(&raw_type)
                                     .map(|x| x.replace(']', ""))
                                     .collect();
-
                                 let mut size_vec: Vec<usize> = Vec::new();
                                 // go over all array sizes
                                 let sizes = words.get(1..words.len()).unwrap();
@@ -556,14 +566,18 @@ impl<'a> Iterator for Lexer<'a> {
                 }
                 '=' => TokenKind::Assign,
                 '(' => {
-                    if self.context == Context::Abi {
-                        self.context = Context::AbiArgs;
+                    match self.context {
+                        Context::Abi => self.context = Context::AbiArgs,
+                        Context::MacroBody => self.context = Context::MacroArgs,
+                        _ => {}
                     }
                     TokenKind::OpenParen
                 }
                 ')' => {
-                    if self.context == Context::AbiArgs {
-                        self.context = Context::Abi;
+                    match self.context {
+                        Context::AbiArgs => self.context = Context::Abi,
+                        Context::MacroArgs => self.context = Context::MacroBody,
+                        _ => {}
                     }
                     TokenKind::CloseParen
                 }
diff --git a/huff_parser/src/lib.rs b/huff_parser/src/lib.rs
index df831aa..4c25c56 100644
--- a/huff_parser/src/lib.rs
+++ b/huff_parser/src/lib.rs
@@ -311,7 +311,7 @@ impl Parser {
         self.match_kind(TokenKind::Ident("x".to_string()))?;
         let tok = self.peek_behind().unwrap().kind;
         let name = match tok {
-            TokenKind::Ident(event_name) => event_name,
+            TokenKind::Ident(const_name) => const_name,
             _ => {
                 tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED IDENT, GOT: {}", tok);
                 let new_spans = self.spans.clone();
@@ -870,8 +870,12 @@ impl Parser {
         match self.current_token.kind.clone() {
             TokenKind::PrimitiveType(prim) => Ok(self.parse_primitive_type(prim)?),
             TokenKind::ArrayType(prim, _) => {
+                // The trick is that when we parse the primitive type
+                // of the array, it will consume the current token which is the ArrayType.
+                // So we have to preserve the token before parsing (and matching thus consuming).
+                let token = self.current_token.kind.clone();
                 let _ = self.parse_primitive_type(prim);
-                Ok(self.match_kind(self.current_token.kind.clone())?)
+                Ok(token)
             }
             kind => Err(ParserError {
                 kind: ParserErrorKind::InvalidArgs(kind),
diff --git a/huff_utils/src/abi.rs b/huff_utils/src/abi.rs
index 08ff814..a8caf45 100644
--- a/huff_utils/src/abi.rs
+++ b/huff_utils/src/abi.rs
@@ -245,20 +245,29 @@ pub enum FunctionParamType {
     Bool,
     /// A String
     String,
-    /// An array of parameters
-    Array(Box<FunctionParamType>),
+    /// Array ; uint256[2][] => Array(Uint(256), [2, 0])
+    Array(Box<FunctionParamType>, Vec<usize>),
     /// Fixed number of bytes
     FixedBytes(usize),
-    /// Fixed size array of parameters
-    FixedArray(Box<FunctionParamType>, usize),
     /// A tuple of parameters
     Tuple(Vec<FunctionParamType>),
 }
 
 impl FunctionParamType {
     /// Convert string to type
-    pub fn convert_string_to_type(string: &str) -> Self {
+    pub fn convert_string_to_type(string: &str) -> Result<Self, Box<dyn std::error::Error>> {
         let input = string.to_string().to_lowercase();
+        let split_input: Vec<&str> = input.split('[').collect();
+        if split_input.len() > 1 {
+            let mut cleaned: Vec<String> = split_input
+                .iter()
+                .map(|x| x.replace(']', ""))
+                .map(|x| if x.is_empty() { "0".to_owned() } else { x })
+                .collect();
+            let func_type = FunctionParamType::convert_string_to_type(&cleaned.remove(0))?;
+            let sizes: Vec<usize> = cleaned.iter().map(|x| x.parse::<usize>().unwrap()).collect();
+            return Ok(Self::Array(Box::new(func_type), sizes))
+        }
         if input.starts_with("uint") {
             // Default to 256 if no size
             let size = match input.get(4..input.len()) {
@@ -268,7 +277,7 @@ impl FunctionParamType {
                 },
                 None => 256,
             };
-            return Self::Uint(size)
+            return Ok(Self::Uint(size))
         }
         if input.starts_with("int") {
             // Default to 256 if no size
@@ -279,38 +288,38 @@ impl FunctionParamType {
                 },
                 None => 256,
             };
-            return Self::Int(size)
+            return Ok(Self::Int(size))
         }
         if input.starts_with("bytes") && input.len() != 5 {
             let size = input.get(5..input.len()).unwrap().parse::<usize>().unwrap();
-            return Self::FixedBytes(size)
+            return Ok(Self::FixedBytes(size))
         }
         if input.starts_with("bool") {
-            return Self::Bool
+            return Ok(Self::Bool)
         }
         if input.starts_with("address") {
-            return Self::Address
+            return Ok(Self::Address)
         }
         if input.starts_with("string") {
-            return Self::String
+            return Ok(Self::String)
         }
         if input == "bytes" {
-            Self::Bytes
+            Ok(Self::Bytes)
         } else {
             tracing::error!("Failed to create FunctionParamType from string: {}", string);
-            panic!("{}", format!("Failed to create FunctionParamType from string: {}", string))
+            Err(format!("Failed to create FunctionParamType from string: {}", string))?
         }
     }
 }
 
 impl From<&str> for FunctionParamType {
     fn from(string: &str) -> Self {
-        FunctionParamType::convert_string_to_type(string)
+        FunctionParamType::convert_string_to_type(string).unwrap()
     }
 }
 
 impl From<String> for FunctionParamType {
     fn from(string: String) -> Self {
-        FunctionParamType::convert_string_to_type(&string)
+        FunctionParamType::convert_string_to_type(&string).unwrap()
     }
 }
diff --git a/huff_utils/src/artifact.rs b/huff_utils/src/artifact.rs
index 2980ff8..3d85d9a 100644
--- a/huff_utils/src/artifact.rs
+++ b/huff_utils/src/artifact.rs
@@ -24,7 +24,7 @@ pub struct Artifact {
 impl Artifact {
     /// Exports an artifact to a json file
     pub fn export(&self, out: &str) -> std::result::Result<(), std::io::Error> {
-        let serialized_artifact = serde_json::to_string(self)?;
+        let serialized_artifact = serde_json::to_string_pretty(self)?;
         let file_path = Path::new(out);
         if let Some(p) = file_path.parent() {
             tracing::debug!(target: "abi", "Creating directory: \"{:?}\"", p);
diff --git a/huff_utils/src/ast.rs b/huff_utils/src/ast.rs
index bddf519..de818e5 100644
--- a/huff_utils/src/ast.rs
+++ b/huff_utils/src/ast.rs
@@ -8,7 +8,11 @@ use crate::{
     evm::Opcode,
     prelude::{Span, TokenKind},
 };
-use std::{collections::BTreeMap, path::PathBuf};
+use std::{
+    collections::BTreeMap,
+    fmt::{Display, Formatter},
+    path::PathBuf,
+};
 
 /// A contained literal
 pub type Literal = [u8; 32];
@@ -626,3 +630,22 @@ pub enum StatementType {
     /// A built-in function call
     BuiltinFunctionCall(BuiltinFunctionCall),
 }
+
+impl Display for StatementType {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            StatementType::Literal(l) => write!(f, "LITERAL: {}", bytes32_to_string(l, true)),
+            StatementType::Opcode(o) => write!(f, "OPCODE: {}", o),
+            StatementType::MacroInvocation(m) => {
+                write!(f, "MACRO INVOCATION: {}", m.macro_name)
+            }
+            StatementType::Constant(c) => write!(f, "CONSTANT: {}", c),
+            StatementType::ArgCall(c) => write!(f, "ARG CALL: {}", c),
+            StatementType::Label(l) => write!(f, "LABEL: {}", l.name),
+            StatementType::LabelCall(l) => write!(f, "LABEL CALL: {}", l),
+            StatementType::BuiltinFunctionCall(b) => {
+                write!(f, "BUILTIN FUNCTION CALL: {:?}", b.kind)
+            }
+        }
+    }
+}
diff --git a/huff_utils/src/error.rs b/huff_utils/src/error.rs
index 277b7e2..4bbbfb9 100644
--- a/huff_utils/src/error.rs
+++ b/huff_utils/src/error.rs
@@ -258,6 +258,9 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         unsupported
                     )
                 }
+                UnpackError::MissingFile(file) => {
+                    write!(f, "\nError: File Not Found \"{}\"\n", file)
+                }
             },
             CompilerError::ParserError(pe) => match &pe.kind {
                 ParserErrorKind::SyntaxError(se) => {
diff --git a/huff_utils/src/files.rs b/huff_utils/src/files.rs
index c26ded6..15ae0bc 100644
--- a/huff_utils/src/files.rs
+++ b/huff_utils/src/files.rs
@@ -5,15 +5,8 @@ use uuid::Uuid;
 #[allow(clippy::to_string_in_format_args)]
 
 /// An aliased output location to derive from the cli arguments.
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Default)]
 pub struct OutputLocation(pub String);
-
-impl Default for OutputLocation {
-    fn default() -> Self {
-        Self("./artifacts/".to_string())
-    }
-}
-
 /// Full File Source
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
 pub struct FullFileSource<'a> {
@@ -68,7 +61,7 @@ impl FileSource {
     pub fn fully_flatten(self_ref: Arc<FileSource>) -> (String, Vec<(Arc<FileSource>, Span)>) {
         // First grab the parent file source
         let mut full_source =
-            if let Some(s) = &self_ref.source { s.clone() } else { "".to_string() };
+            if let Some(s) = &self_ref.source { s.clone() } else { String::default() };
         let span = Span::new(0..full_source.len(), None);
         let mut relative_positions = vec![(Arc::clone(&self_ref), span)];
 
diff --git a/huff_utils/src/io.rs b/huff_utils/src/io.rs
index bdcad60..d6ed321 100644
--- a/huff_utils/src/io.rs
+++ b/huff_utils/src/io.rs
@@ -12,6 +12,8 @@ pub enum UnpackError {
     UnsupportedExtension(String),
     /// Failed to read directory
     InvalidDirectory(String),
+    /// Missing File
+    MissingFile(String),
 }
 
 /// Unpacks huff files into a vec of strings.


Test patch:
diff --git a/huff_codegen/tests/churn.rs b/huff_codegen/tests/churn.rs
index b6b7628..e95de1b 100644
--- a/huff_codegen/tests/churn.rs
+++ b/huff_codegen/tests/churn.rs
@@ -20,7 +20,7 @@ fn churns_into_bytecode() {
     let churn_res =
         cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode);
     assert!(churn_res.is_ok());
-    assert_eq!(churn_res.unwrap().bytecode, "336000556101ac806100116000396000f360003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3".to_lowercase());
+    assert_eq!(churn_res.unwrap().bytecode, "336000556101ac80600e3d393df360003560e01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3".to_lowercase());
 
     // Validate the Codegen Artifact
     assert!(cg.artifact.is_some());
diff --git a/huff_core/tests/compiling.rs b/huff_core/tests/compiling.rs
index 8333831..d43b519 100644
--- a/huff_core/tests/compiling.rs
+++ b/huff_core/tests/compiling.rs
@@ -82,7 +82,6 @@ fn compiles_runtime_bytecode() {
 
     // 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("33600055"));
 
     // assert!(false);
@@ -90,13 +89,13 @@ fn compiles_runtime_bytecode() {
     let inputs: Vec<ethers_core::abi::Token> = vec![];
     // ERC20 Bytecode
     let main_bytecode =
-    "60003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3";
+        "60003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3";
     let constructor_bytecode = "33600055";
     let churn_res =
         cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode);
     assert!(churn_res.is_ok());
     assert_eq!(churn_res.unwrap().bytecode,
-    "336000556101ac806100116000396000f360003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3".to_lowercase()
+        "336000556101ac80600e3d393df360003560e01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3".to_lowercase()
     );
 
     // Validate the Codegen Artifact
diff --git a/huff_core/tests/erc20.rs b/huff_core/tests/erc20.rs
index 78dba6f..90a7279 100644
--- a/huff_core/tests/erc20.rs
+++ b/huff_core/tests/erc20.rs
@@ -8,9 +8,12 @@ use huff_utils::prelude::*;
 
 #[test]
 fn test_erc20_compile() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
 
     // Recurse file deps + generate flattened source
     let file_source = file_sources.get(0).unwrap();
@@ -23,16 +26,12 @@ fn test_erc20_compile() {
         file: Some(Arc::clone(file_source)),
         spans: flattened.1,
     };
-    println!("Full source: {:?}", full_source);
     let lexer = Lexer::new(full_source);
     let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
-    println!("Tokens: {:?}", tokens);
     let mut parser = Parser::new(tokens, Some("../huff-examples/erc20/contracts".to_string()));
     let mut contract = parser.parse().unwrap();
     contract.derive_storage_pointers();
 
-    println!("Got contract: {:?}", contract);
-
     // Create main and constructor bytecode
     let main_bytecode = Codegen::generate_main_bytecode(&contract).unwrap();
     let constructor_bytecode = Codegen::generate_constructor_bytecode(&contract).unwrap();
@@ -43,11 +42,7 @@ fn test_erc20_compile() {
         cg.churn(Arc::clone(file_source), vec![], &main_bytecode, &constructor_bytecode).unwrap();
 
     // Full expected bytecode output (generated from huffc)
-    let expected_bytecode = "336000556101ac806100116000396000f360003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3";
-    let _current_bytecode = "336003556101ac806100116000396000f360003560e01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b6004353360243581600060005260006020015260406000205480821161xxxx578190038260006000526000602001526040600020558281906000600052600060200152604060002054018360006000526000602001526040600020556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3600160005260206000f35b60006000fd5b60035433146100ed5760006000fd5b600435600060243582819060006000526000602001526040600020540183600060005260006020015260406000205580600254016002556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a35b600435600060005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3";
-
-    println!("Expected bytecode: {}", expected_bytecode.to_lowercase());
-    println!("Current bytecode: {}", artifact.bytecode.to_lowercase());
+    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 102bd0b..3a0fd64 100644
--- a/huff_core/tests/erc721.rs
+++ b/huff_core/tests/erc721.rs
@@ -8,9 +8,12 @@ use huff_utils::prelude::*;
 
 #[test]
 fn test_erc721_compile() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc721/contracts/ERC721.huff".to_string(),
-    )]);
+    )])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
 
     // Recurse file deps + generate flattened source
     let file_source = file_sources.get(0).unwrap();
@@ -38,7 +41,7 @@ fn test_erc721_compile() {
 
     // Full expected bytecode output (different from huffc since our storage pointer derivation is
     // depth first)
-    let expected_bytecode = "336000556103b1806100116000396000f360003560e01c8063a9059cbb146100a057806342842e0e146101a3578063b88d4fde146101a9578063095ea7b31461027b578063a22cb46514610310578063081812fc146102f357806340c10f19146101af57806370a082311461025e5780636352211e1461039457806306fdde031461035e57806395d89b4114610364578063c87b56dd1461036a57806301ffc9a714610370578063e985e9c514610376575b6044356024356004358083600160005260006020015260406000205491146100c75761019d565b8033146101005733816000526000602001526040600020546101005782600260005260006020015260406000205433146101005761019d565b6001816003600052600060200152604060002054038160036000526000602001526040600020558160036000526000602001526040600020546001018260036000526000602001526040600020558183600160005260006020015260406000205560008360026000526000602001526040600020557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a4005b60006000fd5b60006000fd5b60006000fd5b60005433146101be5760006000fd5b6024356004356000826001600052600060200152604060002054156101e257610258565b8160036000526000602001526040600020546001018260036000526000602001526040600020558183600160005260006020015260406000205560008360026000526000602001526040600020557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60006000a4005b60006000fd5b600435600360005260006020015260406000205460005260206000f35b6024358060016000526000602001526040600020548033143382600052600060200152604060002054176102ae576102ed565b60043580836002600052600060200152604060002055907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560006000a4005b60006000fd5b600435600260005260006020015260406000205460005260206000f35b60243560043533600052600060200152604060002055600435336024356000527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160006000a4005b60006000fd5b60006000fd5b60006000fd5b60006000fd5b60006000fd5b60243560043560005260006020015260406000205460005260206000f35b600435600160005260006020015260406000205460005260206000f3";
+    let expected_bytecode = "336000556103b180600e3d393df360003560e01c8063a9059cbb146100a057806342842e0e146101a3578063b88d4fde146101a9578063095ea7b31461027b578063a22cb46514610310578063081812fc146102f357806340c10f19146101af57806370a082311461025e5780636352211e1461039457806306fdde031461035e57806395d89b4114610364578063c87b56dd1461036a57806301ffc9a714610370578063e985e9c514610376575b6044356024356004358083600160005260006020015260406000205491146100c75761019d565b8033146101005733816000526000602001526040600020546101005782600260005260006020015260406000205433146101005761019d565b6001816003600052600060200152604060002054038160036000526000602001526040600020558160036000526000602001526040600020546001018260036000526000602001526040600020558183600160005260006020015260406000205560008360026000526000602001526040600020557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a4005b60006000fd5b60006000fd5b60006000fd5b60005433146101be5760006000fd5b6024356004356000826001600052600060200152604060002054156101e257610258565b8160036000526000602001526040600020546001018260036000526000602001526040600020558183600160005260006020015260406000205560008360026000526000602001526040600020557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60006000a4005b60006000fd5b600435600360005260006020015260406000205460005260206000f35b6024358060016000526000602001526040600020548033143382600052600060200152604060002054176102ae576102ed565b60043580836002600052600060200152604060002055907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560006000a4005b60006000fd5b600435600260005260006020015260406000205460005260206000f35b60243560043533600052600060200152604060002055600435336024356000527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160006000a4005b60006000fd5b60006000fd5b60006000fd5b60006000fd5b60006000fd5b60243560043560005260006020015260406000205460005260206000f35b600435600160005260006020015260406000205460005260206000f3";
 
     println!("Expected bytecode: {}", expected_bytecode.to_lowercase());
     println!("Current bytecode: {}", artifact.bytecode.to_lowercase());
diff --git a/huff_core/tests/errors.rs b/huff_core/tests/errors.rs
index 669436f..d647f47 100644
--- a/huff_core/tests/errors.rs
+++ b/huff_core/tests/errors.rs
@@ -186,7 +186,7 @@ fn test_missing_constructor() {
     let mut contract = parser.parse().unwrap();
     contract.derive_storage_pointers();
 
-    // Createconstructor bytecode
+    // Create constructor bytecode
     match Codegen::generate_constructor_bytecode(&contract) {
         Ok(_) => panic!("moose"),
         Err(e) => {
diff --git a/huff_core/tests/gen_artifact.rs b/huff_core/tests/gen_artifact.rs
new file mode 100644
index 0000000..13aef34
--- /dev/null
+++ b/huff_core/tests/gen_artifact.rs
@@ -0,0 +1,96 @@
+use std::sync::Arc;
+
+use huff_core::Compiler;
+use huff_utils::prelude::*;
+
+#[test]
+fn test_missing_constructor() {
+    let source = r#"
+    #define macro MINT() = takes(0) returns (0) {
+        0x04 calldataload   // [to]
+        0x00                // [from (0x00), to]
+        0x24 calldataload   // [value, from, to]
+    }
+
+    #define macro MAIN() = takes(0) returns (0) {
+        0x00 calldataload 0xE0 shr
+        dup1 0x40c10f19 eq mints jumpi
+
+        mints:
+            MINT()
+    }
+    "#;
+
+    // Full source
+    let full_source = FileSource {
+        source: Some(source.to_string()),
+        id: uuid::Uuid::new_v4(),
+        path: "".to_string(),
+        access: None,
+        dependencies: None,
+    };
+
+    // Instantiate a new compiler
+    let compiler = Compiler::new(Arc::new(vec![]), None, None, false);
+
+    // Generate the compile artifact
+    let arc_source = Arc::new(full_source);
+    match compiler.gen_artifact(Arc::clone(&arc_source)) {
+        Ok(artifact) => {
+            assert_eq!(artifact.file, arc_source);
+            assert_eq!(
+                artifact.bytecode,
+                "601a8060093d393df360003560e01c806340c10f1914610011575b6004356000602435"
+                    .to_string()
+            );
+        }
+        _ => panic!("moose"),
+    }
+}
+
+#[test]
+fn test_missing_constructor_with_inputs() {
+    let source = r#"
+    #define macro MINT() = takes(0) returns (0) {
+        0x04 calldataload   // [to]
+        0x00                // [from (0x00), to]
+        0x24 calldataload   // [value, from, to]
+    }
+
+    #define macro MAIN() = takes(0) returns (0) {
+        0x00 calldataload 0xE0 shr
+        dup1 0x40c10f19 eq mints jumpi
+
+        mints:
+            MINT()
+    }
+    "#;
+
+    // Full source
+    let full_source = FileSource {
+        source: Some(source.to_string()),
+        id: uuid::Uuid::new_v4(),
+        path: "".to_string(),
+        access: None,
+        dependencies: None,
+    };
+
+    // Instantiate a new compiler
+    let compiler = Compiler::new(Arc::new(vec![]), None, Some(vec!["0".to_string()]), false);
+
+    // Generate the compile artifact
+    let arc_source = Arc::new(full_source);
+    match compiler.gen_artifact(Arc::clone(&arc_source)) {
+        Ok(_) => panic!("moose"),
+        Err(e) => {
+            assert_eq!(
+                e,
+                CompilerError::CodegenError(CodegenError {
+                    kind: CodegenErrorKind::MissingMacroDefinition("CONSTRUCTOR".to_string()),
+                    span: AstSpan(vec![Span { start: 0, end: 0, file: Some(arc_source) }]),
+                    token: None
+                })
+            )
+        }
+    }
+}
diff --git a/huff_core/tests/recurse_deps.rs b/huff_core/tests/recurse_deps.rs
index eab44c0..3eee579 100644
--- a/huff_core/tests/recurse_deps.rs
+++ b/huff_core/tests/recurse_deps.rs
@@ -5,9 +5,12 @@ use huff_utils::files::FileSource;
 
 #[test]
 fn test_recursing_fs_dependencies() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
     assert_eq!(file_sources.len(), 1);
     let erc20_file_source = file_sources[0].clone();
     let res = Compiler::recurse_deps(Arc::clone(&erc20_file_source));
@@ -22,9 +25,12 @@ fn test_recursing_fs_dependencies() {
 
 #[test]
 fn test_recursing_external_dependencies() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
     assert_eq!(file_sources.len(), 1);
     let erc20_file_source = file_sources[0].clone();
     let res = Compiler::recurse_deps(Arc::clone(&erc20_file_source));
diff --git a/huff_core/tests/sources.rs b/huff_core/tests/sources.rs
index fe2bf78..de2b7e9 100644
--- a/huff_core/tests/sources.rs
+++ b/huff_core/tests/sources.rs
@@ -1,15 +1,18 @@
 use std::{path::PathBuf, sync::Arc};
 
 use huff_core::Compiler;
-use huff_utils::files::FileSource;
+use huff_utils::prelude::*;
 
 #[test]
 fn test_fetch_sources() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![
         PathBuf::from("../huff-examples/erc20/contracts/ERC20.huff".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/utils/Address.huff".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/utils/HashMap.huff".to_string()),
-    ]);
+    ])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
     assert_eq!(file_sources.len(), 3);
     assert_eq!(file_sources[0].path, "../huff-examples/erc20/contracts/ERC20.huff".to_string());
     assert_eq!(
@@ -24,12 +27,20 @@ fn test_fetch_sources() {
 
 #[test]
 fn test_fetch_invalid_sources() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![
+    let paths = vec![
         PathBuf::from("../huff-examples/erc20/contracts/non_existant.huff".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/non_huff.txt".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/random/Address.huff".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/random/".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/utils/".to_string()),
-    ]);
-    assert_eq!(file_sources.len(), 0);
+    ];
+    let file_sources: Vec<Result<Arc<FileSource>, CompilerError>> =
+        Compiler::fetch_sources(paths.clone());
+    for (i, e) in file_sources.iter().enumerate() {
+        let file_loc = String::from(paths[i].to_string_lossy());
+        assert_eq!(
+            e.clone().err().unwrap(),
+            CompilerError::FileUnpackError(UnpackError::MissingFile(file_loc))
+        )
+    }
 }
diff --git a/huff_lexer/tests/hex.rs b/huff_lexer/tests/hex.rs
index b40ec99..b94cb11 100644
--- a/huff_lexer/tests/hex.rs
+++ b/huff_lexer/tests/hex.rs
@@ -19,6 +19,30 @@ fn parses_single_hex() {
     assert!(lexer.eof);
 }
 
+#[test]
+fn parses_bool() {
+    let source = "false true";
+    let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+    let mut lexer = Lexer::new(flattened_source);
+
+    // The first token should be lexed as a Literal representing 0x00
+    let tok = lexer.next().unwrap().unwrap();
+    assert_eq!(tok, Token::new(TokenKind::Literal(str_to_bytes32("0")), Span::new(0..5, None)));
+    assert_eq!(lexer.current_span().deref(), &Span::new(0..5, None));
+
+    let _ = lexer.next(); // Whitespace
+
+    // The second token should be lexed as a Literal representing 0x01
+    let tok = lexer.next().unwrap().unwrap();
+    assert_eq!(tok, Token::new(TokenKind::Literal(str_to_bytes32("1")), Span::new(6..10, None)));
+    assert_eq!(lexer.current_span().deref(), &Span::new(6..10, None));
+
+    // We covered the whole source
+    lexer.next();
+    assert_eq!(lexer.current_span().end, source.len());
+    assert!(lexer.eof);
+}
+
 #[test]
 fn parses_odd_len_hex() {
     let source = "0x1";
diff --git a/huff_parser/tests/abi.rs b/huff_parser/tests/abi.rs
new file mode 100644
index 0000000..7197dd2
--- /dev/null
+++ b/huff_parser/tests/abi.rs
@@ -0,0 +1,25 @@
+use huff_lexer::*;
+use huff_parser::*;
+use huff_utils::{abi::*, prelude::*};
+
+#[test]
+fn build_abi_from_ast() {
+    let source = "#define function test(uint256[2][],string) view returns(uint256)";
+
+    let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(flattened_source);
+    let tokens = lexer
+        .into_iter()
+        .map(|x| x.unwrap())
+        .filter(|x| !matches!(x.kind, TokenKind::Whitespace))
+        .collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, None);
+    let contract = parser.parse().unwrap();
+    let abi = Abi::from(contract);
+
+    assert_eq!(
+        abi.functions.get("test").unwrap().inputs[0].kind,
+        FunctionParamType::Array(Box::new(FunctionParamType::Uint(256)), vec![2, 0])
+    );
+    assert_eq!(abi.functions.get("test").unwrap().inputs[1].kind, FunctionParamType::String);
+}
diff --git a/huff_parser/tests/opcodes.rs b/huff_parser/tests/opcodes.rs
new file mode 100644
index 0000000..eed0d88
--- /dev/null
+++ b/huff_parser/tests/opcodes.rs
@@ -0,0 +1,36 @@
+use huff_lexer::*;
+use huff_parser::*;
+use huff_utils::{evm::Opcode, prelude::*};
+
+#[test]
+fn not_mistaken_as_opcode() {
+    for opcode in OPCODES {
+        let label = format!("{}_label", (*opcode).to_owned());
+        let source = &format!(
+            r#"
+                #define macro IS_AUTHORIZED(some_arg) = takes(0) returns(0) {{}}
+                #define macro MAIN() = takes(0) returns(0) {{
+                    IS_AUTHORIZED({})
+                    {}:
+                        return
+                }}
+            "#,
+            label, label
+        );
+        let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(flattened_source);
+
+        let tokens = lexer
+            .into_iter()
+            .map(|x| x.unwrap())
+            .filter(|x| !matches!(x.kind, TokenKind::Whitespace))
+            .collect::<Vec<Token>>();
+        let actual_label_arg = tokens[tokens.len() - 7].kind.clone();
+        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();
+        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
new file mode 100644
index 0000000..ba6c03d
--- /dev/null
+++ b/huff_utils/tests/abi.rs
@@ -0,0 +1,34 @@
+use huff_utils::abi::*;
+use std::collections::HashMap;
+
+#[test]
+fn convert_function_param_type() {
+    let func_types = [
+        "bool",
+        "string",
+        "address",
+        "uint198",
+        "bytes32",
+        "bytes",
+        "uint[][]",
+        "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::FixedBytes(32)),
+        (5, FunctionParamType::Bytes),
+        (6, FunctionParamType::Array(Box::new(FunctionParamType::Uint(256)), vec![0, 0])),
+        (7, FunctionParamType::Array(Box::new(FunctionParamType::Address), vec![5, 0])),
+        (8, 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());
+    }
+}