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#[derive(Debug, Clone)]
63pub struct Compiler<'a, 'l> {
64 pub evm_version: &'l EVMVersion,
66 pub sources: Arc<Vec<String>>,
68 pub output: Option<String>,
70 pub alternative_main: Option<String>,
72 pub alternative_constructor: Option<String>,
74 pub construct_args: Option<Vec<String>>,
76 pub constant_overrides: Option<BTreeMap<&'a str, Bytes>>,
78 pub bytecode: bool,
80 pub cached: bool,
82 pub file_provider: Arc<dyn FileProvider>,
84 pub relax_jumps: bool,
86 pub no_size_limit: bool,
88}
89
90impl<'a, 'l> Compiler<'a, 'l> {
91 #[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 #[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 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 pub fn execute(&self) -> Result<Vec<Arc<Artifact>>, Arc<CompilerError>> {
183 let file_paths: Vec<PathBuf> = self.file_provider.transform_paths(&self.sources)?;
185
186 let files_fetched: Vec<Result<Arc<FileSource>, CompilerError>> = Self::fetch_sources(file_paths, self.file_provider.clone());
188
189 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 let output = self.get_outputs();
200
201 let mut artifacts: Vec<Arc<Artifact>> = vec![];
209
210 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 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 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 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 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 Compiler::export_artifacts(&artifacts, &output);
255 }
256 }
257
258 Ok(artifacts)
259 }
260
261 pub fn get_flattened_source(file: Arc<FileSource>, file_provider: Arc<dyn FileProvider>) -> Result<String, Arc<CompilerError>> {
264 let remapper = Remapper::new("./");
266 let file_with_deps = Self::recurse_deps(file, &remapper, file_provider, HashSet::new())?;
267
268 let (merged_source, _) = FileSource::fully_flatten(file_with_deps);
270
271 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 pub fn grab_contracts(&self) -> Result<Vec<Contract>, Arc<CompilerError>> {
288 let file_paths: Vec<PathBuf> = self.file_provider.transform_paths(&self.sources)?;
290
291 let files: Vec<Result<Arc<FileSource>, CompilerError>> = Self::fetch_sources(file_paths, self.file_provider.clone());
293
294 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 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 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 let parsed = files
318 .into_par_iter()
319 .map(|file| {
320 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 let lexer = Lexer::new(full_source);
330
331 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 let mut parser = Parser::new(tokens, Some(file.path.clone()));
347
348 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 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 contract.validate_opcodes(self.evm_version).map_err(CompilerError::CodegenError)?;
361 contract.flattened_source = Some(flattened.0.clone());
363
364 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 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 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 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 fn generate_artifact(&self, file: Arc<FileSource>) -> Result<(Artifact, HashSet<&'a str>), CompilerError> {
415 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 let lexer = Lexer::new(full_source);
427
428 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 let mut parser = Parser::new(tokens, Some(file.path.clone()));
444
445 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 contract.validate_opcodes(self.evm_version).map_err(CompilerError::CodegenError)?;
452
453 contract.flattened_source = Some(merged_source.clone());
455
456 let mut source_files = Vec::new();
458 let mut source_map = Vec::new();
459 let mut file_map = std::collections::HashMap::new();
460
461 if flattened.1.is_empty() {
463 source_files.push((file.path.clone(), merged_source.clone()));
465 source_map.push((0, 0, 0)); } else {
467 for (file_source, span) in &flattened.1 {
469 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 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 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 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 contract.runtime_size = Some(main.bytecode.len() / 2);
529
530 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 if e.kind != CodegenErrorKind::MissingMacroDefinition("CONSTRUCTOR".to_string()) || !inputs.is_empty() {
549 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 tracing::warn!(target: "codegen", "Contract has no \"CONSTRUCTOR\" macro definition!");
569 let updated_contract = Codegen::update_table_size(self.evm_version, &contract).map_err(CompilerError::CodegenError)?;
571 (None, updated_contract)
572 }
573 };
574
575 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 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 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 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 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 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 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 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 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 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 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 new_fs.dependencies = dependencies;
708
709 Ok(Arc::new(new_fs))
710 }
711
712 pub fn export_artifacts(artifacts: &Vec<Arc<Artifact>>, output: &OutputLocation) {
717 if output.0.is_empty() {
719 tracing::warn!(target: "core", "Exiting artifact export with empty output location!");
720 return;
721 }
722
723 let is_file = std::path::PathBuf::from(&output.0).extension().is_some();
725
726 artifacts.into_par_iter().for_each(|a| {
728 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 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 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 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 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 pub fn get_outputs(&self) -> OutputLocation {
782 match &self.output {
783 Some(o) => OutputLocation(o.clone()),
784 None => OutputLocation::default(),
785 }
786 }
787}