diff --git a/aztec-up/bin/docker-compose.test.yml b/aztec-up/bin/docker-compose.test.yml index dca6d9f4435d..4a95518d32fb 100644 --- a/aztec-up/bin/docker-compose.test.yml +++ b/aztec-up/bin/docker-compose.test.yml @@ -6,6 +6,7 @@ services: HOST_WORKDIR: "${PWD}" # Loaded from the user shell to show log files absolute path in host volumes: - ./log:/usr/src/yarn-project/aztec/log:rw + - ${HOME}:${HOME} command: start --txe aztec-nargo: diff --git a/docs/docs/guides/smart_contracts/testing_contracts/index.md b/docs/docs/guides/smart_contracts/testing_contracts/index.md index acc6bf3baaed..187035425eff 100644 --- a/docs/docs/guides/smart_contracts/testing_contracts/index.md +++ b/docs/docs/guides/smart_contracts/testing_contracts/index.md @@ -70,7 +70,18 @@ Writing tests in contracts requires importing additional modules from Aztec.nr. ### Deploying contracts ```rust -let deployer = env.deploy("path_to_contract_ts_interface"); + +// Deploy the contract we're currently on + +let deployer = env.deploy_self("ContractName"); + +// Deploy a standalone contract in a path relative to the current one (always from the location of Nargo.toml) + +let deployer = env.deploy("path_to_contract_root_folder_where_nargo_toml_is", "ContractName"); + +// Deploy a contract in a workspace + +let deployer = env.deploy("path_to_workspace_root_folder_where_main_nargo_toml_is@package_name", "ContractName"); // Now one of these can be called, depending on the contract and their possible initialization options. // Remember a contract can only be initialized once. @@ -89,7 +100,7 @@ let my_contract_instance = deployer.without_initializer(); ``` :::warning -At the moment, TXE uses the generated contract TypeScript interfaces to perform deployments, and they must be provided as either an absolute path, a relative path to TXE's location or a module in an npm direct dependency such as `@aztec/noir-contracts.js`. It is not always necessary to deploy a contract in order to test it, but sometimes it's inevitable (when testing functions that depend on the contract being initialized, or contracts that call others for example) **It is important to keep them up to date**, as TXE cannot recompile them on changes. This will be improved in the future. +It is not always necessary to deploy a contract in order to test it, but sometimes it's inevitable (when testing functions that depend on the contract being initialized, or contracts that call others for example) **It is important to keep them up to date**, as TXE cannot recompile them on changes. Think of it as regenerating the bytecode and ABI so it becomes accessible externally. ::: ### Calling functions diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index e72821881105..023f52c13c38 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -7,6 +7,29 @@ keywords: [sandbox, aztec, notes, migration, updating, upgrading] Aztec is in full-speed development. Literally every version breaks compatibility with the previous ones. This page attempts to target errors and difficulties you might encounter when upgrading, and how to resolve them. ## 0.xx.0 + +# [Aztec sandbox] TXE deployment changes + +The way simulated deployments are done in TXE tests has changed to avoid relying on TS interfaces. It is now possible to do it by directly pointing to a Noir standalone contract or workspace: + +```diff +-let deployer = env.deploy("path_to_contract_ts_interface"); ++let deployer = env.deploy("path_to_contract_root_folder_where_nargo_toml_is", "ContractName"); +``` + +Extended syntax for more use cases: + +```rust +// The contract we're testing +env.deploy_self("ContractName"); // We have to provide ContractName since nargo it's ready to support multi-contract files + +// A contract in a workspace +env.deploy("../path/to/workspace@package_name", "ContractName"); // This format allows locating the artifact in the root workspace target folder, regardless of internal code organization +``` + +The deploy function returns a `Deployer`, which requires performing a subsequent call to `without_initializer()`, `with_private_initializer()` or `with_public_initializer()` just like before in order to **actually** deploy the contract. + +## 0.46.3 ### [Aztec sandbox] Command refactor and unification + `aztec test` Sandbox commands have been cleaned up and simplified. Doing `aztec-up` now gets you the following top-level commands: diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 69907c7a74ed..e8a03ea7e12a 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -26,13 +26,14 @@ unconstrained pub fn get_public_context_inputs() -> PublicContextInputs { oracle_get_public_context_inputs() } -unconstrained pub fn deploy( +unconstrained pub fn deploy( path: str, - initializer: str, + name: str, + initializer: str

, args: [Field], public_keys_hash: Field ) -> ContractInstance { - let instance_fields = oracle_deploy(path, initializer, args, public_keys_hash); + let instance_fields = oracle_deploy(path, name, initializer, args, public_keys_hash); ContractInstance::deserialize(instance_fields) } @@ -44,8 +45,8 @@ unconstrained pub fn create_account() -> TestAccount { oracle_create_account() } -unconstrained pub fn add_account(secret: Field, partial_address: PartialAddress) -> TestAccount { - oracle_add_account(secret, partial_address) +unconstrained pub fn add_account(secret: Field) -> TestAccount { + oracle_add_account(secret) } unconstrained pub fn derive_keys(secret: Field) -> PublicKeys { @@ -122,9 +123,10 @@ unconstrained fn oracle_get_private_context_inputs(historical_block_number: u32) unconstrained fn oracle_get_public_context_inputs() -> PublicContextInputs {} #[oracle(deploy)] -unconstrained fn oracle_deploy( +unconstrained fn oracle_deploy( path: str, - initializer: str, + name: str, + initializer: str

, args: [Field], public_keys_hash: Field ) -> [Field; CONTRACT_INSTANCE_LENGTH] {} @@ -140,7 +142,7 @@ unconstrained fn direct_storage_write_oracle( unconstrained fn oracle_create_account() -> TestAccount {} #[oracle(addAccount)] -unconstrained fn oracle_add_account(secret: Field, partial_address: PartialAddress) -> TestAccount {} +unconstrained fn oracle_add_account(secret: Field) -> TestAccount {} #[oracle(deriveKeys)] unconstrained fn oracle_derive_keys(secret: Field) -> PublicKeys {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 55caa1432449..f5c3436c9fe0 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -85,27 +85,11 @@ impl TestEnvironment { } fn create_account_contract(&mut self, secret: Field) -> AztecAddress { - let public_keys = cheatcodes::derive_keys(secret); - let args = [public_keys.ivpk_m.x, public_keys.ivpk_m.y]; - let instance = cheatcodes::deploy( - "@aztec/noir-contracts.js/SchnorrAccount", - "constructor", - args.as_slice(), - public_keys.hash().to_field() - ); + let test_account = cheatcodes::add_account(secret); + let address = test_account.address; cheatcodes::advance_blocks_by(1); - let test_account = cheatcodes::add_account( - secret, - PartialAddress::compute( - instance.contract_class_id, - instance.salt, - instance.initialization_hash, - instance.deployer - ) - ); - let keys = test_account.keys; - let address = instance.to_address(); + let keys = test_account.keys; keys::store_master_key(NULLIFIER_INDEX, address, keys.npk_m); keys::store_master_key(INCOMING_INDEX, address, keys.ivpk_m); @@ -115,14 +99,18 @@ impl TestEnvironment { let selector = FunctionSelector::from_signature("constructor(Field,Field)"); let mut context = self.private_at(get_block_number()); - + let args = [test_account.keys.ivpk_m.x, test_account.keys.ivpk_m.y]; let _ = context.call_private_function(address, selector, args); address } - fn deploy(_self: Self, path: str) -> Deployer { - Deployer { path, public_keys_hash: 0 } + fn deploy(self, path: str, name: str) -> Deployer { + Deployer { path, name, public_keys_hash: 0 } + } + + fn deploy_self(self, name: str) -> Deployer<0, M> { + Deployer { path: "", name, public_keys_hash: 0 } } fn call_private( diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr index 47ca6ab1fed8..a6a7199bab19 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr @@ -29,18 +29,20 @@ pub fn apply_side_effects_private(contract_address: AztecAddress, public_inputs: cheatcodes::add_note_hashes(contract_address, note_hashes); } -struct Deployer { - path: str, - public_keys_hash: Field - } +struct Deployer { + path: str, + name: str, + public_keys_hash: Field +} -impl Deployer { - pub fn with_private_initializer( +impl Deployer { + pub fn with_private_initializer( self, call_interface: C - ) -> ContractInstance where C: CallInterface { + ) -> ContractInstance where C: CallInterface { let instance = cheatcodes::deploy( self.path, + self.name, call_interface.get_name(), call_interface.get_args(), self.public_keys_hash @@ -64,12 +66,13 @@ impl Deployer { instance } - pub fn with_public_initializer( + pub fn with_public_initializer( self, call_interface: C - ) -> ContractInstance where C: CallInterface { + ) -> ContractInstance where C: CallInterface { let instance = cheatcodes::deploy( self.path, + self.name, call_interface.get_name(), call_interface.get_args(), self.public_keys_hash @@ -94,7 +97,7 @@ impl Deployer { } pub fn without_initializer(self) -> ContractInstance { - cheatcodes::deploy(self.path, "", &[], self.public_keys_hash) + cheatcodes::deploy(self.path, self.name, "", &[], self.public_keys_hash) } } diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 011ebdd5d087..97e5e40dec34 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -57,7 +57,7 @@ contract Counter { // Deploy contract and initialize let initializer = Counter::interface().initialize(initial_value as u64, owner, outgoing_viewer); - let counter_contract = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); + let counter_contract = env.deploy_self("Counter").with_private_initializer(initializer); let contract_address = counter_contract.to_address(); // docs:start:txe_test_read_notes diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index 2747bfed0043..fb6181311417 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -257,7 +257,7 @@ contract Parent { let owner = env.create_account(); // Deploy child contract - let child_contract = env.deploy("@aztec/noir-contracts.js/Child").without_initializer(); + let child_contract = env.deploy("./@child_contract", "Child").without_initializer(); let child_contract_address = child_contract.to_address(); cheatcodes::advance_blocks_by(1); diff --git a/noir-projects/noir-contracts/contracts/private_token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/private_token_contract/src/test/utils.nr index 0bc7707c1c83..1825f0921d9a 100644 --- a/noir-projects/noir-contracts/contracts/private_token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/private_token_contract/src/test/utils.nr @@ -16,7 +16,7 @@ pub fn setup(with_account_contracts: bool) -> (&mut TestEnvironment, AztecAddres let owner = env.create_account_contract(1); let recipient = env.create_account_contract(2); // Deploy canonical auth registry - let _auth_registry = env.deploy("@aztec/noir-contracts.js/AuthRegistry").without_initializer(); + let _auth_registry = env.deploy("./@auth_registry_contract", "AuthRegistry").without_initializer(); (owner, recipient) } else { let owner = env.create_account(); @@ -34,7 +34,7 @@ pub fn setup(with_account_contracts: bool) -> (&mut TestEnvironment, AztecAddres "TT00000000000000000000000000000", 18 ); - let token_contract = env.deploy("@aztec/noir-contracts.js/PrivateToken").with_public_initializer(initializer_call_interface); + let token_contract = env.deploy_self("PrivateToken").with_public_initializer(initializer_call_interface); let token_contract_address = token_contract.to_address(); env.advance_block_by(6); (&mut env, token_contract_address, owner, recipient) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr index 0a106b6ab4ce..abbac9623032 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr @@ -15,7 +15,7 @@ pub fn setup(with_account_contracts: bool) -> (&mut TestEnvironment, AztecAddres let owner = env.create_account_contract(1); let recipient = env.create_account_contract(2); // Deploy canonical auth registry - let _auth_registry = env.deploy("@aztec/noir-contracts.js/AuthRegistry").without_initializer(); + let _auth_registry = env.deploy("./@auth_registry_contract", "AuthRegistry").without_initializer(); (owner, recipient) } else { let owner = env.create_account(); @@ -33,7 +33,7 @@ pub fn setup(with_account_contracts: bool) -> (&mut TestEnvironment, AztecAddres "TT00000000000000000000000000000", 18 ); - let token_contract = env.deploy("@aztec/noir-contracts.js/Token").with_public_initializer(initializer_call_interface); + let token_contract = env.deploy_self("Token").with_public_initializer(initializer_call_interface); let token_contract_address = token_contract.to_address(); env.advance_block_by(1); (&mut env, token_contract_address, owner, recipient) 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 ac3af03684f7..c453936568ce 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 @@ -39,7 +39,7 @@ 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)?; 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, @@ -66,7 +66,6 @@ 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>, ) -> Result, CliError> { let program: Program = Program::deserialize_program(bytecode) .map_err(|_| CliError::CircuitDeserializationError())?; @@ -74,7 +73,7 @@ pub(crate) fn execute_program_from_witness( &program, inputs_map, &Bn254BlackBoxSolver, - &mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url), + &mut DefaultForeignCallExecutor::new(true, None, None, None), ) .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 62443d4065c6..6a773a4b3484 100644 --- a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs +++ b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs @@ -49,7 +49,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, None, None, None), debug_vars: DebugVars::default(), } } diff --git a/noir/noir-repo/tooling/fuzzer/src/lib.rs b/noir/noir-repo/tooling/fuzzer/src/lib.rs index 28d7353f35a2..35a614663dc7 100644 --- a/noir/noir-repo/tooling/fuzzer/src/lib.rs +++ b/noir/noir-repo/tooling/fuzzer/src/lib.rs @@ -80,7 +80,7 @@ impl FuzzedExecutor { &self.program.bytecode, initial_witness, &StubbedBlackBoxSolver, - &mut DefaultForeignCallExecutor::::new(false, None), + &mut DefaultForeignCallExecutor::::new(false, None, None, None), ); // TODO: Add handling for `vm.assume` equivalent 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 b4b9b62d6b68..532c684020ad 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs @@ -86,6 +86,8 @@ fn on_test_run_request_inner( &test_function, false, None, + Some(workspace.root_dir.clone()), + Some(package.name.to_string()), &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 987c7dd9cb9d..30785949a46e 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use acvm::{ acir::brillig::{ForeignCallParam, ForeignCallResult}, pwg::ForeignCallWaitInfo, @@ -112,6 +114,10 @@ pub struct DefaultForeignCallExecutor { show_output: bool, /// JSON RPC client to resolve foreign calls external_resolver: Option, + /// Root path to the program or workspace in execution. + root_path: Option, + /// Name of the package in execution + package_name: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -126,10 +132,22 @@ struct ResolveForeignCallRequest { #[serde(flatten)] /// The foreign call which the external RPC server is to provide a response for. function_call: ForeignCallWaitInfo, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Root path to the program or workspace in execution. + root_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + /// Name of the package in execution + package_name: Option, } impl DefaultForeignCallExecutor { - pub fn new(show_output: bool, resolver_url: Option<&str>) -> Self { + pub fn new( + show_output: bool, + resolver_url: Option<&str>, + root_path: Option, + package_name: Option, + ) -> Self { let oracle_resolver = resolver_url.map(|resolver_url| { let mut transport_builder = Builder::new().url(resolver_url).expect("Invalid oracle resolver URL"); @@ -148,6 +166,8 @@ impl DefaultForeignCallExecutor { id: rand::thread_rng().gen(), mocked_responses: Vec::new(), last_mock_id: 0, + root_path, + package_name, } } } @@ -302,6 +322,11 @@ impl Deserialize<'a>> ForeignCallExecutor let encoded_params = vec![build_json_rpc_arg(ResolveForeignCallRequest { session_id: self.id, function_call: foreign_call.clone(), + root_path: self + .root_path + .clone() + .map(|path| path.to_str().unwrap().to_string()), + package_name: self.package_name.clone(), })]; let req = @@ -402,7 +427,8 @@ 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, Some(&url), None, None); let foreign_call = ForeignCallWaitInfo { function: "echo".to_string(), @@ -419,7 +445,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, Some(&url), None, None); let foreign_call = ForeignCallWaitInfo { function: "sum".to_string(), @@ -436,7 +462,8 @@ mod tests { fn foreign_call_executor_id_is_persistent() { let (server, url) = build_oracle_server(); - let mut executor = DefaultForeignCallExecutor::::new(false, Some(&url)); + let mut executor = + DefaultForeignCallExecutor::::new(false, Some(&url), None, None); let foreign_call = ForeignCallWaitInfo { function: "id".to_string(), inputs: Vec::new() }; @@ -451,8 +478,10 @@ mod tests { fn oracle_resolver_rpc_can_distinguish_executors() { let (server, url) = build_oracle_server(); - let mut executor_1 = DefaultForeignCallExecutor::::new(false, Some(&url)); - let mut executor_2 = DefaultForeignCallExecutor::::new(false, Some(&url)); + let mut executor_1 = + DefaultForeignCallExecutor::::new(false, Some(&url), None, None); + let mut executor_2 = + DefaultForeignCallExecutor::::new(false, Some(&url), None, None); let foreign_call = ForeignCallWaitInfo { function: "id".to_string(), inputs: Vec::new() }; diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index 18c6f2530b96..b2af5d9780ba 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use acvm::{ acir::native_types::{WitnessMap, WitnessStack}, BlackBoxFunctionSolver, FieldElement, @@ -23,12 +25,15 @@ impl TestStatus { } } +#[allow(clippy::too_many_arguments)] pub fn run_test>( blackbox_solver: &B, context: &mut Context, test_function: &TestFunction, show_output: bool, foreign_call_resolver_url: Option<&str>, + root_path: Option, + package_name: Option, config: &CompileOptions, ) -> TestStatus { let compiled_program = compile_no_check(context, config, test_function.get_id(), None, false); @@ -40,7 +45,12 @@ 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, + foreign_call_resolver_url, + root_path, + package_name, + ), ); 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 cf9dc1141a1a..0f7773d7ab70 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 @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use acvm::acir::native_types::WitnessStack; use acvm::FieldElement; use bn254_blackbox_solver::Bn254BlackBoxSolver; @@ -65,13 +67,16 @@ pub(crate) fn run(args: ExecuteCommand, config: NargoConfig) -> Result<(), CliEr let binary_packages = workspace.into_iter().filter(|package| package.is_binary()); for package in binary_packages { let program_artifact_path = workspace.package_build_path(package); - let program: CompiledProgram = read_program_from_file(program_artifact_path)?.into(); + let program: CompiledProgram = + read_program_from_file(program_artifact_path.clone())?.into(); let (return_value, witness_stack) = execute_program_and_decode( program, package, &args.prover_name, args.oracle_resolver.as_deref(), + Some(workspace.root_dir.clone()), + Some(package.name.to_string()), )?; println!("[{}] Circuit witness successfully solved", package.name); @@ -92,11 +97,14 @@ fn execute_program_and_decode( package: &Package, prover_name: &str, foreign_call_resolver_url: Option<&str>, + root_path: Option, + package_name: Option, ) -> 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, foreign_call_resolver_url, root_path, package_name)?; // Get the entry point witness for the ABI let main_witness = &witness_stack.peek().expect("Should have at least one witness on the stack").witness; @@ -109,6 +117,8 @@ pub(crate) fn execute_program( compiled_program: &CompiledProgram, inputs_map: &InputMap, foreign_call_resolver_url: Option<&str>, + root_path: Option, + package_name: Option, ) -> Result, CliError> { let initial_witness = compiled_program.abi.encode(inputs_map, None)?; @@ -116,7 +126,12 @@ pub(crate) fn execute_program( &compiled_program.program, initial_witness, &Bn254BlackBoxSolver, - &mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url), + &mut DefaultForeignCallExecutor::new( + true, + foreign_call_resolver_url, + root_path, + package_name, + ), ); match solved_witness_stack_err { Ok(solved_witness_stack) => Ok(solved_witness_stack), 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 27c66c956d90..5afa456d4830 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 @@ -1,4 +1,4 @@ -use std::io::Write; +use std::{io::Write, path::PathBuf}; use acvm::{BlackBoxFunctionSolver, FieldElement}; use bn254_blackbox_solver::Bn254BlackBoxSolver; @@ -92,6 +92,8 @@ pub(crate) fn run(args: TestCommand, config: NargoConfig) -> Result<(), CliError pattern, args.show_output, args.oracle_resolver.as_deref(), + Some(workspace.root_dir.clone()), + Some(package.name.to_string()), &args.compile_options, ) }) @@ -120,6 +122,7 @@ pub(crate) fn run(args: TestCommand, config: NargoConfig) -> Result<(), CliError } } +#[allow(clippy::too_many_arguments)] fn run_tests + Default>( file_manager: &FileManager, parsed_files: &ParsedFiles, @@ -127,6 +130,8 @@ fn run_tests + Default>( fn_name: FunctionNameMatch, show_output: bool, foreign_call_resolver_url: Option<&str>, + root_path: Option, + package_name: Option, compile_options: &CompileOptions, ) -> Result, CliError> { let test_functions = @@ -147,6 +152,8 @@ fn run_tests + Default>( &test_name, show_output, foreign_call_resolver_url, + root_path.clone(), + package_name.clone(), compile_options, ); @@ -158,6 +165,7 @@ fn run_tests + Default>( Ok(test_report) } +#[allow(clippy::too_many_arguments)] fn run_test + Default>( file_manager: &FileManager, parsed_files: &ParsedFiles, @@ -165,6 +173,8 @@ fn run_test + Default>( fn_name: &str, show_output: bool, foreign_call_resolver_url: Option<&str>, + root_path: Option, + package_name: Option, compile_options: &CompileOptions, ) -> TestStatus { // This is really hacky but we can't share `Context` or `S` across threads. @@ -201,6 +211,8 @@ fn run_test + Default>( test_function, show_output, foreign_call_resolver_url, + root_path, + package_name, compile_options, ) } else { 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 cc44b269c6a6..f73d5ce781a3 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-tests.rs @@ -61,6 +61,8 @@ fn run_stdlib_tests() { &test_function, false, None, + None, + None, &CompileOptions::default(), ) } else { diff --git a/yarn-project/aztec/src/bin/index.ts b/yarn-project/aztec/src/bin/index.ts index 5a2835bbfa4c..26a96b7f89e2 100644 --- a/yarn-project/aztec/src/bin/index.ts +++ b/yarn-project/aztec/src/bin/index.ts @@ -1,5 +1,5 @@ import { fileURLToPath } from '@aztec/aztec.js'; -import { injectCommands as injectBuilderCommands } from '@aztec/cli/builder'; +import { injectCommands as injectBuilderCommands } from '@aztec/builder'; import { injectCommands as injectContractCommands } from '@aztec/cli/contracts'; import { injectCommands as injectInfrastructureCommands } from '@aztec/cli/infrastructure'; import { injectCommands as injectL1Commands } from '@aztec/cli/l1'; @@ -23,7 +23,7 @@ async function main() { let program = new Command('aztec'); program.description('Aztec command line interface').version(cliVersion); program = injectAztecCommands(program, userLog, debugLogger); - program = injectBuilderCommands(program, userLog); + program = injectBuilderCommands(program); program = injectContractCommands(program, userLog, debugLogger); program = injectInfrastructureCommands(program, userLog, debugLogger); program = injectL1Commands(program, userLog, debugLogger); diff --git a/yarn-project/builder/Dockerfile b/yarn-project/builder/Dockerfile index 086573488ef6..daf29bc9b04c 100644 --- a/yarn-project/builder/Dockerfile +++ b/yarn-project/builder/Dockerfile @@ -1,5 +1,5 @@ FROM aztecprotocol/yarn-project AS yarn-project -ENTRYPOINT ["node", "--no-warnings", "/usr/src/yarn-project/builder/dest/cli.js"] +ENTRYPOINT ["node", "--no-warnings", "/usr/src/yarn-project/builder/dest/bin/cli.js"] # The version has been updated in yarn-project. # Adding COMMIT_TAG here to rebuild versioned image. diff --git a/yarn-project/builder/aztec-builder-dest b/yarn-project/builder/aztec-builder-dest index c5b74ae02dfa..3a0f43e3061e 100755 --- a/yarn-project/builder/aztec-builder-dest +++ b/yarn-project/builder/aztec-builder-dest @@ -1,3 +1,3 @@ #!/bin/sh SCRIPT_PATH=$(dirname $(realpath $0)) -node --no-warnings $SCRIPT_PATH/dest/cli.js $@ +node --no-warnings $SCRIPT_PATH/dest/bin/cli.js $@ diff --git a/yarn-project/builder/package.json b/yarn-project/builder/package.json index a94c6252f895..acde8d5092b1 100644 --- a/yarn-project/builder/package.json +++ b/yarn-project/builder/package.json @@ -4,7 +4,7 @@ "type": "module", "exports": { ".": "./dest/index.js", - "./cli": "./dest/cli/index.js" + "./cli": "./dest/bin/cli.js" }, "typedocOptions": { "entryPoints": [ @@ -14,7 +14,7 @@ "tsconfig": "./tsconfig.json" }, "bin": { - "aztec-builder": "dest/cli.js" + "aztec-builder": "dest/bin/cli.js" }, "scripts": { "build": "yarn clean && tsc -b", @@ -65,8 +65,8 @@ ] }, "dependencies": { - "@aztec/cli": "workspace:^", "@aztec/foundation": "workspace:^", + "@aztec/types": "workspace:^", "commander": "^12.1.0" }, "devDependencies": { diff --git a/yarn-project/builder/src/cli.ts b/yarn-project/builder/src/bin/cli.ts similarity index 83% rename from yarn-project/builder/src/cli.ts rename to yarn-project/builder/src/bin/cli.ts index 0bbe358b53fd..2e87e6dcec87 100644 --- a/yarn-project/builder/src/cli.ts +++ b/yarn-project/builder/src/bin/cli.ts @@ -1,15 +1,16 @@ #!/usr/bin/env node -import { injectCommands as injectBuilderCommands } from '@aztec/cli/builder'; import { createConsoleLogger } from '@aztec/foundation/log'; import { Command } from 'commander'; +import { injectCommands as injectBuilderCommands } from '../index.js'; + const log = createConsoleLogger('aztec:builder'); const main = async () => { const program = new Command('aztec-builder'); - injectBuilderCommands(program, log); + injectBuilderCommands(program); await program.parseAsync(process.argv); // I force exit here because spawnSync in npm.ts just blocks the process from exiting. Spent a bit of time debugging // it without success and I think it doesn't make sense to invest more time in this. diff --git a/yarn-project/builder/src/contract-interface-gen/codegen.ts b/yarn-project/builder/src/contract-interface-gen/codegen.ts new file mode 100644 index 000000000000..dea5cb8417a0 --- /dev/null +++ b/yarn-project/builder/src/contract-interface-gen/codegen.ts @@ -0,0 +1,106 @@ +/* eslint-disable no-console */ +import { loadContractArtifact } from '@aztec/types/abi'; + +import crypto from 'crypto'; +import { access, mkdir, readFile, readdir, stat, writeFile } from 'fs/promises'; +import path from 'path'; + +import { generateTypescriptContractInterface } from './typescript.js'; + +const cacheFilePath = './codegenCache.json'; +let cache: Record = {}; + +/** Generate code options */ +export type GenerateCodeOptions = { force?: boolean }; + +/** + * Generates Noir interface or Typescript interface for a folder or single file from a Noir compilation artifact. + */ +export async function generateCode(outputPath: string, fileOrDirPath: string, opts: GenerateCodeOptions = {}) { + await readCache(); + const results = []; + const stats = await stat(fileOrDirPath); + + if (stats.isDirectory()) { + const files = (await readdir(fileOrDirPath, { recursive: true, encoding: 'utf-8' })).filter( + file => file.endsWith('.json') && !file.startsWith('debug_'), + ); + for (const file of files) { + const fullPath = path.join(fileOrDirPath, file); + results.push(await generateFromNoirAbi(outputPath, fullPath, opts)); + } + } else if (stats.isFile()) { + results.push(await generateFromNoirAbi(outputPath, fileOrDirPath, opts)); + } + await writeCache(); + return results; +} + +/** + * Generates Noir interface or Typescript interface for a single file Noir compilation artifact. + */ +async function generateFromNoirAbi(outputPath: string, noirAbiPath: string, opts: GenerateCodeOptions = {}) { + const fileName = path.basename(noirAbiPath); + const currentHash = await generateFileHash(noirAbiPath); + const cachedInstance = isCacheValid(fileName, currentHash); + if (cachedInstance && !opts.force) { + console.log(`${fileName} has not changed. Skipping generation.`); + return `${outputPath}/${cachedInstance.contractName}.ts`; + } + + const file = await readFile(noirAbiPath, 'utf8'); + const contract = JSON.parse(file); + const aztecAbi = loadContractArtifact(contract); + + await mkdir(outputPath, { recursive: true }); + + let relativeArtifactPath = path.relative(outputPath, noirAbiPath); + if (relativeArtifactPath === path.basename(noirAbiPath)) { + // Prepend ./ for local import if the folder is the same + relativeArtifactPath = `./${relativeArtifactPath}`; + } + + const tsWrapper = generateTypescriptContractInterface(aztecAbi, relativeArtifactPath); + const outputFilePath = `${outputPath}/${aztecAbi.name}.ts`; + + await writeFile(outputFilePath, tsWrapper); + + updateCache(fileName, aztecAbi.name, currentHash); + return outputFilePath; +} + +async function generateFileHash(filePath: string) { + const fileBuffer = await readFile(filePath); + const hashSum = crypto.createHash('sha256'); + hashSum.update(fileBuffer); + const hex = hashSum.digest('hex'); + return hex; +} + +async function readCache() { + if (await exists(cacheFilePath)) { + const cacheRaw = await readFile(cacheFilePath, 'utf8'); + cache = JSON.parse(cacheRaw); + } +} + +async function writeCache() { + await writeFile(cacheFilePath, JSON.stringify(cache, null, 2), 'utf8'); +} + +function isCacheValid(contractName: string, currentHash: string) { + return cache[contractName]?.hash === currentHash && cache[contractName]; +} + +function updateCache(fileName: string, contractName: string, hash: string): void { + cache[fileName] = { contractName, hash }; +} + +async function exists(filePath: string) { + try { + await access(filePath); + return true; + } catch { + return false; + } +} diff --git a/yarn-project/cli/src/cmds/builder/contract-interface-gen/typescript.ts b/yarn-project/builder/src/contract-interface-gen/typescript.ts similarity index 100% rename from yarn-project/cli/src/cmds/builder/contract-interface-gen/typescript.ts rename to yarn-project/builder/src/contract-interface-gen/typescript.ts diff --git a/yarn-project/builder/src/index.ts b/yarn-project/builder/src/index.ts new file mode 100644 index 000000000000..77df374c353e --- /dev/null +++ b/yarn-project/builder/src/index.ts @@ -0,0 +1,16 @@ +import { type Command } from 'commander'; +import { dirname } from 'path'; + +export function injectCommands(program: Command) { + program + .command('codegen') + .argument('', 'Path to the Noir ABI or project dir.') + .option('-o, --outdir ', 'Output folder for the generated code.') + .option('--force', 'Force code generation even when the contract has not changed.') + .description('Validates and generates an Aztec Contract ABI from Noir ABI.') + .action(async (noirAbiPath: string, { outdir, force }) => { + const { generateCode } = await import('./contract-interface-gen/codegen.js'); + await generateCode(outdir || dirname(noirAbiPath), noirAbiPath, { force }); + }); + return program; +} diff --git a/yarn-project/builder/tsconfig.json b/yarn-project/builder/tsconfig.json index 471eb519c823..3435c3b70e5f 100644 --- a/yarn-project/builder/tsconfig.json +++ b/yarn-project/builder/tsconfig.json @@ -7,10 +7,10 @@ }, "references": [ { - "path": "../cli" + "path": "../foundation" }, { - "path": "../foundation" + "path": "../types" } ], "include": ["src", "src/*.json"] diff --git a/yarn-project/cli/package.json b/yarn-project/cli/package.json index cb1ada4c7f08..d60d222ed5e4 100644 --- a/yarn-project/cli/package.json +++ b/yarn-project/cli/package.json @@ -3,7 +3,6 @@ "version": "0.32.0", "type": "module", "exports": { - "./builder": "./dest/cmds/builder/index.js", "./contracts": "./dest/cmds/contracts/index.js", "./infrastructure": "./dest/cmds/infrastructure/index.js", "./l1": "./dest/cmds/l1/index.js", diff --git a/yarn-project/cli/src/cmds/builder/codegen.ts b/yarn-project/cli/src/cmds/builder/codegen.ts deleted file mode 100644 index d721b5da63ef..000000000000 --- a/yarn-project/cli/src/cmds/builder/codegen.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type GenerateCodeOptions, generateCode } from './contract-interface-gen/codegen.js'; - -/** - * Generates Noir interface or Typescript interface for a folder or single file from a Noir compilation artifact. - */ -export function codegen(outputPath: string, fileOrDirPath: string, opts: GenerateCodeOptions = {}) { - generateCode(outputPath, fileOrDirPath, opts); -} diff --git a/yarn-project/cli/src/cmds/builder/contract-interface-gen/codegen.ts b/yarn-project/cli/src/cmds/builder/contract-interface-gen/codegen.ts deleted file mode 100644 index 4f40886f42af..000000000000 --- a/yarn-project/cli/src/cmds/builder/contract-interface-gen/codegen.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-disable no-console */ -import { loadContractArtifact } from '@aztec/types/abi'; - -import crypto from 'crypto'; -import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs'; -import path from 'path'; - -import { generateTypescriptContractInterface } from './typescript.js'; - -const cacheFilePath = './codegenCache.json'; -let cache: Record = {}; - -/** Generate code options */ -export type GenerateCodeOptions = { force?: boolean }; - -/** - * Generates Noir interface or Typescript interface for a folder or single file from a Noir compilation artifact. - */ -export function generateCode(outputPath: string, fileOrDirPath: string, opts: GenerateCodeOptions = {}) { - readCache(); - const stats = statSync(fileOrDirPath); - - if (stats.isDirectory()) { - const files = readdirSync(fileOrDirPath, { recursive: true, encoding: 'utf-8' }).filter( - file => file.endsWith('.json') && !file.startsWith('debug_'), - ); - for (const file of files) { - const fullPath = path.join(fileOrDirPath, file); - generateFromNoirAbi(outputPath, fullPath, opts); - } - } else if (stats.isFile()) { - generateFromNoirAbi(outputPath, fileOrDirPath, opts); - } - writeCache(); -} - -/** - * Generates Noir interface or Typescript interface for a single file Noir compilation artifact. - */ -function generateFromNoirAbi(outputPath: string, noirAbiPath: string, opts: GenerateCodeOptions = {}) { - const contractName = path.basename(noirAbiPath); - const currentHash = generateFileHash(noirAbiPath); - - if (isCacheValid(contractName, currentHash) && !opts.force) { - console.log(`${contractName} has not changed. Skipping generation.`); - return; - } - - const contract = JSON.parse(readFileSync(noirAbiPath, 'utf8')); - const aztecAbi = loadContractArtifact(contract); - - mkdirSync(outputPath, { recursive: true }); - - let relativeArtifactPath = path.relative(outputPath, noirAbiPath); - if (relativeArtifactPath === path.basename(noirAbiPath)) { - // Prepend ./ for local import if the folder is the same - relativeArtifactPath = `./${relativeArtifactPath}`; - } - - const tsWrapper = generateTypescriptContractInterface(aztecAbi, relativeArtifactPath); - writeFileSync(`${outputPath}/${aztecAbi.name}.ts`, tsWrapper); - - updateCache(contractName, currentHash); -} - -function generateFileHash(filePath: string) { - const fileBuffer = readFileSync(filePath); - const hashSum = crypto.createHash('sha256'); - hashSum.update(fileBuffer); - const hex = hashSum.digest('hex'); - return hex; -} - -function readCache(): void { - if (existsSync(cacheFilePath)) { - const cacheRaw = readFileSync(cacheFilePath, 'utf8'); - cache = JSON.parse(cacheRaw); - } -} - -function writeCache(): void { - writeFileSync(cacheFilePath, JSON.stringify(cache, null, 2), 'utf8'); -} - -function isCacheValid(contractName: string, currentHash: string): boolean { - return cache[contractName] === currentHash; -} - -function updateCache(contractName: string, hash: string): void { - cache[contractName] = hash; -} diff --git a/yarn-project/cli/src/cmds/builder/index.ts b/yarn-project/cli/src/cmds/builder/index.ts deleted file mode 100644 index 971a63a22a5f..000000000000 --- a/yarn-project/cli/src/cmds/builder/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { type LogFn } from '@aztec/foundation/log'; - -import { type Command } from 'commander'; -import { dirname } from 'path'; - -export function injectCommands(program: Command, log: LogFn) { - program - .command('codegen') - .argument('', 'Path to the Noir ABI or project dir.') - .option('-o, --outdir ', 'Output folder for the generated code.') - .option('--force', 'Force code generation even when the contract has not changed.') - .description('Validates and generates an Aztec Contract ABI from Noir ABI.') - .action(async (noirAbiPath: string, { outdir, force }) => { - const { codegen } = await import('./codegen.js'); - codegen(outdir || dirname(noirAbiPath), noirAbiPath, { force }); - }); - - program - .command('update') - .description('Updates Nodejs and Noir dependencies') - .argument('[projectPath]', 'Path to the project directory', process.cwd()) - .option('--contract [paths...]', 'Paths to contracts to update dependencies', []) - .option('--aztec-version ', 'The version to update Aztec packages to. Defaults to latest', 'latest') - .action(async (projectPath: string, options) => { - const { updateProject } = await import('./update.js'); - const { contract, aztecVersion } = options; - await updateProject(projectPath, contract, aztecVersion, log); - }); - return program; -} diff --git a/yarn-project/cli/src/cmds/utils/index.ts b/yarn-project/cli/src/cmds/utils/index.ts index 6ccc824f8459..d5f2801b9613 100644 --- a/yarn-project/cli/src/cmds/utils/index.ts +++ b/yarn-project/cli/src/cmds/utils/index.ts @@ -45,5 +45,17 @@ export function injectCommands(program: Command, log: LogFn) { computeSelector(functionSignature, log); }); + program + .command('update') + .description('Updates Nodejs and Noir dependencies') + .argument('[projectPath]', 'Path to the project directory', process.cwd()) + .option('--contract [paths...]', 'Paths to contracts to update dependencies', []) + .option('--aztec-version ', 'The version to update Aztec packages to. Defaults to latest', 'latest') + .action(async (projectPath: string, options) => { + const { updateProject } = await import('../utils/update.js'); + const { contract, aztecVersion } = options; + await updateProject(projectPath, contract, aztecVersion, log); + }); + return program; } diff --git a/yarn-project/cli/src/cmds/builder/update.ts b/yarn-project/cli/src/cmds/utils/update.ts similarity index 100% rename from yarn-project/cli/src/cmds/builder/update.ts rename to yarn-project/cli/src/cmds/utils/update.ts diff --git a/yarn-project/cli/src/cmds/builder/update/common.ts b/yarn-project/cli/src/cmds/utils/update/common.ts similarity index 100% rename from yarn-project/cli/src/cmds/builder/update/common.ts rename to yarn-project/cli/src/cmds/utils/update/common.ts diff --git a/yarn-project/cli/src/cmds/builder/update/github.ts b/yarn-project/cli/src/cmds/utils/update/github.ts similarity index 100% rename from yarn-project/cli/src/cmds/builder/update/github.ts rename to yarn-project/cli/src/cmds/utils/update/github.ts diff --git a/yarn-project/cli/src/cmds/builder/update/noir.ts b/yarn-project/cli/src/cmds/utils/update/noir.ts similarity index 100% rename from yarn-project/cli/src/cmds/builder/update/noir.ts rename to yarn-project/cli/src/cmds/utils/update/noir.ts diff --git a/yarn-project/cli/src/cmds/builder/update/npm.ts b/yarn-project/cli/src/cmds/utils/update/npm.ts similarity index 100% rename from yarn-project/cli/src/cmds/builder/update/npm.ts rename to yarn-project/cli/src/cmds/utils/update/npm.ts diff --git a/yarn-project/cli/src/cmds/builder/update/utils.ts b/yarn-project/cli/src/cmds/utils/update/utils.ts similarity index 100% rename from yarn-project/cli/src/cmds/builder/update/utils.ts rename to yarn-project/cli/src/cmds/utils/update/utils.ts diff --git a/yarn-project/noir-contracts.js/scripts/generate-types.sh b/yarn-project/noir-contracts.js/scripts/generate-types.sh index 8c1168b24e81..26a7eb20560f 100755 --- a/yarn-project/noir-contracts.js/scripts/generate-types.sh +++ b/yarn-project/noir-contracts.js/scripts/generate-types.sh @@ -37,7 +37,7 @@ for ABI in $(find ../../noir-projects/noir-contracts/target -maxdepth 1 -type f done # Generate types for the contracts -node --no-warnings ../builder/dest/cli.js codegen -o $OUT_DIR artifacts +node --no-warnings ../builder/dest/bin/cli.js codegen -o $OUT_DIR artifacts # Append exports for each generated TypeScript file to index.ts find "$OUT_DIR" -maxdepth 1 -type f -name '*.ts' ! -name 'index.ts' | while read -r TS_FILE; do diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index 14f89c536216..fc21460d9264 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -57,6 +57,7 @@ ] }, "dependencies": { + "@aztec/accounts": "workspace:^", "@aztec/archiver": "workspace:^", "@aztec/aztec.js": "workspace:^", "@aztec/circuit-types": "workspace:^", @@ -64,7 +65,6 @@ "@aztec/foundation": "workspace:^", "@aztec/key-store": "workspace:^", "@aztec/kv-store": "workspace:^", - "@aztec/noir-contracts.js": "workspace:^", "@aztec/pxe": "workspace:^", "@aztec/simulator": "workspace:^", "@aztec/types": "workspace:^", diff --git a/yarn-project/txe/src/index.ts b/yarn-project/txe/src/index.ts index 4bd804b1332f..3af1dee03de1 100644 --- a/yarn-project/txe/src/index.ts +++ b/yarn-project/txe/src/index.ts @@ -1,9 +1,13 @@ +import { loadContractArtifact } from '@aztec/aztec.js'; import { Fr } from '@aztec/foundation/fields'; import { JsonRpcServer } from '@aztec/foundation/json-rpc/server'; import { type Logger } from '@aztec/foundation/log'; +import { readFile, readdir } from 'fs/promises'; +import { join } from 'path'; + import { TXEService } from './txe_service/txe_service.js'; -import { type ForeignCallResult, toForeignCallResult } from './util/encoding.js'; +import { type ForeignCallArray, type ForeignCallResult, fromArray, toForeignCallResult } from './util/encoding.js'; const TXESessions = new Map(); @@ -14,18 +18,50 @@ type MethodNames = { type TXEForeignCallInput = { session_id: number; function: MethodNames | 'reset'; + root_path: string; + package_name: string; inputs: any[]; }; class TXEDispatcher { constructor(private logger: Logger) {} + async #processDeployInputs({ inputs, root_path: rootPath, package_name: packageName }: TXEForeignCallInput) { + const pathStr = fromArray(inputs[0] as ForeignCallArray) + .map(char => String.fromCharCode(char.toNumber())) + .join(''); + const contractName = fromArray(inputs[1] as ForeignCallArray) + .map(char => String.fromCharCode(char.toNumber())) + .join(''); + let artifactPath = ''; + // We're deploying the contract under test + // env.deploy_self("contractName") + if (!pathStr) { + artifactPath = join(rootPath, './target', `${packageName}-${contractName}.json`); + } else { + // We're deploying a contract that belongs in a workspace + // env.deploy("../path/to/workspace/root@packageName", "contractName") + if (pathStr.includes('@')) { + const [workspace, pkg] = pathStr.split('@'); + const targetPath = join(rootPath, workspace, './target'); + this.logger.debug(`Looking for compiled artifact in workspace ${targetPath}`); + artifactPath = join(targetPath, `${pkg}-${contractName}.json`); + } else { + // We're deploying a standalone contract + // env.deploy("../path/to/contract/root", "contractName") + const targetPath = join(rootPath, pathStr, './target'); + this.logger.debug(`Looking for compiled artifact in ${targetPath}`); + [artifactPath] = (await readdir(targetPath)).filter(file => file.endsWith(`-${contractName}.json`)); + } + } + this.logger.debug(`Loading compiled artifact ${artifactPath}`); + const artifact = loadContractArtifact(JSON.parse(await readFile(artifactPath, 'utf-8'))); + inputs.splice(0, 2, artifact); + } + // eslint-disable-next-line camelcase - async resolve_foreign_call({ - session_id: sessionId, - function: functionName, - inputs, - }: TXEForeignCallInput): Promise { + async resolve_foreign_call(callData: TXEForeignCallInput): Promise { + const { session_id: sessionId, function: functionName, inputs } = callData; this.logger.debug(`Calling ${functionName} on session ${sessionId}`); if (!TXESessions.has(sessionId) && functionName != 'reset') { @@ -33,14 +69,22 @@ class TXEDispatcher { TXESessions.set(sessionId, await TXEService.init(this.logger)); } - if (functionName === 'reset') { - TXESessions.delete(sessionId) && - this.logger.info(`Called reset on session ${sessionId}, yeeting it out of existence`); - return toForeignCallResult([]); - } else { - const txeService = TXESessions.get(sessionId); - const response = await (txeService as any)[functionName](...inputs); - return response; + switch (functionName) { + case 'reset': { + TXESessions.delete(sessionId) && + this.logger.info(`Called reset on session ${sessionId}, yeeting it out of existence`); + return toForeignCallResult([]); + } + case 'deploy': { + // Modify inputs and fall through + await this.#processDeployInputs(callData); + } + // eslint-disable-next-line no-fallthrough + default: { + const txeService = TXESessions.get(sessionId); + const response = await (txeService as any)[functionName](...inputs); + return response; + } } } } diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 019e331d2bdb..a8f772931ca1 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -1,3 +1,4 @@ +import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr'; import { L2Block, MerkleTreeId, PublicDataWrite } from '@aztec/circuit-types'; import { Fr, @@ -7,10 +8,11 @@ import { PUBLIC_DATA_SUBTREE_HEIGHT, Point, PublicDataTreeLeaf, + computePartialAddress, getContractInstanceFromDeployParams, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; -import { NoteSelector } from '@aztec/foundation/abi'; +import { type ContractArtifact, NoteSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { type Logger } from '@aztec/foundation/log'; import { KeyStore } from '@aztec/key-store'; @@ -92,27 +94,22 @@ export class TXEService { } async deploy( - path: ForeignCallArray, + artifact: ContractArtifact, initializer: ForeignCallArray, _length: ForeignCallSingle, args: ForeignCallArray, publicKeysHash: ForeignCallSingle, ) { - const pathStr = fromArray(path) - .map(char => String.fromCharCode(char.toNumber())) - .join(''); const initializerStr = fromArray(initializer) .map(char => String.fromCharCode(char.toNumber())) .join(''); const decodedArgs = fromArray(args); const publicKeysHashFr = fromSingle(publicKeysHash); this.logger.debug( - `Deploy ${pathStr} with initializer ${initializerStr}(${decodedArgs}) and public keys hash ${publicKeysHashFr}`, + `Deploy ${artifact.name} with initializer ${initializerStr}(${decodedArgs}) and public keys hash ${publicKeysHashFr}`, ); - const contractModule = await import(pathStr); - // Hacky way of getting the class, the name of the Artifact is always longer - const contractClass = contractModule[Object.keys(contractModule).sort((a, b) => a.length - b.length)[0]]; - const instance = getContractInstanceFromDeployParams(contractClass.artifact, { + + const instance = getContractInstanceFromDeployParams(artifact, { constructorArgs: decodedArgs, skipArgsDecoding: true, salt: Fr.ONE, @@ -121,9 +118,9 @@ export class TXEService { deployer: AztecAddress.ZERO, }); - this.logger.debug(`Deployed ${contractClass.artifact.name} at ${instance.address}`); + this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`); await (this.typedOracle as TXE).addContractInstance(instance); - await (this.typedOracle as TXE).addContractArtifact(contractClass.artifact); + await (this.typedOracle as TXE).addContractArtifact(artifact); return toForeignCallResult([ toArray([ instance.salt, @@ -171,9 +168,26 @@ export class TXEService { ]); } - async addAccount(secret: ForeignCallSingle, partialAddress: ForeignCallSingle) { + async addAccount(secret: ForeignCallSingle) { + const keys = (this.typedOracle as TXE).deriveKeys(fromSingle(secret)); + const args = [keys.publicKeys.masterIncomingViewingPublicKey.x, keys.publicKeys.masterIncomingViewingPublicKey.y]; + const hash = keys.publicKeys.hash(); + const artifact = SchnorrAccountContractArtifact; + const instance = getContractInstanceFromDeployParams(artifact, { + constructorArgs: args, + skipArgsDecoding: true, + salt: Fr.ONE, + publicKeysHash: hash, + constructorArtifact: 'constructor', + deployer: AztecAddress.ZERO, + }); + + this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`); + await (this.typedOracle as TXE).addContractInstance(instance); + await (this.typedOracle as TXE).addContractArtifact(artifact); + const keyStore = (this.typedOracle as TXE).getKeyStore(); - const completeAddress = await keyStore.addAccount(fromSingle(secret), fromSingle(partialAddress)); + const completeAddress = await keyStore.addAccount(fromSingle(secret), computePartialAddress(instance)); const accountStore = (this.typedOracle as TXE).getTXEDatabase(); await accountStore.setAccount(completeAddress.address, completeAddress); this.logger.debug(`Created account ${completeAddress.address}`); diff --git a/yarn-project/txe/tsconfig.json b/yarn-project/txe/tsconfig.json index 7db2bf797787..ec9add87a8b8 100644 --- a/yarn-project/txe/tsconfig.json +++ b/yarn-project/txe/tsconfig.json @@ -6,6 +6,9 @@ "tsBuildInfoFile": ".tsbuildinfo" }, "references": [ + { + "path": "../accounts" + }, { "path": "../archiver" }, @@ -27,9 +30,6 @@ { "path": "../kv-store" }, - { - "path": "../noir-contracts.js" - }, { "path": "../pxe" }, diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 8fb9a895a8a2..57aba5bd3023 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -278,8 +278,8 @@ __metadata: version: 0.0.0-use.local resolution: "@aztec/builder@workspace:builder" dependencies: - "@aztec/cli": "workspace:^" "@aztec/foundation": "workspace:^" + "@aztec/types": "workspace:^" "@jest/globals": ^29.5.0 "@types/jest": ^29.5.0 "@types/node": ^18.7.23 @@ -288,7 +288,7 @@ __metadata: ts-node: ^10.9.1 typescript: ^5.0.4 bin: - aztec-builder: dest/cli.js + aztec-builder: dest/bin/cli.js languageName: unknown linkType: soft @@ -985,6 +985,7 @@ __metadata: version: 0.0.0-use.local resolution: "@aztec/txe@workspace:txe" dependencies: + "@aztec/accounts": "workspace:^" "@aztec/archiver": "workspace:^" "@aztec/aztec.js": "workspace:^" "@aztec/circuit-types": "workspace:^" @@ -992,7 +993,6 @@ __metadata: "@aztec/foundation": "workspace:^" "@aztec/key-store": "workspace:^" "@aztec/kv-store": "workspace:^" - "@aztec/noir-contracts.js": "workspace:^" "@aztec/pxe": "workspace:^" "@aztec/simulator": "workspace:^" "@aztec/types": "workspace:^"