Skip to content

Commit 64dcfcb

Browse files
nchamoaztec-bot
authored andcommitted
feat(txe): allow authorizing cross-contract utility calls in nr tests (#23064)
1 parent c5bc4f1 commit 64dcfcb

9 files changed

Lines changed: 256 additions & 28 deletions

File tree

noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr

Lines changed: 113 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -132,21 +132,22 @@ struct UtilityContextOptions {
132132
/// ```noir
133133
/// env.call_private_opts(from, CallPrivateOptions::new().with_additional_scopes([other]), call);
134134
/// ```
135-
pub struct CallPrivateOptions<let N: u32> {
135+
pub struct CallPrivateOptions<let N: u32, let T: u32> {
136136
additional_scopes: [AztecAddress; N],
137+
authorized_utility_call_targets: [AztecAddress; T],
137138
}
138139

139-
impl CallPrivateOptions<0> {
140+
impl CallPrivateOptions<0, 0> {
140141
/// Creates default `CallPrivateOptions`.
141142
///
142143
/// The default values are the same as if using [`TestEnvironment::call_private`] instead of
143144
/// [`TestEnvironment::call_private_opts`].
144145
pub fn new() -> Self {
145-
CallPrivateOptions { additional_scopes: [] }
146+
CallPrivateOptions { additional_scopes: [], authorized_utility_call_targets: [] }
146147
}
147148
}
148149

149-
impl<let N: u32> CallPrivateOptions<N> {
150+
impl<let N: u32, let T: u32> CallPrivateOptions<N, T> {
150151
/// Grants access to secrets of additional accounts.
151152
///
152153
/// By default only the secrets that belong to the `from` account can be accessed: this function lets contracts
@@ -155,10 +156,27 @@ impl<let N: u32> CallPrivateOptions<N> {
155156
/// Example usage includes accounts withdrawing from an escrow, which may require accessing the escrow's own notes
156157
/// and secrets.
157158
pub fn with_additional_scopes<let N_2: u32>(
158-
_self: Self,
159+
self,
159160
additional_scopes: [AztecAddress; N_2],
160-
) -> CallPrivateOptions<N_2> {
161-
CallPrivateOptions { additional_scopes }
161+
) -> CallPrivateOptions<N_2, T> {
162+
CallPrivateOptions {
163+
additional_scopes,
164+
authorized_utility_call_targets: self.authorized_utility_call_targets,
165+
}
166+
}
167+
168+
/// Authorizes cross-contract utility calls to the given target contracts during this call.
169+
///
170+
/// By default, cross-contract utility calls are denied. This method lets the test author specify which target
171+
/// contracts are allowed to be called via utility functions during execution.
172+
pub fn with_authorized_utility_call_targets<let T_2: u32>(
173+
self,
174+
targets: [AztecAddress; T_2],
175+
) -> CallPrivateOptions<N, T_2> {
176+
CallPrivateOptions {
177+
additional_scopes: self.additional_scopes,
178+
authorized_utility_call_targets: targets,
179+
}
162180
}
163181
}
164182

@@ -169,21 +187,22 @@ impl<let N: u32> CallPrivateOptions<N> {
169187
/// ```noir
170188
/// env.view_private_opts(from, ViewPrivateOptions::new().with_additional_scopes([other]), call);
171189
/// ```
172-
pub struct ViewPrivateOptions<let S: u32> {
190+
pub struct ViewPrivateOptions<let S: u32, let T: u32> {
173191
additional_scopes: [AztecAddress; S],
192+
authorized_utility_call_targets: [AztecAddress; T],
174193
}
175194

176-
impl ViewPrivateOptions<0> {
195+
impl ViewPrivateOptions<0, 0> {
177196
/// Creates default `ViewPrivateOptions`.
178197
///
179198
/// The default values are the same as if using [`TestEnvironment::view_private`] instead of
180199
/// [`TestEnvironment::view_private_opts`].
181200
pub fn new() -> Self {
182-
ViewPrivateOptions { additional_scopes: [] }
201+
ViewPrivateOptions { additional_scopes: [], authorized_utility_call_targets: [] }
183202
}
184203
}
185204

186-
impl<let S: u32> ViewPrivateOptions<S> {
205+
impl<let S: u32, let T: u32> ViewPrivateOptions<S, T> {
187206
/// Grants access to secrets of additional accounts.
188207
///
189208
/// By default only the secrets that belong to the `from` account can be accessed: this function lets contracts
@@ -192,10 +211,27 @@ impl<let S: u32> ViewPrivateOptions<S> {
192211
/// Example usage includes accounts querying an escrow's balance, which may require accessing the escrow's own
193212
/// notes.
194213
pub fn with_additional_scopes<let S2: u32>(
195-
_self: Self,
214+
self,
196215
additional_scopes: [AztecAddress; S2],
197-
) -> ViewPrivateOptions<S2> {
198-
ViewPrivateOptions { additional_scopes }
216+
) -> ViewPrivateOptions<S2, T> {
217+
ViewPrivateOptions {
218+
additional_scopes,
219+
authorized_utility_call_targets: self.authorized_utility_call_targets,
220+
}
221+
}
222+
223+
/// Authorizes cross-contract utility calls to the given target contracts during this call.
224+
///
225+
/// By default, cross-contract utility calls are denied. This method lets the test author specify which target
226+
/// contracts are allowed to be called via utility functions during execution.
227+
pub fn with_authorized_utility_call_targets<let T_2: u32>(
228+
self,
229+
targets: [AztecAddress; T_2],
230+
) -> ViewPrivateOptions<S, T_2> {
231+
ViewPrivateOptions {
232+
additional_scopes: self.additional_scopes,
233+
authorized_utility_call_targets: targets,
234+
}
199235
}
200236
}
201237

@@ -207,6 +243,40 @@ struct EventDiscoveryOptions {
207243
contract_address: Option<AztecAddress>,
208244
}
209245

246+
/// Configuration for [`TestEnvironment::execute_utility_opts`].
247+
///
248+
/// Constructed by calling [`ExecuteUtilityOptions::new`] and then chaining methods:
249+
///
250+
/// ```noir
251+
/// env.execute_utility_opts(
252+
/// ExecuteUtilityOptions::new().with_authorized_utility_call_targets([target]),
253+
/// SomeContract::at(addr).some_utility_function(),
254+
/// );
255+
/// ```
256+
pub struct ExecuteUtilityOptions<let T: u32> {
257+
authorized_utility_call_targets: [AztecAddress; T],
258+
}
259+
260+
impl ExecuteUtilityOptions<0> {
261+
/// Creates default `ExecuteUtilityOptions`.
262+
pub fn new() -> Self {
263+
ExecuteUtilityOptions { authorized_utility_call_targets: [] }
264+
}
265+
}
266+
267+
impl<let T: u32> ExecuteUtilityOptions<T> {
268+
/// Authorizes cross-contract utility calls to the given target contracts during this call.
269+
///
270+
/// By default, cross-contract utility calls are denied. This method lets the test author specify which target
271+
/// contracts are allowed to be called via utility functions during execution.
272+
pub fn with_authorized_utility_call_targets<let T_2: u32>(
273+
_self: Self,
274+
targets: [AztecAddress; T_2],
275+
) -> ExecuteUtilityOptions<T_2> {
276+
ExecuteUtilityOptions { authorized_utility_call_targets: targets }
277+
}
278+
}
279+
210280
/// Configuration values for [`TestEnvironment::deploy_opts`]. Meant to be used by calling `new` and then chaining
211281
/// methods setting each value, e.g.:
212282
/// ```noir
@@ -629,10 +699,10 @@ impl TestEnvironment {
629699
}
630700

631701
/// Variant of `call_private` which allows specifying multiple configuration values via `CallPrivateOptions`.
632-
pub unconstrained fn call_private_opts<let M: u32, let N: u32, let S: u32, T>(
702+
pub unconstrained fn call_private_opts<let M: u32, let N: u32, let S: u32, let U: u32, T>(
633703
_self: Self,
634704
from: AztecAddress,
635-
opts: CallPrivateOptions<S>,
705+
opts: CallPrivateOptions<S, U>,
636706
call: PrivateCall<M, N, T>,
637707
) -> T
638708
where
@@ -646,6 +716,7 @@ impl TestEnvironment {
646716
hash_args(call.args),
647717
/*is_static=*/ false,
648718
opts.additional_scopes,
719+
opts.authorized_utility_call_targets,
649720
);
650721

651722
T::deserialize(serialized_return_values)
@@ -670,10 +741,10 @@ impl TestEnvironment {
670741
}
671742

672743
/// Variant of `view_private` which allows specifying multiple configuration values via `ViewPrivateOptions`.
673-
pub unconstrained fn view_private_opts<let M: u32, let N: u32, let S: u32, T>(
744+
pub unconstrained fn view_private_opts<let M: u32, let N: u32, let S: u32, let U: u32, T>(
674745
_self: Self,
675746
from: AztecAddress,
676-
opts: ViewPrivateOptions<S>,
747+
opts: ViewPrivateOptions<S, U>,
677748
call: PrivateStaticCall<M, N, T>,
678749
) -> T
679750
where
@@ -687,6 +758,7 @@ impl TestEnvironment {
687758
hash_args(call.args),
688759
/*is_static=*/ true,
689760
opts.additional_scopes,
761+
opts.authorized_utility_call_targets,
690762
);
691763

692764
T::deserialize(serialized_return_values)
@@ -701,12 +773,32 @@ impl TestEnvironment {
701773
/// let contract_addr = env.deploy("SampleContract").without_initializer();
702774
/// let return_value = env.execute_utility(SampleContract::at(contract_addr).sample_utility_function());
703775
/// ```
704-
pub unconstrained fn execute_utility<let M: u32, let N: u32, T>(_self: Self, call: UtilityCall<M, N, T>) -> T
776+
pub unconstrained fn execute_utility<let M: u32, let N: u32, T>(
777+
self: Self,
778+
call: UtilityCall<M, N, T>,
779+
) -> T
705780
where
706781
T: Deserialize,
707782
{
708-
let serialized_return_values =
709-
txe_oracles::execute_utility_function(call.target_contract, call.selector, call.args);
783+
self.execute_utility_opts(ExecuteUtilityOptions::new(), call)
784+
}
785+
786+
/// Variant of `execute_utility` which allows specifying configuration values via `ExecuteUtilityOptions`, such as
787+
/// authorizing cross-contract utility calls.
788+
pub unconstrained fn execute_utility_opts<let M: u32, let N: u32, let U: u32, T>(
789+
_self: Self,
790+
opts: ExecuteUtilityOptions<U>,
791+
call: UtilityCall<M, N, T>,
792+
) -> T
793+
where
794+
T: Deserialize,
795+
{
796+
let serialized_return_values = txe_oracles::execute_utility_function(
797+
call.target_contract,
798+
call.selector,
799+
call.args,
800+
opts.authorized_utility_call_targets,
801+
);
710802

711803
T::deserialize(serialized_return_values)
712804
}

noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ pub unconstrained fn deploy<let M: u32, let N: u32, let P: u32>(
3636
ContractInstance::deserialize(instance_fields)
3737
}
3838

39-
pub unconstrained fn private_call_new_flow<let M: u32, let N: u32, let S: u32>(
39+
pub unconstrained fn private_call_new_flow<let M: u32, let N: u32, let S: u32, let T: u32>(
4040
from: Option<AztecAddress>,
4141
contract_address: AztecAddress,
4242
function_selector: FunctionSelector,
4343
args: [Field; M],
4444
args_hash: Field,
4545
is_static_call: bool,
4646
additional_scopes: [AztecAddress; S],
47+
authorized_utility_call_targets: [AztecAddress; T],
4748
) -> [Field; N] {
4849
private_call_new_flow_oracle(
4950
from,
@@ -53,6 +54,7 @@ pub unconstrained fn private_call_new_flow<let M: u32, let N: u32, let S: u32>(
5354
args_hash,
5455
is_static_call,
5556
additional_scopes,
57+
authorized_utility_call_targets,
5658
)
5759
}
5860

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

71-
pub unconstrained fn execute_utility_function<let M: u32, let N: u32>(
73+
pub unconstrained fn execute_utility_function<let M: u32, let N: u32, let T: u32>(
7274
contract_address: AztecAddress,
7375
function_selector: FunctionSelector,
7476
args: [Field; M],
77+
authorized_utility_call_targets: [AztecAddress; T],
7578
) -> [Field; N] {
76-
execute_utility_function_oracle(contract_address, function_selector, args)
79+
execute_utility_function_oracle(
80+
contract_address,
81+
function_selector,
82+
args,
83+
authorized_utility_call_targets,
84+
)
7785
}
7886

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

211219
#[oracle(aztec_txe_privateCallNewFlow)]
212-
unconstrained fn private_call_new_flow_oracle<let M: u32, let N: u32, let S: u32>(
220+
unconstrained fn private_call_new_flow_oracle<let M: u32, let N: u32, let S: u32, let T: u32>(
213221
_from: Option<AztecAddress>,
214222
_contract_address: AztecAddress,
215223
_function_selector: FunctionSelector,
216224
_args: [Field; M],
217225
_args_hash: Field,
218226
_is_static_call: bool,
219227
_additional_scopes: [AztecAddress; S],
228+
_authorized_utility_call_targets: [AztecAddress; T],
220229
) -> [Field; N] {}
221230

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

230239
#[oracle(aztec_txe_executeUtilityFunction)]
231-
unconstrained fn execute_utility_function_oracle<let M: u32, let N: u32>(
240+
unconstrained fn execute_utility_function_oracle<let M: u32, let N: u32, let T: u32>(
232241
contract_address: AztecAddress,
233242
function_selector: FunctionSelector,
234243
args: [Field; M],
244+
authorized_utility_call_targets: [AztecAddress; T],
235245
) -> [Field; N] {}
236246

237247
#[oracle(aztec_txe_setTopLevelTXEContext)]

noir-projects/noir-contracts/contracts/test/nested_utility_contract/src/main.nr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
use aztec::macros::aztec;
55

6+
mod test;
7+
68
#[aztec]
79
pub contract NestedUtility {
810
use aztec::macros::functions::external;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate::NestedUtility;
2+
use aztec::{
3+
protocol::address::AztecAddress,
4+
test::helpers::test_environment::{CallPrivateOptions, ExecuteUtilityOptions, TestEnvironment},
5+
};
6+
7+
unconstrained fn setup() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress) {
8+
let mut env = TestEnvironment::new();
9+
let account = env.create_light_account();
10+
let addr_a = env.deploy("NestedUtility").without_initializer();
11+
let addr_b = env.deploy("NestedUtility").without_initializer();
12+
(env, account, addr_a, addr_b)
13+
}
14+
15+
#[test]
16+
unconstrained fn same_contract_utility_call_from_utility_succeeds() {
17+
let (env, _, addr_a, _) = setup();
18+
19+
let result: Field = env.execute_utility(NestedUtility::at(addr_a).pow_utility(2, 10));
20+
assert_eq(result, 1024);
21+
}
22+
23+
#[test]
24+
unconstrained fn same_contract_utility_call_from_private_succeeds() {
25+
let (env, account, addr_a, _) = setup();
26+
27+
let result: Field =
28+
env.call_private(account, NestedUtility::at(addr_a).pow_private(2, 10));
29+
assert_eq(result, 1024);
30+
}
31+
32+
#[test(should_fail_with = "Cross-contract utility call denied")]
33+
unconstrained fn cross_contract_utility_call_from_utility_denied_by_default() {
34+
let (env, _, addr_a, addr_b) = setup();
35+
36+
let _: Field = env.execute_utility(
37+
NestedUtility::at(addr_a).delegate_pow_utility(addr_b, 2, 3),
38+
);
39+
}
40+
41+
#[test(should_fail_with = "Cross-contract utility call denied")]
42+
unconstrained fn cross_contract_utility_call_from_private_denied_by_default() {
43+
let (env, account, addr_a, addr_b) = setup();
44+
45+
let _: Field = env.call_private(
46+
account,
47+
NestedUtility::at(addr_a).delegate_pow_private(addr_b, 2, 3),
48+
);
49+
}
50+
51+
#[test]
52+
unconstrained fn cross_contract_utility_call_from_utility_succeeds_with_authorization() {
53+
let (env, _, addr_a, addr_b) = setup();
54+
55+
let result: Field = env.execute_utility_opts(
56+
ExecuteUtilityOptions::new().with_authorized_utility_call_targets([addr_b]),
57+
NestedUtility::at(addr_a).delegate_pow_utility(addr_b, 2, 3),
58+
);
59+
assert_eq(result, 8);
60+
}
61+
62+
#[test]
63+
unconstrained fn cross_contract_utility_call_from_private_succeeds_with_authorization() {
64+
let (env, account, addr_a, addr_b) = setup();
65+
66+
let result: Field = env.call_private_opts(
67+
account,
68+
CallPrivateOptions::new().with_authorized_utility_call_targets([addr_b]),
69+
NestedUtility::at(addr_a).delegate_pow_private(addr_b, 2, 3),
70+
);
71+
assert_eq(result, 8);
72+
}

0 commit comments

Comments
 (0)