diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index 50b65919f1e..0686e98aff2 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -2501,7 +2501,6 @@ name = "nargo" version = "0.30.0" dependencies = [ "acvm", - "codespan-reporting", "fm", "iter-extended", "jsonrpc", @@ -2546,8 +2545,10 @@ dependencies = [ "nargo_fmt", "nargo_toml", "noir_debugger", + "noir_fuzzer", "noir_lsp", "noirc_abi", + "noirc_artifacts", "noirc_driver", "noirc_errors", "noirc_frontend", @@ -2557,6 +2558,7 @@ dependencies = [ "pprof 0.13.0", "predicates 2.1.5", "prettytable-rs", + "proptest", "rayon", "serde", "serde_json", @@ -2677,6 +2679,7 @@ dependencies = [ "easy-repl", "fm", "nargo", + "noirc_artifacts", "noirc_driver", "noirc_errors", "noirc_frontend", @@ -2688,6 +2691,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "noir_fuzzer" +version = "0.30.0" +dependencies = [ + "acvm", + "nargo", + "noirc_abi", + "noirc_artifacts", + "proptest", + "rand 0.8.5", +] + [[package]] name = "noir_grumpkin" version = "0.1.0" @@ -2713,6 +2728,7 @@ dependencies = [ "nargo", "nargo_fmt", "nargo_toml", + "noirc_artifacts", "noirc_driver", "noirc_errors", "noirc_frontend", @@ -2740,6 +2756,7 @@ dependencies = [ "inferno", "nargo", "noirc_abi", + "noirc_artifacts", "noirc_driver", "noirc_errors", "serde", @@ -2761,6 +2778,7 @@ dependencies = [ "gloo-utils", "js-sys", "nargo", + "noirc_artifacts", "noirc_driver", "noirc_errors", "noirc_evaluator", @@ -2778,10 +2796,11 @@ version = "0.30.0" dependencies = [ "acvm", "iter-extended", - "noirc_frontend", "noirc_printable_type", "num-bigint", "num-traits", + "proptest", + "proptest-derive", "serde", "serde_json", "strum", @@ -2811,6 +2830,21 @@ dependencies = [ name = "noirc_arena" version = "0.30.0" +[[package]] +name = "noirc_artifacts" +version = "0.30.0" +dependencies = [ + "acvm", + "codespan-reporting", + "fm", + "noirc_abi", + "noirc_driver", + "noirc_errors", + "noirc_printable_type", + "serde", + "tempfile", +] + [[package]] name = "noirc_driver" version = "0.30.0" @@ -3381,9 +3415,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", @@ -3393,12 +3427,23 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.7.4", + "regex-syntax 0.8.2", "rusty-fork", "tempfile", "unarray", ] +[[package]] +name = "proptest-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf16337405ca084e9c78985114633b6827711d22b9e6ef6c6c0d665eb3f0b6e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3630,12 +3675,6 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" -[[package]] -name = "regex-syntax" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" - [[package]] name = "regex-syntax" version = "0.8.2" diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index be4cd81ab58..2a794398db0 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -15,10 +15,12 @@ members = [ # Crates related to tooling built on top of the Noir compiler "tooling/lsp", "tooling/debugger", + "tooling/fuzzer", "tooling/nargo", "tooling/nargo_fmt", "tooling/nargo_cli", "tooling/nargo_toml", + "tooling/noirc_artifacts", "tooling/noirc_abi", "tooling/noirc_abi_wasm", "tooling/acvm_cli", @@ -69,12 +71,14 @@ noirc_frontend = { path = "compiler/noirc_frontend" } noirc_printable_type = { path = "compiler/noirc_printable_type" } # Noir tooling workspace dependencies +noir_fuzzer = { path = "tooling/fuzzer" } nargo = { path = "tooling/nargo" } nargo_fmt = { path = "tooling/nargo_fmt" } nargo_toml = { path = "tooling/nargo_toml" } noir_lsp = { path = "tooling/lsp" } noir_debugger = { path = "tooling/debugger" } noirc_abi = { path = "tooling/noirc_abi" } +noirc_artifacts = { path = "tooling/noirc_artifacts" } bb_abstraction_leaks = { path = "tooling/bb_abstraction_leaks" } acvm_cli = { path = "tooling/acvm_cli" } @@ -143,6 +147,8 @@ flate2 = "1.0.24" color-eyre = "0.6.2" rand = "0.8.5" proptest = "1.2.0" +proptest-derive = "0.4.0" + im = { version = "15.1", features = ["serde"] } tracing = "0.1.40" tracing-web = "0.1.3" diff --git a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index e959c61732a..cf89e6ed209 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -3,14 +3,17 @@ use std::collections::BTreeMap; use acvm::acir::circuit::ErrorSelector; use acvm::AcirField; use iter_extended::vecmap; -use noirc_abi::{Abi, AbiErrorType, AbiParameter, AbiReturnType, AbiType, AbiValue}; -use noirc_frontend::ast::Visibility; +use noirc_abi::{ + Abi, AbiErrorType, AbiParameter, AbiReturnType, AbiType, AbiValue, AbiVisibility, Sign, +}; +use noirc_frontend::ast::{Signedness, Visibility}; use noirc_frontend::{ hir::Context, hir_def::{expr::HirArrayLiteral, function::Param, stmt::HirPattern, types::Type}, macros_api::{HirExpression, HirLiteral}, node_interner::{FuncId, NodeInterner}, }; +use noirc_frontend::{TypeBinding, TypeVariableKind}; /// Arranges a function signature and a generated circuit's return witnesses into a /// `noirc_abi::Abi`. @@ -21,15 +24,102 @@ pub(super) fn gen_abi( error_types: BTreeMap, ) -> Abi { let (parameters, return_type) = compute_function_abi(context, func_id); - let return_type = return_type - .map(|typ| AbiReturnType { abi_type: typ, visibility: return_visibility.into() }); + let return_type = return_type.map(|typ| AbiReturnType { + abi_type: typ, + visibility: to_abi_visibility(return_visibility), + }); let error_types = error_types .into_iter() - .map(|(selector, typ)| (selector, AbiErrorType::from_type(context, &typ))) + .map(|(selector, typ)| (selector, build_abi_error_type(context, &typ))) .collect(); Abi { parameters, return_type, error_types } } +fn build_abi_error_type(context: &Context, typ: &Type) -> AbiErrorType { + match typ { + Type::FmtString(len, item_types) => { + let length = len.evaluate_to_u32().expect("Cannot evaluate fmt length"); + let Type::Tuple(item_types) = item_types.as_ref() else { + unreachable!("FmtString items must be a tuple") + }; + let item_types = + item_types.iter().map(|typ| abi_type_from_hir_type(context, typ)).collect(); + AbiErrorType::FmtString { length, item_types } + } + _ => AbiErrorType::Custom(abi_type_from_hir_type(context, typ)), + } +} + +pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { + match typ { + Type::FieldElement => AbiType::Field, + Type::Array(size, typ) => { + let length = size + .evaluate_to_u32() + .expect("Cannot have variable sized arrays as a parameter to main"); + let typ = typ.as_ref(); + AbiType::Array { length, typ: Box::new(abi_type_from_hir_type(context, typ)) } + } + Type::Integer(sign, bit_width) => { + let sign = match sign { + Signedness::Unsigned => Sign::Unsigned, + Signedness::Signed => Sign::Signed, + }; + + AbiType::Integer { sign, width: (*bit_width).into() } + } + Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) + | Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { + TypeBinding::Bound(typ) => abi_type_from_hir_type(context, typ), + TypeBinding::Unbound(_) => { + abi_type_from_hir_type(context, &Type::default_int_or_field_type()) + } + }, + Type::Bool => AbiType::Boolean, + Type::String(size) => { + let size = size + .evaluate_to_u32() + .expect("Cannot have variable sized strings as a parameter to main"); + AbiType::String { length: size } + } + + Type::Struct(def, args) => { + let struct_type = def.borrow(); + let fields = struct_type.get_fields(args); + let fields = + vecmap(fields, |(name, typ)| (name, abi_type_from_hir_type(context, &typ))); + // For the ABI, we always want to resolve the struct paths from the root crate + let path = context.fully_qualified_struct_path(context.root_crate_id(), struct_type.id); + AbiType::Struct { fields, path } + } + Type::Alias(def, args) => abi_type_from_hir_type(context, &def.borrow().get_type(args)), + Type::Tuple(fields) => { + let fields = vecmap(fields, |typ| abi_type_from_hir_type(context, typ)); + AbiType::Tuple { fields } + } + Type::Error + | Type::Unit + | Type::Constant(_) + | Type::TraitAsType(..) + | Type::TypeVariable(_, _) + | Type::NamedGeneric(..) + | Type::Forall(..) + | Type::Code + | Type::Slice(_) + | Type::Function(_, _, _) => unreachable!("{typ} cannot be used in the abi"), + Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"), + Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), + } +} + +fn to_abi_visibility(value: Visibility) -> AbiVisibility { + match value { + Visibility::Public => AbiVisibility::Public, + Visibility::Private => AbiVisibility::Private, + Visibility::DataBus => AbiVisibility::DataBus, + } +} + pub(super) fn compute_function_abi( context: &Context, func_id: &FuncId, @@ -38,7 +128,7 @@ pub(super) fn compute_function_abi( let (parameters, return_type) = func_meta.function_signature(); let parameters = into_abi_params(context, parameters); - let return_type = return_type.map(|typ| AbiType::from_type(context, &typ)); + let return_type = return_type.map(|typ| abi_type_from_hir_type(context, &typ)); (parameters, return_type) } @@ -58,8 +148,8 @@ fn into_abi_params(context: &Context, params: Vec) -> Vec { let param_name = get_param_name(&pattern, &context.def_interner) .expect("Abi for tuple and struct parameters is unimplemented") .to_owned(); - let as_abi = AbiType::from_type(context, &typ); - AbiParameter { name: param_name, typ: as_abi, visibility: vis.into() } + let as_abi = abi_type_from_hir_type(context, &typ); + AbiParameter { name: param_name, typ: as_abi, visibility: to_abi_visibility(vis) } }) } diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index f8043a60f8c..2c8679eaa95 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -3,7 +3,7 @@ #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] -use abi_gen::value_from_hir_expression; +use abi_gen::{abi_type_from_hir_type, value_from_hir_expression}; use acvm::acir::circuit::ExpressionWidth; use clap::Args; use fm::{FileId, FileManager}; @@ -468,7 +468,7 @@ fn compile_contract_inner( let typ = context.def_interner.get_struct(struct_id); let typ = typ.borrow(); let fields = vecmap(typ.get_fields(&[]), |(name, typ)| { - (name, AbiType::from_type(context, &typ)) + (name, abi_type_from_hir_type(context, &typ)) }); let path = context.fully_qualified_struct_path(context.root_crate_id(), typ.id); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs index 4859ac5f97c..b86912940eb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs @@ -130,15 +130,6 @@ pub(super) fn recursive_non_entrypoint_function( } } -/// Test functions cannot have arguments in order to be executable. -pub(super) fn test_function_with_args(func: &NoirFunction) -> Option { - if func.attributes().is_test_function() && !func.parameters().is_empty() { - Some(ResolverError::TestFunctionHasParameters { span: func.name_ident().span() }) - } else { - None - } -} - /// Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime. pub(super) fn unconstrained_function_args( function_args: &[(Type, ExprId, Span)], diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index 37ad74b78b0..cbc480358db 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -569,7 +569,6 @@ impl<'context> Elaborator<'context> { self.run_lint(|elaborator| { lints::low_level_function_outside_stdlib(func, elaborator.crate_id).map(Into::into) }); - self.run_lint(|_| lints::test_function_with_args(func).map(Into::into)); self.run_lint(|_| { lints::recursive_non_entrypoint_function(func, is_entry_point).map(Into::into) }); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs index 06f6dda7142..319f38e9741 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -50,8 +50,6 @@ pub enum ResolverError { NoSuchNumericTypeVariable { path: crate::ast::Path }, #[error("Closures cannot capture mutable variables")] CapturedMutableVariable { span: Span }, - #[error("Test functions are not allowed to have any parameters")] - TestFunctionHasParameters { span: Span }, #[error("Only struct types can be used in constructor expressions")] NonStructUsedInConstructor { typ: Type, span: Span }, #[error("Only struct types can have generics")] @@ -245,11 +243,6 @@ impl<'a> From<&'a ResolverError> for Diagnostic { "Mutable variable".into(), *span, ), - ResolverError::TestFunctionHasParameters { span } => Diagnostic::simple_error( - "Test functions cannot have any parameters".into(), - "Try removing the parameters or moving the test into a wrapper function".into(), - *span, - ), ResolverError::NonStructUsedInConstructor { typ, span } => Diagnostic::simple_error( "Only struct types can be used in constructor expressions".into(), format!("{typ} has no fields to construct it with"), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 01f58ba4c27..4613ca4ac55 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -23,7 +23,7 @@ use crate::hir_def::expr::{ use crate::hir_def::function::FunctionBody; use crate::hir_def::traits::{Trait, TraitConstraint}; use crate::macros_api::SecondaryAttribute; -use crate::token::{Attributes, FunctionAttribute}; +use crate::token::Attributes; use regex::Regex; use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::rc::Rc; @@ -1042,14 +1042,6 @@ impl<'a> Resolver<'a> { }); } - if matches!(attributes.function, Some(FunctionAttribute::Test { .. })) - && !parameters.is_empty() - { - self.push_err(ResolverError::TestFunctionHasParameters { - span: func.name_ident().span(), - }); - } - let mut typ = Type::Function(parameter_types, return_type, Box::new(Type::Unit)); if !generics.is_empty() { diff --git a/noir/noir-repo/compiler/wasm/Cargo.toml b/noir/noir-repo/compiler/wasm/Cargo.toml index 03e59b3e269..f18148b6af1 100644 --- a/noir/noir-repo/compiler/wasm/Cargo.toml +++ b/noir/noir-repo/compiler/wasm/Cargo.toml @@ -17,6 +17,7 @@ acvm = { workspace = true, features = ["bn254"] } fm.workspace = true nargo.workspace = true noirc_driver.workspace = true +noirc_artifacts.workspace = true noirc_frontend = { workspace = true, features = ["bn254"] } noirc_errors.workspace = true noirc_evaluator.workspace = true diff --git a/noir/noir-repo/compiler/wasm/src/compile.rs b/noir/noir-repo/compiler/wasm/src/compile.rs index f1495e0b438..59b0e00e49f 100644 --- a/noir/noir-repo/compiler/wasm/src/compile.rs +++ b/noir/noir-repo/compiler/wasm/src/compile.rs @@ -2,10 +2,8 @@ use acvm::acir::circuit::ExpressionWidth; use fm::FileManager; use gloo_utils::format::JsValueSerdeExt; use js_sys::{JsString, Object}; -use nargo::{ - artifacts::{contract::ContractArtifact, program::ProgramArtifact}, - parse_all, -}; +use nargo::parse_all; +use noirc_artifacts::{contract::ContractArtifact, program::ProgramArtifact}; use noirc_driver::{ add_dep, file_manager_with_stdlib, prepare_crate, prepare_dependency, CompileOptions, }; diff --git a/noir/noir-repo/test_programs/noir_test_success/fuzzer_checks/Nargo.toml b/noir/noir-repo/test_programs/noir_test_success/fuzzer_checks/Nargo.toml new file mode 100644 index 00000000000..cd09d0d344d --- /dev/null +++ b/noir/noir-repo/test_programs/noir_test_success/fuzzer_checks/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "fuzzer_checks" +type = "bin" +authors = [""] +[dependencies] diff --git a/noir/noir-repo/test_programs/noir_test_success/fuzzer_checks/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/fuzzer_checks/src/main.nr new file mode 100644 index 00000000000..2b928db092e --- /dev/null +++ b/noir/noir-repo/test_programs/noir_test_success/fuzzer_checks/src/main.nr @@ -0,0 +1,6 @@ + +#[test(should_fail_with = "42 is not allowed")] +fn finds_magic_value(x: u32) { + let x = x as u64; + assert(2 * x != 42, "42 is not allowed"); +} diff --git a/noir/noir-repo/tooling/debugger/Cargo.toml b/noir/noir-repo/tooling/debugger/Cargo.toml index a3bf12f5368..05b28f9d95a 100644 --- a/noir/noir-repo/tooling/debugger/Cargo.toml +++ b/noir/noir-repo/tooling/debugger/Cargo.toml @@ -18,6 +18,7 @@ noirc_frontend.workspace = true noirc_printable_type.workspace = true noirc_errors.workspace = true noirc_driver.workspace = true +noirc_artifacts.workspace = true thiserror.workspace = true codespan-reporting.workspace = true dap.workspace = true diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 4436ce0ec3d..cb36988bf0b 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -10,9 +10,9 @@ use acvm::{BlackBoxFunctionSolver, FieldElement}; use codespan_reporting::files::{Files, SimpleFile}; use fm::FileId; -use nargo::artifacts::debug::{DebugArtifact, StackFrame}; use nargo::errors::{ExecutionError, Location}; use nargo::NargoError; +use noirc_artifacts::debug::{DebugArtifact, StackFrame}; use noirc_driver::DebugFile; use std::collections::BTreeMap; diff --git a/noir/noir-repo/tooling/debugger/src/dap.rs b/noir/noir-repo/tooling/debugger/src/dap.rs index 40b77f0ad2a..77abf3093cd 100644 --- a/noir/noir-repo/tooling/debugger/src/dap.rs +++ b/noir/noir-repo/tooling/debugger/src/dap.rs @@ -24,7 +24,7 @@ use dap::types::{ Breakpoint, DisassembledInstruction, Scope, Source, StackFrame, SteppingGranularity, StoppedEventReason, Thread, Variable, }; -use nargo::artifacts::debug::DebugArtifact; +use noirc_artifacts::debug::DebugArtifact; use fm::FileId; use noirc_driver::CompiledProgram; diff --git a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs index 03b1a35dfa5..62443d4065c 100644 --- a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs +++ b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs @@ -3,10 +3,8 @@ use acvm::{ pwg::ForeignCallWaitInfo, AcirField, FieldElement, }; -use nargo::{ - artifacts::debug::{DebugArtifact, DebugVars, StackFrame}, - ops::{DefaultForeignCallExecutor, ForeignCallExecutor}, -}; +use nargo::ops::{DefaultForeignCallExecutor, ForeignCallExecutor}; +use noirc_artifacts::debug::{DebugArtifact, DebugVars, StackFrame}; use noirc_errors::debug_info::{DebugFnId, DebugVarId}; use noirc_printable_type::ForeignCallError; diff --git a/noir/noir-repo/tooling/debugger/src/lib.rs b/noir/noir-repo/tooling/debugger/src/lib.rs index 9168a6228f0..9d0059ee495 100644 --- a/noir/noir-repo/tooling/debugger/src/lib.rs +++ b/noir/noir-repo/tooling/debugger/src/lib.rs @@ -13,7 +13,7 @@ use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; use acvm::{BlackBoxFunctionSolver, FieldElement}; -use nargo::artifacts::debug::DebugArtifact; +use noirc_artifacts::debug::DebugArtifact; use nargo::NargoError; use noirc_driver::CompiledProgram; diff --git a/noir/noir-repo/tooling/debugger/src/repl.rs b/noir/noir-repo/tooling/debugger/src/repl.rs index 07f9333d51c..7d8c6e0947d 100644 --- a/noir/noir-repo/tooling/debugger/src/repl.rs +++ b/noir/noir-repo/tooling/debugger/src/repl.rs @@ -5,9 +5,10 @@ use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap}; use acvm::brillig_vm::brillig::Opcode as BrilligOpcode; use acvm::{BlackBoxFunctionSolver, FieldElement}; +use nargo::NargoError; use crate::foreign_calls::DefaultDebugForeignCallExecutor; -use nargo::{artifacts::debug::DebugArtifact, NargoError}; +use noirc_artifacts::debug::DebugArtifact; use easy_repl::{command, CommandStatus, Repl}; use noirc_printable_type::PrintableValueDisplay; diff --git a/noir/noir-repo/tooling/debugger/src/source_code_printer.rs b/noir/noir-repo/tooling/debugger/src/source_code_printer.rs index e298eb8aadd..e9586b786bd 100644 --- a/noir/noir-repo/tooling/debugger/src/source_code_printer.rs +++ b/noir/noir-repo/tooling/debugger/src/source_code_printer.rs @@ -1,5 +1,5 @@ use codespan_reporting::files::Files; -use nargo::artifacts::debug::DebugArtifact; +use noirc_artifacts::debug::DebugArtifact; use noirc_errors::Location; use owo_colors::OwoColorize; use std::ops::Range; @@ -143,7 +143,7 @@ fn render_line( // // Consider for example the file (line numbers added to facilitate this doc): // ``` -// 1 use dep::std::hash::poseidon; +// 1 use std::hash::poseidon; // 2 // 3 fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field) { // 4 let hash1 = poseidon::bn254::hash_2(x1); @@ -157,7 +157,7 @@ fn render_line( // // If the location to render is `poseidon::bn254::hash_2(x1)`, we'll render the file as: // ``` -// 1 use dep::std::hash::poseidon; +// 1 use std::hash::poseidon; // 2 // 3 fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field) { // 4 let hash1 = poseidon::bn254::hash_2(x1); @@ -224,7 +224,7 @@ mod tests { use crate::source_code_printer::PrintedLine::Content; use acvm::acir::circuit::OpcodeLocation; use fm::FileManager; - use nargo::artifacts::debug::DebugArtifact; + use noirc_artifacts::debug::DebugArtifact; use noirc_errors::{debug_info::DebugInfo, Location, Span}; use std::collections::BTreeMap; use std::ops::Range; diff --git a/noir/noir-repo/tooling/fuzzer/Cargo.toml b/noir/noir-repo/tooling/fuzzer/Cargo.toml new file mode 100644 index 00000000000..ef49d707d6a --- /dev/null +++ b/noir/noir-repo/tooling/fuzzer/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "noir_fuzzer" +description = "A fuzzer for Noir programs" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acvm.workspace = true +nargo.workspace = true +noirc_artifacts.workspace = true +noirc_abi.workspace = true +proptest.workspace = true +rand.workspace = true diff --git a/noir/noir-repo/tooling/fuzzer/src/dictionary/mod.rs b/noir/noir-repo/tooling/fuzzer/src/dictionary/mod.rs new file mode 100644 index 00000000000..bf2ab87be29 --- /dev/null +++ b/noir/noir-repo/tooling/fuzzer/src/dictionary/mod.rs @@ -0,0 +1,124 @@ +//! This module defines how to build a dictionary of values which are likely to be correspond +//! to significant inputs during fuzzing by inspecting the [Program] being fuzzed. +//! +//! This dictionary can be fed into the fuzzer's [strategy][proptest::strategy::Strategy] in order to bias it towards +//! generating these values to ensure they get proper coverage. +use std::collections::HashSet; + +use acvm::{ + acir::{ + circuit::{ + brillig::{BrilligBytecode, BrilligInputs}, + directives::Directive, + opcodes::{BlackBoxFuncCall, FunctionInput}, + Circuit, Opcode, Program, + }, + native_types::Expression, + }, + brillig_vm::brillig::Opcode as BrilligOpcode, + AcirField, +}; + +/// Constructs a [HashSet] of values pulled from a [Program] which are likely to be correspond +/// to significant inputs during fuzzing. +pub(super) fn build_dictionary_from_program(program: &Program) -> HashSet { + let constrained_dictionaries = program.functions.iter().map(build_dictionary_from_circuit); + let unconstrained_dictionaries = + program.unconstrained_functions.iter().map(build_dictionary_from_unconstrained_function); + let dictionaries = constrained_dictionaries.chain(unconstrained_dictionaries); + + let mut constants: HashSet = HashSet::new(); + for dictionary in dictionaries { + constants.extend(dictionary); + } + constants +} + +fn build_dictionary_from_circuit(circuit: &Circuit) -> HashSet { + let mut constants: HashSet = HashSet::new(); + + fn insert_expr(dictionary: &mut HashSet, expr: &Expression) { + let quad_coefficients = expr.mul_terms.iter().map(|(k, _, _)| *k); + let linear_coefficients = expr.linear_combinations.iter().map(|(k, _)| *k); + let coefficients = linear_coefficients.chain(quad_coefficients); + + dictionary.extend(coefficients.clone()); + dictionary.insert(expr.q_c); + + // We divide the constant term by any coefficients in the expression to aid solving constraints such as `2 * x - 4 == 0`. + let scaled_constants = coefficients.map(|coefficient| expr.q_c / coefficient); + dictionary.extend(scaled_constants); + } + + fn insert_array_len(dictionary: &mut HashSet, array: &[T]) { + let array_length = array.len() as u128; + dictionary.insert(F::from(array_length)); + dictionary.insert(F::from(array_length - 1)); + } + + for opcode in &circuit.opcodes { + match opcode { + Opcode::AssertZero(expr) + | Opcode::Call { predicate: Some(expr), .. } + | Opcode::MemoryOp { predicate: Some(expr), .. } + | Opcode::Directive(Directive::ToLeRadix { a: expr, .. }) => { + insert_expr(&mut constants, expr) + } + + Opcode::MemoryInit { init, .. } => insert_array_len(&mut constants, init), + + Opcode::BrilligCall { inputs, predicate, .. } => { + for input in inputs { + match input { + BrilligInputs::Single(expr) => insert_expr(&mut constants, expr), + BrilligInputs::Array(exprs) => { + exprs.iter().for_each(|expr| insert_expr(&mut constants, expr)); + insert_array_len(&mut constants, exprs); + } + BrilligInputs::MemoryArray(_) => (), + } + } + if let Some(predicate) = predicate { + insert_expr(&mut constants, predicate) + } + } + + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { num_bits, .. }, + }) => { + let field = 1u128.wrapping_shl(*num_bits); + constants.insert(F::from(field)); + constants.insert(F::from(field - 1)); + } + _ => (), + } + } + + constants +} + +fn build_dictionary_from_unconstrained_function( + function: &BrilligBytecode, +) -> HashSet { + let mut constants: HashSet = HashSet::new(); + + for opcode in &function.bytecode { + match opcode { + BrilligOpcode::Cast { bit_size, .. } => { + let field = 1u128.wrapping_shl(*bit_size); + constants.insert(F::from(field)); + constants.insert(F::from(field - 1)); + } + BrilligOpcode::Const { bit_size, value, .. } => { + constants.insert(*value); + + let field = 1u128.wrapping_shl(*bit_size); + constants.insert(F::from(field)); + constants.insert(F::from(field - 1)); + } + _ => (), + } + } + + constants +} diff --git a/noir/noir-repo/tooling/fuzzer/src/lib.rs b/noir/noir-repo/tooling/fuzzer/src/lib.rs new file mode 100644 index 00000000000..28d7353f35a --- /dev/null +++ b/noir/noir-repo/tooling/fuzzer/src/lib.rs @@ -0,0 +1,96 @@ +//! This module has been adapted from Foundry's fuzzing implementation for the EVM. +//! https://github.com/foundry-rs/foundry/blob/6a85dbaa62f1c305f31cab37781232913055ae28/crates/evm/evm/src/executors/fuzz/mod.rs#L40 +//! +//! Code is used under the MIT license. + +use acvm::{blackbox_solver::StubbedBlackBoxSolver, FieldElement}; +use dictionary::build_dictionary_from_program; +use noirc_abi::InputMap; +use proptest::test_runner::{TestCaseError, TestError, TestRunner}; + +mod dictionary; +mod strategies; +mod types; + +use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome, FuzzTestResult}; + +use noirc_artifacts::program::ProgramArtifact; + +use nargo::ops::{execute_program, DefaultForeignCallExecutor}; + +/// An executor for Noir programs which which provides fuzzing support using [`proptest`]. +/// +/// After instantiation, calling `fuzz` will proceed to hammer the program with +/// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the +/// configuration which can be overridden via [environment variables](proptest::test_runner::Config) +pub struct FuzzedExecutor { + /// The program to be fuzzed + program: ProgramArtifact, + + /// The fuzzer + runner: TestRunner, +} + +impl FuzzedExecutor { + /// Instantiates a fuzzed executor given a testrunner + pub fn new(program: ProgramArtifact, runner: TestRunner) -> Self { + Self { program, runner } + } + + /// Fuzzes the provided program. + pub fn fuzz(&self) -> FuzzTestResult { + let dictionary = build_dictionary_from_program(&self.program.bytecode); + let strategy = strategies::arb_input_map(&self.program.abi, dictionary); + + let run_result: Result<(), TestError> = + self.runner.clone().run(&strategy, |input_map| { + let fuzz_res = self.single_fuzz(input_map)?; + + match fuzz_res { + FuzzOutcome::Case(_) => Ok(()), + FuzzOutcome::CounterExample(CounterExampleOutcome { + exit_reason: status, + .. + }) => Err(TestCaseError::fail(status)), + } + }); + + match run_result { + Ok(()) => FuzzTestResult { success: true, reason: None, counterexample: None }, + + Err(TestError::Abort(reason)) => FuzzTestResult { + success: false, + reason: Some(reason.to_string()), + counterexample: None, + }, + Err(TestError::Fail(reason, counterexample)) => { + let reason = reason.to_string(); + let reason = if reason.is_empty() { None } else { Some(reason) }; + + FuzzTestResult { success: false, reason, counterexample: Some(counterexample) } + } + } + } + + /// Granular and single-step function that runs only one fuzz and returns either a `CaseOutcome` + /// or a `CounterExampleOutcome` + pub fn single_fuzz(&self, input_map: InputMap) -> Result { + let initial_witness = self.program.abi.encode(&input_map, None).unwrap(); + let result = execute_program( + &self.program.bytecode, + initial_witness, + &StubbedBlackBoxSolver, + &mut DefaultForeignCallExecutor::::new(false, None), + ); + + // TODO: Add handling for `vm.assume` equivalent + + match result { + Ok(_) => Ok(FuzzOutcome::Case(CaseOutcome { case: input_map })), + Err(err) => Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { + exit_reason: err.to_string(), + counterexample: input_map, + })), + } + } +} diff --git a/noir/noir-repo/tooling/fuzzer/src/strategies/int.rs b/noir/noir-repo/tooling/fuzzer/src/strategies/int.rs new file mode 100644 index 00000000000..d11cafcfae5 --- /dev/null +++ b/noir/noir-repo/tooling/fuzzer/src/strategies/int.rs @@ -0,0 +1,83 @@ +use proptest::{ + strategy::{NewTree, Strategy}, + test_runner::TestRunner, +}; +use rand::Rng; + +/// Strategy for signed ints (up to i128). +/// The strategy combines 2 different strategies, each assigned a specific weight: +/// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` +/// param). Then generate a value for this bit size. +/// 2. Generate a random value around the edges (+/- 3 around min, 0 and max possible value) +#[derive(Debug)] +pub struct IntStrategy { + /// Bit size of int (e.g. 128) + bits: usize, + /// The weight for edge cases (+/- 3 around 0 and max possible value) + edge_weight: usize, + /// The weight for purely random values + random_weight: usize, +} + +impl IntStrategy { + /// Create a new strategy. + /// # Arguments + /// * `bits` - Size of int in bits + pub fn new(bits: usize) -> Self { + Self { bits, edge_weight: 10usize, random_weight: 50usize } + } + + fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { + let rng = runner.rng(); + + let offset = rng.gen_range(0..4); + // Choose if we want values around min, -0, +0, or max + let kind = rng.gen_range(0..4); + let start = match kind { + 0 => self.type_min() + offset, + 1 => -offset - 1i128, + 2 => offset, + 3 => self.type_max() - offset, + _ => unreachable!(), + }; + Ok(proptest::num::i128::BinarySearch::new(start)) + } + + fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { + let rng = runner.rng(); + + let start: i128 = rng.gen_range(self.type_min()..=self.type_max()); + Ok(proptest::num::i128::BinarySearch::new(start)) + } + + fn type_max(&self) -> i128 { + if self.bits < 128 { + (1i128 << (self.bits - 1)) - 1 + } else { + i128::MAX + } + } + + fn type_min(&self) -> i128 { + if self.bits < 128 { + -(1i128 << (self.bits - 1)) + } else { + i128::MIN + } + } +} + +impl Strategy for IntStrategy { + type Tree = proptest::num::i128::BinarySearch; + type Value = i128; + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + let total_weight = self.random_weight + self.edge_weight; + let bias = runner.rng().gen_range(0..total_weight); + // randomly select one of 2 strategies + match bias { + x if x < self.edge_weight => self.generate_edge_tree(runner), + _ => self.generate_random_tree(runner), + } + } +} diff --git a/noir/noir-repo/tooling/fuzzer/src/strategies/mod.rs b/noir/noir-repo/tooling/fuzzer/src/strategies/mod.rs new file mode 100644 index 00000000000..46187a28d5b --- /dev/null +++ b/noir/noir-repo/tooling/fuzzer/src/strategies/mod.rs @@ -0,0 +1,99 @@ +use int::IntStrategy; +use prop::collection::vec; +use proptest::prelude::*; + +use acvm::{AcirField, FieldElement}; + +use noirc_abi::{input_parser::InputValue, Abi, AbiType, InputMap, Sign}; +use std::collections::{BTreeMap, HashSet}; +use uint::UintStrategy; + +mod int; +mod uint; + +pub(super) fn arb_value_from_abi_type( + abi_type: &AbiType, + dictionary: HashSet, +) -> SBoxedStrategy { + match abi_type { + AbiType::Field => vec(any::(), 32) + .prop_map(|bytes| InputValue::Field(FieldElement::from_be_bytes_reduce(&bytes))) + .sboxed(), + AbiType::Integer { width, sign } if sign == &Sign::Unsigned => { + UintStrategy::new(*width as usize, dictionary) + .prop_map(|uint| InputValue::Field(uint.into())) + .sboxed() + } + AbiType::Integer { width, .. } => { + let shift = 2i128.pow(*width); + IntStrategy::new(*width as usize) + .prop_map(move |mut int| { + if int < 0 { + int += shift + } + InputValue::Field(int.into()) + }) + .sboxed() + } + AbiType::Boolean => { + any::().prop_map(|val| InputValue::Field(FieldElement::from(val))).sboxed() + } + + AbiType::String { length } => { + // Strings only allow ASCII characters as each character must be able to be represented by a single byte. + let string_regex = format!("[[:ascii:]]{{{length}}}"); + proptest::string::string_regex(&string_regex) + .expect("parsing of regex should always succeed") + .prop_map(InputValue::String) + .sboxed() + } + AbiType::Array { length, typ } => { + let length = *length as usize; + let elements = vec(arb_value_from_abi_type(typ, dictionary), length..=length); + + elements.prop_map(InputValue::Vec).sboxed() + } + + AbiType::Struct { fields, .. } => { + let fields: Vec> = fields + .iter() + .map(|(name, typ)| { + (Just(name.clone()), arb_value_from_abi_type(typ, dictionary.clone())).sboxed() + }) + .collect(); + + fields + .prop_map(|fields| { + let fields: BTreeMap<_, _> = fields.into_iter().collect(); + InputValue::Struct(fields) + }) + .sboxed() + } + + AbiType::Tuple { fields } => { + let fields: Vec<_> = + fields.iter().map(|typ| arb_value_from_abi_type(typ, dictionary.clone())).collect(); + fields.prop_map(InputValue::Vec).sboxed() + } + } +} + +pub(super) fn arb_input_map( + abi: &Abi, + dictionary: HashSet, +) -> BoxedStrategy { + let values: Vec<_> = abi + .parameters + .iter() + .map(|param| { + (Just(param.name.clone()), arb_value_from_abi_type(¶m.typ, dictionary.clone())) + }) + .collect(); + + values + .prop_map(|values| { + let input_map: InputMap = values.into_iter().collect(); + input_map + }) + .boxed() +} diff --git a/noir/noir-repo/tooling/fuzzer/src/strategies/uint.rs b/noir/noir-repo/tooling/fuzzer/src/strategies/uint.rs new file mode 100644 index 00000000000..94610dbc829 --- /dev/null +++ b/noir/noir-repo/tooling/fuzzer/src/strategies/uint.rs @@ -0,0 +1,98 @@ +use std::collections::HashSet; + +use acvm::{AcirField, FieldElement}; +use proptest::{ + strategy::{NewTree, Strategy}, + test_runner::TestRunner, +}; +use rand::Rng; + +/// Value tree for unsigned ints (up to u128). +/// The strategy combines 2 different strategies, each assigned a specific weight: +/// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` +/// param). Then generate a value for this bit size. +/// 2. Generate a random value around the edges (+/- 3 around 0 and max possible value) +#[derive(Debug)] +pub struct UintStrategy { + /// Bit size of uint (e.g. 128) + bits: usize, + /// A set of fixtures to be generated + fixtures: Vec, + /// The weight for edge cases (+/- 3 around 0 and max possible value) + edge_weight: usize, + /// The weight for fixtures + fixtures_weight: usize, + /// The weight for purely random values + random_weight: usize, +} + +impl UintStrategy { + /// Create a new strategy. + /// # Arguments + /// * `bits` - Size of uint in bits + /// * `fixtures` - Set of `FieldElements` representing values which the fuzzer weight towards testing. + pub fn new(bits: usize, fixtures: HashSet) -> Self { + Self { + bits, + fixtures: fixtures.into_iter().collect(), + edge_weight: 10usize, + fixtures_weight: 40usize, + random_weight: 50usize, + } + } + + fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { + let rng = runner.rng(); + // Choose if we want values around 0 or max + let is_min = rng.gen_bool(0.5); + let offset = rng.gen_range(0..4); + let start = if is_min { offset } else { self.type_max().saturating_sub(offset) }; + Ok(proptest::num::u128::BinarySearch::new(start)) + } + + fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { + // generate random cases if there's no fixtures + if self.fixtures.is_empty() { + return self.generate_random_tree(runner); + } + + // Generate value tree from fixture. + let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + if fixture.num_bits() <= self.bits as u32 { + return Ok(proptest::num::u128::BinarySearch::new(fixture.to_u128())); + } + + // If fixture is not a valid type, generate random value. + self.generate_random_tree(runner) + } + + fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { + let rng = runner.rng(); + let start = rng.gen_range(0..=self.type_max()); + + Ok(proptest::num::u128::BinarySearch::new(start)) + } + + fn type_max(&self) -> u128 { + if self.bits < 128 { + (1 << self.bits) - 1 + } else { + u128::MAX + } + } +} + +impl Strategy for UintStrategy { + type Tree = proptest::num::u128::BinarySearch; + type Value = u128; + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; + let bias = runner.rng().gen_range(0..total_weight); + // randomly select one of 3 strategies + match bias { + x if x < self.edge_weight => self.generate_edge_tree(runner), + x if x < self.edge_weight + self.fixtures_weight => self.generate_fixtures_tree(runner), + _ => self.generate_random_tree(runner), + } + } +} diff --git a/noir/noir-repo/tooling/fuzzer/src/types.rs b/noir/noir-repo/tooling/fuzzer/src/types.rs new file mode 100644 index 00000000000..dbd4ba94ec1 --- /dev/null +++ b/noir/noir-repo/tooling/fuzzer/src/types.rs @@ -0,0 +1,42 @@ +use noirc_abi::InputMap; + +type CounterExample = InputMap; + +/// The outcome of a fuzz test +#[derive(Debug)] +pub struct FuzzTestResult { + /// Whether the test case was successful. This means that the program executed + /// properly, or that there was a constraint failure and that the test was expected to fail + /// (has the `should_fail` attribute) + pub success: bool, + + /// If there was a constraint failure, this field will be populated. Note that the test can + /// still be successful (i.e self.success == true) when it's expected to fail. + pub reason: Option, + + /// Minimal reproduction test case for failing fuzz tests + pub counterexample: Option, +} + +/// Returned by a single fuzz in the case of a successful run +#[derive(Debug)] +pub struct CaseOutcome { + /// Data of a single fuzz test case + pub case: InputMap, +} + +/// Returned by a single fuzz when a counterexample has been discovered +#[derive(Debug)] +pub struct CounterExampleOutcome { + /// Minimal reproduction test case for failing test + pub counterexample: CounterExample, + /// The status of the call + pub exit_reason: String, +} + +/// Outcome of a single fuzz +#[derive(Debug)] +pub enum FuzzOutcome { + Case(CaseOutcome), + CounterExample(CounterExampleOutcome), +} diff --git a/noir/noir-repo/tooling/lsp/Cargo.toml b/noir/noir-repo/tooling/lsp/Cargo.toml index a599b096e52..ac3e3b1d30a 100644 --- a/noir/noir-repo/tooling/lsp/Cargo.toml +++ b/noir/noir-repo/tooling/lsp/Cargo.toml @@ -19,6 +19,7 @@ nargo_toml.workspace = true noirc_driver.workspace = true noirc_errors.workspace = true noirc_frontend.workspace = true +noirc_artifacts.workspace = true serde.workspace = true serde_json.workspace = true tower.workspace = true diff --git a/noir/noir-repo/tooling/lsp/src/requests/profile_run.rs b/noir/noir-repo/tooling/lsp/src/requests/profile_run.rs index 7d06bc87c85..57bc3299455 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/profile_run.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/profile_run.rs @@ -5,11 +5,9 @@ use std::{ use acvm::acir::circuit::ExpressionWidth; use async_lsp::{ErrorCode, ResponseError}; -use nargo::{ - artifacts::debug::DebugArtifact, insert_all_files_for_workspace_into_file_manager, - ops::report_errors, -}; +use nargo::{insert_all_files_for_workspace_into_file_manager, ops::report_errors}; use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use noirc_artifacts::debug::DebugArtifact; use noirc_driver::{ file_manager_with_stdlib, CompileOptions, DebugFile, NOIR_ARTIFACT_VERSION_STRING, }; diff --git a/noir/noir-repo/tooling/lsp/src/solver.rs b/noir/noir-repo/tooling/lsp/src/solver.rs index 0fcac73b905..9d1185e3a79 100644 --- a/noir/noir-repo/tooling/lsp/src/solver.rs +++ b/noir/noir-repo/tooling/lsp/src/solver.rs @@ -24,6 +24,14 @@ impl BlackBoxFunctionSolver for WrapperSolver { self.0.pedersen_commitment(inputs, domain_separator) } + fn pedersen_hash( + &self, + inputs: &[acvm::FieldElement], + domain_separator: u32, + ) -> Result { + self.0.pedersen_hash(inputs, domain_separator) + } + fn multi_scalar_mul( &self, points: &[acvm::FieldElement], @@ -36,14 +44,6 @@ impl BlackBoxFunctionSolver for WrapperSolver { self.0.multi_scalar_mul(points, scalars_lo, scalars_hi) } - fn pedersen_hash( - &self, - inputs: &[acvm::FieldElement], - domain_separator: u32, - ) -> Result { - self.0.pedersen_hash(inputs, domain_separator) - } - fn ec_add( &self, input1_x: &acvm::FieldElement, diff --git a/noir/noir-repo/tooling/nargo/Cargo.toml b/noir/noir-repo/tooling/nargo/Cargo.toml index 8abec267d20..b0cf1cfcbb1 100644 --- a/noir/noir-repo/tooling/nargo/Cargo.toml +++ b/noir/noir-repo/tooling/nargo/Cargo.toml @@ -18,13 +18,12 @@ noirc_errors.workspace = true noirc_frontend.workspace = true noirc_printable_type.workspace = true iter-extended.workspace = true -serde.workspace = true thiserror.workspace = true -codespan-reporting.workspace = true tracing.workspace = true rayon = "1.8.0" jsonrpc.workspace = true rand.workspace = true +serde.workspace = true [dev-dependencies] # TODO: This dependency is used to generate unit tests for `get_all_paths_in_dir` diff --git a/noir/noir-repo/tooling/nargo/src/lib.rs b/noir/noir-repo/tooling/nargo/src/lib.rs index 3deced041f8..c0c7602d14d 100644 --- a/noir/noir-repo/tooling/nargo/src/lib.rs +++ b/noir/noir-repo/tooling/nargo/src/lib.rs @@ -7,7 +7,6 @@ //! This name was used because it sounds like `cargo` and //! Noir Package Manager abbreviated is npm, which is already taken. -pub mod artifacts; pub mod constants; pub mod errors; pub mod ops; diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index 7c7e7b9d5ca..e0e54449a6f 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -32,6 +32,8 @@ noirc_driver.workspace = true noirc_frontend = { workspace = true, features = ["bn254"] } noirc_abi.workspace = true noirc_errors.workspace = true +noir_fuzzer.workspace = true +noirc_artifacts.workspace = true acvm = { workspace = true, features = ["bn254"] } bn254_blackbox_solver.workspace = true toml.workspace = true @@ -54,6 +56,7 @@ color-eyre.workspace = true tokio = { version = "1.0", features = ["io-std", "rt"] } dap.workspace = true clap-markdown = { git = "https://github.com/noir-lang/clap-markdown", rev = "450d759532c88f0dba70891ceecdbc9ff8f25d2b", optional = true } +proptest.workspace = true notify = "6.1.1" notify-debouncer-full = "0.3.1" diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs index bd76cf24805..e83b1728c93 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -3,7 +3,6 @@ use std::path::Path; use std::time::Duration; use fm::FileManager; -use nargo::artifacts::program::ProgramArtifact; use nargo::ops::{collect_errors, compile_contract, compile_program, report_errors}; use nargo::package::Package; use nargo::workspace::Workspace; @@ -11,7 +10,7 @@ use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::file_manager_with_stdlib; use noirc_driver::NOIR_ARTIFACT_VERSION_STRING; -use noirc_driver::{CompilationResult, CompileOptions, CompiledContract, CompiledProgram}; +use noirc_driver::{CompilationResult, CompileOptions, CompiledContract}; use noirc_frontend::graph::CrateName; @@ -121,39 +120,22 @@ pub(super) fn compile_workspace_full( let compiled_workspace = compile_workspace(&workspace_file_manager, &parsed_files, workspace, compile_options); - let (compiled_programs, compiled_contracts) = report_errors( + report_errors( compiled_workspace, &workspace_file_manager, compile_options.deny_warnings, compile_options.silence_warnings, )?; - let (binary_packages, contract_packages): (Vec<_>, Vec<_>) = workspace - .into_iter() - .filter(|package| !package.is_library()) - .cloned() - .partition(|package| package.is_binary()); - - // Save build artifacts to disk. - for (package, program) in binary_packages.into_iter().zip(compiled_programs) { - let program = nargo::ops::transform_program(program, compile_options.expression_width); - save_program(program.clone(), &package, &workspace.target_directory_path()); - } - let circuit_dir = workspace.target_directory_path(); - for (package, contract) in contract_packages.into_iter().zip(compiled_contracts) { - let contract = nargo::ops::transform_contract(contract, compile_options.expression_width); - save_contract(contract, &package, &circuit_dir, compile_options.show_artifact_paths); - } - Ok(()) } -pub(super) fn compile_workspace( +fn compile_workspace( file_manager: &FileManager, parsed_files: &ParsedFiles, workspace: &Workspace, compile_options: &CompileOptions, -) -> CompilationResult<(Vec, Vec)> { +) -> CompilationResult<()> { let (binary_packages, contract_packages): (Vec<_>, Vec<_>) = workspace .into_iter() .filter(|package| !package.is_library()) @@ -161,32 +143,20 @@ pub(super) fn compile_workspace( .partition(|package| package.is_binary()); // Compile all of the packages in parallel. - let program_results: Vec> = binary_packages - .par_iter() - .map(|package| { - let program_artifact_path = workspace.package_build_path(package); - let cached_program: Option = - read_program_from_file(program_artifact_path) - .ok() - .filter(|p| p.noir_version == NOIR_ARTIFACT_VERSION_STRING) - .map(|p| p.into()); - - compile_program(file_manager, parsed_files, package, compile_options, cached_program) - }) - .collect(); - let contract_results: Vec> = contract_packages - .par_iter() - .map(|package| compile_contract(file_manager, parsed_files, package, compile_options)) - .collect(); - - // Collate any warnings/errors which were encountered during compilation. - let compiled_programs = collect_errors(program_results); - let compiled_contracts = collect_errors(contract_results); + let program_warnings_or_errors: CompilationResult<()> = + compile_programs(file_manager, parsed_files, workspace, &binary_packages, compile_options); + let contract_warnings_or_errors: CompilationResult<()> = compiled_contracts( + file_manager, + parsed_files, + &contract_packages, + compile_options, + &workspace.target_directory_path(), + ); - match (compiled_programs, compiled_contracts) { - (Ok((programs, program_warnings)), Ok((contracts, contract_warnings))) => { + match (program_warnings_or_errors, contract_warnings_or_errors) { + (Ok((_, program_warnings)), Ok((_, contract_warnings))) => { let warnings = [program_warnings, contract_warnings].concat(); - Ok(((programs, contracts), warnings)) + Ok(((), warnings)) } (Err(program_errors), Err(contract_errors)) => { Err([program_errors, contract_errors].concat()) @@ -195,22 +165,79 @@ pub(super) fn compile_workspace( } } -pub(super) fn save_program(program: CompiledProgram, package: &Package, circuit_dir: &Path) { - let program_artifact = ProgramArtifact::from(program.clone()); - save_program_to_file(&program_artifact, &package.name, circuit_dir); +fn compile_programs( + file_manager: &FileManager, + parsed_files: &ParsedFiles, + workspace: &Workspace, + binary_packages: &[Package], + compile_options: &CompileOptions, +) -> CompilationResult<()> { + let load_cached_program = |package| { + let program_artifact_path = workspace.package_build_path(package); + read_program_from_file(program_artifact_path) + .ok() + .filter(|p| p.noir_version == NOIR_ARTIFACT_VERSION_STRING) + .map(|p| p.into()) + }; + + let program_results: Vec> = binary_packages + .par_iter() + .map(|package| { + let (program, warnings) = compile_program( + file_manager, + parsed_files, + package, + compile_options, + load_cached_program(package), + )?; + let program = nargo::ops::transform_program(program, compile_options.expression_width); + save_program_to_file( + &program.clone().into(), + &package.name, + workspace.target_directory_path(), + ); + Ok(((), warnings)) + }) + .collect(); + + // Collate any warnings/errors which were encountered during compilation. + collect_errors(program_results).map(|(_, warnings)| ((), warnings)) +} + +fn compiled_contracts( + file_manager: &FileManager, + parsed_files: &ParsedFiles, + contract_packages: &[Package], + compile_options: &CompileOptions, + target_dir: &Path, +) -> CompilationResult<()> { + let contract_results: Vec> = contract_packages + .par_iter() + .map(|package| { + let (contract, warnings) = + compile_contract(file_manager, parsed_files, package, compile_options)?; + let contract = + nargo::ops::transform_contract(contract, compile_options.expression_width); + save_contract(contract, package, target_dir, compile_options.show_artifact_paths); + Ok(((), warnings)) + }) + .collect(); + + // Collate any warnings/errors which were encountered during compilation. + collect_errors(contract_results).map(|(_, warnings)| ((), warnings)) } fn save_contract( contract: CompiledContract, package: &Package, - circuit_dir: &Path, + target_dir: &Path, show_artifact_paths: bool, ) { let contract_name = contract.name.clone(); let artifact_path = save_contract_to_file( &contract.into(), &format!("{}-{}", package.name, contract_name), - circuit_dir, + target_dir, ); if show_artifact_paths { println!("Saved contract artifact to: {}", artifact_path.display()); diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs index 086dddc27e5..778009bf791 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -6,7 +6,6 @@ use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; use fm::FileManager; -use nargo::artifacts::debug::DebugArtifact; use nargo::constants::PROVER_INPUT_FILE; use nargo::errors::CompileError; use nargo::ops::{compile_program, compile_program_with_debug_instrumenter, report_errors}; @@ -16,6 +15,7 @@ use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::{Format, InputValue}; use noirc_abi::InputMap; +use noirc_artifacts::debug::DebugArtifact; use noirc_driver::{ file_manager_with_stdlib, CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING, }; diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs index b548336275b..cf9dc1141a1 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -3,7 +3,6 @@ use acvm::FieldElement; use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; -use nargo::artifacts::debug::DebugArtifact; use nargo::constants::PROVER_INPUT_FILE; use nargo::errors::try_to_diagnose_runtime_error; use nargo::ops::DefaultForeignCallExecutor; @@ -11,6 +10,7 @@ use nargo::package::Package; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::{Format, InputValue}; use noirc_abi::InputMap; +use noirc_artifacts::debug::DebugArtifact; use noirc_driver::{CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING}; use noirc_frontend::graph::CrateName; diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/fs/program.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/fs/program.rs index ba017651667..caeaafd4ab3 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/fs/program.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/fs/program.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use nargo::artifacts::{contract::ContractArtifact, program::ProgramArtifact}; +use noirc_artifacts::{contract::ContractArtifact, program::ProgramArtifact}; use noirc_frontend::graph::CrateName; use crate::errors::FilesystemError; diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs index 7c50e907dc9..3759fb31c76 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs @@ -3,11 +3,9 @@ use std::collections::HashMap; use acvm::acir::circuit::ExpressionWidth; use clap::Args; use iter_extended::vecmap; -use nargo::{ - artifacts::{debug::DebugArtifact, program::ProgramArtifact}, - package::Package, -}; +use nargo::package::Package; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use noirc_artifacts::{debug::DebugArtifact, program::ProgramArtifact}; use noirc_driver::{CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; use noirc_errors::{debug_info::OpCodesCount, Location}; use noirc_frontend::graph::CrateName; diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs index 99c284e5019..0511f9cafdd 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs @@ -10,7 +10,8 @@ use nargo::{ }; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{ - check_crate, file_manager_with_stdlib, CompileOptions, NOIR_ARTIFACT_VERSION_STRING, + check_crate, compile_no_check, file_manager_with_stdlib, CompileOptions, + NOIR_ARTIFACT_VERSION_STRING, }; use noirc_frontend::{ graph::CrateName, @@ -185,14 +186,47 @@ fn run_test + Default>( let blackbox_solver = S::default(); - nargo::ops::run_test( - &blackbox_solver, - &mut context, - test_function, - show_output, - foreign_call_resolver_url, - compile_options, - ) + let test_function_has_no_arguments = context + .def_interner + .function_meta(&test_function.get_id()) + .function_signature() + .0 + .is_empty(); + + if test_function_has_no_arguments { + nargo::ops::run_test( + &blackbox_solver, + &mut context, + test_function, + show_output, + foreign_call_resolver_url, + compile_options, + ) + } else { + use noir_fuzzer::FuzzedExecutor; + use proptest::test_runner::TestRunner; + + let compiled_program = + compile_no_check(&mut context, compile_options, test_function.get_id(), None, false); + match compiled_program { + Ok(compiled_program) => { + let runner = TestRunner::default(); + + let fuzzer = FuzzedExecutor::new(compiled_program.into(), runner); + + let result = fuzzer.fuzz(); + if result.success { + TestStatus::Pass + } else { + TestStatus::Fail { + message: result.reason.unwrap_or_default(), + error_diagnostic: None, + } + } + } + Err(err) => TestStatus::CompileError(err.into()), + } + } } fn get_tests_in_package( diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs index 0bb967e7502..7fd396d6961 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs @@ -1,7 +1,8 @@ +use std::io::Write; use std::{collections::BTreeMap, path::PathBuf}; -use acvm::blackbox_solver::StubbedBlackBoxSolver; -use noirc_driver::{check_crate, file_manager_with_stdlib, CompileOptions}; +use fm::FileManager; +use noirc_driver::{check_crate, compile_no_check, file_manager_with_stdlib, CompileOptions}; use noirc_frontend::hir::FunctionNameMatch; use nargo::{ @@ -9,6 +10,7 @@ use nargo::{ package::{Package, PackageType}, parse_all, prepare_package, }; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; #[test] fn run_stdlib_tests() { @@ -23,7 +25,7 @@ fn run_stdlib_tests() { root_dir: PathBuf::from("."), package_type: PackageType::Binary, entry_path: PathBuf::from("main.nr"), - name: "dummy".parse().unwrap(), + name: "stdlib".parse().unwrap(), dependencies: BTreeMap::new(), }; @@ -44,19 +46,136 @@ fn run_stdlib_tests() { let test_report: Vec<(String, TestStatus)> = test_functions .into_iter() .map(|(test_name, test_function)| { - let status = run_test( - &StubbedBlackBoxSolver, - &mut context, - &test_function, - false, - None, - &CompileOptions::default(), - ); + let test_function_has_no_arguments = context + .def_interner + .function_meta(&test_function.get_id()) + .function_signature() + .0 + .is_empty(); + let status = if test_function_has_no_arguments { + run_test( + &bn254_blackbox_solver::Bn254BlackBoxSolver, + &mut context, + &test_function, + false, + None, + &CompileOptions::default(), + ) + } else { + use noir_fuzzer::FuzzedExecutor; + use proptest::test_runner::TestRunner; + + let compiled_program = compile_no_check( + &mut context, + &CompileOptions::default(), + test_function.get_id(), + None, + false, + ); + match compiled_program { + Ok(compiled_program) => { + let runner = TestRunner::default(); + + let fuzzer = FuzzedExecutor::new(compiled_program.into(), runner); + + let result = fuzzer.fuzz(); + if result.success { + TestStatus::Pass + } else { + TestStatus::Fail { + message: result.reason.unwrap_or_default(), + error_diagnostic: None, + } + } + } + Err(err) => TestStatus::CompileError(err.into()), + } + }; (test_name, status) }) .collect(); assert!(!test_report.is_empty(), "Could not find any tests within the stdlib"); + display_test_report(&file_manager, &dummy_package, &CompileOptions::default(), &test_report); assert!(test_report.iter().all(|(_, status)| !status.failed())); } + +// This code is copied from `src/cli/test_cmd.rs`. +// This should be abstracted into a proper test runner at some point. +fn display_test_report( + file_manager: &FileManager, + package: &Package, + compile_options: &CompileOptions, + test_report: &[(String, TestStatus)], +) { + let writer = StandardStream::stderr(ColorChoice::Always); + let mut writer = writer.lock(); + + for (test_name, test_status) in test_report { + write!(writer, "[{}] Testing {test_name}... ", package.name) + .expect("Failed to write to stderr"); + writer.flush().expect("Failed to flush writer"); + + match &test_status { + TestStatus::Pass { .. } => { + writer + .set_color(ColorSpec::new().set_fg(Some(Color::Green))) + .expect("Failed to set color"); + writeln!(writer, "ok").expect("Failed to write to stderr"); + } + TestStatus::Fail { message, error_diagnostic } => { + writer + .set_color(ColorSpec::new().set_fg(Some(Color::Red))) + .expect("Failed to set color"); + writeln!(writer, "FAIL\n{message}\n").expect("Failed to write to stderr"); + if let Some(diag) = error_diagnostic { + noirc_errors::reporter::report_all( + file_manager.as_file_map(), + &[diag.clone()], + compile_options.deny_warnings, + compile_options.silence_warnings, + ); + } + } + TestStatus::CompileError(err) => { + noirc_errors::reporter::report_all( + file_manager.as_file_map(), + &[err.clone()], + compile_options.deny_warnings, + compile_options.silence_warnings, + ); + } + } + writer.reset().expect("Failed to reset writer"); + } + + write!(writer, "[{}] ", package.name).expect("Failed to write to stderr"); + + let count_all = test_report.len(); + let count_failed = test_report.iter().filter(|(_, status)| status.failed()).count(); + let plural = if count_all == 1 { "" } else { "s" }; + if count_failed == 0 { + writer.set_color(ColorSpec::new().set_fg(Some(Color::Green))).expect("Failed to set color"); + write!(writer, "{count_all} test{plural} passed").expect("Failed to write to stderr"); + writer.reset().expect("Failed to reset writer"); + writeln!(writer).expect("Failed to write to stderr"); + } else { + let count_passed = count_all - count_failed; + let plural_failed = if count_failed == 1 { "" } else { "s" }; + let plural_passed = if count_passed == 1 { "" } else { "s" }; + + if count_passed != 0 { + writer + .set_color(ColorSpec::new().set_fg(Some(Color::Green))) + .expect("Failed to set color"); + write!(writer, "{count_passed} test{plural_passed} passed, ",) + .expect("Failed to write to stderr"); + } + + writer.set_color(ColorSpec::new().set_fg(Some(Color::Red))).expect("Failed to set color"); + writeln!(writer, "{count_failed} test{plural_failed} failed") + .expect("Failed to write to stderr"); + writer.reset().expect("Failed to reset writer"); + } +} diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/contract.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/contract.nr index 97a6ebd6b77..14b1af4a848 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/contract.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/contract.nr @@ -5,7 +5,7 @@ contract Benchmarking { use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; - use dep::value_note::{utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}}; + use value_note::{utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}}; use dep::aztec::{ context::Context, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr index 1c0d4564b5e..ec734b57970 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr @@ -1,10 +1,10 @@ -impl Type {} +impl MyType {} -impl Type {} +impl MyType {} -impl Type {} +impl MyType {} -impl Type { +impl MyType { fn method(self) {} fn method(mut self) {} @@ -12,10 +12,10 @@ impl Type { fn method(&mut self) {} } -impl Type { +impl MyType { fn method(self) {} } -impl Type { +impl MyType { fn method(self) {} } diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/import_braces.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/import_braces.nr index 49c9d09001e..9c74c477f5f 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/import_braces.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/import_braces.nr @@ -1 +1 @@ -use dep::std::hash::sha256; +use std::hash::sha256; diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/let.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/let.nr index c57801155a0..7ff69e74306 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/let.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/let.nr @@ -44,12 +44,12 @@ fn let_() { } }; - let expr = Expr { + let expr = MyExpr { // A boolean literal (true, false). kind: ExprKind::Bool(true) }; - let expr = Expr { /*A boolean literal (true, false).*/ kind: ExprKind::Bool(true) }; + let expr = MyExpr { /*A boolean literal (true, false).*/ kind: ExprKind::Bool(true) }; let mut V = dep::crate2::MyStruct { Q: x }; let mut V = dep::crate2::MyStruct {}; diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/print.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/print.nr index 3bce0941da2..d8404f674b0 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/print.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/print.nr @@ -1,5 +1,3 @@ -use dep::std; - fn main() { std::print("Hello world"); std::println("Hello world"); diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/print2.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/print2.nr index 3bce0941da2..d8404f674b0 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/print2.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/print2.nr @@ -1,5 +1,3 @@ -use dep::std; - fn main() { std::print("Hello world"); std::println("Hello world"); diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/singleton_import.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/singleton_import.nr new file mode 100644 index 00000000000..bb1bad500d9 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/singleton_import.nr @@ -0,0 +1,2 @@ +use dep::std; +use some_crate; diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/contract.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/contract.nr index 97a6ebd6b77..14b1af4a848 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/contract.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/contract.nr @@ -5,7 +5,7 @@ contract Benchmarking { use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; - use dep::value_note::{utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}}; + use value_note::{utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}}; use dep::aztec::{ context::Context, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr index 1f111371a43..ea909dfad44 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr @@ -1,10 +1,10 @@ -impl Type {} +impl MyType {} -impl Type {} +impl MyType {} -impl Type {} +impl MyType {} -impl Type { +impl MyType { fn method(self) {} fn method(mut self) {} @@ -12,10 +12,10 @@ impl Type { fn method(&mut self) {} } -impl Type { +impl MyType { fn method(self) {} } -impl Type { +impl MyType { fn method(self) {} -} \ No newline at end of file +} diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/import_braces.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/import_braces.nr index 88c7e9562a8..0647bbaa580 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/import_braces.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/import_braces.nr @@ -1 +1 @@ -use dep::std::hash::{sha256}; \ No newline at end of file +use std::hash::{sha256}; diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/let.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/let.nr index 67c4ab8bd52..37cdc6655c7 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/let.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/let.nr @@ -20,11 +20,11 @@ fn let_() { let person = Person { first_name: "John", last_name: "Doe", home_address: Address { street: "123 Main St", city: "Exampleville", zip_code: "12345", master: Person { first_name: "John", last_name: "Doe", home_address: Address { street: "123 Main St", city: "Exampleville", zip_code: "12345" } } } }; - let expr = Expr {// A boolean literal (true, false). + let expr = MyExpr {// A boolean literal (true, false). kind: ExprKind::Bool(true), }; - let expr = Expr {/*A boolean literal (true, false).*/kind: ExprKind::Bool(true),}; + let expr = MyExpr {/*A boolean literal (true, false).*/kind: ExprKind::Bool(true),}; let mut V = dep::crate2::MyStruct { Q: x }; let mut V = dep::crate2::MyStruct {}; diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/print.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/print.nr index 3bce0941da2..d8404f674b0 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/print.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/print.nr @@ -1,5 +1,3 @@ -use dep::std; - fn main() { std::print("Hello world"); std::println("Hello world"); diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/print2.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/print2.nr index 3bce0941da2..d8404f674b0 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/print2.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/print2.nr @@ -1,5 +1,3 @@ -use dep::std; - fn main() { std::print("Hello world"); std::println("Hello world"); diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/singleton_import.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/singleton_import.nr new file mode 100644 index 00000000000..bb1bad500d9 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/singleton_import.nr @@ -0,0 +1,2 @@ +use dep::std; +use some_crate; diff --git a/noir/noir-repo/tooling/noirc_abi/Cargo.toml b/noir/noir-repo/tooling/noirc_abi/Cargo.toml index baae2dfa35e..4c0c1f75e42 100644 --- a/noir/noir-repo/tooling/noirc_abi/Cargo.toml +++ b/noir/noir-repo/tooling/noirc_abi/Cargo.toml @@ -11,7 +11,6 @@ license.workspace = true [dependencies] acvm.workspace = true iter-extended.workspace = true -noirc_frontend.workspace = true noirc_printable_type.workspace = true toml.workspace = true serde_json = "1.0" @@ -23,13 +22,9 @@ num-traits = "0.2" [dev-dependencies] strum = "0.24" strum_macros = "0.24" +proptest.workspace = true +proptest-derive.workspace = true [features] -bn254 = [ - "acvm/bn254", - "noirc_frontend/bn254", -] -bls12_381 = [ - "acvm/bls12_381", - "noirc_frontend/bls12_381", -] \ No newline at end of file +bn254 = ["acvm/bn254"] +bls12_381 = ["acvm/bls12_381"] diff --git a/noir/noir-repo/tooling/noirc_abi/src/arbitrary.rs b/noir/noir-repo/tooling/noirc_abi/src/arbitrary.rs new file mode 100644 index 00000000000..aecb620b79d --- /dev/null +++ b/noir/noir-repo/tooling/noirc_abi/src/arbitrary.rs @@ -0,0 +1,154 @@ +use iter_extended::{btree_map, vecmap}; +use prop::collection::vec; +use proptest::prelude::*; + +use acvm::{AcirField, FieldElement}; + +use crate::{ + input_parser::InputValue, Abi, AbiParameter, AbiReturnType, AbiType, AbiVisibility, InputMap, + Sign, +}; +use std::collections::{BTreeMap, HashSet}; + +pub(super) use proptest_derive::Arbitrary; + +/// Mutates an iterator of mutable references to [`String`]s to ensure that all values are unique. +fn ensure_unique_strings<'a>(iter: impl Iterator) { + let mut seen_values: HashSet = HashSet::default(); + for value in iter { + while seen_values.contains(value.as_str()) { + value.push('1'); + } + seen_values.insert(value.clone()); + } +} + +proptest::prop_compose! { + pub(super) fn arb_field_from_integer(bit_size: u32)(value: u128)-> FieldElement { + let width = (bit_size % 128).clamp(1, 127); + let max_value = 2u128.pow(width) - 1; + FieldElement::from(value.clamp(0, max_value)) + } +} + +fn arb_value_from_abi_type(abi_type: &AbiType) -> SBoxedStrategy { + match abi_type { + AbiType::Field => vec(any::(), 32) + .prop_map(|bytes| InputValue::Field(FieldElement::from_be_bytes_reduce(&bytes))) + .sboxed(), + AbiType::Integer { width, .. } => { + arb_field_from_integer(*width).prop_map(InputValue::Field).sboxed() + } + + AbiType::Boolean => { + any::().prop_map(|val| InputValue::Field(FieldElement::from(val))).sboxed() + } + + AbiType::String { length } => { + // Strings only allow ASCII characters as each character must be able to be represented by a single byte. + let string_regex = format!("[[:ascii:]]{{{length}}}"); + proptest::string::string_regex(&string_regex) + .expect("parsing of regex should always succeed") + .prop_map(InputValue::String) + .sboxed() + } + AbiType::Array { length, typ } => { + let length = *length as usize; + let elements = vec(arb_value_from_abi_type(typ), length..=length); + + elements.prop_map(InputValue::Vec).sboxed() + } + + AbiType::Struct { fields, .. } => { + let fields: Vec> = fields + .iter() + .map(|(name, typ)| (Just(name.clone()), arb_value_from_abi_type(typ)).sboxed()) + .collect(); + + fields + .prop_map(|fields| { + let fields: BTreeMap<_, _> = fields.into_iter().collect(); + InputValue::Struct(fields) + }) + .sboxed() + } + + AbiType::Tuple { fields } => { + let fields: Vec<_> = fields.iter().map(arb_value_from_abi_type).collect(); + fields.prop_map(InputValue::Vec).sboxed() + } + } +} + +fn arb_primitive_abi_type() -> SBoxedStrategy { + const MAX_STRING_LEN: u32 = 1000; + proptest::prop_oneof![ + Just(AbiType::Field), + Just(AbiType::Boolean), + any::<(Sign, u32)>().prop_map(|(sign, width)| { + let width = (width % 128).clamp(1, 127); + AbiType::Integer { sign, width } + }), + // restrict length of strings to avoid running out of memory + (1..MAX_STRING_LEN).prop_map(|length| AbiType::String { length }), + ] + .sboxed() +} + +pub(super) fn arb_abi_type() -> BoxedStrategy { + let leaf = arb_primitive_abi_type(); + + leaf.prop_recursive( + 8, // up to 8 levels deep + 256, // Shoot for maximum size of 256 nodes + 10, // We put up to 10 items per collection + |inner| { + prop_oneof![ + (1..10u32, inner.clone()) + .prop_map(|(length, typ)| { AbiType::Array { length, typ: Box::new(typ) } }) + .boxed(), + prop::collection::vec(inner.clone(), 1..10) + .prop_map(|fields| { AbiType::Tuple { fields } }) + .boxed(), + (".*", prop::collection::vec((".+", inner), 1..10)) + .prop_map(|(path, mut fields)| { + // Require that all field names are unique. + ensure_unique_strings(fields.iter_mut().map(|(field_name, _)| field_name)); + AbiType::Struct { path, fields } + }) + .boxed(), + ] + }, + ) + .boxed() +} + +fn arb_abi_param_and_value() -> BoxedStrategy<(AbiParameter, InputValue)> { + arb_abi_type() + .prop_flat_map(|typ| { + let value = arb_value_from_abi_type(&typ); + let param = arb_abi_param(typ); + (param, value) + }) + .boxed() +} + +fn arb_abi_param(typ: AbiType) -> SBoxedStrategy { + (".+", any::()) + .prop_map(move |(name, visibility)| AbiParameter { name, typ: typ.clone(), visibility }) + .sboxed() +} + +prop_compose! { + pub(super) fn arb_abi_and_input_map() + (mut parameters_with_values in proptest::collection::vec(arb_abi_param_and_value(), 0..100), return_type: Option) + -> (Abi, InputMap) { + // Require that all parameter names are unique. + ensure_unique_strings(parameters_with_values.iter_mut().map(|(param_name,_)| &mut param_name.name)); + + let parameters = vecmap(¶meters_with_values, |(param, _)| param.clone()); + let input_map = btree_map(parameters_with_values, |(param, value)| (param.name, value)); + + (Abi { parameters, return_type, error_types: BTreeMap::default() }, input_map) + } +} diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index 86c982b5b0d..c3e1ade04fa 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -13,8 +13,6 @@ use acvm::{ use errors::AbiError; use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; -use noirc_frontend::ast::{Signedness, Visibility}; -use noirc_frontend::{hir::Context, Type, TypeBinding, TypeVariableKind}; use noirc_printable_type::{ decode_value as printable_type_decode_value, PrintableType, PrintableValue, PrintableValueDisplay, @@ -27,6 +25,9 @@ use std::{collections::BTreeMap, str}; // // This ABI has nothing to do with ACVM or ACIR. Although they implicitly have a relationship +#[cfg(test)] +mod arbitrary; + pub mod errors; pub mod input_parser; mod serialization; @@ -77,6 +78,7 @@ pub enum AbiType { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(test, derive(arbitrary::Arbitrary))] #[serde(rename_all = "lowercase")] /// Represents whether the parameter is public or known only to the prover. pub enum AbiVisibility { @@ -87,27 +89,8 @@ pub enum AbiVisibility { DataBus, } -impl From for AbiVisibility { - fn from(value: Visibility) -> Self { - match value { - Visibility::Public => AbiVisibility::Public, - Visibility::Private => AbiVisibility::Private, - Visibility::DataBus => AbiVisibility::DataBus, - } - } -} - -impl From<&Visibility> for AbiVisibility { - fn from(value: &Visibility) -> Self { - match value { - Visibility::Public => AbiVisibility::Public, - Visibility::Private => AbiVisibility::Private, - Visibility::DataBus => AbiVisibility::DataBus, - } - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(test, derive(arbitrary::Arbitrary))] #[serde(rename_all = "lowercase")] pub enum Sign { Unsigned, @@ -115,70 +98,6 @@ pub enum Sign { } impl AbiType { - pub fn from_type(context: &Context, typ: &Type) -> Self { - // Note; use strict_eq instead of partial_eq when comparing field types - // in this method, you most likely want to distinguish between public and private - match typ { - Type::FieldElement => Self::Field, - Type::Array(size, typ) => { - let length = size - .evaluate_to_u32() - .expect("Cannot have variable sized arrays as a parameter to main"); - let typ = typ.as_ref(); - Self::Array { length, typ: Box::new(Self::from_type(context, typ)) } - } - Type::Integer(sign, bit_width) => { - let sign = match sign { - Signedness::Unsigned => Sign::Unsigned, - Signedness::Signed => Sign::Signed, - }; - - Self::Integer { sign, width: (*bit_width).into() } - } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) - | Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { - TypeBinding::Bound(typ) => Self::from_type(context, typ), - TypeBinding::Unbound(_) => { - Self::from_type(context, &Type::default_int_or_field_type()) - } - }, - Type::Bool => Self::Boolean, - Type::String(size) => { - let size = size - .evaluate_to_u32() - .expect("Cannot have variable sized strings as a parameter to main"); - Self::String { length: size } - } - - Type::Struct(def, args) => { - let struct_type = def.borrow(); - let fields = struct_type.get_fields(args); - let fields = vecmap(fields, |(name, typ)| (name, Self::from_type(context, &typ))); - // For the ABI, we always want to resolve the struct paths from the root crate - let path = - context.fully_qualified_struct_path(context.root_crate_id(), struct_type.id); - Self::Struct { fields, path } - } - Type::Alias(def, args) => Self::from_type(context, &def.borrow().get_type(args)), - Type::Tuple(fields) => { - let fields = vecmap(fields, |typ| Self::from_type(context, typ)); - Self::Tuple { fields } - } - Type::Error - | Type::Unit - | Type::Constant(_) - | Type::TraitAsType(..) - | Type::TypeVariable(_, _) - | Type::NamedGeneric(..) - | Type::Forall(..) - | Type::Code - | Type::Slice(_) - | Type::Function(_, _, _) => unreachable!("{typ} cannot be used in the abi"), - Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"), - Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), - } - } - /// Returns the number of field elements required to represent the type once encoded. pub fn field_count(&self) -> u32 { match self { @@ -228,10 +147,12 @@ impl From<&AbiType> for PrintableType { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(test, derive(arbitrary::Arbitrary))] /// An argument or return value of the circuit's `main` function. pub struct AbiParameter { pub name: String, #[serde(rename = "type")] + #[cfg_attr(test, proptest(strategy = "arbitrary::arb_abi_type()"))] pub typ: AbiType, pub visibility: AbiVisibility, } @@ -243,16 +164,20 @@ impl AbiParameter { } #[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(test, derive(arbitrary::Arbitrary))] pub struct AbiReturnType { + #[cfg_attr(test, proptest(strategy = "arbitrary::arb_abi_type()"))] pub abi_type: AbiType, pub visibility: AbiVisibility, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[cfg_attr(test, derive(arbitrary::Arbitrary))] pub struct Abi { /// An ordered list of the arguments to the program's `main` function, specifying their types and visibility. pub parameters: Vec, pub return_type: Option, + #[cfg_attr(test, proptest(strategy = "proptest::prelude::Just(BTreeMap::from([]))"))] pub error_types: BTreeMap, } @@ -543,22 +468,6 @@ pub enum AbiErrorType { FmtString { length: u32, item_types: Vec }, Custom(AbiType), } -impl AbiErrorType { - pub fn from_type(context: &Context, typ: &Type) -> Self { - match typ { - Type::FmtString(len, item_types) => { - let length = len.evaluate_to_u32().expect("Cannot evaluate fmt length"); - let Type::Tuple(item_types) = item_types.as_ref() else { - unreachable!("FmtString items must be a tuple") - }; - let item_types = - item_types.iter().map(|typ| AbiType::from_type(context, typ)).collect(); - Self::FmtString { length, item_types } - } - _ => Self::Custom(AbiType::from_type(context, typ)), - } - } -} pub fn display_abi_error( fields: &[F], @@ -590,56 +499,18 @@ pub fn display_abi_error( #[cfg(test)] mod test { - use std::collections::BTreeMap; + use proptest::prelude::*; - use acvm::{AcirField, FieldElement}; + use crate::arbitrary::arb_abi_and_input_map; - use crate::{ - input_parser::InputValue, Abi, AbiParameter, AbiReturnType, AbiType, AbiVisibility, - InputMap, - }; + proptest! { + #[test] + fn encoding_and_decoding_returns_original_witness_map((abi, input_map) in arb_abi_and_input_map()) { + let witness_map = abi.encode(&input_map, None).unwrap(); + let (decoded_inputs, return_value) = abi.decode(&witness_map).unwrap(); - #[test] - fn witness_encoding_roundtrip() { - let abi = Abi { - parameters: vec![ - AbiParameter { - name: "thing1".to_string(), - typ: AbiType::Array { length: 2, typ: Box::new(AbiType::Field) }, - visibility: AbiVisibility::Public, - }, - AbiParameter { - name: "thing2".to_string(), - typ: AbiType::Field, - visibility: AbiVisibility::Public, - }, - ], - return_type: Some(AbiReturnType { - abi_type: AbiType::Field, - visibility: AbiVisibility::Public, - }), - error_types: BTreeMap::default(), - }; - - // Note we omit return value from inputs - let inputs: InputMap = BTreeMap::from([ - ( - "thing1".to_string(), - InputValue::Vec(vec![ - InputValue::Field(FieldElement::one()), - InputValue::Field(FieldElement::one()), - ]), - ), - ("thing2".to_string(), InputValue::Field(FieldElement::zero())), - ]); - - let witness_map = abi.encode(&inputs, None).unwrap(); - let (reconstructed_inputs, return_value) = abi.decode(&witness_map).unwrap(); - - for (key, expected_value) in inputs { - assert_eq!(reconstructed_inputs[&key], expected_value); + prop_assert_eq!(decoded_inputs, input_map); + prop_assert_eq!(return_value, None); } - - assert!(return_value.is_none()); } } diff --git a/noir/noir-repo/tooling/noirc_artifacts/Cargo.toml b/noir/noir-repo/tooling/noirc_artifacts/Cargo.toml new file mode 100644 index 00000000000..4249604f949 --- /dev/null +++ b/noir/noir-repo/tooling/noirc_artifacts/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "noirc_artifacts" +description = "Definitions of Nargo's build artifacts" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acvm.workspace = true +fm.workspace = true +noirc_abi.workspace = true +noirc_driver.workspace = true +noirc_errors.workspace = true +noirc_printable_type.workspace = true +serde.workspace = true +codespan-reporting.workspace = true + + +[dev-dependencies] +tempfile.workspace = true diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/contract.rs b/noir/noir-repo/tooling/noirc_artifacts/src/contract.rs similarity index 100% rename from noir/noir-repo/tooling/nargo/src/artifacts/contract.rs rename to noir/noir-repo/tooling/noirc_artifacts/src/contract.rs diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/debug.rs b/noir/noir-repo/tooling/noirc_artifacts/src/debug.rs similarity index 99% rename from noir/noir-repo/tooling/nargo/src/artifacts/debug.rs rename to noir/noir-repo/tooling/noirc_artifacts/src/debug.rs index 21102c40fcf..11a3e1c4dd7 100644 --- a/noir/noir-repo/tooling/nargo/src/artifacts/debug.rs +++ b/noir/noir-repo/tooling/noirc_artifacts/src/debug.rs @@ -186,7 +186,7 @@ impl<'a> Files<'a> for DebugArtifact { #[cfg(test)] mod tests { - use crate::artifacts::debug::DebugArtifact; + use crate::debug::DebugArtifact; use acvm::acir::circuit::OpcodeLocation; use fm::FileManager; use noirc_errors::{debug_info::DebugInfo, Location, Span}; diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs b/noir/noir-repo/tooling/noirc_artifacts/src/debug_vars.rs similarity index 100% rename from noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs rename to noir/noir-repo/tooling/noirc_artifacts/src/debug_vars.rs diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/mod.rs b/noir/noir-repo/tooling/noirc_artifacts/src/lib.rs similarity index 73% rename from noir/noir-repo/tooling/nargo/src/artifacts/mod.rs rename to noir/noir-repo/tooling/noirc_artifacts/src/lib.rs index c7b3736f90b..77873ed9409 100644 --- a/noir/noir-repo/tooling/nargo/src/artifacts/mod.rs +++ b/noir/noir-repo/tooling/noirc_artifacts/src/lib.rs @@ -1,8 +1,14 @@ +#![forbid(unsafe_code)] +#![warn(unused_crate_dependencies, unused_extern_crates)] +#![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] + //! This module defines the structure of Nargo's different compilation artifacts. //! //! These artifacts are intended to remain independent of any applications being built on top of Noir. //! Should any projects require/desire a different artifact format, it's expected that they will write a transformer //! to generate them using these artifacts as a starting point. + pub mod contract; pub mod debug; mod debug_vars; diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/program.rs b/noir/noir-repo/tooling/noirc_artifacts/src/program.rs similarity index 100% rename from noir/noir-repo/tooling/nargo/src/artifacts/program.rs rename to noir/noir-repo/tooling/noirc_artifacts/src/program.rs diff --git a/noir/noir-repo/tooling/profiler/Cargo.toml b/noir/noir-repo/tooling/profiler/Cargo.toml index baebe9292e6..d33e99f1a4c 100644 --- a/noir/noir-repo/tooling/profiler/Cargo.toml +++ b/noir/noir-repo/tooling/profiler/Cargo.toml @@ -17,6 +17,7 @@ path = "src/main.rs" [dependencies] color-eyre.workspace = true clap.workspace = true +noirc_artifacts.workspace = true nargo.workspace = true const_format.workspace = true serde.workspace = true diff --git a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs index 4f51eed4ba3..38e7fff5f42 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -7,12 +7,12 @@ use clap::Args; use codespan_reporting::files::Files; use color_eyre::eyre::{self, Context}; use inferno::flamegraph::{from_lines, Options}; -use nargo::artifacts::debug::DebugArtifact; use serde::{Deserialize, Serialize}; use acir::circuit::OpcodeLocation; -use nargo::artifacts::program::ProgramArtifact; use nargo::errors::Location; +use noirc_artifacts::debug::DebugArtifact; +use noirc_artifacts::program::ProgramArtifact; use noirc_errors::reporter::line_and_column_from_span; #[derive(Debug, Clone, Args)] @@ -262,7 +262,7 @@ mod tests { use acir::circuit::{OpcodeLocation, Program}; use color_eyre::eyre::{self}; use fm::{FileId, FileManager}; - use nargo::artifacts::program::ProgramArtifact; + use noirc_artifacts::program::ProgramArtifact; use noirc_driver::DebugFile; use noirc_errors::{ debug_info::{DebugInfo, ProgramDebugInfo},