diff --git a/noir-projects/Dockerfile b/noir-projects/Dockerfile index 574738e2a1b..949712a7366 100644 --- a/noir-projects/Dockerfile +++ b/noir-projects/Dockerfile @@ -14,11 +14,11 @@ WORKDIR /usr/src/noir-projects COPY . . # Build WORKDIR /usr/src/noir-projects/noir-contracts -RUN ./bootstrap.sh && nargo test --silence-warnings +RUN ./bootstrap.sh && nargo test --silence-warnings --mock-oracle-resolver WORKDIR /usr/src/noir-projects/noir-protocol-circuits -RUN ./bootstrap.sh && nargo test --silence-warnings +RUN ./bootstrap.sh && nargo test --silence-warnings --mock-oracle-resolver WORKDIR /usr/src/noir-projects/aztec-nr -RUN nargo test --silence-warnings +RUN nargo test --silence-warnings --mock-oracle-resolver FROM scratch COPY --from=builder /usr/src/noir-projects /usr/src/noir-projects \ No newline at end of file diff --git a/noir-projects/Earthfile b/noir-projects/Earthfile index ccdfe81acf1..1b519d523cf 100644 --- a/noir-projects/Earthfile +++ b/noir-projects/Earthfile @@ -26,6 +26,6 @@ build: test: FROM +build - RUN cd noir-protocol-circuits && nargo test --silence-warnings - RUN cd aztec-nr && nargo test --silence-warnings - RUN cd noir-contracts && nargo test --silence-warnings + RUN cd noir-protocol-circuits && nargo test --silence-warnings --mock-oracle-resolver + RUN cd aztec-nr && nargo test --silence-warnings --mock-oracle-resolver + RUN cd noir-contracts && nargo test --silence-warnings --mock-oracle-resolver diff --git a/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs index 4e36dbd1f22..d2e25ae0088 100644 --- a/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs @@ -7,7 +7,7 @@ use clap::Args; use crate::cli::fs::inputs::{read_bytecode_from_file, read_inputs_from_file}; use crate::errors::CliError; -use nargo::ops::{execute_program, DefaultForeignCallExecutor}; +use nargo::ops::{execute_program, DefaultForeignCallExecutor, ResolverOpts}; use super::fs::witness::{create_output_witness_string, save_witness_to_dir}; @@ -38,7 +38,8 @@ pub(crate) struct ExecuteCommand { fn run_command(args: ExecuteCommand) -> Result { let bytecode = read_bytecode_from_file(&args.working_directory, &args.bytecode)?; let circuit_inputs = read_inputs_from_file(&args.working_directory, &args.input_witness)?; - let output_witness = execute_program_from_witness(circuit_inputs, &bytecode, None)?; + let output_witness = + execute_program_from_witness(circuit_inputs, &bytecode, &ResolverOpts::none())?; assert_eq!(output_witness.length(), 1, "ACVM CLI only supports a witness stack of size 1"); let output_witness_string = create_output_witness_string( &output_witness.peek().expect("Should have a witness stack item").witness, @@ -65,7 +66,7 @@ pub(crate) fn run(args: ExecuteCommand) -> Result { pub(crate) fn execute_program_from_witness( inputs_map: WitnessMap, bytecode: &[u8], - foreign_call_resolver_url: Option<&str>, + resolver_opts: &ResolverOpts, ) -> Result { let blackbox_solver = Bn254BlackBoxSolver::new(); let program: Program = Program::deserialize_program(bytecode) @@ -74,7 +75,7 @@ pub(crate) fn execute_program_from_witness( &program, inputs_map, &blackbox_solver, - &mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url), + &mut DefaultForeignCallExecutor::new(true, resolver_opts), ) .map_err(CliError::CircuitExecutionError) } diff --git a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs index 209439f5f92..974eb7a4a79 100644 --- a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs +++ b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs @@ -5,7 +5,7 @@ use acvm::{ }; use nargo::{ artifacts::debug::{DebugArtifact, DebugVars, StackFrame}, - ops::{DefaultForeignCallExecutor, ForeignCallExecutor}, + ops::{DefaultForeignCallExecutor, ForeignCallExecutor, ResolverOpts}, }; use noirc_errors::debug_info::{DebugFnId, DebugVarId}; use noirc_printable_type::ForeignCallError; @@ -51,7 +51,7 @@ pub struct DefaultDebugForeignCallExecutor { impl DefaultDebugForeignCallExecutor { pub fn new(show_output: bool) -> Self { Self { - executor: DefaultForeignCallExecutor::new(show_output, None), + executor: DefaultForeignCallExecutor::new(show_output, &ResolverOpts::none()), debug_vars: DebugVars::default(), } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs index 1844a3d9bf0..b37769d91a8 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs @@ -3,7 +3,7 @@ use std::future::{self, Future}; use async_lsp::{ErrorCode, ResponseError}; use nargo::{ insert_all_files_for_workspace_into_file_manager, - ops::{run_test, TestStatus}, + ops::{run_test, ResolverOpts, TestStatus}, prepare_package, }; use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; @@ -86,7 +86,7 @@ fn on_test_run_request_inner( &mut context, &test_function, false, - None, + &ResolverOpts::none(), &CompileOptions::default(), ); let result = match test_result { diff --git a/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs b/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs index 90f6659ad28..41326932001 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs @@ -94,6 +94,51 @@ impl MockedCall { } } +trait ExternalResolver: std::fmt::Debug { + fn execute(&self, call: &ForeignCallWaitInfo) -> ForeignCallResult; +} + +#[derive(Debug)] +struct JsonRpcExternalResolver { + client: Client, +} + +impl JsonRpcExternalResolver { + fn new(resolver_url: &str) -> Self { + let mut transport_builder = + Builder::new().url(resolver_url).expect("Invalid oracle resolver URL"); + + if let Some(Ok(timeout)) = + std::env::var("NARGO_FOREIGN_CALL_TIMEOUT").ok().map(|timeout| timeout.parse()) + { + let timeout_duration = std::time::Duration::from_millis(timeout); + transport_builder = transport_builder.timeout(timeout_duration); + }; + + let client = Client::with_transport(transport_builder.build()); + + JsonRpcExternalResolver { client } + } +} + +impl ExternalResolver for JsonRpcExternalResolver { + fn execute(&self, call: &ForeignCallWaitInfo) -> ForeignCallResult { + let encoded_params: Vec<_> = call.inputs.iter().map(build_json_rpc_arg).collect(); + let request = self.client.build_request(call.function.as_str(), &encoded_params); + let response = self.client.send_request(request).expect("Failed to send request"); + response.result().expect("Failed to parse response") + } +} + +#[derive(Debug)] +struct MockExternalResolver; + +impl ExternalResolver for MockExternalResolver { + fn execute(&self, _call: &ForeignCallWaitInfo) -> ForeignCallResult { + FieldElement::from(0_usize).into() + } +} + #[derive(Debug, Default)] pub struct DefaultForeignCallExecutor { /// Mocks have unique ids used to identify them in Noir, allowing to update or remove them. @@ -102,27 +147,54 @@ pub struct DefaultForeignCallExecutor { mocked_responses: Vec, /// Whether to print [`ForeignCall::Print`] output. show_output: bool, - /// JSON RPC client to resolve foreign calls - external_resolver: Option, + /// External resolver for foreign calls + external_resolver: Option>, +} + +#[derive(Debug)] +pub struct ResolverOpts { + pub resolver_url: Option, + pub mock_resolver: bool, +} + +impl ResolverOpts { + pub fn new(resolver_url: Option<&str>, mock_resolver: bool) -> Self { + Self { + resolver_url: resolver_url.map(|resolver_url| resolver_url.to_string()), + mock_resolver, + } + } + + pub fn none() -> Self { + Self { resolver_url: None, mock_resolver: false } + } + + pub fn rpc(resolver_url: &str) -> Self { + Self { resolver_url: Some(resolver_url.to_string()), mock_resolver: false } + } + + pub fn mock() -> Self { + Self { resolver_url: None, mock_resolver: true } + } } impl DefaultForeignCallExecutor { - pub fn new(show_output: bool, resolver_url: Option<&str>) -> Self { - let oracle_resolver = resolver_url.map(|resolver_url| { - let mut transport_builder = - Builder::new().url(resolver_url).expect("Invalid oracle resolver URL"); - - if let Some(Ok(timeout)) = - std::env::var("NARGO_FOREIGN_CALL_TIMEOUT").ok().map(|timeout| timeout.parse()) - { - let timeout_duration = std::time::Duration::from_millis(timeout); - transport_builder = transport_builder.timeout(timeout_duration); + pub fn new(show_output: bool, resolver: &ResolverOpts) -> Self { + let ResolverOpts { resolver_url, mock_resolver } = resolver; + let external_resolver: Option> = + match (resolver_url, mock_resolver) { + (Some(_), true) => { + panic!("Cannot have both an external resolver and a mock resolver") + } + (Some(resolver_url), _) => { + Some(Box::new(JsonRpcExternalResolver::new(resolver_url.as_str()))) + } + (None, true) => Some(Box::new(MockExternalResolver {})), + _ => None, }; - Client::with_transport(transport_builder.build()) - }); DefaultForeignCallExecutor { show_output, - external_resolver: oracle_resolver, + external_resolver, ..DefaultForeignCallExecutor::default() } } @@ -269,19 +341,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { Ok(result.into()) } - (None, Some(external_resolver)) => { - let encoded_params: Vec<_> = - foreign_call.inputs.iter().map(build_json_rpc_arg).collect(); - - let req = - external_resolver.build_request(foreign_call_name, &encoded_params); - - let response = external_resolver.send_request(req)?; - - let parsed_response: ForeignCallResult = response.result()?; - - Ok(parsed_response) - } + (None, Some(external_resolver)) => Ok(external_resolver.execute(foreign_call)), (None, None) => panic!( "No mock for foreign call {}({:?})", foreign_call_name, &foreign_call.inputs @@ -302,7 +362,9 @@ mod tests { use jsonrpc_derive::rpc; use jsonrpc_http_server::{Server, ServerBuilder}; - use crate::ops::{DefaultForeignCallExecutor, ForeignCallExecutor}; + use crate::ops::{ + foreign_calls::ResolverOpts, DefaultForeignCallExecutor, ForeignCallExecutor, + }; #[allow(unreachable_pub)] #[rpc] @@ -349,7 +411,7 @@ mod tests { fn test_oracle_resolver_echo() { let (server, url) = build_oracle_server(); - let mut executor = DefaultForeignCallExecutor::new(false, Some(&url)); + let mut executor = DefaultForeignCallExecutor::new(false, &ResolverOpts::rpc(&url)); let foreign_call = ForeignCallWaitInfo { function: "echo".to_string(), @@ -366,7 +428,7 @@ mod tests { fn test_oracle_resolver_sum() { let (server, url) = build_oracle_server(); - let mut executor = DefaultForeignCallExecutor::new(false, Some(&url)); + let mut executor = DefaultForeignCallExecutor::new(false, &ResolverOpts::rpc(&url)); let foreign_call = ForeignCallWaitInfo { function: "sum".to_string(), @@ -378,4 +440,17 @@ mod tests { server.close(); } + + #[test] + fn test_mock_resolver() { + let mut executor = DefaultForeignCallExecutor::new(false, &ResolverOpts::mock()); + + let foreign_call = ForeignCallWaitInfo { + function: "sum".to_string(), + inputs: vec![ForeignCallParam::Array(vec![1_usize.into(), 2_usize.into()])], + }; + + let result = executor.execute(&foreign_call); + assert_eq!(result.unwrap(), FieldElement::from(0_usize).into()); + } } diff --git a/noir/noir-repo/tooling/nargo/src/ops/mod.rs b/noir/noir-repo/tooling/nargo/src/ops/mod.rs index cada2f0e915..3606c4e2933 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/mod.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/mod.rs @@ -3,7 +3,9 @@ pub use self::compile::{ compile_workspace, report_errors, }; pub use self::execute::execute_program; -pub use self::foreign_calls::{DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor}; +pub use self::foreign_calls::{ + DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor, ResolverOpts, +}; pub use self::optimize::{optimize_contract, optimize_program}; pub use self::transform::{transform_contract, transform_program}; diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index 86dd8cd7cd5..094bccb913d 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -9,7 +9,7 @@ use noirc_frontend::hir::{def_map::TestFunction, Context}; use crate::{errors::try_to_diagnose_runtime_error, NargoError}; -use super::{execute_program, DefaultForeignCallExecutor}; +use super::{execute_program, DefaultForeignCallExecutor, ResolverOpts}; pub enum TestStatus { Pass, @@ -28,7 +28,7 @@ pub fn run_test( context: &mut Context, test_function: &TestFunction, show_output: bool, - foreign_call_resolver_url: Option<&str>, + resolver_opts: &ResolverOpts, config: &CompileOptions, ) -> TestStatus { let compiled_program = compile_no_check(context, config, test_function.get_id(), None, false); @@ -40,7 +40,7 @@ pub fn run_test( &compiled_program.program, WitnessMap::new(), blackbox_solver, - &mut DefaultForeignCallExecutor::new(show_output, foreign_call_resolver_url), + &mut DefaultForeignCallExecutor::new(show_output, resolver_opts), ); test_status_program_compile_pass( test_function, 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 854ad559012..f2ac266e093 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 @@ -5,7 +5,7 @@ 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::{compile_program, report_errors, DefaultForeignCallExecutor}; +use nargo::ops::{compile_program, report_errors, DefaultForeignCallExecutor, ResolverOpts}; use nargo::package::Package; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; @@ -46,6 +46,10 @@ pub(crate) struct ExecuteCommand { /// JSON RPC url to solve oracle calls #[clap(long)] oracle_resolver: Option, + + /// Use a mock oracle resolver that returns zero to every request + #[clap(long)] + mock_oracle_resolver: bool, } pub(crate) fn run( @@ -91,11 +95,14 @@ pub(crate) fn run( let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); + let resolver_opts = + ResolverOpts::new(args.oracle_resolver.as_deref(), args.mock_oracle_resolver); + let (return_value, witness_stack) = execute_program_and_decode( compiled_program, package, &args.prover_name, - args.oracle_resolver.as_deref(), + &resolver_opts, )?; println!("[{}] Circuit witness successfully solved", package.name); @@ -115,12 +122,12 @@ fn execute_program_and_decode( program: CompiledProgram, package: &Package, prover_name: &str, - foreign_call_resolver_url: Option<&str>, + resolver_opts: &ResolverOpts, ) -> Result<(Option, WitnessStack), CliError> { // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &program.abi)?; - let witness_stack = execute_program(&program, &inputs_map, foreign_call_resolver_url)?; + let witness_stack = execute_program(&program, &inputs_map, resolver_opts)?; let public_abi = program.abi.public_abi(); // Get the entry point witness for the ABI let main_witness = @@ -133,7 +140,7 @@ fn execute_program_and_decode( pub(crate) fn execute_program( compiled_program: &CompiledProgram, inputs_map: &InputMap, - foreign_call_resolver_url: Option<&str>, + resolver_opts: &ResolverOpts, ) -> Result { let blackbox_solver = Bn254BlackBoxSolver::new(); @@ -143,7 +150,7 @@ pub(crate) fn execute_program( &compiled_program.program, initial_witness, &blackbox_solver, - &mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url), + &mut DefaultForeignCallExecutor::new(true, resolver_opts), ); match solved_witness_stack_err { Ok(solved_witness_stack) => Ok(solved_witness_stack), diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/prove_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/prove_cmd.rs index b9e4bca9e69..32a2b7e1163 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/prove_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/prove_cmd.rs @@ -1,6 +1,6 @@ use clap::Args; use nargo::constants::{PROVER_INPUT_FILE, VERIFIER_INPUT_FILE}; -use nargo::ops::{compile_program, report_errors}; +use nargo::ops::{compile_program, report_errors, ResolverOpts}; use nargo::package::Package; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; @@ -48,6 +48,10 @@ pub(crate) struct ProveCommand { /// JSON RPC url to solve oracle calls #[clap(long)] oracle_resolver: Option, + + /// Use a mock oracle resolver that returns zero to every request + #[clap(long)] + mock_oracle_resolver: bool, } pub(crate) fn run( @@ -92,6 +96,9 @@ pub(crate) fn run( let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); + let resolver_opts = + ResolverOpts::new(args.oracle_resolver.as_deref(), args.mock_oracle_resolver); + prove_package( backend, &workspace, @@ -100,7 +107,7 @@ pub(crate) fn run( &args.prover_name, &args.verifier_name, args.verify, - args.oracle_resolver.as_deref(), + &resolver_opts, )?; } @@ -116,13 +123,13 @@ pub(crate) fn prove_package( prover_name: &str, verifier_name: &str, check_proof: bool, - foreign_call_resolver_url: Option<&str>, + resolver_opts: &ResolverOpts, ) -> Result<(), CliError> { // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &compiled_program.abi)?; - let witness_stack = execute_program(&compiled_program, &inputs_map, foreign_call_resolver_url)?; + let witness_stack = execute_program(&compiled_program, &inputs_map, resolver_opts)?; // Write public inputs into Verifier.toml let public_abi = compiled_program.abi.public_abi(); 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 88a804d5cf4..037400f67e4 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 @@ -5,8 +5,10 @@ use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; use fm::FileManager; use nargo::{ - insert_all_files_for_workspace_into_file_manager, ops::TestStatus, package::Package, parse_all, - prepare_package, + insert_all_files_for_workspace_into_file_manager, + ops::{ResolverOpts, TestStatus}, + package::Package, + parse_all, prepare_package, }; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{ @@ -52,6 +54,10 @@ pub(crate) struct TestCommand { /// JSON RPC url to solve oracle calls #[clap(long)] oracle_resolver: Option, + + /// Use a mock oracle resolver that returns zero to every request + #[clap(long)] + mock_oracle_resolver: bool, } pub(crate) fn run( @@ -84,6 +90,9 @@ pub(crate) fn run( None => FunctionNameMatch::Anything, }; + let resolver_opts = + ResolverOpts::new(args.oracle_resolver.as_deref(), args.mock_oracle_resolver); + let test_reports: Vec> = workspace .into_iter() .par_bridge() @@ -94,7 +103,7 @@ pub(crate) fn run( package, pattern, args.show_output, - args.oracle_resolver.as_deref(), + &resolver_opts, &args.compile_options, ) }) @@ -129,7 +138,7 @@ fn run_tests( package: &Package, fn_name: FunctionNameMatch, show_output: bool, - foreign_call_resolver_url: Option<&str>, + resolver_opts: &ResolverOpts, compile_options: &CompileOptions, ) -> Result, CliError> { let test_functions = @@ -149,7 +158,7 @@ fn run_tests( package, &test_name, show_output, - foreign_call_resolver_url, + resolver_opts, compile_options, ); @@ -167,7 +176,7 @@ fn run_test( package: &Package, fn_name: &str, show_output: bool, - foreign_call_resolver_url: Option<&str>, + resolver_opts: &nargo::ops::ResolverOpts, compile_options: &CompileOptions, ) -> TestStatus { // This is really hacky but we can't share `Context` or `S` across threads. @@ -193,7 +202,7 @@ fn run_test( &mut context, test_function, show_output, - foreign_call_resolver_url, + resolver_opts, compile_options, ) } 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 9d377cfaee9..b02ee6cf600 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs @@ -5,7 +5,7 @@ use noirc_driver::{check_crate, file_manager_with_stdlib, CompileOptions}; use noirc_frontend::hir::FunctionNameMatch; use nargo::{ - ops::{report_errors, run_test, TestStatus}, + ops::{report_errors, run_test, ResolverOpts, TestStatus}, package::{Package, PackageType}, parse_all, prepare_package, }; @@ -49,7 +49,7 @@ fn stdlib_noir_tests() { &mut context, &test_function, false, - None, + &ResolverOpts::none(), &CompileOptions::default(), );