Skip to content

Commit

Permalink
feat: Implement basic contracts (#944)
Browse files Browse the repository at this point in the history
* Implement basic contracts

* Remove target directory

* Add doc comment
  • Loading branch information
jfecher authored Mar 6, 2023
1 parent 7264e03 commit 8ba3ab2
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 89 deletions.
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

0 comments on commit 8ba3ab2

Please sign in to comment.