aztec-nr - noir_aztec::test::helpers::test_environment

Struct TestEnvironment

pub struct TestEnvironment
{ /* private fields */ }

This represents an Aztec test run, and contains all the methods utilized during one to interact with the network and manipulate its state (e.g. create accounts, make contract calls, etc.). Each test is expected to have its own instance of TestEnvironment, as tests execute in parallel and so cannot share this object.

Most tests will begin by creating a TestEnvironment variable, and then make multiple calls to its different methods. E.g.:

#[test]
fn sample_test() {
    let mut env = TestEnvironment::new();

    // Create an account to call contracts from
    let account = env.create_light_account();
    // Deploy a compiled contract
    let contract_addr = env.deploy("MyContract").without_initializer();

    // Call a contract private function and get the return value
    let result = env.private_call(account, MyContract::at(contract_addr).sample_private_function());
    assert_eq(result, expected);
}

Implementations

impl TestEnvironment

pub unconstrained fn new() -> Self

Creates a new TestEnvironment. This function should only be called once per test.

pub unconstrained fn public_context<Env, T>( self, f: unconstrained fn[Env](PublicContext) -> T, ) -> T

Creates a PublicContext, which allows using aztec-nr features as if inside a public contract function. Useful for low-level testing of public state variables and utilities.

A new block is automatically mined once public_context returns, containing all of the side effects caused by its execution (e.g. public storage writes). It is however NOT possible to make any contract calls from this public context - use call_public for testing contract calls (though usage of call_public is forbidden while inside public_context).

Receives a callback function which is called with the created PublicContext - this function is where testing logic is expected to reside. Any values returned by it are bubbled-up and returned to the caller of public_context. Do NOT return the PublicContext from the callback function, or use it in any way outside of it - it becomes invalid once public_context returns.

See public_context_at for a variant that allows specifying the contract address.

Sample usage

env.public_context(|context| {
  let state_var = PublicMutable::new(context, STORAGE_SLOT);
  state_var.write(some_value);
  assert_eq(state_var.read(), some_value);
});

Advanced usage with returns

let read_value = env.public_context(|context| {
  let state_var = PublicMutable::new(context, STORAGE_SLOT);
  state_var.read()
});
pub unconstrained fn public_context_at<Env, T>( self, addr: AztecAddress, f: unconstrained fn[Env](PublicContext) -> T, ) -> T

Variant of public_context which allows specifying the contract address in which the public context will execute, which will affect note and nullifier siloing, storage access, etc.

pub unconstrained fn private_context<Env, T>( self, f: unconstrained fn[Env](&mut PrivateContext) -> T, ) -> T

Creates a PrivateContext, which allows using aztec-nr features as if inside a private contract function. Useful for low-level testing of private state variables and utilities.

A new block is automatically mined once private_context returns, containing all of the side effects caused by its execution (e.g. note and nullifier emission). It is however NOT possible to make any contract calls from this private context, neither private nor enqueued public calls - use call_private and call_public for testing contract calls (though usage of these is forbidden while inside private_context).

Receives a callback function which is called with the created PrivateContext - this function is where testing logic is expected to reside. Any values returned by it are bubbled-up and returned to the caller of private_context. Do NOT return the PrivateContext from the callback function, or use it in any way outside of it - it becomes invalid once private_context returns.

See private_context_at for a variant that allows specifying the contract address, or private_context_opts for even more configurability.

Sample usage

env.private_context(|context| {
  let state_var = PrivateMutable::new(context, STORAGE_SLOT);
  let note = SampleNote::new(some_value);
  state_var.initialize(note);
  assert_eq(state_var.get_note(), note);
});

Advanced usage with returns

let note = env.private_context(|context| {
  let state_var = PrivateMutable::new(context, STORAGE_SLOT);
  state_var.get_note()
});
pub unconstrained fn private_context_at<Env, T>( self, addr: AztecAddress, f: unconstrained fn[Env](&mut PrivateContext) -> T, ) -> T

Variant of private_context which allows specifying the contract address in which the private context will execute, which will affect note and nullifier siloing, storage access, etc.

pub unconstrained fn private_context_opts<Env, T>( _self: Self, opts: PrivateContextOptions, f: unconstrained fn[Env](&mut PrivateContext) -> T, ) -> T

Variant of private_context which allows specifying multiple configuration values via PrivateContextOptions.

pub unconstrained fn utility_context<Env, T>( self, f: unconstrained fn[Env](UtilityContext) -> T, ) -> T

Creates a UtilityContext, which allows using aztec-nr features as if inside a utility contract function. Useful for low-level testing of private and public state variable utilities.

Receives a callback function which is called with the created Utility - this function is where testing logic is expected to reside. Any values returned by it are bubbled-up and returned to the caller of utility_context. Do NOT return the Utility from the callback function, or use it in any way outside of it - it becomes invalid once utility_context returns.

See utility_context_at for a variant that allows specifying the contract address.

Sample usage

env.utility_context(|context| {
  let state_var = PrivateMutable::new(context, STORAGE_SLOT);
  let note = SampleNote::new(some_value);
  assert_eq(state_var.view_note(), note);
});

Advanced usage with returns

let note = env.utility_context(|context| {
  let state_var = PrivateMutable::new(context, STORAGE_SLOT);
  state_var.view_note()
});
pub unconstrained fn utility_context_at<Env, T>( self, addr: AztecAddress, f: unconstrained fn[Env](UtilityContext) -> T, ) -> T

Variant of utility_context which allows specifying the contract address in which the utility context will execute, which will affect note and storage access.

pub unconstrained fn next_block_number(_self: Self) -> u32

Returns the number of the next block to be mined.

pub unconstrained fn last_block_number(_self: Self) -> u32

Returns the number of the last mined block. This is the default anchor block for private_context and private_call.

pub unconstrained fn last_block_timestamp(_self: Self) -> u64

Returns the timestamp of the last mined block. Note that block timestamps do not automatically advance - this must be done by calling mine_block_at or set_next_block_timestamp.

pub unconstrained fn mine_block_at(self, timestamp: u64)

Mines an empty block (i.e. with no transactions) at timestamp, causing followup public executions to occur at that timestamp (e.g. via call_public or public_context), and making it possible to create private executions at the specified timestamp (e.g. via call_private or private_context).

pub unconstrained fn set_next_block_timestamp(self, timestamp: u64)

Sets the timestamp of the next block to be mined, causing followup public executions to occur at that timestamp (e.g. via call_public or public_context).

pub unconstrained fn mine_block(_self: Self)

Mines an empty block (i.e. with no transactions). Note that block timestamps do not automatically advance - this must be done by calling mine_block_at or set_next_block_timestamp.

pub unconstrained fn advance_next_block_timestamp_by(_self: Self, duration: u64)

Sets the timestamp of the next block to be mined to be ahead of the last one by duration.

pub unconstrained fn create_contract_account(&mut self) -> AztecAddress

Creates a new account that can be used as the from parameter in contract calls, e.g. in private_call or public_call, or be made the owner or recipient of notes in private_context.

The returned account has a full set of privacy keys, so it can nullify notes, receive messages, etc. It also has an associated SchnorrAccount contract that can process authwit requests - the authwits can be added via the add_private_authwit_from_call and add_public_authwit_from_call helper functions. If authwits are not required, consider using create_light_account instead, which is a faster variant of this function.

Each call to create_contract_account will return a different address, and so it can be called repeatedly to generate multiple addresses. These addresses are also different from the ones that create_light_account returns, and so these two functions can be mixed and match to create a set of unique accounts.

pub unconstrained fn create_light_account(&mut self) -> AztecAddress

Creates a new account that can be used as the from parameter in contract calls, e.g. in private_call or public_call, or be made the owner or recipient of notes in private_context. This is a faster variant of create_contract_account, but comes with reduced capabilities.

The returned account has a full set of privacy keys, so it can nullify notes, receive messages, etc. It doesn't however have an associated account contract, so it cannot process private authwit requests. If calling contracts that rely on private authwits, use create_contract_account instead.

Each call to create_light_account will return a different address, and so it can be called repeatedly to generate multiple addresses. These addresses are also different from the ones that create_contract_account returns, and so these two functions can be mixed and match to create a set of unique accounts.

pub unconstrained fn deploy<let N: u32>( &mut self, path: str<N>, ) -> ContractDeployment<N>

Prepares a contract for deployment, so that its private, public and utility functions can be called. The contract must be already compiled before tests are run, and must be recompiled if changed for the tests to deploy the updated version.

In order to finalize the deployment, the proper initializer function must be specified via one of the associated methods. For more information on how to specify the path and initialization, read the sections below.

Path

Contracts can be deployed from either the same crate as the test, from a different crate in the same workspace, or from an altogether independent crate.

If deploying contracts from the same crate as the test, just refer to it by its name:

TestEnvironment::new().deploy("MyContract");

If deploying contracts from a different crate in the same workspace, use @crate_name/contract_name:

TestEnvironment::new().deploy("@my_contract_crate/MyContract");

If deploying contracts from a crate not in the workspace, use path_to_crate/contract_name, with the crate path relative to the current workspace:

TestEnvironment::new().deploy("../my_other_crate/MyContract");

Initialization

If no initializer function needs to be called, use without_initializer:

let my_contract = TestEnvironment::new().deploy("MyContract").without_initializer();

For private initializers, use with_private_initializer:

let my_contract = TestEnvironment::new().deploy("MyContract").with_private_initializer(
  PrivateInitContract::interface().private_init_fn(init_args)
);

For public initializers, use with_public_initializer:

let my_contract = TestEnvironment::new().deploy("MyContract").with_public_initializer(
  PublicInitContract::interface().public_init_fn(init_args)
);
pub unconstrained fn call_private<let M: u32, let N: u32, T>( _self: Self, from: AztecAddress, call: PrivateCall<M, N, T>, ) -> T
where T: Deserialize

Performs a private contract function call, including the processing of any nested private calls and enqueued public calls. Returns the result of the called function.

The function is called by the from address - use create_contract_account or create_light_account to generate sender addresses, depending on whether support for private authwit verification is required or not. Note that execution will begin directly in the called function - no entrypoint function in from will be executed.

A transaction is created containing all side effects of the call, which is then included in a block that gets mined by the time call_private returns. It is therefore possible to chain multiple private or public function calls that operate on the result of prior calls.

The call value can be obtained by calling the appropriate method on a contract type. E.g.:

let caller = env.create_light_account();
let contract_addr = env.deploy("SampleContract").without_initializer();
let return_value = env.call_private(caller, SampleContract::at(contract_addr).sample_private_function());
pub unconstrained fn view_private<let M: u32, let N: u32, T>( _self: Self, call: PrivateStaticCall<M, N, T>, ) -> T
where T: Deserialize

Variant of call_private for private #[view] functions.

Unlike call_private, no transaction is created and no block is mined (since #[view] functions are only executable in a static context, and these produce no side effects).

pub unconstrained fn simulate_utility<let M: u32, let N: u32, T>( _self: Self, call: UtilityCall<M, N, T>, ) -> T
where T: Deserialize

Performs a utility contract function call and returns the result of the called function.

The call value can be obtained by calling the appropriate method on a contract type. E.g.:

let caller = env.create_light_account();
let contract_addr = env.deploy("SampleContract").without_initializer();
let return_value = env.simulate_utility(SampleContract::at(contract_addr).sample_utility_function());
pub unconstrained fn call_public<let M: u32, let N: u32, T>( _self: Self, from: AztecAddress, call: PublicCall<M, N, T>, ) -> T
where T: Deserialize

Performs a public contract function call, including the processing of any nested public calls. Returns the result of the called function.

The function is called by the from address - use create_contract_account or create_light_account to generate sender addresses. Note that execution will begin directly in the called function - no private entrypoint function in from will be executed - in fact no private execution of any kind will be performed.

A transaction is created containing all side effects of the call, which is then included in a block that gets mined by the time call_public returns. It is therefore possible to chain multiple private or public function calls that operate on the result of prior calls.

The call value can be obtained by calling the appropriate method on a contract type. E.g.:

let caller = env.create_light_account();
let contract_addr = env.deploy("SampleContract").without_initializer();
let return_value = env.call_public(caller, SampleContract::at(contract_addr).sample_public_function());
pub unconstrained fn call_public_incognito<let M: u32, let N: u32, T>( _self: Self, from: AztecAddress, call: PublicCall<M, N, T>, ) -> T
where T: Deserialize

Performs a public contract function call, including the processing of any nested public calls. Returns the result of the called function. Variant of call_public, but the from address (msg_sender) is set to "null".

pub unconstrained fn view_public<let M: u32, let N: u32, T>( _self: Self, call: PublicStaticCall<M, N, T>, ) -> T
where T: Deserialize

Variant of call_public for public #[view] functions.

Unlike call_public, no transaction is created and no block is mined (since #[view] functions are only executable in a static context, and these produce no side effects).

pub unconstrained fn view_public_incognito<let M: u32, let N: u32, T>( _self: Self, call: PublicStaticCall<M, N, T>, ) -> T
where T: Deserialize

Variant of view_public, but the from address (msg_sender) is set to "null"

Unlike call_public, no transaction is created and no block is mined (since #[view] functions are only executable in a static context, and these produce no side effects).

pub unconstrained fn discover_note<Note>(self, emission: NoteEmission<Note>)
where Note: Packable, Note: NoteType, Note: NoteHash

Discovers a note wrapped in a NoteEmission value, which is expected to have been created in the last transaction (typically via private_context()) so that it can be retrieved in later transactions. This mimics the normal note discovery process that takes places automatically in contracts.

NoteEmission values are typically returned by aztec-nr state variables that create notes and need to notify a recipient of their existence. Instead of going through the message encoding, encryption and emission that would regularly take place in a contract, discover_note simply takes the NoteEmission and processes it as needed to discover the underlying note.

See discover_note_at for a variant that allows specifying the contract address the note belongs to.

Sample usage

The most common way to invoke this function is to obtain an emission created during the execution of a private_context:

let emission = env.private_context(|context| create_note(context, storage_slot, note));

env.discover_note(emission);

env.private_context(|context| { let (retrieved_note, _) = get_note::<MockNote>(context, storage_slot); });
pub unconstrained fn discover_note_at<Note>( self, addr: AztecAddress, emission: NoteEmission<Note>, )
where Note: Packable, Note: NoteType, Note: NoteHash

Variant of discover_note which allows specifying the contract address the note belongs to, which is required when the note was not emitted in the default address used by private_context.

let emission = env.private_context_at(contract_address, |context| {
    create_note(context, storage_slot, note)
});

env.discover_note_at(contract_address, emission);

env.private_context_at(contract_address, |context| {
    let (retrieved_note, _) = get_note::<MockNote>(context, storage_slot);
});