Skip to content

Commit af6e4cf

Browse files
authored
chore: Accumulated backports to v4-next (#23059)
BEGIN_COMMIT_OVERRIDE feat(txe): add tx private logs to tx side effects oracle (#22889) chore: route backport CI failure notifications to #backports channel (#21779) END_COMMIT_OVERRIDE
2 parents 20b65b4 + 9e17f5b commit af6e4cf

33 files changed

Lines changed: 719 additions & 101 deletions

File tree

.github/workflows/ci3.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ jobs:
121121
if [ -n "${SLACK_BOT_TOKEN}" ]; then
122122
read -r -d '' data <<EOF || true
123123
{
124-
"channel": "#team-alpha",
124+
"channel": "#backports",
125125
"text": "CI3 failed on backport PR: <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }} - ${{ github.event.pull_request.title }}>\n<https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>"
126126
}
127127
EOF

docs/docs-developers/docs/aztec-nr/debugging.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,33 @@ LOG_LEVEL="silent;debug:simulator"
9999
| `No public key registered for address` | Call `wallet.registerSender(...)` |
100100
| `Direct invocation of ... functions is not supported` | Use `self.call()`, `self.view()`, or `self.enqueue()` to [call contract functions](framework-description/calling_contracts.md) |
101101
| `Failed to solve brillig function` | Check function parameters and note validity |
102+
| `Cross-contract utility call denied` | Configure an `authorizeUtilityCall` [execution hook](#cross-contract-utility-call-denied) on your PXE |
103+
104+
#### Cross-contract utility call denied
105+
106+
When a contract executes a utility function that calls into a different contract, PXE asks an **execution hook** whether the call should be allowed. If no hook is configured, or the hook denies the request, you will see:
107+
108+
```
109+
Cross-contract utility call denied: <reason>. <caller> attempted to call <target>:<selector> (<name>).
110+
```
111+
112+
To fix this, pass an `authorizeUtilityCall` hook when creating your PXE:
113+
114+
```typescript
115+
import { PXE } from "@aztec/pxe/server";
116+
117+
const pxe = await PXE.create({
118+
// ...other options
119+
hooks: {
120+
authorizeUtilityCall: async (request) => {
121+
// Inspect request.caller, request.target, request.functionSelector, etc.
122+
return { authorized: true };
123+
},
124+
},
125+
});
126+
```
127+
128+
The hook receives a `UtilityCallAuthorizationRequest` with the caller address, target address, function selector, function name, arguments, and caller context (`'private'` or `'utility'`). Return `{ authorized: true }` to allow or `{ authorized: false, reason: '...' }` to deny with a message.
102129

103130
### Circuit Errors
104131

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

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

1010
## TBD
1111

12+
### [Aztec.nr] TXE `call_public_incognito` no longer takes a `from` parameter
13+
14+
`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.
15+
16+
```diff
17+
- env.call_public_incognito(sender, SampleContract::at(addr).some_function());
18+
+ env.call_public_incognito(SampleContract::at(addr).some_function());
19+
```
20+
21+
If you need to call a public function *with* a sender, use `call_public` instead.
22+
23+
### [Aztec.nr] TXE `view_public_incognito` is deprecated
24+
25+
`TestEnvironment::view_public_incognito` is now deprecated in favor of `view_public`, which has the same behavior (null `msg_sender`, static call).
26+
27+
```diff
28+
- env.view_public_incognito(SampleContract::at(addr).some_view());
29+
+ env.view_public(SampleContract::at(addr).some_view());
30+
```
31+
1232
### [PXE] `proveTx` takes an options bag
1333

1434
`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:

docs/netlify.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,3 +813,8 @@
813813
# PXE: capsule operation attempted with a scope not in the allowed scopes list
814814
from = "/errors/10"
815815
to = "/developers/docs/aztec-nr/framework-description/advanced/how_to_use_capsules"
816+
817+
[[redirects]]
818+
# PXE: cross-contract utility call denied by execution hook
819+
from = "/errors/11"
820+
to = "/developers/docs/aztec-nr/debugging#cross-contract-utility-call-denied"

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

Lines changed: 118 additions & 27 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
@@ -998,14 +1086,17 @@ impl TestEnvironment {
9981086
// `MessageContext`, and might contain information about data (e.g. notes, events) contained in the message. In
9991087
// a real contract this data would have either been supplied by the `process_message` caller, of retrieved
10001088
// along with the message from a tagged log.
1001-
let (tx_hash, unique_note_hashes_in_tx, nullifiers_in_tx) = txe_oracles::get_last_tx_effects();
1089+
let effects = txe_oracles::get_last_tx_effects();
10021090

10031091
// Real messages would also have a recipient, a concept which is currently half-supported in `TestEnvironment`
10041092
// as events are properly scoped by recipient, but notes use a global scope instead. We therefore simply set
10051093
// the zero address as the recipient if one is not supplied, which PXE accepts as a scope despite this not
10061094
// being a registered account.
1007-
let message_context =
1008-
MessageContext { tx_hash, unique_note_hashes_in_tx, first_nullifier_in_tx: nullifiers_in_tx.get(0) };
1095+
let message_context = MessageContext {
1096+
tx_hash: effects.tx_hash,
1097+
unique_note_hashes_in_tx: effects.note_hashes,
1098+
first_nullifier_in_tx: effects.nullifiers.get(0),
1099+
};
10091100

10101101
// The scope controls which PXE account can see discovered notes/events. We use the zero address as the
10111102
// default scope if no recipient is supplied, which PXE accepts despite this not being a registered account.

0 commit comments

Comments
 (0)