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 3 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
83 changes: 56 additions & 27 deletions crates/nargo/src/cli/compile_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use acvm::acir::circuit::Circuit;
use acvm::ProofSystemCompiler;
use noirc_driver::Driver;
use noirc_frontend::node_interner::FuncId;
use std::path::{Path, PathBuf};

use clap::Args;
Expand All @@ -18,48 +20,75 @@ pub(crate) struct CompileCommand {
/// Issue a warning for each unused variable instead of an error
#[arg(short, long)]
allow_warnings: bool,
}

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.allow_warnings,
)?;
/// Compile each contract function used within the program
#[arg(short, long)]
contracts: bool,
}

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.allow_warnings)?;

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: impl AsRef<Path>) -> Result<Driver, 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);
Ok(driver)
}

fn compile_and_preprocess_circuit<P: AsRef<Path>>(
circuit_name: &str,
program_dir: P,
circuit_dir: P,
allow_warnings: bool,
) -> Result<PathBuf, CliError> {
let compiled_program = compile_circuit(program_dir, false, allow_warnings)?;
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(false, args.allow_warnings, main, true)
.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,
show_ssa: bool,
allow_warnings: bool,
) -> 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(show_ssa, allow_warnings, true).map_err(|_| CliError::CompilationError)
}

driver.into_compiled_program(show_ssa, allow_warnings).map_err(|_| CliError::CompilationError)
fn check_crate(program_dir: impl AsRef<Path>, allow_warnings: bool) -> Result<Driver, CliError> {
let mut driver = setup_driver(program_dir)?;
driver.check_crate(allow_warnings).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/test_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ fn run_test(
let backend = crate::backends::ConcreteBackend;

let program = driver
.compile_no_check(false, allow_warnings, Some(main), show_output)
.compile_no_check(false, allow_warnings, main, show_output)
.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 }
}
58 changes: 35 additions & 23 deletions crates/noirc_driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,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 @@ -34,8 +34,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(false, false).unwrap_or_else(|_| std::process::exit(1))
driver.compile_main(false, false, true).unwrap_or_else(|_| std::process::exit(1))
}

/// Compiles a file and returns true if compilation was successful
Expand Down Expand Up @@ -139,13 +138,34 @@ 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,
show_ssa: bool,
allow_warnings: bool,
show_output: bool,
) -> Result<CompiledProgram, ReportedError> {
self.check_crate(allow_warnings)?;
self.compile_no_check(show_ssa, allow_warnings, None, true)
let main = self.main_function();
self.compile_no_check(show_ssa, allow_warnings, main, show_output)
}

/// 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!
Expand All @@ -155,25 +175,9 @@ impl Driver {
show_ssa: bool,
allow_warnings: bool,
// Optional override to provide a different `main` function to start execution
main_function: Option<FuncId>,
main_function: FuncId,
show_output: bool,
) -> 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 @@ -206,6 +210,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
2 changes: 1 addition & 1 deletion crates/noirc_driver/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ fn main() {
driver.add_dep(LOCAL_CRATE, crate_id1, "coo4");
driver.add_dep(LOCAL_CRATE, crate_id2, "coo3");

driver.into_compiled_program(false, false).ok();
driver.compile_main(false, false, true).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
60 changes: 56 additions & 4 deletions crates/noirc_frontend/src/hir/def_map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,8 @@ impl CrateDefMap {

// Allocate a default Module for the root, giving it a ModuleId
let mut modules: Arena<ModuleData> = Arena::default();
let root = modules.insert(ModuleData::default());

// Set the origin of the root module
modules[root].origin = ModuleOrigin::CrateRoot(root_file_id);
let origin = ModuleOrigin::CrateRoot(root_file_id);
let root = modules.insert(ModuleData::new(None, origin, false));

let def_map = CrateDefMap {
root: LocalModuleId(root),
Expand Down Expand Up @@ -129,6 +127,60 @@ impl CrateDefMap {
functions.filter(|id| interner.function_meta(id).attributes == Some(Attribute::Test))
})
}

/// Go through all modules in this crate, find all `contract ... { ... }` declarations,
/// and collect them all into a Vec.
pub fn get_all_contracts(&self) -> Vec<Contract> {
self.modules
.iter()
.filter_map(|(id, module)| {
if module.is_contract {
let functions = module
.scope
.values()
.values()
.filter_map(|(id, _)| id.as_function())
.collect();

let name = self.get_module_path(id, module.parent);
Some(Contract { name, functions })
} else {
None
}
})
.collect()
}

/// Find a child module's name by inspecting its parent.
/// Currently required as modules do not store their own names.
fn get_module_path(&self, child_id: Index, parent: Option<LocalModuleId>) -> String {
if let Some(id) = parent {
let parent = &self.modules[id.0];
let name = parent
.children
.iter()
.find(|(_, id)| id.0 == child_id)
.map(|(name, _)| &name.0.contents)
.expect("Child module was not a child of the given parent module");

let parent_name = self.get_module_path(id.0, parent.parent);
if parent_name.is_empty() {
name.to_string()
} else {
format!("{parent_name}.{name}")
}
} else {
String::new()
}
}
}

/// A 'contract' in Noir source code with the given name and functions.
/// This is not an AST node, it is just a convenient form to return for CrateDefMap::get_all_contracts.
pub struct Contract {
jfecher marked this conversation as resolved.
Show resolved Hide resolved
/// To keep `name` semi-unique, it is prefixed with the names of parent modules via CrateDefMap::get_module_path
pub name: String,
pub functions: Vec<FuncId>,
}

/// Given a FileId, fetch the File, from the FileManager and parse it's content
Expand Down
Loading