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
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;
@@ -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>>(
2 changes: 1 addition & 1 deletion crates/nargo/src/cli/contract_cmd.rs
Original file line number Diff line number Diff line change
@@ -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)]
14 changes: 5 additions & 9 deletions crates/nargo/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
@@ -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)?;

2 changes: 1 addition & 1 deletion crates/nargo/src/cli/test_cmd.rs
Original file line number Diff line number Diff line change
@@ -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();
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
@@ -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;
@@ -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)]
@@ -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() };
@@ -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
@@ -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();
@@ -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)
}
3 changes: 1 addition & 2 deletions crates/noirc_driver/src/main.rs
Original file line number Diff line number Diff line change
@@ -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
@@ -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,
};
@@ -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,
@@ -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,
@@ -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));

Loading