Skip to content

Commit 05eaf1e

Browse files
nventuroAztecBot
authored andcommitted
chore: cherry-pick #22968 'feat: allow setting additional scopes in nr tests' (with conflicts)
Conflicted files (preserved as-is for review): - docs/docs-developers/docs/resources/migration_notes.md - noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr - yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts - yarn-project/stdlib/src/aztec-address/index.ts
1 parent 3560200 commit 05eaf1e

11 files changed

Lines changed: 363 additions & 62 deletions

File tree

docs/docs-developers/docs/resources/migration_notes.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,151 @@ Aztec is in active development. Each version may introduce breaking changes that
99

1010
## TBD
1111

12+
<<<<<<< HEAD
13+
=======
14+
### [Aztec.nr] TXE `call_public_incognito` no longer takes a `from` parameter
15+
16+
`TestEnvironment::call_public_incognito` previously accepted a `from` address that was silently ignored (the function always uses a null `msg_sender`). The `from` parameter has been removed.
17+
18+
```diff
19+
- env.call_public_incognito(sender, SampleContract::at(addr).some_function());
20+
+ env.call_public_incognito(SampleContract::at(addr).some_function());
21+
```
22+
23+
If you need to call a public function *with* a sender, use `call_public` instead.
24+
25+
### [Aztec.nr] TXE `view_public_incognito` is deprecated
26+
27+
`TestEnvironment::view_public_incognito` is now deprecated in favor of `view_public`, which has the same behavior (null `msg_sender`, static call).
28+
29+
```diff
30+
- env.view_public_incognito(SampleContract::at(addr).some_view());
31+
+ env.view_public(SampleContract::at(addr).some_view());
32+
```
33+
34+
### [Aztec.js] `DeployMethod` address-affecting parameters move to construction time
35+
36+
Salt, deployer, and public keys are now passed when the `DeployMethod` is constructed, not on every call to `send` / `simulate` / `request` / `getInstance`. This locks the contract address once it is determined and prevents the silent salt-cache poisoning bug where the address could change between calls.
37+
38+
`contractAddressSalt`, `deployer`, and `universalDeploy` have been removed from `DeployOptions`, `RequestDeployOptions`, and `SimulateDeployOptions`. They now live on a new `DeployInstantiationOptions` argument passed at construction. `deployer` and `universalDeploy` are mutually exclusive; passing both throws. `Contract.deployWithPublicKeys` and the generated `MyContract.deployWithPublicKeys(...)` factories have been removed; pass `publicKeys` via the `instantiation` argument of `deploy(...)` instead. The buggy synchronous `address` and `partialAddress` getters have been removed and replaced with `getAddress()` and `getPartialAddress()` (both `async`).
39+
40+
The compact form keeps working: `MyContract.deploy(wallet, ...args).send({ from: alice })` deploys with `deployer = alice` and `salt = random()`, exactly as before. The deployer is locked the first time `send` / `simulate` / `profile` is called (from `options.from`, with `NO_FROM` or undefined → universal) and cannot change after that:
41+
42+
- Subsequent `send` / `simulate` / `profile` calls with a `from` that would imply a different deployer throw, instead of silently producing a different address.
43+
- A lock to universal (`AztecAddress.ZERO`) is the only one compatible with any sender, since the universal address does not depend on `from`.
44+
- A lock to a concrete address only accepts that exact `from` on subsequent calls.
45+
46+
**Migration:**
47+
48+
Universal deployment with a fixed salt:
49+
50+
```diff
51+
- const deploy = MyContract.deploy(wallet, ...args);
52+
- await deploy.send({
53+
- from: alice,
54+
- contractAddressSalt: salt,
55+
- universalDeploy: true,
56+
- });
57+
+ const deploy = MyContract.deploy(wallet, ...args, { salt, universalDeploy: true });
58+
+ await deploy.send({ from: alice });
59+
```
60+
61+
Non-universal deploy where `from` doubles as the deployer:
62+
63+
```diff
64+
- const deploy = MyContract.deploy(wallet, ...args);
65+
- await deploy.send({ from: alice, contractAddressSalt: salt });
66+
+ const deploy = MyContract.deploy(wallet, ...args, { salt });
67+
+ await deploy.send({ from: alice });
68+
```
69+
70+
If you need to read the address before sending, lock the deployer at construction:
71+
72+
```typescript
73+
const deploy = MyContract.deploy(wallet, ...args, { salt, deployer: alice });
74+
const address = await deploy.getAddress(); // resolves; deployer was locked at construction
75+
await deploy.send({ from: alice }); // deploys at the address `getAddress` returned
76+
```
77+
78+
Universal deploys can be sent by any account, since the universal address does not depend on `from`:
79+
80+
```typescript
81+
const deploy = MyContract.deploy(wallet, ...args, { universalDeploy: true });
82+
await deploy.send({ from: bob }); // OK, universal accepts any sender
83+
```
84+
85+
A lock to a concrete deployer rejects sending from a different account, instead of silently deploying at a different address:
86+
87+
```typescript
88+
const deploy = MyContract.deploy(wallet, ...args, { deployer: alice });
89+
await deploy.send({ from: bob }); // throws: deployer is locked to alice
90+
```
91+
92+
`deployWithPublicKeys` is gone; pass `publicKeys` in the instantiation options instead:
93+
94+
```diff
95+
- const deploy = MyContract.deployWithPublicKeys(publicKeys, wallet, ...args);
96+
+ const deploy = MyContract.deploy(wallet, ...args, { publicKeys });
97+
```
98+
99+
`ContractDeployer.deploy(...)` now takes the instantiation argument as its first parameter (pass `{}` to use defaults and rely on lazy locking from `from`):
100+
101+
```diff
102+
- const cd = new ContractDeployer(artifact, wallet);
103+
- await cd.deploy(...ctorArgs).send({ from: alice, contractAddressSalt: salt });
104+
+ const cd = new ContractDeployer(artifact, wallet);
105+
+ await cd.deploy(ctorArgs, { salt }).send({ from: alice });
106+
```
107+
108+
The synchronous `address` / `partialAddress` getters are gone:
109+
110+
```diff
111+
- const address = deploy.address; // sync, possibly undefined
112+
- const partial = await deploy.partialAddress; // sync getter wrapping async value
113+
+ const address = await deploy.getAddress(); // requires the deployer to be locked
114+
+ const partial = await deploy.getPartialAddress(); // requires the deployer to be locked
115+
```
116+
117+
`getInstance()` no longer takes options; use the construction-time instantiation instead:
118+
119+
```diff
120+
- const instance = await deploy.getInstance({ contractAddressSalt: salt });
121+
+ const deploy = MyContract.deploy(wallet, ...args, { salt, deployer: alice });
122+
+ const instance = await deploy.getInstance();
123+
```
124+
125+
### [aztec-up] Bundled binaries are no longer exposed under bare names on `PATH`
126+
127+
The Aztec installer previously placed bundled binaries directly into `$HOME/.aztec/current/bin` under bare names (`forge`, `nargo`, `bb`, `pxe`, ...). Anything with the same name in your own `PATH` was silently shadowed in unrelated projects.
128+
129+
Every bundled binary is now exposed only under an `aztec-` prefixed name in `$HOME/.aztec/current/bin`. Bare names are not on `PATH` at all and resolve to your own install (if any).
130+
131+
| Was on `PATH` | Now |
132+
| ------------------ | ------------------------ |
133+
| `forge` | `aztec-forge` |
134+
| `cast` | `aztec-cast` |
135+
| `anvil` | `aztec-anvil` |
136+
| `chisel` | `aztec-chisel` |
137+
| `nargo` | `aztec-nargo` |
138+
| `noir-profiler` | `aztec-noir-profiler` |
139+
| `bb` | `aztec-bb` |
140+
| `bb-cli` | `aztec-bb-cli` |
141+
| `pxe` | `aztec-pxe` |
142+
| `txe` | `aztec-txe` |
143+
| `validator-client` | `aztec-validator-client` |
144+
| `blob-client` | `aztec-blob-client` |
145+
146+
`aztec`, `aztec-wallet`, and `aztec-up` keep their existing names.
147+
148+
If you relied on a bundled bare-name binary for general use:
149+
150+
- For Aztec contract work, prefer `aztec compile` and `aztec test`.
151+
- For other Noir / Foundry commands, invoke the `aztec-*` symlink directly (e.g. `aztec-nargo fmt`, `aztec-forge build`).
152+
- Or install Foundry / nargo separately via `foundryup` / `noirup`.
153+
154+
If you set `Noir: Nargo Path` in the VS Code Noir extension to `$HOME/.aztec/current/bin/nargo`, change it to `$HOME/.aztec/current/bin/aztec-nargo` (the symlink is a drop-in for `nargo`). See the [Noir VSCode Extension guide](../aztec-nr/installation.md) for details.
155+
156+
>>>>>>> 45f7a03ebe (feat: allow setting additional scopes in nr tests (#22968))
12157
### [PXE] `proveTx` takes an options bag
13158

14159
`PXE.proveTx` used to accept `scopes` as a positional argument; it now takes an options bag consistent with `simulateTx` and `profileTx`, and adds an optional `senderForTags` field. Update direct callers:

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

Lines changed: 112 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,80 @@ struct UtilityContextOptions {
125125
contract_address: Option<AztecAddress>,
126126
}
127127

128+
/// Configuration for [`TestEnvironment::call_private_opts`].
129+
///
130+
/// Constructed by calling [`CallPrivateOptions::new`] and then chaining methods setting each value:
131+
///
132+
/// ```noir
133+
/// env.call_private_opts(from, CallPrivateOptions::new().with_additional_scopes([other]), call);
134+
/// ```
135+
pub struct CallPrivateOptions<let N: u32> {
136+
additional_scopes: [AztecAddress; N],
137+
}
138+
139+
impl CallPrivateOptions<0> {
140+
/// Creates default `CallPrivateOptions`.
141+
///
142+
/// The default values are the same as if using [`TestEnvironment::call_private`] instead of
143+
/// [`TestEnvironment::call_private_opts`].
144+
pub fn new() -> Self {
145+
CallPrivateOptions { additional_scopes: [] }
146+
}
147+
}
148+
149+
impl<let N: u32> CallPrivateOptions<N> {
150+
/// Grants access to secrets of additional accounts.
151+
///
152+
/// By default only the secrets that belong to the `from` account can be accessed: this function lets contracts
153+
/// retrieve private information from other accounts during execution.
154+
///
155+
/// Example usage includes accounts withdrawing from an escrow, which may require accessing the escrow's own notes
156+
/// and secrets.
157+
pub fn with_additional_scopes<let N_2: u32>(
158+
_self: Self,
159+
additional_scopes: [AztecAddress; N_2],
160+
) -> CallPrivateOptions<N_2> {
161+
CallPrivateOptions { additional_scopes }
162+
}
163+
}
164+
165+
/// Configuration for [`TestEnvironment::view_private_opts`].
166+
///
167+
/// Constructed by calling [`ViewPrivateOptions::new`] and then chaining methods setting each value:
168+
///
169+
/// ```noir
170+
/// env.view_private_opts(from, ViewPrivateOptions::new().with_additional_scopes([other]), call);
171+
/// ```
172+
pub struct ViewPrivateOptions<let S: u32> {
173+
additional_scopes: [AztecAddress; S],
174+
}
175+
176+
impl ViewPrivateOptions<0> {
177+
/// Creates default `ViewPrivateOptions`.
178+
///
179+
/// The default values are the same as if using [`TestEnvironment::view_private`] instead of
180+
/// [`TestEnvironment::view_private_opts`].
181+
pub fn new() -> Self {
182+
ViewPrivateOptions { additional_scopes: [] }
183+
}
184+
}
185+
186+
impl<let S: u32> ViewPrivateOptions<S> {
187+
/// Grants access to secrets of additional accounts.
188+
///
189+
/// By default only the secrets that belong to the `from` account can be accessed: this function lets contracts
190+
/// retrieve private information from other accounts during execution.
191+
///
192+
/// Example usage includes accounts querying an escrow's balance, which may require accessing the escrow's own
193+
/// notes.
194+
pub fn with_additional_scopes<let S2: u32>(
195+
_self: Self,
196+
additional_scopes: [AztecAddress; S2],
197+
) -> ViewPrivateOptions<S2> {
198+
ViewPrivateOptions { additional_scopes }
199+
}
200+
}
201+
128202
struct NoteDiscoveryOptions {
129203
contract_address: Option<AztecAddress>,
130204
}
@@ -544,20 +618,34 @@ impl TestEnvironment {
544618
/// let return_value = env.call_private(caller, SampleContract::at(contract_addr).sample_private_function());
545619
/// ```
546620
pub unconstrained fn call_private<let M: u32, let N: u32, T>(
621+
self: Self,
622+
from: AztecAddress,
623+
call: PrivateCall<M, N, T>,
624+
) -> T
625+
where
626+
T: Deserialize,
627+
{
628+
self.call_private_opts(from, CallPrivateOptions::new(), call)
629+
}
630+
631+
/// 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>(
547633
_self: Self,
548634
from: AztecAddress,
635+
opts: CallPrivateOptions<S>,
549636
call: PrivateCall<M, N, T>,
550637
) -> T
551638
where
552639
T: Deserialize,
553640
{
554641
let serialized_return_values = txe_oracles::private_call_new_flow(
555-
from,
642+
Option::some(from),
556643
call.target_contract,
557644
call.selector,
558645
call.args,
559646
hash_args(call.args),
560647
/*is_static=*/ false,
648+
opts.additional_scopes,
561649
);
562650

563651
T::deserialize(serialized_return_values)
@@ -571,20 +659,34 @@ impl TestEnvironment {
571659
/// The `from` parameter specifies the account from whose perspective the view is executed. This affects
572660
/// scope isolation - only notes scoped to this account will be visible during execution.
573661
pub unconstrained fn view_private<let M: u32, let N: u32, T>(
662+
self: Self,
663+
from: AztecAddress,
664+
call: PrivateStaticCall<M, N, T>,
665+
) -> T
666+
where
667+
T: Deserialize,
668+
{
669+
self.view_private_opts(from, ViewPrivateOptions::new(), call)
670+
}
671+
672+
/// 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>(
574674
_self: Self,
575675
from: AztecAddress,
676+
opts: ViewPrivateOptions<S>,
576677
call: PrivateStaticCall<M, N, T>,
577678
) -> T
578679
where
579680
T: Deserialize,
580681
{
581682
let serialized_return_values = txe_oracles::private_call_new_flow(
582-
from,
683+
Option::some(from),
583684
call.target_contract,
584685
call.selector,
585686
call.args,
586687
hash_args(call.args),
587688
/*is_static=*/ true,
689+
opts.additional_scopes,
588690
);
589691

590692
T::deserialize(serialized_return_values)
@@ -705,16 +807,12 @@ impl TestEnvironment {
705807
/// Performs a public contract function call, including the processing of any nested public calls. Returns the
706808
/// result of the called function. Variant of `call_public`, but the `from` address (`msg_sender`) is set to
707809
/// "null".
708-
pub unconstrained fn call_public_incognito<let M: u32, let N: u32, T>(
709-
_self: Self,
710-
from: AztecAddress,
711-
call: PublicCall<M, N, T>,
712-
) -> T
810+
pub unconstrained fn call_public_incognito<let M: u32, let N: u32, T>(_self: Self, call: PublicCall<M, N, T>) -> T
713811
where
714812
T: Deserialize,
715813
{
716814
let serialized_return_values = txe_oracles::public_call_new_flow(
717-
Option::some(from),
815+
Option::none(),
718816
call.target_contract,
719817
call.selector,
720818
call.args,
@@ -727,13 +825,14 @@ impl TestEnvironment {
727825
/// Variant of `call_public` for public `#[view]` functions.
728826
///
729827
/// Unlike `call_public`, no transaction is created and no block is mined (since `#[view]` functions are only
730-
/// executable in a static context, and these produce no side effects).
828+
/// executable in a static context, and these produce no side effects). The `msg_sender` is set to "null", since
829+
/// view calls have no real caller.
731830
pub unconstrained fn view_public<let M: u32, let N: u32, T>(_self: Self, call: PublicStaticCall<M, N, T>) -> T
732831
where
733832
T: Deserialize,
734833
{
735834
let serialized_return_values = txe_oracles::public_call_new_flow(
736-
Option::some(AztecAddress::zero()),
835+
Option::none(),
737836
call.target_contract,
738837
call.selector,
739838
call.args,
@@ -743,26 +842,15 @@ impl TestEnvironment {
743842
T::deserialize(serialized_return_values)
744843
}
745844

746-
/// Variant of `view_public`, but the `from` address (`msg_sender`) is set to "null"
747-
///
748-
/// Unlike `call_public`, no transaction is created and no block is mined (since `#[view]` functions are only
749-
/// executable in a static context, and these produce no side effects).
845+
#[deprecated("use `TestEnvironment::view_public` instead")]
750846
pub unconstrained fn view_public_incognito<let M: u32, let N: u32, T>(
751-
_self: Self,
847+
self: Self,
752848
call: PublicStaticCall<M, N, T>,
753849
) -> T
754850
where
755851
T: Deserialize,
756852
{
757-
let serialized_return_values = txe_oracles::public_call_new_flow(
758-
Option::none(),
759-
call.target_contract,
760-
call.selector,
761-
call.args,
762-
true,
763-
);
764-
765-
T::deserialize(serialized_return_values)
853+
self.view_public(call)
766854
}
767855

768856
/// Discovers a note from a [`NoteMessage`], which is expected to have been created in the last transaction

0 commit comments

Comments
 (0)