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
4 changes: 2 additions & 2 deletions boxes/boxes/vanilla/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use dep::aztec::macros::aztec;
pub contract PrivateVoting {
use dep::aztec::keys::getters::get_public_keys;
use dep::aztec::macros::{
functions::{external, initializer, internal},
functions::{external, initializer, only_self},
storage::storage,
};
use dep::aztec::state_vars::{Map, PublicImmutable, PublicMutable};
Expand Down Expand Up @@ -47,7 +47,7 @@ pub contract PrivateVoting {
}

#[external("public")]
#[internal]
#[only_self]
fn add_to_tally_public(candidate: Field) {
assert(self.storage.vote_ended.read() == false, "Vote has ended"); // assert that vote has not ended
let new_tally = self.storage.tally.at(candidate).read() + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,21 @@ When a function is annotated with `#[noinitcheck]`:
- The Aztec macro processor skips the [insertion of the initialization check](#initializer-functions-initializer) for this specific function
- The function can be called at any time, even if the contract hasn't been initialized yet

## `Internal` functions #[internal]
## #[only_self]

External functions marked with #[only_self] attribute can only be called by the contract itself - if other contracts try to make the call it will fail.

This attribute is commonly used when an action starts in private but needs to be completed in public. The public
function must be marked with #[only_self] to restrict access to only the contract itself. A typical example is a private
token mint operation that needs to enqueue a call to a public function to update the publicly tracked total token
supply.

It is also useful in private functions when dealing with tasks of an unknown size but with a large upper bound (e.g. when needing to process an unknown amount of notes or nullifiers) as they allow splitting the work in multiple circuits, possibly resulting in performance improvements for low-load scenarios.

This macro inserts a check at the beginning of the function to ensure that the caller is the contract itself. This is done by adding the following assertion:

```rust
assert(context.msg_sender() == context.this_address(), "Function can only be called internally");
assert(self.msg_sender() == self.address, "Function can only be called by the same contract");
```

## Implementing notes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ fn get_config_value() -> Field {

View functions cannot modify contract state. They're akin to Ethereum's `view` functions.

## Define internal functions
## Define only-self functions

Create contract-only functions using the `#[internal]` annotation:
Create contract-only functions using the `#[only_self]` annotation:

```rust
#[external("public")]
#[internal]
#[only_self]
fn update_counter_public(item: Field) {
// logic
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ This pattern is commonly used in bridge contracts (like the [uniswap example con

```rust
#[external("public")]
#[internal]
#[only_self]
fn _approve_and_execute_action(
target_contract: AztecAddress,
bridge_contract: AztecAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ It is also worth mentioning Noir's `unconstrained` function type [here (Noir doc
- `#[initializer]` - If one or more functions are marked as an initializer, then one of them must be called before any non-initializer functions
- `#[noinitcheck]` - The function is able to be called before an initializer (if one exists)
- `#[view]` - Makes calls to the function static
- `#[internal]` - Function can only be called from within the contract
- `#[only_self]` - Available only for `external` functions - any external caller except the current contract is rejected.
- `#[internal]` - NOT YET IMPLEMENTED - Function can only be called from within the contract and the call itself is inlined (e.g. akin to EVM's JUMP and not EVM's CALL)
- `#[note]` - Creates a custom note
- `#[storage]` - Defines contract storage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Since the public call is made asynchronously, any return values or side effects

#include_code enqueue_public /noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr rust

It is also possible to create public functions that can _only_ be invoked by privately enqueueing a call from the same contract, which can be very useful to update public state after private execution (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[internal]`.
It is also possible to create public functions that can _only_ be invoked by privately enqueueing a call from the same contract, which can be very useful to update public state after private execution (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[only_self]`.

A common pattern is to enqueue public calls to check some validity condition on public state, e.g. that a deadline has not expired or that some public value is set.

Expand Down
43 changes: 34 additions & 9 deletions docs/docs/developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Because Aztec has native account abstraction, the very first function call of a

Previously (before this change) we'd been silently setting this first `msg_sender` to be `AztecAddress::from_field(-1);`, and enforcing this value in the protocol's kernel circuits. Now we're passing explicitness to smart contract developers by wrapping `msg_sender` in an `Option` type. We'll explain the syntax shortly.

We've also added a new protocol feature. Previously (before this change) whenever a public function call was enqueued by a private function (a so-called private->public call), the called public function (and hence the whole world) would be able to see `msg_sender`. For some use cases, visibility of `msg_sender` is important, to ensure the caller executed certain checks in private-land. For `#[internal]` public functions, visibility of `msg_sender` is unavoidable (the caller of an internal function must be the same contract address by definition). But for _some_ use cases, a visible `msg_sender` is an unnecessary privacy leakage.
We've also added a new protocol feature. Previously (before this change) whenever a public function call was enqueued by a private function (a so-called private->public call), the called public function (and hence the whole world) would be able to see `msg_sender`. For some use cases, visibility of `msg_sender` is important, to ensure the caller executed certain checks in private-land. For `#[only_self]` public functions, visibility of `msg_sender` is unavoidable (the caller of an `#[only_self]` function must be the same contract address by definition). But for _some_ use cases, a visible `msg_sender` is an unnecessary privacy leakage.
We therefore have added a feature where `msg_sender` can be optionally set to `Option<AztecAddress>::none()` for enqueued public function calls (aka private->public calls). We've been colloquially referring to this as "setting msg_sender to null".

#### Aztec.nr diffs
Expand Down Expand Up @@ -212,6 +212,7 @@ Aztec contracts now automatically inject a `self` parameter into every contract
- `self.emit(...)` - Emit events

And soon to be implemented also:

- `self.call(...)` - Make contract calls

#### How it works
Expand Down Expand Up @@ -258,15 +259,15 @@ fn new_transfer(amount: u128, recipient: AztecAddress) {

Storage and context are no longer injected into the function as standalone variables and instead you need to access them via `self`:

```diff
- let balance = storage.balances.at(owner).read();
+ let balance = self.storage.balances.at(owner).read();
```
```diff
- let balance = storage.balances.at(owner).read();
+ let balance = self.storage.balances.at(owner).read();
```

```diff
- context.push_nullifier(nullifier);
+ self.context.push_nullifier(nullifier);
```
```diff
- context.push_nullifier(nullifier);
+ self.context.push_nullifier(nullifier);
```

Note that `context` is expected to be use only when needing to access a low-level API (like directly emitting a nullifier).

Expand Down Expand Up @@ -331,6 +332,30 @@ fn withdraw(amount: u128, recipient: AztecAddress) {
}
```

### Renaming #[internal] as #[only_self]

We want for internal to mean the same as in Solidity where internal function can be called only from the same contract
and is also inlined (EVM JUMP opcode and not EVM CALL). The original implementation of our `#[internal]` macro also
results in the function being callable only from the same contract but it results in a different call (hence it doesn't
map to EVM JUMP). This is very confusing for people that know Solidity hence we are doing the rename. A true
`#[internal]` will be introduced in the future.

To migrate your contracts simply rename all the occurrences of `#[internal]` with `#[only_self]` and update the imports:

```diff
- use aztec::macros::functions::internal;
+ use aztec::macros::functions::only_self;
```

```diff
#[external("public")]
- #[internal]
+ #[only_self]
fn _deduct_public_balance(owner: AztecAddress, amount: u64) {
...
}
```

### Replacing #[private], #[public], #[utility] with #[external(...)] macro

The original naming was not great in that it did not sufficiently communicate what the given macro did.
Expand Down
8 changes: 4 additions & 4 deletions docs/examples/contracts/bob_token_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use aztec::macros::aztec;
pub contract BobToken {
// docs:end:start
use aztec::{
macros::{functions::{external, initializer, internal}, storage::storage},
macros::{functions::{external, initializer, only_self}, storage::storage},
protocol_types::address::AztecAddress, state_vars::Map,
state_vars::public_mutable::PublicMutable,
};
Expand Down Expand Up @@ -86,7 +86,7 @@ pub contract BobToken {

// docs:start:_deduct_public_balance
#[external("public")]
#[internal]
#[only_self]
fn _deduct_public_balance(owner: AztecAddress, amount: u64) {
let balance = self.storage.public_balances.at(owner).read();
assert(balance >= amount, "Insufficient public BOB tokens");
Expand Down Expand Up @@ -120,7 +120,7 @@ pub contract BobToken {

// docs:start:_assert_is_owner
#[external("public")]
#[internal]
#[only_self]
fn _assert_is_owner(address: AztecAddress) {
assert_eq(address, self.storage.owner.read(), "Only Giggle can mint BOB tokens");
}
Expand Down Expand Up @@ -153,7 +153,7 @@ pub contract BobToken {
}

#[external("public")]
#[internal]
#[only_self]
fn _credit_public_balance(owner: AztecAddress, amount: u64) {
let balance = self.storage.public_balances.at(owner).read();
self.storage.public_balances.at(owner).write(balance + amount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod nft;
#[aztec]
pub contract NFTPunk {
use dep::aztec::{
macros::{storage::storage, functions::{external, utility, initializer, internal}},
macros::{storage::storage, functions::{external, utility, initializer, only_self}},
protocol_types::{address::AztecAddress},
state_vars::{PrivateSet, PublicImmutable, delayed_public_mutable::DelayedPublicMutable, Map}
};
Expand Down Expand Up @@ -38,7 +38,7 @@ pub contract NFTPunk {

// docs:start:mark_nft_exists
#[external("public")]
#[internal]
#[only_self]
fn _mark_nft_exists(token_id: Field, exists: bool) {
self.storage.nfts.at(token_id).schedule_value_change(exists);
}
Expand Down
31 changes: 17 additions & 14 deletions noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,22 @@ use crate::macros::{
},
utils::{is_fn_external, module_has_initializer},
};
use super::utils::{fn_has_noinitcheck, is_fn_initializer, is_fn_internal, is_fn_view};
use super::utils::{fn_has_noinitcheck, is_fn_initializer, is_fn_only_self, is_fn_view};
use auth_registry::AUTHORIZE_ONCE_REGISTRY;

// Note: The internal naming below is deprecated and will soon be renamed. For this reason it's so weird (having
// external and internal applicable to one function).
//
// Functions can have multiple attributes applied to them, e.g. a single function can have #[external("public")],
// #[view] and #[internal]. However. the order in which this will be evaluated is unknown, which makes combining them
// #[view] and #[only_self]. However. the order in which this will be evaluated is unknown, which makes combining them
// tricky.
//
// Our strategy is to have functions accept at most one #[external(...)] attribute that takes a parameter ("private",
// "public", or "utility") to specify the function type. The private and public handlers that are triggered based on
// the arg of #[external(...)] attribute contains the code for all other attributes, but they only run if
// the corresponding marker attribute has been applied to the function.
//
// For example, the "private" handler of #[external(...)] knows about #[internal] and what it should do, but it only
// does it if it sees that the private function in question also has the `internal` attribute applied. `#[internal]`
// For example, the "private" handler of #[external(...)] knows about #[only_self] and what it should do, but it only
// does it if it sees that the private function in question also has the `only_self` attribute applied. `#[only_self]`
// itself does nothing - it is what we call a 'marker' attribute, that only exists for `#[external("private")]`
// and `#[external(public)]` to check if it's been applied. Therefore, the execution order of `#[internal]` and
// and `#[external(public)]` to check if it's been applied. Therefore, the execution order of `#[only_self]` and
// `#[external(...)]` is irrelevant.

/// An initializer function is similar to a constructor:
Expand Down Expand Up @@ -74,8 +71,14 @@ pub comptime fn noinitcheck(f: FunctionDefinition) {
}
}

/// Internal functions can only be called by the contract itself, typically from private into public.
pub comptime fn internal(f: FunctionDefinition) {
/// Functions marked with #[only_self] attribute can only be called by the contract itself.
///
/// # Usage
/// This attribute is commonly used when an action starts in private but needs to be completed in public. The public
/// function must be marked with #[only_self] to restrict access to only the contract itself. A typical example is a
/// private token mint operation that needs to enqueue a call to a public function to update the publicly tracked total
/// token supply.
pub comptime fn only_self(f: FunctionDefinition) {
// Marker attribute - see the comment above

if !is_fn_external(f) {
Expand All @@ -84,7 +87,7 @@ pub comptime fn internal(f: FunctionDefinition) {
// function is run.
let name = f.name();
panic(
f"The #[internal] attribute can only be applied to #[external(\"private\")] or #[external(\"public\")] functions - {name} is neither",
f"The #[only_self] attribute can only be applied to #[external(\"private\")] or #[external(\"public\")] functions - {name} is neither",
);
}
}
Expand Down Expand Up @@ -244,7 +247,7 @@ comptime fn utility(f: FunctionDefinition) -> Quoted {
fn_abi_export
}

/// Utility functions cannot be used with the following modifiers: #[authorize_once], #[internal], #[view],
/// Utility functions cannot be used with the following modifiers: #[authorize_once], #[only_self], #[view],
/// #[initializer], and #[noinitcheck]. Since we cannot enforce a specific ordering between these modifiers and
/// #[external(...)], and we cannot access the #[external(...)] argument from within these modifiers' implementations
/// (as accessing EXTERNAL_REGISTRY would require enforcing ordering), we perform these compatibility checks here in
Expand All @@ -257,10 +260,10 @@ comptime fn post_external_utility_checks(f: FunctionDefinition) {
);
}

if is_fn_internal(f) {
if is_fn_only_self(f) {
let name = f.name();
panic(
f"The #[internal] attribute cannot be applied to #[external(\"utility\")] functions - {name}",
f"The #[only_self] attribute cannot be applied to #[external(\"utility\")] functions - {name}",
);
}

Expand Down
16 changes: 8 additions & 8 deletions noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::macros::{
notes::NOTES,
utils::{
fn_has_authorize_once, fn_has_noinitcheck, is_fn_contract_library_method, is_fn_external,
is_fn_initializer, is_fn_internal, is_fn_test, is_fn_view, modify_fn_body,
is_fn_initializer, is_fn_only_self, is_fn_test, is_fn_view, modify_fn_body,
module_has_initializer, module_has_storage,
},
};
Expand Down Expand Up @@ -67,8 +67,8 @@ pub(crate) comptime fn transform_private(f: FunctionDefinition) {
let function_name = f.name();

// Modifications introduced by the different marker attributes.
let internal_check = if is_fn_internal(f) {
let assertion_message = f"Function {function_name} can only be called internally";
let internal_check = if is_fn_only_self(f) {
let assertion_message = f"Function {function_name} can only be called by the same contract";
quote { assert(self.msg_sender().unwrap() == self.address, $assertion_message); }
} else {
quote {}
Expand Down Expand Up @@ -233,19 +233,19 @@ pub(crate) comptime fn transform_public(f: FunctionDefinition) {
};
};

let name = f.name();
let function_name = f.name();

// Modifications introduced by the different marker attributes.
let internal_check = if is_fn_internal(f) {
let assertion_message = f"Function {name} can only be called internally";
let internal_check = if is_fn_only_self(f) {
let assertion_message = f"Function {function_name} can only be called by the same contract";
quote { assert(self.msg_sender().unwrap() == self.address, $assertion_message); }
} else {
quote {}
};

let view_check = if is_fn_view(f) {
let name = f.name();
let assertion_message =
f"Function {name} can only be called statically".as_ctstring().as_quoted_str();
f"Function {function_name} can only be called statically".as_ctstring().as_quoted_str();
quote { assert(self.context.is_static_call(), $assertion_message); }
} else {
quote {}
Expand Down
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/aztec/src/macros/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub(crate) comptime fn is_fn_view(f: FunctionDefinition) -> bool {
f.has_named_attribute("view")
}

pub(crate) comptime fn is_fn_internal(f: FunctionDefinition) -> bool {
f.has_named_attribute("internal")
pub(crate) comptime fn is_fn_only_self(f: FunctionDefinition) -> bool {
f.has_named_attribute("only_self")
}

pub(crate) comptime fn is_fn_initializer(f: FunctionDefinition) -> bool {
Expand Down
1 change: 1 addition & 0 deletions noir-projects/noir-contracts/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ members = [
"contracts/test/no_constructor_contract",
"contracts/test/note_getter_contract",
"contracts/test/offchain_effect_contract",
"contracts/test/only_self_contract",
"contracts/test/oracle_version_check_contract",
"contracts/test/parent_contract",
"contracts/test/pending_note_hashes_contract",
Expand Down
Loading
Loading