Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement basic contracts #944

Merged
merged 4 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 58 additions & 29 deletions crates/nargo/src/cli/compile_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use acvm::acir::circuit::Circuit;
use acvm::ProofSystemCompiler;
use noirc_driver::CompileOptions;
use noirc_driver::Driver;
use noirc_frontend::node_interner::FuncId;
use std::path::{Path, PathBuf};

use clap::Args;
Expand All @@ -16,49 +18,76 @@ pub(crate) struct CompileCommand {
/// The name of the ACIR file
circuit_name: String,

/// Compile each contract function used within the program
#[arg(short, long)]
contracts: bool,

#[clap(flatten)]
compile_options: CompileOptions,
}

pub(crate) fn run(args: CompileCommand, config: NargoConfig) -> Result<(), CliError> {
let mut circuit_path = config.program_dir.clone();
circuit_path.push(TARGET_DIR);

let circuit_path = compile_and_preprocess_circuit(
&args.circuit_name,
config.program_dir,
circuit_path,
&args.compile_options,
)?;

println!("Generated ACIR code into {}", circuit_path.display());
pub(crate) fn run(mut args: CompileCommand, config: NargoConfig) -> Result<(), CliError> {
let driver = check_crate(&config.program_dir, &args.compile_options)?;

let mut circuit_dir = config.program_dir;
circuit_dir.push(TARGET_DIR);

// If contracts is set we're compiling every function in a 'contract' rather than just 'main'.
if args.contracts {
let circuit_name = args.circuit_name.clone();

for contract in driver.get_all_contracts() {
for function in contract.functions {
let name = driver.function_name(function);
args.circuit_name = format!("{}-{}-{name}", circuit_name, &contract.name);
compile_and_save_program(&driver, function, &args, &circuit_dir)?;
}
}
Ok(())
} else {
let main = driver.main_function();
compile_and_save_program(&driver, main, &args, &circuit_dir)
}
}

Ok(())
fn setup_driver(program_dir: &Path) -> Result<Driver, CliError> {
let backend = crate::backends::ConcreteBackend;
let mut driver = Resolver::resolve_root_config(program_dir, backend.np_language())?;
add_std_lib(&mut driver);
Ok(driver)
}

fn compile_and_preprocess_circuit<P: AsRef<Path>>(
circuit_name: &str,
program_dir: P,
circuit_dir: P,
compile_options: &CompileOptions,
) -> Result<PathBuf, CliError> {
let compiled_program = compile_circuit(program_dir, compile_options)?;
let circuit_path = save_program_to_file(&compiled_program, circuit_name, &circuit_dir);
/// Compile and save a program to disk with the given main function.
fn compile_and_save_program(
driver: &Driver,
main: FuncId,
args: &CompileCommand,
circuit_dir: &Path,
) -> Result<(), CliError> {
let compiled_program = driver
.compile_no_check(&args.compile_options, main)
.map_err(|_| CliError::Generic(format!("'{}' failed to compile", args.circuit_name)))?;

let circuit_path = save_program_to_file(&compiled_program, &args.circuit_name, circuit_dir);

preprocess_with_path(circuit_name, circuit_dir, &compiled_program.circuit)?;
preprocess_with_path(&args.circuit_name, circuit_dir, &compiled_program.circuit)?;

Ok(circuit_path)
println!("Generated ACIR code into {}", circuit_path.display());
Ok(())
}

pub(crate) fn compile_circuit<P: AsRef<Path>>(
program_dir: P,
pub(crate) fn compile_circuit(
program_dir: &Path,
compile_options: &CompileOptions,
) -> Result<noirc_driver::CompiledProgram, CliError> {
let backend = crate::backends::ConcreteBackend;
let mut driver = Resolver::resolve_root_config(program_dir.as_ref(), backend.np_language())?;
add_std_lib(&mut driver);
let mut driver = setup_driver(program_dir)?;
driver.compile_main(compile_options).map_err(|_| CliError::CompilationError)
}

driver.into_compiled_program(compile_options).map_err(|_| CliError::CompilationError)
fn check_crate(program_dir: &Path, options: &CompileOptions) -> Result<Driver, CliError> {
let mut driver = setup_driver(program_dir)?;
driver.check_crate(options).map_err(|_| CliError::CompilationError)?;
Ok(driver)
}

fn preprocess_with_path<P: AsRef<Path>>(
Expand Down
2 changes: 1 addition & 1 deletion crates/nargo/src/cli/contract_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub(crate) struct ContractCommand {
}

pub(crate) fn run(args: ContractCommand, config: NargoConfig) -> Result<(), CliError> {
let compiled_program = compile_circuit(config.program_dir.clone(), &args.compile_options)?;
let compiled_program = compile_circuit(&config.program_dir, &args.compile_options)?;

let backend = crate::backends::ConcreteBackend;
#[allow(deprecated)]
Expand Down
14 changes: 5 additions & 9 deletions crates/nargo/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,15 @@ pub(crate) fn run(args: ExecuteCommand, config: NargoConfig) -> Result<(), CliEr
Ok(())
}

fn execute_with_path<P: AsRef<Path>>(
program_dir: P,
fn execute_with_path(
program_dir: &Path,
compile_options: &CompileOptions,
) -> Result<(Option<InputValue>, WitnessMap), CliError> {
let compiled_program = compile_circuit(&program_dir, compile_options)?;
let compiled_program = compile_circuit(program_dir, compile_options)?;

// Parse the initial witness values from Prover.toml
let (inputs_map, _) = read_inputs_from_file(
&program_dir,
PROVER_INPUT_FILE,
Format::Toml,
&compiled_program.abi,
)?;
let (inputs_map, _) =
read_inputs_from_file(program_dir, PROVER_INPUT_FILE, Format::Toml, &compiled_program.abi)?;

let solved_witness = execute_program(&compiled_program, &inputs_map)?;

Expand Down
2 changes: 1 addition & 1 deletion crates/nargo/src/cli/test_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fn run_test(
let backend = crate::backends::ConcreteBackend;

let program = driver
.compile_no_check(config, Some(main))
.compile_no_check(config, main)
.map_err(|_| CliError::Generic(format!("Test '{test_name}' failed to compile")))?;

let mut solved_witness = BTreeMap::new();
Expand Down
5 changes: 5 additions & 0 deletions crates/nargo/tests/test_data/contracts/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
2 changes: 2 additions & 0 deletions crates/nargo/tests/test_data/contracts/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x = 3
y = 2
8 changes: 8 additions & 0 deletions crates/nargo/tests/test_data/contracts/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main(x : Field, y : pub Field) {
constrain Foo::double(x) == Foo::triple(y);
}

contract Foo {
fn double(x: Field) -> Field { x * 2 }
fn triple(x: Field) -> Field { x * 3 }
}
68 changes: 41 additions & 27 deletions crates/noirc_driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use noirc_abi::FunctionSignature;
use noirc_errors::{reporter, ReportedError};
use noirc_evaluator::create_circuit;
use noirc_frontend::graph::{CrateId, CrateName, CrateType, LOCAL_CRATE};
use noirc_frontend::hir::def_map::CrateDefMap;
use noirc_frontend::hir::def_map::{Contract, CrateDefMap};
use noirc_frontend::hir::Context;
use noirc_frontend::monomorphization::monomorphize;
use noirc_frontend::node_interner::FuncId;
Expand All @@ -23,7 +23,7 @@ pub struct Driver {
language: Language,
}

#[derive(Args, Clone, Debug, Default)]
#[derive(Args, Clone, Debug)]
pub struct CompileOptions {
/// Emit debug information for the intermediate SSA IR
#[arg(short, long)]
Expand All @@ -38,6 +38,12 @@ pub struct CompileOptions {
pub show_output: bool,
}

impl Default for CompileOptions {
fn default() -> Self {
Self { show_ssa: false, allow_warnings: false, show_output: true }
}
}

impl Driver {
pub fn new(np_language: &Language) -> Self {
let mut driver = Driver { context: Context::default(), language: np_language.clone() };
Expand All @@ -50,10 +56,7 @@ impl Driver {
pub fn compile_file(root_file: PathBuf, np_language: acvm::Language) -> CompiledProgram {
let mut driver = Driver::new(&np_language);
driver.create_local_crate(root_file, CrateType::Binary);

driver
.into_compiled_program(&CompileOptions::default())
.unwrap_or_else(|_| std::process::exit(1))
driver.compile_main(&CompileOptions::default()).unwrap_or_else(|_| std::process::exit(1))
}

/// Compiles a file and returns true if compilation was successful
Expand Down Expand Up @@ -158,38 +161,41 @@ impl Driver {
Some(func_meta.into_function_signature(&self.context.def_interner))
}

pub fn into_compiled_program(
mut self,
/// Run the frontend to check the crate for errors then compile the main function if there were none
pub fn compile_main(
&mut self,
options: &CompileOptions,
) -> Result<CompiledProgram, ReportedError> {
self.check_crate(options)?;
self.compile_no_check(options, None)
let main = self.main_function();
self.compile_no_check(options, main)
}

/// Returns the FuncId of the 'main' funciton.
/// - Expects check_crate to be called beforehand
/// - Panics if no main function is found
pub fn main_function(&self) -> FuncId {
// Find the local crate, one should always be present
let local_crate = self.context.def_map(LOCAL_CRATE).unwrap();

// Check the crate type
// We don't panic here to allow users to `evaluate` libraries which will do nothing
if self.context.crate_graph[LOCAL_CRATE].crate_type != CrateType::Binary {
println!("cannot compile crate into a program as the local crate is not a binary. For libraries, please use the check command");
std::process::exit(1);
};

// All Binaries should have a main function
local_crate.main_function().expect("cannot compile a program with no main function")
}

/// Compile the current crate. Assumes self.check_crate is called beforehand!
#[allow(deprecated)]
pub fn compile_no_check(
&self,
options: &CompileOptions,
// Optional override to provide a different `main` function to start execution
main_function: Option<FuncId>,
main_function: FuncId,
) -> Result<CompiledProgram, ReportedError> {
// Find the local crate, one should always be present
let local_crate = self.context.def_map(LOCAL_CRATE).unwrap();

// If no override for the `main` function has been provided, attempt to find it.
let main_function = main_function.unwrap_or_else(|| {
// Check the crate type
// We don't panic here to allow users to `evaluate` libraries which will do nothing
if self.context.crate_graph[LOCAL_CRATE].crate_type != CrateType::Binary {
println!("cannot compile crate into a program as the local crate is not a binary. For libraries, please use the check command");
std::process::exit(1);
};

// All Binaries should have a main function
local_crate.main_function().expect("cannot compile a program with no main function")
});

let program = monomorphize(main_function, &self.context.def_interner);

let np_language = self.language.clone();
Expand Down Expand Up @@ -228,6 +234,14 @@ impl Driver {
.collect()
}

/// Return a Vec of all `contract` declarations in the source code and the functions they contain
pub fn get_all_contracts(&self) -> Vec<Contract> {
self.context
.def_map(LOCAL_CRATE)
.expect("The local crate should be analyzed already")
.get_all_contracts()
}

pub fn function_name(&self, id: FuncId) -> &str {
self.context.def_interner.function_name(&id)
}
Expand Down
3 changes: 1 addition & 2 deletions crates/noirc_driver/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ fn main() {
driver.add_dep(LOCAL_CRATE, crate_id1, "coo4");
driver.add_dep(LOCAL_CRATE, crate_id2, "coo3");

let config = CompileOptions::default();
driver.into_compiled_program(&config).ok();
driver.compile_main(&CompileOptions::default()).ok();
}
28 changes: 15 additions & 13 deletions crates/noirc_frontend/src/hir/def_collector/dc_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl<'a> ModCollector<'a> {
let name = struct_definition.name.clone();

// Create the corresponding module for the struct namespace
let id = match self.push_child_module(&name, self.file_id, false, errors) {
let id = match self.push_child_module(&name, self.file_id, false, false, errors) {
Some(local_id) => StructId(ModuleId { krate, local_id }),
None => continue,
};
Expand Down Expand Up @@ -195,7 +195,13 @@ impl<'a> ModCollector<'a> {
errors: &mut Vec<FileDiagnostic>,
) {
for submodule in submodules {
if let Some(child) = self.push_child_module(&submodule.name, file_id, true, errors) {
if let Some(child) = self.push_child_module(
&submodule.name,
file_id,
true,
submodule.is_contract,
errors,
) {
collect_defs(
self.def_collector,
submodule.contents,
Expand Down Expand Up @@ -234,7 +240,9 @@ impl<'a> ModCollector<'a> {
let ast = parse_file(&mut context.file_manager, child_file_id, errors);

// Add module into def collector and get a ModuleId
if let Some(child_mod_id) = self.push_child_module(mod_name, child_file_id, true, errors) {
if let Some(child_mod_id) =
self.push_child_module(mod_name, child_file_id, true, false, errors)
{
collect_defs(
self.def_collector,
ast,
Expand All @@ -254,21 +262,15 @@ impl<'a> ModCollector<'a> {
mod_name: &Ident,
file_id: FileId,
add_to_parent_scope: bool,
is_contract: bool,
errors: &mut Vec<FileDiagnostic>,
) -> Option<LocalModuleId> {
// Create a new default module
let module_id = self.def_collector.def_map.modules.insert(ModuleData::default());
let parent = Some(self.module_id);
let new_module = ModuleData::new(parent, ModuleOrigin::File(file_id), is_contract);
let module_id = self.def_collector.def_map.modules.insert(new_module);

let modules = &mut self.def_collector.def_map.modules;

// Update the child module to reference the parent
modules[module_id].parent = Some(self.module_id);

// Update the origin of the child module
// Also note that the FileId is where this module is defined and not declared
// To find out where the module was declared, you need to check its parent
modules[module_id].origin = ModuleOrigin::File(file_id);

// Update the parent module to reference the child
modules[self.module_id.0].children.insert(mod_name.clone(), LocalModuleId(module_id));

Expand Down
Loading