Skip to content

Commit

Permalink
feat: TXE nr deployments, dependency cleanup for CLI (#7548)
Browse files Browse the repository at this point in the history
This PR eliminates unnecessary dependencies from TXE (namely
`@aztec/noir-contracts.js`) and introduces a new format for simulated
contract deployments in tests.

Leveraging two new variables introduced in nargo foreign calls that
provide context to TXE (`root_path` and `package_name`), we are able to
directly locate the `.json` artifacts resulting from compilation and use
those to deploy bytecode. The API looks like this:
```rust
// The contract we're testing
env.deploy_self("ContractName").with{out/_public/_private}_initializer(...); // We have to provide ContractName since nargo it's ready to support multi-contract files

// A contract from a workspace
env.deploy("../path/to/workspace@package_name", "ContractName").with{out/_public/_private}_initializer(...); // This format allows locating the artifact in the root workspace target folder, regardless of internal code organization

// An isolated contract
env.deploy("../path/to/contract", "ContractName").with{out/_public/_private}_initializer(...);
```

All paths originate and point to a program root (where `Nargo.toml`
lives)
  • Loading branch information
Thunkar authored Jul 24, 2024
1 parent a316fcd commit 92ff2fa
Show file tree
Hide file tree
Showing 45 changed files with 401 additions and 241 deletions.
1 change: 1 addition & 0 deletions aztec-up/bin/docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 13 additions & 2 deletions docs/docs/guides/smart_contracts/testing_contracts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
23 changes: 23 additions & 0 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
18 changes: 10 additions & 8 deletions noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ unconstrained pub fn get_public_context_inputs() -> PublicContextInputs {
oracle_get_public_context_inputs()
}

unconstrained pub fn deploy<let N: u32, let M: u32>(
unconstrained pub fn deploy<let N: u32, let M: u32, let P: u32>(
path: str<N>,
initializer: str<M>,
name: str<M>,
initializer: str<P>,
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)
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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<let N: u32, let M: u32>(
unconstrained fn oracle_deploy<let N: u32, let M: u32, let P: u32>(
path: str<N>,
initializer: str<M>,
name: str<M>,
initializer: str<P>,
args: [Field],
public_keys_hash: Field
) -> [Field; CONTRACT_INSTANCE_LENGTH] {}
Expand All @@ -140,7 +142,7 @@ unconstrained fn direct_storage_write_oracle<let N: u32>(
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 {}
Expand Down
32 changes: 10 additions & 22 deletions noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<let N: u32>(_self: Self, path: str<N>) -> Deployer<N> {
Deployer { path, public_keys_hash: 0 }
fn deploy<N, M>(self, path: str<N>, name: str<M>) -> Deployer<N, M> {
Deployer { path, name, public_keys_hash: 0 }
}

fn deploy_self<M>(self, name: str<M>) -> Deployer<0, M> {
Deployer { path: "", name, public_keys_hash: 0 }
}

fn call_private<C, let M: u32, T, Env, let N: u32>(
Expand Down
23 changes: 13 additions & 10 deletions noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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<let N: u32> {
path: str<N>,
public_keys_hash: Field
}
struct Deployer<let N: u32, let M: u32> {
path: str<N>,
name: str<M>,
public_keys_hash: Field
}

impl<let N: u32> Deployer<N> {
pub fn with_private_initializer<C, let M: u32, Env>(
impl<let N: u32, let M: u32> Deployer<N, M> {
pub fn with_private_initializer<C, let P: u32, Env>(
self,
call_interface: C
) -> ContractInstance where C: CallInterface<M, PrivateContextInputs, PrivateCircuitPublicInputs, Env> {
) -> ContractInstance where C: CallInterface<P, PrivateContextInputs, PrivateCircuitPublicInputs, Env> {
let instance = cheatcodes::deploy(
self.path,
self.name,
call_interface.get_name(),
call_interface.get_args(),
self.public_keys_hash
Expand All @@ -64,12 +66,13 @@ impl<let N: u32> Deployer<N> {
instance
}

pub fn with_public_initializer<C, let M: u32, T, Env>(
pub fn with_public_initializer<C, let P: u32, T, Env>(
self,
call_interface: C
) -> ContractInstance where C: CallInterface<M, PublicContextInputs, T, Env> {
) -> ContractInstance where C: CallInterface<P, PublicContextInputs, T, Env> {
let instance = cheatcodes::deploy(
self.path,
self.name,
call_interface.get_name(),
call_interface.get_args(),
self.public_keys_hash
Expand All @@ -94,7 +97,7 @@ impl<let N: u32> Deployer<N> {
}

pub fn without_initializer(self) -> ContractInstance {
cheatcodes::deploy(self.path, "", &[], self.public_keys_hash)
cheatcodes::deploy(self.path, self.name, "", &[], self.public_keys_hash)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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)
Expand Down
5 changes: 2 additions & 3 deletions noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub(crate) struct ExecuteCommand {
fn run_command(args: ExecuteCommand) -> Result<String, CliError> {
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,
Expand All @@ -66,15 +66,14 @@ pub(crate) fn run(args: ExecuteCommand) -> Result<String, CliError> {
pub(crate) fn execute_program_from_witness(
inputs_map: WitnessMap<FieldElement>,
bytecode: &[u8],
foreign_call_resolver_url: Option<&str>,
) -> Result<WitnessStack<FieldElement>, CliError> {
let program: Program<FieldElement> = Program::deserialize_program(bytecode)
.map_err(|_| CliError::CircuitDeserializationError())?;
execute_program(
&program,
inputs_map,
&Bn254BlackBoxSolver,
&mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url),
&mut DefaultForeignCallExecutor::new(true, None, None, None),
)
.map_err(CliError::CircuitExecutionError)
}
2 changes: 1 addition & 1 deletion noir/noir-repo/tooling/debugger/src/foreign_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand Down
2 changes: 1 addition & 1 deletion noir/noir-repo/tooling/fuzzer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl FuzzedExecutor {
&self.program.bytecode,
initial_witness,
&StubbedBlackBoxSolver,
&mut DefaultForeignCallExecutor::<FieldElement>::new(false, None),
&mut DefaultForeignCallExecutor::<FieldElement>::new(false, None, None, None),
);

// TODO: Add handling for `vm.assume` equivalent
Expand Down
2 changes: 2 additions & 0 deletions noir/noir-repo/tooling/lsp/src/requests/test_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,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 {
Expand Down
Loading

0 comments on commit 92ff2fa

Please sign in to comment.