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
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,46 @@ self.enqueue(Token::at(token_address).mint_to_public(recipient, amount));
:::info
Public functions execute after all private execution completes. Return values are not available in the private context. Learn more about [call types](../../foundational-topics/call_types.md).
:::

## Utility calls

Utility functions can call other utility functions. These calls run entirely offchain in PXE and are never proven, so no guarantees are made on the correctness of their results.

### Utility-to-utility calls

From a utility function, use `self.call()` as with other call types:

```rust
let balance = self.call(Token::at(token_address).get_balance_of(owner));
```

To call a utility function of your own contract, use the `self.call_self` stubs instead:

```rust
self.call_self.my_utility_function(args)
```

### Private-to-utility calls

Private functions can also call utility functions, through `self.utility`. The call executes unconstrained code, so it must be wrapped in `unsafe`, and its result is not proven: use it to inform logic, never as an input to a constrained assertion without validating it first.

```rust
// Safety: result is unconstrained
let balance = unsafe { self.utility.call(Token::at(token_address).get_balance_of(owner)) };
```

The same-contract stubs are available here as `self.utility.call_self`:

```rust
unsafe { self.utility.call_self.my_utility_function(args) }
```

Public functions cannot call utility functions: they run on the sequencer, which has no access to private state or PXE.

### Cross-contract authorization

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.

Make the error message PXE emits on lack of authorization contain an link to an errors page with the netlify redirect set to point at this section


A utility call to the calling contract itself always succeeds. A call to a _different_ contract can expose that contract's private state to the caller, so PXE asks the wallet to authorize it before proceeding. If the wallet denies the call, or no `authorizeUtilityCall` hook is configured, the simulation fails with `Cross-contract utility call denied`. See [execution hooks](../../foundational-topics/pxe/execution_hooks.md#authorizeutilitycall) for how wallets handle these requests, and for authorizing targets in Noir tests with `with_authorized_utility_call_targets`.

### `msg_sender` in nested utility calls

A utility function called from another contract can read the calling contract's address via `self.msg_sender()`, mirroring private and public functions. A utility function invoked directly by an application has no caller: `self.msg_sender()` panics, and `self.context.maybe_msg_sender()` returns `Option::none()`. Within a simulation PXE takes this value from the actual call graph, but utility execution as a whole is unconstrained, so contracts must not rely on it as a security guarantee.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Public functions operate on public state, similar to EVM contracts. They can wri

Create offchain query functions using the `#[external("utility")]` annotation with `unconstrained`.

Utility functions are standalone unconstrained functions that cannot be called from private or public functions. They are meant to be called by _applications_ to perform auxiliary tasks like querying contract state or processing offchain messages. Example:
Utility functions are unconstrained functions that applications call to perform auxiliary tasks, like querying contract state or processing offchain messages. Their execution is never proven, so no guarantees are made on the correctness of their results. They can also be called from other utility functions and from private functions. See [utility calls](../calling_contracts.md#utility-calls). Public functions cannot call them. Example:

#include_code get_counter /docs/examples/contracts/counter_contract/src/main.nr rust

Expand Down
4 changes: 3 additions & 1 deletion docs/docs-developers/docs/foundational-topics/call_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,13 @@ Public functions can be called either directly in a public context (as shown abo

### Utility

Contract functions marked with `#[external("utility")]` cannot be called as part of a transaction. They are only invoked by applications that interact with contracts for:
Contract functions marked with `#[external("utility")]` are never proven as part of a transaction, even when called from a private function. They are invoked by applications that interact with contracts for:

- **State queries**: Reading from both private and public state via an offchain client
- **Local state management**: Modifying contract-related PXE state (e.g., processing logs in Aztec.nr)

Utility functions can also be called from other utility functions, and from private functions as unconstrained code. Calls that cross a contract boundary require wallet authorization. See [utility calls](../aztec-nr/framework-description/calling_contracts.md#utility-calls) for how to make them.

Since utility execution is unconstrained and relies heavily on oracle calls, no guarantees are made on the correctness of results. However, you can verify that the bytecode being executed is correct, since a contract's address includes a commitment to all of its utility functions.

### aztec.js
Expand Down
2 changes: 1 addition & 1 deletion docs/netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@
[[redirects]]
# PXE: cross-contract utility call denied by execution hook
from = "/errors/11"
to = "/developers/docs/aztec-nr/debugging#cross-contract-utility-call-denied"
to = "/developers/docs/aztec-nr/framework-description/calling_contracts#cross-contract-authorization"

[[redirects]]
# Incompatible oracle version between test and test environment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,11 +436,24 @@ pub struct PrivateUtilityCalls<CallSelf> {
impl<CallSelf> PrivateUtilityCalls<CallSelf> {
/// Makes a utility contract call from a private function.
///
/// Note: only same-contract utility calls are currently supported. See TODO(F-29).
/// The call executes unconstrained code, so it must be wrapped in `unsafe`, and its result is not part of any
/// circuit proof: use it to inform logic, never as an input to a constrained assertion without validating it
/// first.
///
/// The callee observes this contract's address as its `msg_sender`: see the `msg_sender` docs on
/// [`ContractSelfUtility`](crate::contract_self::ContractSelfUtility).
///
/// To call one of this contract's own utility functions, prefer the type-safe `self.utility.call_self` stubs.
///
/// ## Authorization
///
/// A call to this contract itself always succeeds. A call to a different contract can expose that contract's
/// private state to the caller, so it requires explicit authorization. A call that is not authorized fails.
///
/// # Example
/// ```noir
/// unsafe { self.utility.call(Token::at(self.address).get_balance_of(owner)) }
/// // Safety: result is unconstrained
/// unsafe { self.utility.call(Token::at(address).get_balance_of(owner)) }
/// ```
pub unconstrained fn call<let M: u32, let N: u32, T>(_self: Self, call: UtilityCall<M, N, T>) -> T
where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,17 @@ impl<Storage, CallSelf> ContractSelfUtility<Storage, CallSelf> {

/// Makes a utility contract call from another utility function.
///
/// Note: only same-contract utility calls are currently supported. See TODO(F-29).
/// The call runs entirely offchain in PXE and is never proven, so no guarantees are made on the correctness of
/// its result.
///
/// The callee observes this contract's address as its `msg_sender`: see [`ContractSelfUtility::msg_sender`].
///
/// To call one of this contract's own utility functions, prefer the type-safe `self.call_self` stubs.
///
/// ## Authorization
///
/// A call to this contract itself always succeeds. A call to a different contract can expose that contract's
/// private state to the caller, so it requires explicit authorization. A call that is not authorized fails.
///
/// # Example
/// ```noir
Expand Down
Loading