|
@@ -6,6 +6,7 @@ use clap::{
|
|
|
value_parser, Arg, ArgMatches, Command,
|
|
value_parser, Arg, ArgMatches, Command,
|
|
|
};
|
|
};
|
|
|
use clap_complete::{generate, Shell};
|
|
use clap_complete::{generate, Shell};
|
|
|
|
|
+use itertools::Itertools;
|
|
|
use solang::{
|
|
use solang::{
|
|
|
abi,
|
|
abi,
|
|
|
codegen::{codegen, OptimizationLevel, Options},
|
|
codegen::{codegen, OptimizationLevel, Options},
|
|
@@ -16,7 +17,7 @@ use solang::{
|
|
|
Target,
|
|
Target,
|
|
|
};
|
|
};
|
|
|
use std::{
|
|
use std::{
|
|
|
- collections::HashMap,
|
|
|
|
|
|
|
+ collections::{HashMap, HashSet},
|
|
|
ffi::{OsStr, OsString},
|
|
ffi::{OsStr, OsString},
|
|
|
fs::{create_dir_all, File},
|
|
fs::{create_dir_all, File},
|
|
|
io::prelude::*,
|
|
io::prelude::*,
|
|
@@ -56,6 +57,13 @@ fn main() {
|
|
|
"ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm",
|
|
"ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm",
|
|
|
]),
|
|
]),
|
|
|
)
|
|
)
|
|
|
|
|
+ .arg(
|
|
|
|
|
+ Arg::new("CONTRACT")
|
|
|
|
|
+ .help("Contract names to compile (defaults to all)")
|
|
|
|
|
+ .value_delimiter(',')
|
|
|
|
|
+ .action(ArgAction::Append)
|
|
|
|
|
+ .long("contract"),
|
|
|
|
|
+ )
|
|
|
.arg(
|
|
.arg(
|
|
|
Arg::new("OPT")
|
|
Arg::new("OPT")
|
|
|
.help("Set llvm optimizer level")
|
|
.help("Set llvm optimizer level")
|
|
@@ -111,6 +119,13 @@ fn main() {
|
|
|
.num_args(1)
|
|
.num_args(1)
|
|
|
.value_parser(ValueParser::os_string()),
|
|
.value_parser(ValueParser::os_string()),
|
|
|
)
|
|
)
|
|
|
|
|
+ .arg(
|
|
|
|
|
+ Arg::new("OUTPUTMETA")
|
|
|
|
|
+ .help("output directory for metadata")
|
|
|
|
|
+ .long("output-meta")
|
|
|
|
|
+ .num_args(1)
|
|
|
|
|
+ .value_parser(ValueParser::os_string()),
|
|
|
|
|
+ )
|
|
|
.arg(
|
|
.arg(
|
|
|
Arg::new("IMPORTPATH")
|
|
Arg::new("IMPORTPATH")
|
|
|
.help("Directory to search for solidity files")
|
|
.help("Directory to search for solidity files")
|
|
@@ -416,12 +431,35 @@ fn compile(matches: &ArgMatches) {
|
|
|
|
|
|
|
|
let mut errors = false;
|
|
let mut errors = false;
|
|
|
|
|
|
|
|
|
|
+ // Build a map of requested contract names, and a flag specifying whether it was found or not
|
|
|
|
|
+ let contract_names: HashSet<&str> = if let Some(values) = matches.get_many::<String>("CONTRACT")
|
|
|
|
|
+ {
|
|
|
|
|
+ values.map(|v| v.as_str()).collect()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ HashSet::new()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
for filename in matches.get_many::<OsString>("INPUT").unwrap() {
|
|
for filename in matches.get_many::<OsString>("INPUT").unwrap() {
|
|
|
- match process_file(filename, &mut resolver, target, matches, &mut json, &opt) {
|
|
|
|
|
- Ok(ns) => namespaces.push(ns),
|
|
|
|
|
- Err(_) => {
|
|
|
|
|
- errors = true;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // TODO: this could be parallelized using e.g. rayon
|
|
|
|
|
+ let ns = process_file(filename, &mut resolver, target, matches, &opt);
|
|
|
|
|
+
|
|
|
|
|
+ namespaces.push((ns, filename));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let mut json_contracts = HashMap::new();
|
|
|
|
|
+
|
|
|
|
|
+ let std_json = *matches.get_one("STD-JSON").unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ for (ns, _) in &namespaces {
|
|
|
|
|
+ if std_json {
|
|
|
|
|
+ let mut out = ns.diagnostics_as_json(&resolver);
|
|
|
|
|
+ json.errors.append(&mut out);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ns.print_diagnostics(&resolver, verbose);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ns.diagnostics.any_errors() {
|
|
|
|
|
+ errors = true;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -429,20 +467,50 @@ fn compile(matches: &ArgMatches) {
|
|
|
exit(0);
|
|
exit(0);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let std_json = *matches.get_one("STD-JSON").unwrap();
|
|
|
|
|
|
|
+ // Ensure we have at least one contract
|
|
|
|
|
+ if !errors && namespaces.iter().all(|(ns, _)| ns.contracts.is_empty()) {
|
|
|
|
|
+ eprintln!("error: no contacts found");
|
|
|
|
|
+ errors = true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if errors {
|
|
|
|
|
- if std_json {
|
|
|
|
|
- println!("{}", serde_json::to_string(&json).unwrap());
|
|
|
|
|
- exit(0);
|
|
|
|
|
- } else {
|
|
|
|
|
- eprintln!("error: not all contracts are valid");
|
|
|
|
|
- exit(1);
|
|
|
|
|
|
|
+ // Ensure we have all the requested contracts
|
|
|
|
|
+ let not_found: Vec<_> = contract_names
|
|
|
|
|
+ .iter()
|
|
|
|
|
+ .filter(|name| {
|
|
|
|
|
+ !namespaces
|
|
|
|
|
+ .iter()
|
|
|
|
|
+ .flat_map(|(ns, _)| ns.contracts.iter())
|
|
|
|
|
+ .any(|contract| **name == contract.name)
|
|
|
|
|
+ })
|
|
|
|
|
+ .collect();
|
|
|
|
|
+
|
|
|
|
|
+ if !errors && !not_found.is_empty() {
|
|
|
|
|
+ eprintln!("error: contacts {} not found", not_found.iter().join(", "));
|
|
|
|
|
+ errors = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if !errors {
|
|
|
|
|
+ for (ns, filename) in &namespaces {
|
|
|
|
|
+ for contract_no in 0..ns.contracts.len() {
|
|
|
|
|
+ contract_results(
|
|
|
|
|
+ contract_no,
|
|
|
|
|
+ filename,
|
|
|
|
|
+ matches,
|
|
|
|
|
+ ns,
|
|
|
|
|
+ &mut json_contracts,
|
|
|
|
|
+ &opt,
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if std_json {
|
|
if std_json {
|
|
|
println!("{}", serde_json::to_string(&json).unwrap());
|
|
println!("{}", serde_json::to_string(&json).unwrap());
|
|
|
|
|
+ exit(0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if errors {
|
|
|
|
|
+ exit(1);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -455,13 +523,15 @@ fn shell_complete(mut app: Command, matches: &ArgMatches) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-fn output_file(matches: &ArgMatches, stem: &str, ext: &str) -> PathBuf {
|
|
|
|
|
- Path::new(
|
|
|
|
|
|
|
+fn output_file(matches: &ArgMatches, stem: &str, ext: &str, meta: bool) -> PathBuf {
|
|
|
|
|
+ let dir = if meta {
|
|
|
matches
|
|
matches
|
|
|
- .get_one::<OsString>("OUTPUT")
|
|
|
|
|
- .unwrap_or(&OsString::from(".")),
|
|
|
|
|
- )
|
|
|
|
|
- .join(format!("{stem}.{ext}"))
|
|
|
|
|
|
|
+ .get_one::<OsString>("OUTPUTMETA")
|
|
|
|
|
+ .or_else(|| matches.get_one::<OsString>("OUTPUT"))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ matches.get_one::<OsString>("OUTPUT")
|
|
|
|
|
+ };
|
|
|
|
|
+ Path::new(dir.unwrap_or(&OsString::from("."))).join(format!("{stem}.{ext}"))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
fn process_file(
|
|
fn process_file(
|
|
@@ -469,13 +539,9 @@ fn process_file(
|
|
|
resolver: &mut FileResolver,
|
|
resolver: &mut FileResolver,
|
|
|
target: solang::Target,
|
|
target: solang::Target,
|
|
|
matches: &ArgMatches,
|
|
matches: &ArgMatches,
|
|
|
- json: &mut JsonResult,
|
|
|
|
|
opt: &Options,
|
|
opt: &Options,
|
|
|
-) -> Result<Namespace, ()> {
|
|
|
|
|
|
|
+) -> Namespace {
|
|
|
let verbose = *matches.get_one("VERBOSE").unwrap();
|
|
let verbose = *matches.get_one("VERBOSE").unwrap();
|
|
|
- let std_json = *matches.get_one("STD-JSON").unwrap();
|
|
|
|
|
-
|
|
|
|
|
- let mut json_contracts = HashMap::new();
|
|
|
|
|
|
|
|
|
|
// resolve phase
|
|
// resolve phase
|
|
|
let mut ns = solang::parse_and_resolve(filename, resolver, target);
|
|
let mut ns = solang::parse_and_resolve(filename, resolver, target);
|
|
@@ -483,17 +549,10 @@ fn process_file(
|
|
|
// codegen all the contracts; some additional errors/warnings will be detected here
|
|
// codegen all the contracts; some additional errors/warnings will be detected here
|
|
|
codegen(&mut ns, opt);
|
|
codegen(&mut ns, opt);
|
|
|
|
|
|
|
|
- if std_json {
|
|
|
|
|
- let mut out = ns.diagnostics_as_json(resolver);
|
|
|
|
|
- json.errors.append(&mut out);
|
|
|
|
|
- } else {
|
|
|
|
|
- ns.print_diagnostics(resolver, verbose);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
if let Some("ast-dot") = matches.get_one::<String>("EMIT").map(|v| v.as_str()) {
|
|
if let Some("ast-dot") = matches.get_one::<String>("EMIT").map(|v| v.as_str()) {
|
|
|
let filepath = PathBuf::from(filename);
|
|
let filepath = PathBuf::from(filename);
|
|
|
let stem = filepath.file_stem().unwrap().to_string_lossy();
|
|
let stem = filepath.file_stem().unwrap().to_string_lossy();
|
|
|
- let dot_filename = output_file(matches, &stem, "dot");
|
|
|
|
|
|
|
+ let dot_filename = output_file(matches, &stem, "dot", false);
|
|
|
|
|
|
|
|
if verbose {
|
|
if verbose {
|
|
|
eprintln!("info: Saving graphviz dot {}", dot_filename.display());
|
|
eprintln!("info: Saving graphviz dot {}", dot_filename.display());
|
|
@@ -507,98 +566,98 @@ fn process_file(
|
|
|
eprintln!("{}: error: {}", dot_filename.display(), err);
|
|
eprintln!("{}: error: {}", dot_filename.display(), err);
|
|
|
exit(1);
|
|
exit(1);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return Ok(ns);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if ns.contracts.is_empty() || ns.diagnostics.any_errors() {
|
|
|
|
|
- return Err(());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ ns
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // emit phase
|
|
|
|
|
- for contract_no in 0..ns.contracts.len() {
|
|
|
|
|
- let resolved_contract = &ns.contracts[contract_no];
|
|
|
|
|
|
|
+fn contract_results(
|
|
|
|
|
+ contract_no: usize,
|
|
|
|
|
+ filename: &OsStr,
|
|
|
|
|
+ matches: &ArgMatches,
|
|
|
|
|
+ ns: &Namespace,
|
|
|
|
|
+ json_contracts: &mut HashMap<String, JsonContract>,
|
|
|
|
|
+ opt: &Options,
|
|
|
|
|
+) {
|
|
|
|
|
+ let verbose = *matches.get_one("VERBOSE").unwrap();
|
|
|
|
|
+ let std_json = *matches.get_one("STD-JSON").unwrap();
|
|
|
|
|
|
|
|
- if !resolved_contract.instantiable {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let resolved_contract = &ns.contracts[contract_no];
|
|
|
|
|
|
|
|
- if let Some("cfg") = matches.get_one::<String>("EMIT").map(|v| v.as_str()) {
|
|
|
|
|
- println!("{}", resolved_contract.print_cfg(&ns));
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if !resolved_contract.instantiable {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if verbose {
|
|
|
|
|
- if target == solang::Target::Solana {
|
|
|
|
|
- eprintln!(
|
|
|
|
|
- "info: contract {} uses at least {} bytes account data",
|
|
|
|
|
- resolved_contract.name, resolved_contract.fixed_layout_size,
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if let Some("cfg") = matches.get_one::<String>("EMIT").map(|v| v.as_str()) {
|
|
|
|
|
+ println!("{}", resolved_contract.print_cfg(ns));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ if verbose {
|
|
|
|
|
+ if ns.target == solang::Target::Solana {
|
|
|
eprintln!(
|
|
eprintln!(
|
|
|
- "info: Generating LLVM IR for contract {} with target {}",
|
|
|
|
|
- resolved_contract.name, ns.target
|
|
|
|
|
|
|
+ "info: contract {} uses at least {} bytes account data",
|
|
|
|
|
+ resolved_contract.name, resolved_contract.fixed_layout_size,
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let context = inkwell::context::Context::create();
|
|
|
|
|
- let filename_string = filename.to_string_lossy();
|
|
|
|
|
|
|
+ eprintln!(
|
|
|
|
|
+ "info: Generating LLVM IR for contract {} with target {}",
|
|
|
|
|
+ resolved_contract.name, ns.target
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- let binary = resolved_contract.binary(&ns, &context, &filename_string, opt);
|
|
|
|
|
|
|
+ let context = inkwell::context::Context::create();
|
|
|
|
|
+ let filename_string = filename.to_string_lossy();
|
|
|
|
|
|
|
|
- if save_intermediates(&binary, matches) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let binary = resolved_contract.binary(ns, &context, &filename_string, opt);
|
|
|
|
|
|
|
|
- let code = binary.code(Generate::Linked).expect("llvm build");
|
|
|
|
|
|
|
+ if save_intermediates(&binary, matches) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if std_json {
|
|
|
|
|
- json_contracts.insert(
|
|
|
|
|
- binary.name.to_owned(),
|
|
|
|
|
- JsonContract {
|
|
|
|
|
- abi: abi::ethereum::gen_abi(contract_no, &ns),
|
|
|
|
|
- ewasm: Some(EwasmContract {
|
|
|
|
|
- wasm: hex::encode_upper(code),
|
|
|
|
|
- }),
|
|
|
|
|
- minimum_space: None,
|
|
|
|
|
- },
|
|
|
|
|
- );
|
|
|
|
|
- } else {
|
|
|
|
|
- let bin_filename = output_file(matches, &binary.name, target.file_extension());
|
|
|
|
|
|
|
+ let code = binary.code(Generate::Linked).expect("llvm build");
|
|
|
|
|
|
|
|
- if verbose {
|
|
|
|
|
- eprintln!(
|
|
|
|
|
- "info: Saving binary {} for contract {}",
|
|
|
|
|
- bin_filename.display(),
|
|
|
|
|
- binary.name
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if std_json {
|
|
|
|
|
+ json_contracts.insert(
|
|
|
|
|
+ binary.name,
|
|
|
|
|
+ JsonContract {
|
|
|
|
|
+ abi: abi::ethereum::gen_abi(contract_no, ns),
|
|
|
|
|
+ ewasm: Some(EwasmContract {
|
|
|
|
|
+ wasm: hex::encode_upper(code),
|
|
|
|
|
+ }),
|
|
|
|
|
+ minimum_space: None,
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let bin_filename = output_file(matches, &binary.name, ns.target.file_extension(), false);
|
|
|
|
|
|
|
|
- let mut file = create_file(&bin_filename);
|
|
|
|
|
|
|
+ if verbose {
|
|
|
|
|
+ eprintln!(
|
|
|
|
|
+ "info: Saving binary {} for contract {}",
|
|
|
|
|
+ bin_filename.display(),
|
|
|
|
|
+ binary.name
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- file.write_all(&code).unwrap();
|
|
|
|
|
|
|
+ let mut file = create_file(&bin_filename);
|
|
|
|
|
|
|
|
- let (abi_bytes, abi_ext) = abi::generate_abi(contract_no, &ns, &code, verbose);
|
|
|
|
|
- let abi_filename = output_file(matches, &binary.name, abi_ext);
|
|
|
|
|
|
|
+ file.write_all(&code).unwrap();
|
|
|
|
|
|
|
|
- if verbose {
|
|
|
|
|
- eprintln!(
|
|
|
|
|
- "info: Saving metadata {} for contract {}",
|
|
|
|
|
- abi_filename.display(),
|
|
|
|
|
- binary.name
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let (metadata, meta_ext) = abi::generate_abi(contract_no, ns, &code, verbose);
|
|
|
|
|
+ let meta_filename = output_file(matches, &binary.name, meta_ext, true);
|
|
|
|
|
|
|
|
- let mut file = create_file(&abi_filename);
|
|
|
|
|
- file.write_all(abi_bytes.as_bytes()).unwrap();
|
|
|
|
|
|
|
+ if verbose {
|
|
|
|
|
+ eprintln!(
|
|
|
|
|
+ "info: Saving metadata {} for contract {}",
|
|
|
|
|
+ meta_filename.display(),
|
|
|
|
|
+ binary.name
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- json.contracts
|
|
|
|
|
- .insert(filename.to_string_lossy().to_string(), json_contracts);
|
|
|
|
|
-
|
|
|
|
|
- Ok(ns)
|
|
|
|
|
|
|
+ let mut file = create_file(&meta_filename);
|
|
|
|
|
+ file.write_all(metadata.as_bytes()).unwrap();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
fn save_intermediates(binary: &solang::emit::binary::Binary, matches: &ArgMatches) -> bool {
|
|
fn save_intermediates(binary: &solang::emit::binary::Binary, matches: &ArgMatches) -> bool {
|
|
@@ -606,7 +665,7 @@ fn save_intermediates(binary: &solang::emit::binary::Binary, matches: &ArgMatche
|
|
|
|
|
|
|
|
match matches.get_one::<String>("EMIT").map(|v| v.as_str()) {
|
|
match matches.get_one::<String>("EMIT").map(|v| v.as_str()) {
|
|
|
Some("llvm-ir") => {
|
|
Some("llvm-ir") => {
|
|
|
- let llvm_filename = output_file(matches, &binary.name, "ll");
|
|
|
|
|
|
|
+ let llvm_filename = output_file(matches, &binary.name, "ll", false);
|
|
|
|
|
|
|
|
if verbose {
|
|
if verbose {
|
|
|
eprintln!(
|
|
eprintln!(
|
|
@@ -622,7 +681,7 @@ fn save_intermediates(binary: &solang::emit::binary::Binary, matches: &ArgMatche
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Some("llvm-bc") => {
|
|
Some("llvm-bc") => {
|
|
|
- let bc_filename = output_file(matches, &binary.name, "bc");
|
|
|
|
|
|
|
+ let bc_filename = output_file(matches, &binary.name, "bc", false);
|
|
|
|
|
|
|
|
if verbose {
|
|
if verbose {
|
|
|
eprintln!(
|
|
eprintln!(
|
|
@@ -646,7 +705,7 @@ fn save_intermediates(binary: &solang::emit::binary::Binary, matches: &ArgMatche
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- let obj_filename = output_file(matches, &binary.name, "o");
|
|
|
|
|
|
|
+ let obj_filename = output_file(matches, &binary.name, "o", false);
|
|
|
|
|
|
|
|
if verbose {
|
|
if verbose {
|
|
|
eprintln!(
|
|
eprintln!(
|
|
@@ -669,7 +728,7 @@ fn save_intermediates(binary: &solang::emit::binary::Binary, matches: &ArgMatche
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- let obj_filename = output_file(matches, &binary.name, "asm");
|
|
|
|
|
|
|
+ let obj_filename = output_file(matches, &binary.name, "asm", false);
|
|
|
|
|
|
|
|
if verbose {
|
|
if verbose {
|
|
|
eprintln!(
|
|
eprintln!(
|