Skip to main content

huff_neo_core/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![warn(unused_extern_crates)]
4#![forbid(unsafe_code)]
5
6use huff_neo_codegen::*;
7use huff_neo_lexer::*;
8use huff_neo_parser::*;
9use huff_neo_utils::ast::span::AstSpan;
10use huff_neo_utils::file::file_provider::{FileProvider, FileSystemFileProvider, InMemoryFileProvider};
11use huff_neo_utils::file::file_source::FileSource;
12use huff_neo_utils::file::full_file_source::{FullFileSource, OutputLocation};
13use huff_neo_utils::file::remapper::Remapper;
14#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
15use huff_neo_utils::wasm::IntoParallelIterator;
16use huff_neo_utils::{prelude::*, time};
17#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
18use rayon::prelude::*;
19use regex::Regex;
20use std::collections::HashSet;
21use std::{
22    collections::{BTreeMap, HashMap},
23    ffi::OsString,
24    fs,
25    iter::Iterator,
26    path::PathBuf,
27    sync::Arc,
28};
29use tracing_subscriber::{EnvFilter, filter::Directive};
30
31pub(crate) mod cache;
32
33/// ## The Core Huff Compiler
34///
35/// #### Usage
36///
37/// The canonical way to instantiate a Compiler instance is using the public associated
38/// [new](Compiler::new) function.
39///
40/// Let's say we want to create a Compiler for the `ERC20.huff` contract located in [huff-examples](https://github.com/huff-language/huff-examples/blob/main/erc20/contracts/ERC20.huff).
41///
42/// We want our Compiler to output to an `artifact:Vec<ethers_core::abi::token::Token>s` directory,
43/// with no constructor arguments, and no verbose output:
44///
45/// ```rust
46/// use huff_neo_core::Compiler;
47/// use huff_neo_utils::prelude::EVMVersion;
48/// use std::sync::Arc;
49///
50/// let compiler = Compiler::new(
51///     &EVMVersion::default(),
52///     Arc::new(vec!["../../resources/erc20/ERC20.huff".to_string()]),
53///     Some("./artifacts".to_string()),
54///     None,
55///     None,
56///     None,
57///     None,
58///     false,
59///     false
60/// );
61/// ```
62#[derive(Debug, Clone)]
63pub struct Compiler<'a, 'l> {
64    /// The EVM version to compile for
65    pub evm_version: &'l EVMVersion,
66    /// The location of the files to compile
67    pub sources: Arc<Vec<String>>,
68    /// The output location
69    pub output: Option<String>,
70    /// Macro to use a main
71    pub alternative_main: Option<String>,
72    /// Constructor macro to use
73    pub alternative_constructor: Option<String>,
74    /// Constructor Input Arguments
75    pub construct_args: Option<Vec<String>>,
76    /// Constant Overrides
77    pub constant_overrides: Option<BTreeMap<&'a str, Bytes>>,
78    /// Generate and log bytecode
79    pub bytecode: bool,
80    /// Whether to check cached artifacts
81    pub cached: bool,
82    /// The implementation of a FileReader
83    pub file_provider: Arc<dyn FileProvider>,
84    /// Whether to apply jump relaxation optimization
85    pub relax_jumps: bool,
86    /// Skip the contract and initcode size limit validation (EIP-170/EIP-3860, raised by EIP-7954 in Amsterdam)
87    pub no_size_limit: bool,
88}
89
90impl<'a, 'l> Compiler<'a, 'l> {
91    /// Public associated function to instantiate a new compiler.
92    #[allow(clippy::too_many_arguments)]
93    pub fn new(
94        evm_version: &'l EVMVersion,
95        sources: Arc<Vec<String>>,
96        output: Option<String>,
97        alternative_main: Option<String>,
98        alternative_constructor: Option<String>,
99        construct_args: Option<Vec<String>>,
100        constant_overrides: Option<BTreeMap<&'a str, Bytes>>,
101        verbose: bool,
102        cached: bool,
103    ) -> Self {
104        if cfg!(feature = "verbose") || verbose {
105            Compiler::init_tracing_subscriber(Some(vec![tracing::Level::INFO.into()]));
106        }
107        Self {
108            evm_version,
109            sources,
110            output,
111            alternative_main,
112            alternative_constructor,
113            construct_args,
114            constant_overrides,
115            bytecode: false,
116            cached,
117            file_provider: Arc::new(FileSystemFileProvider {}),
118            relax_jumps: false,
119            no_size_limit: false,
120        }
121    }
122
123    /// Creates a new instance of a compiler with an in-memory FileReader from the supplied sources
124    /// map.
125    #[allow(clippy::too_many_arguments)]
126    pub fn new_in_memory(
127        evm_version: &'l EVMVersion,
128        sources: Arc<Vec<String>>,
129        file_sources: HashMap<String, String>,
130        alternative_main: Option<String>,
131        alternative_constructor: Option<String>,
132        construct_args: Option<Vec<String>>,
133        constant_overrides: Option<BTreeMap<&'a str, Bytes>>,
134        verbose: bool,
135    ) -> Self {
136        if cfg!(feature = "verbose") || verbose {
137            Compiler::init_tracing_subscriber(Some(vec![tracing::Level::INFO.into()]));
138        }
139        Self {
140            evm_version,
141            sources,
142            output: None,
143            alternative_main,
144            alternative_constructor,
145            construct_args,
146            constant_overrides,
147            bytecode: false,
148            cached: false,
149            file_provider: Arc::new(InMemoryFileProvider::new(file_sources)),
150            relax_jumps: false,
151            no_size_limit: false,
152        }
153    }
154
155    /// Tracing
156    ///
157    /// Creates a new tracing subscriber to span the compilation process.
158    pub fn init_tracing_subscriber(directives: Option<Vec<Directive>>) {
159        let subscriber_builder = tracing_subscriber::fmt();
160        let mut env_filter = EnvFilter::from_default_env();
161        if let Some(dv) = directives {
162            for d in dv {
163                env_filter = env_filter.add_directive(d);
164            }
165        }
166        if let Err(e) = subscriber_builder.with_env_filter(env_filter).try_init() {
167            println!("Failed to initialize tracing!\nError: {e:?}")
168        }
169    }
170
171    /// Executor
172    ///
173    /// The core compilation process.
174    ///
175    /// ### Steps
176    ///
177    /// 1. Transform inputs into File Paths with [transform_paths](FileProvider::transform_paths).
178    /// 2. Fetch file sources in parallel with [fetch_sources](Compiler::fetch_sources).
179    /// 3. Recurse file dependencies in parallel with [recurse_deps](Compiler::recurse_deps).
180    /// 4. For each top-level file generate the artifact using [gen_artifact](Compiler::gen_artifact).
181    /// 5. Return the compiling error(s) or successfully generated artifacts.
182    pub fn execute(&self) -> Result<Vec<Arc<Artifact>>, Arc<CompilerError>> {
183        // Grab the input files
184        let file_paths: Vec<PathBuf> = self.file_provider.transform_paths(&self.sources)?;
185
186        // Parallel file fetching
187        let files_fetched: Vec<Result<Arc<FileSource>, CompilerError>> = Self::fetch_sources(file_paths, self.file_provider.clone());
188
189        // Check for errors
190        let mut files = Vec::new();
191        for file in files_fetched {
192            match file {
193                Ok(f) => files.push(f),
194                Err(err) => return Err(Arc::new(err.clone())),
195            }
196        }
197
198        // Grab the output
199        let output = self.get_outputs();
200
201        // TODO: Parallelize Artifact Caching
202        // rayon::spawn({
203        //     let cloned_files: Vec<Arc<FileSource>> = files.iter().map(Arc::clone).collect();
204        //     let ol: OutputLocation = output.clone();
205        //     || cache::get_cached_artifacts(cloned_files, ol)
206        // });
207
208        let mut artifacts: Vec<Arc<Artifact>> = vec![];
209
210        // Get Cached or Generate Artifacts
211        tracing::debug!(target: "core", "Output directory: {}", output.0);
212        match cache::get_cached_artifacts(&files, &output, &self.get_constructor_args(), &self.constant_overrides_hex()) {
213            Some(arts) => artifacts = arts,
214            None => {
215                tracing::debug!(target: "core", "FINISHED RECURSING DEPENDENCIES!");
216                // Parallel Dependency Resolution
217                let recursed_file_sources: Vec<Result<Arc<FileSource>, Arc<CompilerError>>> = files
218                    .into_par_iter()
219                    .map(|v| Self::recurse_deps(v, &Remapper::new("./"), self.file_provider.clone(), HashSet::new()))
220                    .collect();
221
222                // Unpack recursed dependencies into FileSources
223                let mut files = vec![];
224                for fs in recursed_file_sources {
225                    files.push(fs?);
226                }
227                tracing::info!(target: "core", "COMPILER RECURSED {} FILE DEPENDENCIES", files.len());
228
229                // Parallel Compilation
230                let potential_artifacts: Vec<Result<(Artifact, HashSet<&str>), CompilerError>> =
231                    files.into_par_iter().map(|f| self.generate_artifact(f)).collect();
232
233                let mut gen_errors: Vec<CompilerError> = vec![];
234                let mut found_overrides = HashSet::new();
235
236                // Output errors + return OR print # of successfully compiled files
237                for r in potential_artifacts {
238                    match r {
239                        Ok((a, found)) => {
240                            artifacts.push(Arc::new(a));
241                            found_overrides.extend(found);
242                        }
243                        Err(ce) => gen_errors.push(ce),
244                    }
245                }
246
247                if !gen_errors.is_empty() {
248                    tracing::error!(target: "core", "{} FILES FAILED TO COMPILE", gen_errors.len());
249                    return Err(Arc::new(CompilerError::FailedCompiles(gen_errors)));
250                }
251                self.validate_constant_overrides(&found_overrides).map_err(Arc::new)?;
252
253                // Export
254                Compiler::export_artifacts(&artifacts, &output);
255            }
256        }
257
258        Ok(artifacts)
259    }
260
261    /// Get the flattened source code for a file with all dependencies resolved
262    /// and #include statements commented out
263    pub fn get_flattened_source(file: Arc<FileSource>, file_provider: Arc<dyn FileProvider>) -> Result<String, Arc<CompilerError>> {
264        // Recurse through dependencies
265        let remapper = Remapper::new("./");
266        let file_with_deps = Self::recurse_deps(file, &remapper, file_provider, HashSet::new())?;
267
268        // Flatten the source
269        let (merged_source, _) = FileSource::fully_flatten(file_with_deps);
270
271        // Find and comment out #include statements
272        let include_regex = Regex::new(r#"#include\s+["'][^"']*["']"#).unwrap();
273        let final_source = include_regex.replace_all(&merged_source, "// $0").to_string();
274
275        Ok(final_source)
276    }
277
278    /// Grab the ASTs for all file sources.
279    ///
280    /// ### Steps
281    ///
282    /// 1. Transform inputs into File Paths with [transform_paths](FileProvider::transform_paths).
283    /// 2. Fetch file sources in parallel with [fetch_sources](Compiler::fetch_sources).
284    /// 3. Recurse file dependencies in parallel with [recurse_deps](Compiler::recurse_deps).
285    /// 4. For each top-level file, parse its contents and return a vec of [Contract]
286    ///    ASTs.
287    pub fn grab_contracts(&self) -> Result<Vec<Contract>, Arc<CompilerError>> {
288        // Grab the input files
289        let file_paths: Vec<PathBuf> = self.file_provider.transform_paths(&self.sources)?;
290
291        // Parallel file fetching
292        let files: Vec<Result<Arc<FileSource>, CompilerError>> = Self::fetch_sources(file_paths, self.file_provider.clone());
293
294        // Unwrap errors
295        let mut errors = files.iter().filter_map(|rfs| rfs.as_ref().err()).collect::<Vec<&CompilerError>>();
296        if !errors.is_empty() {
297            let error = errors.remove(0);
298            return Err(Arc::new(error.clone()));
299        }
300
301        // Unpack files into their file sources
302        let files = files.iter().filter_map(|fs| fs.clone().ok()).collect::<Vec<Arc<FileSource>>>();
303
304        let recursed_file_sources: Vec<Result<Arc<FileSource>, Arc<CompilerError>>> = files
305            .into_par_iter()
306            .map(|f| Self::recurse_deps(f, &Remapper::new("./"), self.file_provider.clone(), HashSet::new()))
307            .collect();
308
309        // Unpack recursed dependencies into FileSources
310        let mut files = vec![];
311        for fs in recursed_file_sources {
312            files.push(fs?);
313        }
314        tracing::info!(target: "core", "COMPILER RECURSED {} FILE DEPENDENCIES", files.len());
315
316        // Parse file sources and collect ASTs in parallel
317        let parsed = files
318            .into_par_iter()
319            .map(|file| {
320                // Fully Flatten a file into a source string containing source code of file and all
321                // its dependencies
322                let flattened = FileSource::fully_flatten(Arc::clone(&file));
323                tracing::info!(target: "core", "FLATTENED SOURCE FILE \"{}\"", file.path);
324                let full_source = FullFileSource { source: &flattened.0, file: Some(Arc::clone(&file)), spans: flattened.1.clone() };
325                tracing::debug!(target: "core", "GOT FULL SOURCE FOR PATH: {:?}", file.path);
326
327                // Perform Lexical Analysis
328                // Create a new lexer from the FileSource, flattening dependencies
329                let lexer = Lexer::new(full_source);
330
331                // Grab the tokens from the lexer
332                let mut tokens = vec![];
333                for token in lexer.into_iter() {
334                    match token {
335                        Ok(token) => tokens.push(token),
336                        Err(err) => {
337                            return Err(Arc::new(CompilerError::LexicalError(err)));
338                        }
339                    }
340                }
341                tracing::info!(target: "core", "LEXICAL ANALYSIS COMPLETE FOR \"{}\"", file.path);
342                tracing::info!(target: "core", "└─ TOKEN COUNT: {}", tokens.len());
343                let found_overrides = self.found_constant_overrides(&tokens);
344
345                // Parser incantation
346                let mut parser = Parser::new(tokens, Some(file.path.clone()));
347
348                // Parse into an AST
349                let parse_res = parser.parse().map_err(CompilerError::ParserError);
350                let mut contract = parse_res?;
351                contract.derive_storage_pointers();
352                contract.add_override_constants(&self.constant_overrides);
353                // Resolve `__codesize(RUNTIME)` as in a full compilation; without a compilable runtime
354                // macro it stays unresolved and only fails where it is used
355                contract.runtime_size =
356                    Codegen::generate_main_bytecode(self.evm_version, &contract, self.alternative_main.clone(), self.relax_jumps)
357                        .ok()
358                        .map(|bytecode| bytecode.len() / 2);
359                // Validate opcodes against EVM version
360                contract.validate_opcodes(self.evm_version).map_err(CompilerError::CodegenError)?;
361                // Store the flattened source for debugging
362                contract.flattened_source = Some(flattened.0.clone());
363
364                // Build source files list and mapping for multi-file debugging
365                let mut source_files = Vec::new();
366                let mut source_map = Vec::new();
367                let mut file_map = std::collections::HashMap::new();
368
369                for (file_source, span) in &flattened.1 {
370                    // Get or create file ID
371                    let file_id = if let Some(&id) = file_map.get(&file_source.path) {
372                        id
373                    } else {
374                        let id = source_files.len();
375                        if let Some(source) = &file_source.source {
376                            tracing::debug!(target: "core", "Adding source file {}: {}", id, file_source.path);
377                            source_files.push((file_source.path.clone(), source.clone()));
378                            file_map.insert(file_source.path.clone(), id);
379                        }
380                        id
381                    };
382
383                    // Add mapping entry (flattened_start, file_id, original_start)
384                    // The third parameter should be 0 since each file starts at position 0 in its original source
385                    tracing::debug!(target: "core", "Source map entry: flattened_start={}, file_id={}, file_path={}", 
386                        span.start, file_id, file_source.path);
387                    source_map.push((span.start, file_id, 0));
388                }
389
390                contract.source_files = source_files;
391                contract.source_map = source_map;
392
393                tracing::info!(target: "core", "PARSED CONTRACT [{}]", file.path);
394                Ok((contract, found_overrides))
395            })
396            .collect::<Result<Vec<_>, Arc<CompilerError>>>()?;
397
398        let (contracts, found_overrides): (Vec<Contract>, Vec<HashSet<&str>>) = parsed.into_iter().unzip();
399        self.validate_constant_overrides(&found_overrides.into_iter().flatten().collect()).map_err(Arc::new)?;
400        Ok(contracts)
401    }
402
403    /// Artifact Generation
404    ///
405    /// Compiles a FileSource into an Artifact.
406    pub fn gen_artifact(&self, file: Arc<FileSource>) -> Result<Artifact, CompilerError> {
407        let (artifact, found_overrides) = self.generate_artifact(file)?;
408        self.validate_constant_overrides(&found_overrides)?;
409        Ok(artifact)
410    }
411
412    /// Compiles a FileSource into an Artifact, together with the constant overrides its source
413    /// mentions so they can be validated once all files are compiled.
414    fn generate_artifact(&self, file: Arc<FileSource>) -> Result<(Artifact, HashSet<&'a str>), CompilerError> {
415        // Fully Flatten a file into a source string containing source code of file and all
416        // its dependencies
417        let flattened = FileSource::fully_flatten(Arc::clone(&file));
418        let (merged_source, spans) = (flattened.0.clone(), flattened.1.clone());
419
420        tracing::info!(target: "core", "FLATTENED SOURCE FILE \"{}\"", file.path);
421        let full_source = FullFileSource { source: &merged_source, file: Some(Arc::clone(&file)), spans };
422        tracing::debug!(target: "core", "GOT FULL SOURCE FOR PATH: {:?}", file.path);
423
424        // Perform Lexical Analysis
425        // Create a new lexer from the FileSource, flattening dependencies
426        let lexer = Lexer::new(full_source);
427
428        // Grab the tokens from the lexer
429        let mut tokens = Vec::new();
430        for token_result in lexer {
431            match token_result {
432                Ok(token) => tokens.push(token),
433                Err(err) => {
434                    return Err(CompilerError::LexicalError(err));
435                }
436            }
437        }
438        tracing::info!(target: "core", "LEXICAL ANALYSIS COMPLETE FOR \"{}\"", file.path);
439        tracing::info!(target: "core", "└─ TOKEN COUNT: {}", tokens.len());
440        let found_overrides = self.found_constant_overrides(&tokens);
441
442        // Parser incantation
443        let mut parser = Parser::new(tokens, Some(file.path.clone()));
444
445        // Parse into an AST
446        let parse_res = parser.parse().map_err(CompilerError::ParserError);
447        let mut contract = parse_res?;
448        contract.derive_storage_pointers();
449        contract.add_override_constants(&self.constant_overrides);
450        // Validate opcodes against EVM version
451        contract.validate_opcodes(self.evm_version).map_err(CompilerError::CodegenError)?;
452
453        // Store the flattened source and build multi-file mapping
454        contract.flattened_source = Some(merged_source.clone());
455
456        // Build source files list and mapping for multi-file debugging
457        let mut source_files = Vec::new();
458        let mut source_map = Vec::new();
459        let mut file_map = std::collections::HashMap::new();
460
461        // For single files, ensure we still have the main file in source_files
462        if flattened.1.is_empty() {
463            // Single file case - add the main file
464            source_files.push((file.path.clone(), merged_source.clone()));
465            source_map.push((0, 0, 0)); // Main file starts at position 0
466        } else {
467            // Multi-file case - process all included files
468            for (file_source, span) in &flattened.1 {
469                // Get or create file ID
470                let file_id = if let Some(&id) = file_map.get(&file_source.path) {
471                    id
472                } else {
473                    let id = source_files.len();
474                    if let Some(source) = &file_source.source {
475                        tracing::debug!(target: "core", "Adding source file {}: {}", id, file_source.path);
476                        source_files.push((file_source.path.clone(), source.clone()));
477                        file_map.insert(file_source.path.clone(), id);
478                    }
479                    id
480                };
481
482                // Add mapping entry (flattened_start, file_id, original_start)
483                // The third parameter should be 0 since each file starts at position 0 in its original source
484                tracing::debug!(target: "core", "Source map entry: flattened_start={}, file_id={}, file_path={}", 
485                    span.start, file_id, file_source.path);
486                source_map.push((span.start, file_id, 0));
487            }
488        }
489
490        contract.source_files = source_files;
491        contract.source_map = source_map;
492
493        tracing::info!(target: "core", "PARSED CONTRACT [{}]", file.path);
494
495        // Primary Bytecode Generation
496        let mut cg = Codegen::new();
497        let main = match Codegen::generate_main_bytecode_with_sourcemap(
498            self.evm_version,
499            &contract,
500            self.alternative_main.clone(),
501            self.relax_jumps,
502        ) {
503            Ok(result) => result,
504            Err(mut e) => {
505                tracing::error!(target: "core", "FAILED TO GENERATE MAIN BYTECODE FOR CONTRACT");
506                // Add File Source to Span
507                e.span = AstSpan(
508                    e.span
509                        .0
510                        .into_iter()
511                        .map(|mut s| {
512                            if s.file.is_none() {
513                                s.file = Some(Arc::clone(&file));
514                            }
515                            s
516                        })
517                        .collect::<Vec<Span>>(),
518                )
519                .boxed();
520                tracing::error!(target: "core", "Roll Failed with CodegenError: {:?}", e.kind);
521                return Err(CompilerError::CodegenError(e));
522            }
523        };
524        tracing::info!(target: "core", "MAIN BYTECODE GENERATED [{}]", main.bytecode);
525
526        // Expose the runtime byte length to constructor codegen so `__codesize(RUNTIME)`
527        // (directly or via a derived constant) resolves.
528        contract.runtime_size = Some(main.bytecode.len() / 2);
529
530        // Order of constructor assembly:
531        //   1. generate_constructor_macro_bytecode — expand the macro into raw bytes, detect
532        //      whether the body contains its own RETURN (custom bootstrap).
533        //   2. assemble_artifact (below) — given main's table offsets and the now-known body
534        //      length, compute bootstrap size, then call gen_table_bytecode to fill
535        //      __tablestart placeholders and emit constructor-only tables into the deployment tail.
536        //   3. Concatenate [ctor_body][bootstrap][main_bytecode][ctor_only_tables][args].
537        let inputs = self.get_constructor_args();
538        let (constructor, contract_for_churn) = match Codegen::generate_constructor_macro_bytecode(
539            self.evm_version,
540            &contract,
541            self.alternative_constructor.clone(),
542            self.relax_jumps,
543        ) {
544            Ok((ctor, updated_contract)) => (Some(ctor), updated_contract),
545            Err(mut e) => {
546                // Return any errors except if the inputs is empty and the constructor
547                // definition is missing
548                if e.kind != CodegenErrorKind::MissingMacroDefinition("CONSTRUCTOR".to_string()) || !inputs.is_empty() {
549                    // Add File Source to Span
550                    let mut errs = e
551                        .span
552                        .0
553                        .into_iter()
554                        .map(|mut s| {
555                            if s.file.is_none() {
556                                s.file = Some(Arc::clone(&file));
557                            }
558                            s
559                        })
560                        .collect::<Vec<Span>>();
561                    errs.dedup();
562                    e.span = AstSpan(errs).boxed();
563                    tracing::error!(target: "codegen", "Constructor inputs provided, but contract missing \"CONSTRUCTOR\" macro!");
564                    return Err(CompilerError::CodegenError(e));
565                }
566
567                // If the kind is a missing constructor we can ignore it
568                tracing::warn!(target: "codegen", "Contract has no \"CONSTRUCTOR\" macro definition!");
569                // Still need an updated contract for churn (table sizes populated).
570                let updated_contract = Codegen::update_table_size(self.evm_version, &contract).map_err(CompilerError::CodegenError)?;
571                (None, updated_contract)
572            }
573        };
574
575        // Encode Constructor Arguments
576        let encoded_inputs =
577            Codegen::encode_constructor_args(&inputs, Codegen::declared_constructor_types(&contract).as_deref()).map_err(|mut e| {
578                e.span = AstSpan(vec![Span { start: 0, end: 0, file: Some(Arc::clone(&file)) }]).boxed();
579                CompilerError::CodegenError(e)
580            })?;
581        tracing::info!(target: "core", "ENCODED {} INPUTS", encoded_inputs.len());
582
583        // Generate Artifact with ABI
584        let churn_res =
585            cg.assemble_artifact(self.evm_version, file, &contract_for_churn, encoded_inputs, main, constructor, self.no_size_limit);
586        match churn_res {
587            Ok(mut artifact) => {
588                // Then we can have the code gen output the artifact
589                let abiout = cg.abi_gen(contract, None);
590                match abiout {
591                    Ok(abi) => {
592                        tracing::info!(target: "core", "GENERATED ABI");
593                        artifact.abi = Some(abi)
594                    }
595                    Err(e) => {
596                        tracing::error!(target: "core", "ARTIFACT GENERATION FAILED: {:?}", e)
597                    }
598                }
599                artifact.constant_overrides = self.constant_overrides_hex();
600                artifact.constructor_args = self.get_constructor_args();
601                Ok((artifact, found_overrides))
602            }
603            Err(e) => {
604                tracing::error!(target: "core", "CODEGEN ERRORED!\nError: {:?}", e);
605                Err(CompilerError::CodegenError(e))
606            }
607        }
608    }
609
610    /// Get the file sources for a vec of PathBufs
611    pub fn fetch_sources(paths: Vec<PathBuf>, reader: Arc<dyn FileProvider>) -> Vec<Result<Arc<FileSource>, CompilerError>> {
612        paths.into_par_iter().map(|pb| reader.read_file(pb)).collect()
613    }
614
615    /// Recurses file dependencies
616    pub fn recurse_deps(
617        fs: Arc<FileSource>,
618        remapper: &Remapper,
619        reader: Arc<dyn FileProvider>,
620        mut walk_level: HashSet<String>,
621    ) -> Result<Arc<FileSource>, Arc<CompilerError>> {
622        tracing::debug!(target: "core", "RECURSING DEPENDENCIES FOR {}", fs.path);
623        // Check if we've already walked this path
624        if walk_level.contains(&fs.path) {
625            return Ok(Arc::new(FileSource::default()));
626        }
627        walk_level.insert(fs.path.clone());
628        let mut new_fs = FileSource { path: fs.path.clone(), ..Default::default() };
629        let file_source = if let Some(s) = &fs.source {
630            s.clone()
631        } else {
632            // Read from path
633            let new_source = match fs::read_to_string(&fs.path) {
634                Ok(source) => source,
635                Err(_) => {
636                    tracing::error!(target: "core", "FILE READ FAILED: \"{}\"!", fs.path);
637                    return Err(Arc::new(CompilerError::PathBufRead(OsString::from(&fs.path))));
638                }
639            };
640            new_fs.access = Some(time::get_current_time());
641            new_source
642        };
643        let imports: Vec<String> = Lexer::lex_imports(&file_source);
644        new_fs.source = Some(file_source);
645        if !imports.is_empty() {
646            tracing::info!(target: "core", "IMPORT LEXICAL ANALYSIS COMPLETE ON {:?}", imports);
647        }
648
649        // Check that no empty imports paths are present
650        if imports.iter().any(|i| i.is_empty()) {
651            return Err(Arc::new(CompilerError::EmptyImportPath(OsString::from(&fs.path))));
652        }
653
654        let localized_imports: Vec<String> = imports
655            .into_iter()
656            .map(|mut import| {
657                // Check for foundry toml remappings
658                match remapper.remap(&import) {
659                    Some(remapped) => {
660                        tracing::debug!(target: "core", "REMAPPED IMPORT PATH \"{}\"", import);
661                        import = remapped;
662                    }
663                    None => {
664                        import = FileSource::localize_file(&fs.path, &import).unwrap_or_default().replacen(
665                            "contracts/contracts",
666                            "contracts",
667                            1,
668                        );
669                    }
670                }
671                import
672            })
673            .collect();
674        if !localized_imports.is_empty() {
675            tracing::info!(target: "core", "LOCALIZED IMPORTS {:?}", localized_imports);
676        }
677        let import_bufs: Vec<PathBuf> = reader.transform_paths(&localized_imports)?;
678        let potentials: Result<Vec<Arc<FileSource>>, CompilerError> =
679            Self::fetch_sources(import_bufs, reader.clone()).into_iter().collect();
680        let file_sources = match potentials {
681            Ok(p) => p,
682            Err(e) => return Err(Arc::new(e)),
683        };
684        if !file_sources.is_empty() {
685            tracing::info!(target: "core", "FETCHED {} FILE SOURCES", file_sources.len());
686        }
687
688        // Now that we have all the file sources, we have to recurse and get their source
689        let dependency_results: Vec<_> = file_sources
690            .into_par_iter()
691            .map(|inner_fs| Self::recurse_deps(Arc::clone(&inner_fs), remapper, reader.clone(), walk_level.clone()))
692            .collect();
693
694        // Verify that all dependencies were successfully fetched
695        let mut dependencies = Vec::with_capacity(dependency_results.len());
696        for dependency in dependency_results {
697            match dependency {
698                Ok(dep) => dependencies.push(dep),
699                Err(e) => {
700                    tracing::error!(target: "core", "NESTED DEPENDENCY RESOLUTION FAILED: \"{:?}\"", e);
701                    return Err(e);
702                }
703            }
704        }
705
706        // Finally set the parent deps
707        new_fs.dependencies = dependencies;
708
709        Ok(Arc::new(new_fs))
710    }
711
712    /// Export Artifacts
713    ///
714    /// Writes each artifact as JSON to the output file, or below the output directory at the path
715    /// of its source file. Other files in the output directory are left alone.
716    pub fn export_artifacts(artifacts: &Vec<Arc<Artifact>>, output: &OutputLocation) {
717        // Exit if empty output location
718        if output.0.is_empty() {
719            tracing::warn!(target: "core", "Exiting artifact export with empty output location!");
720            return;
721        }
722
723        // Is the output a directory or a file?
724        let is_file = std::path::PathBuf::from(&output.0).extension().is_some();
725
726        // Export the artifacts with parallelized io
727        artifacts.into_par_iter().for_each(|a| {
728            // If it's a file type, we just export to `output.0`
729            let json_out = match is_file {
730                true => output.0.clone(),
731                false => format!("{}/{}.json", output.0, a.file.path.replacen("./", "", 1)),
732            };
733
734            if let Err(e) = a.export(&json_out) {
735                tracing::error!(target: "core", "ARTIFACT EXPORT FAILED!\nError: {:?}", e);
736            }
737            tracing::info!(target: "core", "EXPORTED ARTIFACT TO \"{}\"", json_out);
738        });
739    }
740
741    /// Returns the constant overrides as hex values by constant name.
742    fn constant_overrides_hex(&self) -> BTreeMap<String, String> {
743        self.constant_overrides.iter().flatten().map(|(name, value)| (name.to_string(), format!("0x{value}"))).collect()
744    }
745
746    /// Returns the constant overrides mentioned in a file's tokens.
747    fn found_constant_overrides(&self, tokens: &[Token]) -> HashSet<&'a str> {
748        let Some(overrides) = &self.constant_overrides else {
749            return HashSet::new();
750        };
751        tokens
752            .iter()
753            .filter_map(|token| match &token.kind {
754                TokenKind::Ident(name) => overrides.get_key_value(name.as_str()).map(|(key, _)| *key),
755                _ => None,
756            })
757            .collect()
758    }
759
760    /// Rejects constant overrides whose name appears in none of the compiled sources.
761    ///
762    /// An override may set a constant the source references without defining it, and when
763    /// several files are compiled it may target only some of them. So only a name that no
764    /// source mentions is rejected: it cannot affect the output and is most likely a typo.
765    fn validate_constant_overrides(&self, found_overrides: &HashSet<&str>) -> Result<(), CompilerError> {
766        match self.constant_overrides.iter().flatten().map(|(name, _)| *name).find(|name| !found_overrides.contains(name)) {
767            Some(name) => Err(CompilerError::UnknownConstantOverride(name.to_string())),
768            None => Ok(()),
769        }
770    }
771
772    /// Derives Constructor Input Arguments
773    pub fn get_constructor_args(&self) -> Vec<String> {
774        match &self.construct_args {
775            Some(construct_args) => construct_args.clone(),
776            None => vec![],
777        }
778    }
779
780    /// Derives an output location
781    pub fn get_outputs(&self) -> OutputLocation {
782        match &self.output {
783            Some(o) => OutputLocation(o.clone()),
784            None => OutputLocation::default(),
785        }
786    }
787}