Function Macros
This page explains three types of functions that exist on Aztec - public, private, and unconstrained; as well as all macros.
All Aztec macros
In addition to the function macros in Noir, Aztec also has its own macros for specific functions. An Aztec contract function can be annotated with more than 1 macro.
It is also worth mentioning Noir's unconstrained
function type here.
#[aztec(public)]
or#[aztec(private)]
- Whether the function is public or private (more in next section)#[aztec(initializer)]
- If one or more functions are marked as an initializer, then one of them must be called before any non-initilizer functions#[aztec(noinitcheck)]
- The function is able to be called before an initializer (if one exists)#[aztec(view)]
- Makes calls to the function static (see also Static calls)#[aztec(internal)]
- Function can only be called from within the contract
Example
Public, Private, and unconstrained types
For a deeper dive into how some of these functions work under the hood, check out the Inner Workings page.
Public
Functions
A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function.
All data inserted into private storage from a public function will be publicly viewable (not private).
To create a public function you can annotate it with the #[aztec(public)]
attribute. This will make the public context available within the function's execution scope.
#[aztec(public)]
fn set_minter(minter: AztecAddress, approve: bool) {
assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin");
storage.minters.at(minter).write(approve);
}
Source code: noir-projects/noir-contracts/contracts/token_contract/src/main.nr#L153-L163
Private
Functions
A private function operates on private information, and is executed by the user. Annotate the function with the #[aztec(private)]
attribute to tell the compiler it's a private function. This will make the private context available within the function's execution scope.
#[aztec(private)]
fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) {
let pending_shields = storage.pending_shields;
let secret_hash = compute_secret_hash(secret);
// Get 1 note (set_limit(1)) which has amount stored in field with index 0 (select(0, amount)) and secret_hash
// stored in field with index 1 (select(1, secret_hash)).
let mut options = NoteGetterOptions::new();
options = options.select(TransparentNote::properties().amount, amount, Option::none()).select(
TransparentNote::properties().secret_hash,
secret_hash,
Option::none()
).set_limit(1);
let notes = pending_shields.get_notes(options);
let note = notes.get_unchecked(0);
// Remove the note from the pending shields set
pending_shields.remove(note);
// Add the token note to user's balances set
// Note: Using context.msg_sender() as a sender below makes this incompatible with escrows because we send
// outgoing logs to that address and to send outgoing logs you need to get a hold of ovsk_m.
let from = context.msg_sender();
storage.balances.add(to, U128::from_integer(amount)).emit(encode_and_encrypt_note(&mut context, from, to));
}
Source code: noir-projects/noir-contracts/contracts/token_contract/src/main.nr#L271-L295
unconstrained
functions
Unconstrained functions are an underlying part of Noir. In short, they are functions which are not directly constrained and therefore should be seen as un-trusted. That they are un-trusted means that the developer must make sure to constrain their return values when used. Note: Calling an unconstrained function from a private function means that you are injecting unconstrained values.
Beyond using them inside your other functions, they are convenient for providing an interface that reads storage, applies logic and returns values to a UI or test. Below is a snippet from exposing the balance_of_private
function from a token implementation, which allows a user to easily read their balance, similar to the balanceOf
function in the ERC20 standard.
unconstrained fn balance_of_private(owner: AztecAddress) -> pub Field {
storage.balances.balance_of(owner).to_field()
}
Source code: noir-projects/noir-contracts/contracts/token_contract/src/main.nr#L409-L413
Note, that unconstrained functions can have access to both public and private data when executed on the user's device. This is possible since it is not actually part of the circuits that are executed in contract execution.