Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 113 additions & 21 deletions noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr
Original file line number Diff line number Diff line change
Expand Up @@ -132,21 +132,22 @@ struct UtilityContextOptions {
/// ```noir
/// env.call_private_opts(from, CallPrivateOptions::new().with_additional_scopes([other]), call);
/// ```
pub struct CallPrivateOptions<let N: u32> {
pub struct CallPrivateOptions<let N: u32, let T: u32> {
additional_scopes: [AztecAddress; N],
authorized_utility_call_targets: [AztecAddress; T],
}

impl CallPrivateOptions<0> {
impl CallPrivateOptions<0, 0> {
/// Creates default `CallPrivateOptions`.
///
/// The default values are the same as if using [`TestEnvironment::call_private`] instead of
/// [`TestEnvironment::call_private_opts`].
pub fn new() -> Self {
CallPrivateOptions { additional_scopes: [] }
CallPrivateOptions { additional_scopes: [], authorized_utility_call_targets: [] }
}
}

impl<let N: u32> CallPrivateOptions<N> {
impl<let N: u32, let T: u32> CallPrivateOptions<N, T> {
/// Grants access to secrets of additional accounts.
///
/// By default only the secrets that belong to the `from` account can be accessed: this function lets contracts
Expand All @@ -155,10 +156,27 @@ impl<let N: u32> CallPrivateOptions<N> {
/// Example usage includes accounts withdrawing from an escrow, which may require accessing the escrow's own notes
/// and secrets.
pub fn with_additional_scopes<let N_2: u32>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be docs (error page? idk) that tell you how to fix the issue when you run into it, incl. the fact that if in a test you can do this. They should also explain why this is not allowed by default.

_self: Self,
self,
additional_scopes: [AztecAddress; N_2],
) -> CallPrivateOptions<N_2> {
CallPrivateOptions { additional_scopes }
) -> CallPrivateOptions<N_2, T> {
CallPrivateOptions {
additional_scopes,
authorized_utility_call_targets: self.authorized_utility_call_targets,
}
}

/// Authorizes cross-contract utility calls to the given target contracts during this call.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loooooooooooooooooooooooooooooooong

///
/// By default, cross-contract utility calls are denied. This method lets the test author specify which target
/// contracts are allowed to be called via utility functions during execution.
pub fn with_authorized_utility_call_targets<let T_2: u32>(
self,
targets: [AztecAddress; T_2],
) -> CallPrivateOptions<N, T_2> {
CallPrivateOptions {
additional_scopes: self.additional_scopes,
authorized_utility_call_targets: targets,
}
}
}

Expand All @@ -169,21 +187,22 @@ impl<let N: u32> CallPrivateOptions<N> {
/// ```noir
/// env.view_private_opts(from, ViewPrivateOptions::new().with_additional_scopes([other]), call);
/// ```
pub struct ViewPrivateOptions<let S: u32> {
pub struct ViewPrivateOptions<let S: u32, let T: u32> {
additional_scopes: [AztecAddress; S],
authorized_utility_call_targets: [AztecAddress; T],
}

impl ViewPrivateOptions<0> {
impl ViewPrivateOptions<0, 0> {
/// Creates default `ViewPrivateOptions`.
///
/// The default values are the same as if using [`TestEnvironment::view_private`] instead of
/// [`TestEnvironment::view_private_opts`].
pub fn new() -> Self {
ViewPrivateOptions { additional_scopes: [] }
ViewPrivateOptions { additional_scopes: [], authorized_utility_call_targets: [] }
}
}

impl<let S: u32> ViewPrivateOptions<S> {
impl<let S: u32, let T: u32> ViewPrivateOptions<S, T> {
/// Grants access to secrets of additional accounts.
///
/// By default only the secrets that belong to the `from` account can be accessed: this function lets contracts
Expand All @@ -192,10 +211,27 @@ impl<let S: u32> ViewPrivateOptions<S> {
/// Example usage includes accounts querying an escrow's balance, which may require accessing the escrow's own
/// notes.
pub fn with_additional_scopes<let S2: u32>(
_self: Self,
self,
additional_scopes: [AztecAddress; S2],
) -> ViewPrivateOptions<S2> {
ViewPrivateOptions { additional_scopes }
) -> ViewPrivateOptions<S2, T> {
ViewPrivateOptions {
additional_scopes,
authorized_utility_call_targets: self.authorized_utility_call_targets,
}
}

/// Authorizes cross-contract utility calls to the given target contracts during this call.
///
/// By default, cross-contract utility calls are denied. This method lets the test author specify which target
/// contracts are allowed to be called via utility functions during execution.
pub fn with_authorized_utility_call_targets<let T_2: u32>(
self,
targets: [AztecAddress; T_2],
) -> ViewPrivateOptions<S, T_2> {
ViewPrivateOptions {
additional_scopes: self.additional_scopes,
authorized_utility_call_targets: targets,
}
}
}

Expand All @@ -207,6 +243,40 @@ struct EventDiscoveryOptions {
contract_address: Option<AztecAddress>,
}

/// Configuration for [`TestEnvironment::execute_utility_opts`].
///
/// Constructed by calling [`ExecuteUtilityOptions::new`] and then chaining methods:
///
/// ```noir
/// env.execute_utility_opts(
/// ExecuteUtilityOptions::new().with_authorized_utility_call_targets([target]),
/// SomeContract::at(addr).some_utility_function(),
/// );
/// ```
pub struct ExecuteUtilityOptions<let T: u32> {
authorized_utility_call_targets: [AztecAddress; T],
}

impl ExecuteUtilityOptions<0> {
/// Creates default `ExecuteUtilityOptions`.
pub fn new() -> Self {
ExecuteUtilityOptions { authorized_utility_call_targets: [] }
}
}

impl<let T: u32> ExecuteUtilityOptions<T> {
/// Authorizes cross-contract utility calls to the given target contracts during this call.
///
/// By default, cross-contract utility calls are denied. This method lets the test author specify which target
/// contracts are allowed to be called via utility functions during execution.
pub fn with_authorized_utility_call_targets<let T_2: u32>(
_self: Self,
targets: [AztecAddress; T_2],
) -> ExecuteUtilityOptions<T_2> {
ExecuteUtilityOptions { authorized_utility_call_targets: targets }
}
}

/// Configuration values for [`TestEnvironment::deploy_opts`]. Meant to be used by calling `new` and then chaining
/// methods setting each value, e.g.:
/// ```noir
Expand Down Expand Up @@ -629,10 +699,10 @@ impl TestEnvironment {
}

/// Variant of `call_private` which allows specifying multiple configuration values via `CallPrivateOptions`.
pub unconstrained fn call_private_opts<let M: u32, let N: u32, let S: u32, T>(
pub unconstrained fn call_private_opts<let M: u32, let N: u32, let S: u32, let U: u32, T>(
_self: Self,
from: AztecAddress,
opts: CallPrivateOptions<S>,
opts: CallPrivateOptions<S, U>,
call: PrivateCall<M, N, T>,
) -> T
where
Expand All @@ -646,6 +716,7 @@ impl TestEnvironment {
hash_args(call.args),
/*is_static=*/ false,
opts.additional_scopes,
opts.authorized_utility_call_targets,
);

T::deserialize(serialized_return_values)
Expand All @@ -670,10 +741,10 @@ impl TestEnvironment {
}

/// Variant of `view_private` which allows specifying multiple configuration values via `ViewPrivateOptions`.
pub unconstrained fn view_private_opts<let M: u32, let N: u32, let S: u32, T>(
pub unconstrained fn view_private_opts<let M: u32, let N: u32, let S: u32, let U: u32, T>(
_self: Self,
from: AztecAddress,
opts: ViewPrivateOptions<S>,
opts: ViewPrivateOptions<S, U>,
call: PrivateStaticCall<M, N, T>,
) -> T
where
Expand All @@ -687,6 +758,7 @@ impl TestEnvironment {
hash_args(call.args),
/*is_static=*/ true,
opts.additional_scopes,
opts.authorized_utility_call_targets,
);

T::deserialize(serialized_return_values)
Expand All @@ -701,12 +773,32 @@ impl TestEnvironment {
/// let contract_addr = env.deploy("SampleContract").without_initializer();
/// let return_value = env.execute_utility(SampleContract::at(contract_addr).sample_utility_function());
/// ```
pub unconstrained fn execute_utility<let M: u32, let N: u32, T>(_self: Self, call: UtilityCall<M, N, T>) -> T
pub unconstrained fn execute_utility<let M: u32, let N: u32, T>(
self: Self,
call: UtilityCall<M, N, T>,
) -> T
where
T: Deserialize,
{
let serialized_return_values =
txe_oracles::execute_utility_function(call.target_contract, call.selector, call.args);
self.execute_utility_opts(ExecuteUtilityOptions::new(), call)
}

/// Variant of `execute_utility` which allows specifying configuration values via `ExecuteUtilityOptions`, such as
/// authorizing cross-contract utility calls.
pub unconstrained fn execute_utility_opts<let M: u32, let N: u32, let U: u32, T>(
_self: Self,
opts: ExecuteUtilityOptions<U>,
call: UtilityCall<M, N, T>,
) -> T
where
T: Deserialize,
{
let serialized_return_values = txe_oracles::execute_utility_function(
call.target_contract,
call.selector,
call.args,
opts.authorized_utility_call_targets,
);

T::deserialize(serialized_return_values)
}
Expand Down
20 changes: 15 additions & 5 deletions noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ pub unconstrained fn deploy<let M: u32, let N: u32, let P: u32>(
ContractInstance::deserialize(instance_fields)
}

pub unconstrained fn private_call_new_flow<let M: u32, let N: u32, let S: u32>(
pub unconstrained fn private_call_new_flow<let M: u32, let N: u32, let S: u32, let T: u32>(
from: Option<AztecAddress>,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; M],
args_hash: Field,
is_static_call: bool,
additional_scopes: [AztecAddress; S],
authorized_utility_call_targets: [AztecAddress; T],
) -> [Field; N] {
private_call_new_flow_oracle(
from,
Expand All @@ -53,6 +54,7 @@ pub unconstrained fn private_call_new_flow<let M: u32, let N: u32, let S: u32>(
args_hash,
is_static_call,
additional_scopes,
authorized_utility_call_targets,
)
}

Expand All @@ -68,12 +70,18 @@ pub unconstrained fn public_call_new_flow<let M: u32, let N: u32>(
public_call_new_flow_oracle(from, contract_address, calldata, is_static_call)
}

pub unconstrained fn execute_utility_function<let M: u32, let N: u32>(
pub unconstrained fn execute_utility_function<let M: u32, let N: u32, let T: u32>(
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; M],
authorized_utility_call_targets: [AztecAddress; T],
) -> [Field; N] {
execute_utility_function_oracle(contract_address, function_selector, args)
execute_utility_function_oracle(
contract_address,
function_selector,
args,
authorized_utility_call_targets,
)
}

#[oracle(aztec_txe_getNextBlockNumber)]
Expand Down Expand Up @@ -209,14 +217,15 @@ pub unconstrained fn add_account(secret: Field) -> TestAccount {}
pub unconstrained fn add_authwit(address: AztecAddress, message_hash: Field) {}

#[oracle(aztec_txe_privateCallNewFlow)]
unconstrained fn private_call_new_flow_oracle<let M: u32, let N: u32, let S: u32>(
unconstrained fn private_call_new_flow_oracle<let M: u32, let N: u32, let S: u32, let T: u32>(
_from: Option<AztecAddress>,
_contract_address: AztecAddress,
_function_selector: FunctionSelector,
_args: [Field; M],
_args_hash: Field,
_is_static_call: bool,
_additional_scopes: [AztecAddress; S],
_authorized_utility_call_targets: [AztecAddress; T],
) -> [Field; N] {}

#[oracle(aztec_txe_publicCallNewFlow)]
Expand All @@ -228,10 +237,11 @@ unconstrained fn public_call_new_flow_oracle<let M: u32, let N: u32>(
) -> [Field; N] {}

#[oracle(aztec_txe_executeUtilityFunction)]
unconstrained fn execute_utility_function_oracle<let M: u32, let N: u32>(
unconstrained fn execute_utility_function_oracle<let M: u32, let N: u32, let T: u32>(
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; M],
authorized_utility_call_targets: [AztecAddress; T],
) -> [Field; N] {}

#[oracle(aztec_txe_setTopLevelTXEContext)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

use aztec::macros::aztec;

mod test;

#[aztec]
pub contract NestedUtility {
use aztec::macros::functions::external;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::NestedUtility;
use aztec::{
protocol::address::AztecAddress,
test::helpers::test_environment::{CallPrivateOptions, ExecuteUtilityOptions, TestEnvironment},
};

unconstrained fn setup() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress) {
let mut env = TestEnvironment::new();
let account = env.create_light_account();
let addr_a = env.deploy("NestedUtility").without_initializer();
let addr_b = env.deploy("NestedUtility").without_initializer();
(env, account, addr_a, addr_b)
}

#[test]
unconstrained fn same_contract_utility_call_from_utility_succeeds() {
let (env, _, addr_a, _) = setup();

let result: Field = env.execute_utility(NestedUtility::at(addr_a).pow_utility(2, 10));
assert_eq(result, 1024);
}

#[test]
unconstrained fn same_contract_utility_call_from_private_succeeds() {
let (env, account, addr_a, _) = setup();

let result: Field =
env.call_private(account, NestedUtility::at(addr_a).pow_private(2, 10));
assert_eq(result, 1024);
}

#[test(should_fail_with = "Cross-contract utility call denied")]
unconstrained fn cross_contract_utility_call_from_utility_denied_by_default() {
let (env, _, addr_a, addr_b) = setup();

let _: Field = env.execute_utility(
NestedUtility::at(addr_a).delegate_pow_utility(addr_b, 2, 3),
);
}

#[test(should_fail_with = "Cross-contract utility call denied")]
unconstrained fn cross_contract_utility_call_from_private_denied_by_default() {
let (env, account, addr_a, addr_b) = setup();

let _: Field = env.call_private(
account,
NestedUtility::at(addr_a).delegate_pow_private(addr_b, 2, 3),
);
}

#[test]
unconstrained fn cross_contract_utility_call_from_utility_succeeds_with_authorization() {
let (env, _, addr_a, addr_b) = setup();

let result: Field = env.execute_utility_opts(
ExecuteUtilityOptions::new().with_authorized_utility_call_targets([addr_b]),
NestedUtility::at(addr_a).delegate_pow_utility(addr_b, 2, 3),
);
assert_eq(result, 8);
}

#[test]
unconstrained fn cross_contract_utility_call_from_private_succeeds_with_authorization() {
let (env, account, addr_a, addr_b) = setup();

let result: Field = env.call_private_opts(
account,
CallPrivateOptions::new().with_authorized_utility_call_targets([addr_b]),
NestedUtility::at(addr_a).delegate_pow_private(addr_b, 2, 3),
);
assert_eq(result, 8);
}
Loading
Loading