Skip to content

Commit 93a47fc

Browse files
authored
refactor!: introducing #[external(...)] macro (#17827)
Closes https://linear.app/aztec-labs/issue/F-19/rename-privateorpublicorutility-to-externalprivateorpublicorutility Having `#[private]` and `#[public]` macros was misleading because in Solidity these define function visibility while in Aztec's case they define the environment in which the functions are executed. For this reason I introduce `#[external(...)]` macro while the argument of the attribute defines the execution environment: `private`, `public` or `utility`. ## Notes for reviewer - I did a refactor in a [PR up the stack](#17840) which allowed me to drop a lot of the complexity introduced in this PR. For this reason I encourage you to check that one first to have context on what in this PR is a problem and what is not. It might make sense to just merge these 2 PRs into 1 but I think that would make it all harder to review. - This PR touches a lot of files but all the juicy bits are in the `noir-projects/aztec-nr/aztec/src/macros/` directory so I encourage you to just check that first. The `noir-contracts` dir can be completely ignored as there I just update the imports and the macros. - The new naming is now in conflict with the `#[internal]` macro as having both external and internal applied to a function is valid: <img width="874" height="182" alt="image" src="https://github.com/user-attachments/assets/4582a51a-225c-4a4c-9b9a-d2c2d413d927" /> This issue will be tackled in the near future.
2 parents cf5e0d5 + 711bbee commit 93a47fc

File tree

106 files changed

+1040
-871
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+1040
-871
lines changed

boxes/boxes/react/src/contracts/src/main.nr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use aztec::macros::aztec;
44
contract BoxReact {
55
use aztec::{
66
messages::message_delivery::MessageDelivery,
7-
macros::{functions::{initializer, private, utility}, storage::storage},
7+
macros::{functions::{external, initializer}, storage::storage},
88
protocol_types::address::AztecAddress,
99
state_vars::{Map, PrivateMutable},
1010
};
@@ -15,7 +15,7 @@ contract BoxReact {
1515
numbers: Map<AztecAddress, PrivateMutable<ValueNote, Context>, Context>,
1616
}
1717

18-
#[private]
18+
#[external("private")]
1919
#[initializer]
2020
fn constructor(number: Field, owner: AztecAddress) {
2121
let numbers = storage.numbers;
@@ -28,7 +28,7 @@ contract BoxReact {
2828
);
2929
}
3030

31-
#[private]
31+
#[external("private")]
3232
fn setNumber(number: Field, owner: AztecAddress) {
3333
let numbers = storage.numbers;
3434

@@ -44,7 +44,7 @@ contract BoxReact {
4444
}
4545

4646

47-
#[utility]
47+
#[external("utility")]
4848
unconstrained fn getNumber(owner: AztecAddress) -> ValueNote {
4949
let numbers = storage.numbers;
5050
numbers.at(owner).view_note()

boxes/boxes/vanilla/contracts/src/main.nr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use dep::aztec::macros::aztec;
1212
pub contract PrivateVoting {
1313
use dep::aztec::keys::getters::get_public_keys;
1414
use dep::aztec::macros::{
15-
functions::{initializer, internal, private, public, utility},
15+
functions::{external, initializer, internal},
1616
storage::storage,
1717
};
1818
use dep::aztec::state_vars::{Map, PublicImmutable, PublicMutable};
@@ -26,15 +26,15 @@ pub contract PrivateVoting {
2626
active_at_block: PublicImmutable<u32, Context>, // when people can start voting
2727
}
2828

29-
#[public]
29+
#[external("public")]
3030
#[initializer]
3131
fn constructor(admin: AztecAddress) {
3232
storage.admin.write(admin);
3333
storage.vote_ended.write(false);
3434
storage.active_at_block.initialize(context.block_number());
3535
}
3636

37-
#[private]
37+
#[external("private")]
3838
fn cast_vote(candidate: Field) {
3939
let msg_sender_npk_m_hash = get_public_keys(context.msg_sender().unwrap()).npk_m.hash();
4040

@@ -46,21 +46,21 @@ pub contract PrivateVoting {
4646
);
4747
}
4848

49-
#[public]
49+
#[external("public")]
5050
#[internal]
5151
fn add_to_tally_public(candidate: Field) {
5252
assert(storage.vote_ended.read() == false, "Vote has ended"); // assert that vote has not ended
5353
let new_tally = storage.tally.at(candidate).read() + 1;
5454
storage.tally.at(candidate).write(new_tally);
5555
}
5656

57-
#[public]
57+
#[external("public")]
5858
fn end_vote() {
5959
assert(storage.admin.read().eq(context.msg_sender().unwrap()), "Only admin can end votes"); // assert that caller is admin
6060
storage.vote_ended.write(true);
6161
}
6262

63-
#[utility]
63+
#[external("utility")]
6464
unconstrained fn get_vote(candidate: Field) -> Field {
6565
storage.tally.at(candidate).read()
6666
}

boxes/boxes/vite/src/contracts/src/main.nr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use aztec::macros::aztec;
44
contract BoxReact {
55
use aztec::{
66
messages::message_delivery::MessageDelivery,
7-
macros::{functions::{initializer, private, utility}, storage::storage},
7+
macros::{functions::{external, initializer}, storage::storage},
88
protocol_types::address::AztecAddress,
99
state_vars::{Map, PrivateMutable},
1010
};
@@ -15,7 +15,7 @@ contract BoxReact {
1515
numbers: Map<AztecAddress, PrivateMutable<ValueNote, Context>, Context>,
1616
}
1717

18-
#[private]
18+
#[external("private")]
1919
#[initializer]
2020
fn constructor(number: Field, owner: AztecAddress) {
2121
let numbers = storage.numbers;
@@ -28,7 +28,7 @@ contract BoxReact {
2828
);
2929
}
3030

31-
#[private]
31+
#[external("private")]
3232
fn setNumber(number: Field, owner: AztecAddress) {
3333
let numbers = storage.numbers;
3434

@@ -43,7 +43,7 @@ contract BoxReact {
4343
);
4444
}
4545

46-
#[utility]
46+
#[external("utility")]
4747
unconstrained fn getNumber(owner: AztecAddress) -> ValueNote {
4848
let numbers = storage.numbers;
4949
numbers.at(owner).view_note()

boxes/init/src/main.nr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use dep::aztec::macros::aztec;
33

44
#[aztec]
55
contract Main {
6-
#[private]
6+
#[external("private")]
77
#[initializer]
88
fn constructor() { }
99
}

docs/docs/developers/docs/concepts/call_types.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ While Ethereum contracts are defined by bytecode that runs on the EVM, Aztec con
9797

9898
### Private Execution
9999

100-
Contract functions marked with `#[private]` can only be called privately, and as such 'run' in the user's device. Since they're circuits, their 'execution' is actually the generation of a zk-SNARK proof that'll later be sent to the sequencer for verification.
100+
Contract functions marked with `#[external("private")]` can only be called privately, and as such 'run' in the user's device. Since they're circuits, their 'execution' is actually the generation of a zk-SNARK proof that'll later be sent to the sequencer for verification.
101101

102102
#### Private Calls
103103

@@ -153,7 +153,7 @@ For this reason it is encouraged to try to avoid public function calls and inste
153153

154154
### Public Execution
155155

156-
Contract functions marked with `#[public]` can only be called publicly, and are executed by the sequencer. The computation model is very similar to the EVM: all state, parameters, etc. are known to the entire network, and no data is private. Static execution like the EVM's `STATICCALL` is possible too, with similar semantics (state can be accessed but not modified, etc.).
156+
Contract functions marked with `#[external("public")]` can only be called publicly, and are executed by the sequencer. The computation model is very similar to the EVM: all state, parameters, etc. are known to the entire network, and no data is private. Static execution like the EVM's `STATICCALL` is possible too, with similar semantics (state can be accessed but not modified, etc.).
157157

158158
Since private calls are always run in a user's device, it is not possible to perform any private execution from a public context. A reasonably good mental model for public execution is that of an EVM in which some work has already been done privately, and all that is know about it is its correctness and side-effects (new notes and nullifiers, enqueued public calls, etc.). A reverted public execution will also revert the private side-effects.
159159

@@ -167,7 +167,7 @@ This is the same function that was called by privately enqueuing a call to it! P
167167

168168
### Utility
169169

170-
Contract functions marked with `#[utility]` cannot be called as part of a transaction, and are only invoked by applications that interact with contracts to perform state queries from an offchain client (from both private and public state!) or to modify local contract-related PXE state (e.g. when processing logs in Aztec.nr). No guarantees are made on the correctness of the result since the entire execution is unconstrained and heavily reliant on oracle calls. It is possible however to verify that the bytecode being executed is the correct one, since a contract's address includes a commitment to all of its utility functions.
170+
Contract functions marked with `#[external("utility")]` cannot be called as part of a transaction, and are only invoked by applications that interact with contracts to perform state queries from an offchain client (from both private and public state!) or to modify local contract-related PXE state (e.g. when processing logs in Aztec.nr). No guarantees are made on the correctness of the result since the entire execution is unconstrained and heavily reliant on oracle calls. It is possible however to verify that the bytecode being executed is the correct one, since a contract's address includes a commitment to all of its utility functions.
171171

172172
### aztec.js
173173

docs/docs/developers/docs/concepts/smart_contracts/contract_upgrades.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Contract upgrades in Aztec have to be initiated by the contract that wishes to b
3232
use dep::aztec::protocol_types::contract_class_id::ContractClassId;
3333
use contract_instance_registry::ContractInstanceRegistry;
3434

35-
#[private]
35+
#[external("private")]
3636
fn update_to(new_class_id: ContractClassId) {
3737
ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS)
3838
.update(new_class_id)
@@ -43,7 +43,7 @@ fn update_to(new_class_id: ContractClassId) {
4343
The `update` function in the registry is a public function, so you can enqueue it from a private function like the example or call it from a public function directly.
4444

4545
:::note
46-
Recall that `#[private]` means calling this function preserves privacy, and it still CAN be called externally by anyone.
46+
Recall that `#[external("private")]` means calling this function preserves privacy, and it still CAN be called externally by anyone.
4747
So the `update_to` function above allows anyone to update the contract that implements it. A more complete implementation should have a proper authorization systems to secure contracts from malicious upgrades.
4848
:::
4949

@@ -54,7 +54,7 @@ This means that they have a delay before entering into effect. The default delay
5454
use dep::aztec::protocol_types::contract_class_id::ContractClassId;
5555
use contract_instance_registry::ContractInstanceRegistry;
5656

57-
#[private]
57+
#[external("private")]
5858
fn set_update_delay(new_delay: u64) {
5959
ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS)
6060
.set_update_delay(new_delay)
@@ -107,7 +107,7 @@ Consider this contract as an example:
107107
contract Updatable {
108108
...
109109

110-
#[private]
110+
#[external("private")]
111111
fn update_to(new_class_id: ContractClassId) {
112112
ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS).update(new_class_id).enqueue(
113113
&mut context,
@@ -164,7 +164,7 @@ await RandomContract.at(address, wallet);
164164

165165
```rust
166166
contract Updatable {
167-
#[private]
167+
#[external("private")]
168168
fn update_to(new_class_id: ContractClassId) {
169169
// TODO: Add access control
170170
assert(context.msg_sender() == owner, "Unauthorized");
@@ -175,7 +175,7 @@ contract Updatable {
175175
.enqueue(&mut context);
176176
}
177177

178-
#[private]
178+
#[external("private")]
179179
fn set_update_delay(new_delay: u64) {
180180
// TODO: Add access control
181181
ContractInstanceRegistry::at(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS)

docs/docs/developers/docs/concepts/smart_contracts/functions/attributes.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ On this page you will learn about function attributes and macros.
99

1010
If you are looking for a reference of function macros, go [here](../../../reference/smart_contract_reference/macros.md).
1111

12-
## Private functions #[private]
12+
# External functions #[external("...")]
1313

14-
A private function operates on private information, and is executed by the user on their device. Annotate the function with the `#[private]` attribute to tell the compiler it's a private function. This will make the [private context](./context.md#the-private-context) available within the function's execution scope. The compiler will create a circuit to define this function.
14+
Like in Solidity, external functions can be called from outside the contract.
15+
There are 3 types of external functions differing in the execution environment they are executed in: private, public, and utility.
16+
We will describe each type in the following sections.
1517

16-
`#[private]` is just syntactic sugar. At compile time, the Aztec.nr framework inserts code that allows the function to interact with the [kernel](../../advanced/circuits/kernels/private_kernel.md).
18+
## Private functions #[external("private")]
19+
20+
A private function operates on private information, and is executed by the user on their device. Annotate the function with the `#[external("private")]` attribute to tell the compiler it's a private function. This will make the [private context](./context.md#the-private-context) available within the function's execution scope. The compiler will create a circuit to define this function.
21+
22+
`#[external("private")]` is just syntactic sugar. At compile time, the Aztec.nr framework inserts code that allows the function to interact with the [kernel](../../advanced/circuits/kernels/private_kernel.md).
1723

1824
To help illustrate how this interacts with the internals of Aztec and its kernel circuits, we can take an example private function, and explore what it looks like after Aztec.nr's macro expansion.
1925

@@ -76,9 +82,9 @@ Any state variables declared in the `Storage` struct can now be accessed as norm
7682

7783
This function takes the application context, and converts it into the `PrivateCircuitPublicInputs` structure. This structure is then passed to the kernel circuit.
7884

79-
## Utility functions #[utility]
85+
## Utility functions #[external("utility")]
8086

81-
Contract functions marked with `#[utility]` are used to perform state queries from an offchain client (from both private and public state!) or to modify local contract-related PXE state (e.g. when processing logs in Aztec.nr), and are never included in any transaction. No guarantees are made on the correctness of the result since the entire execution is unconstrained and heavily reliant on [oracle calls](https://noir-lang.org/docs/explainers/explainer-oracle).
87+
Contract functions marked with `#[external("utility")]` are used to perform state queries from an offchain client (from both private and public state!) or to modify local contract-related PXE state (e.g. when processing logs in Aztec.nr), and are never included in any transaction. No guarantees are made on the correctness of the result since the entire execution is unconstrained and heavily reliant on [oracle calls](https://noir-lang.org/docs/explainers/explainer-oracle).
8288

8389
Any programming language could be used to construct these queries, since all they do is perform arbitrary computation on data that is either publicly available from any node, or locally available from the PXE. Utility functions exist as Noir contract code because they let developers utilize the rest of the contract code directly by being part of the same Noir crate, and e.g. use the same libraries, structs, etc. instead of having to rely on manual computation of storage slots, struct layout and padding, and so on.
8490

@@ -110,15 +116,15 @@ Beyond using them inside your other functions, they are convenient for providing
110116
Note, that utility functions can have access to both private and (historical) public data when executed on the user's device. This is possible since these functions are not invoked as part of transactions, so we don't need to worry about preventing a contract from e.g. accidentally using stale or unverified public state.
111117
:::
112118

113-
## Public functions #[public]
119+
## Public functions #[external("public")]
114120

115121
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.
116122

117123
:::note
118124
All data inserted into private storage from a public function will be publicly viewable (not private).
119125
:::
120126

121-
To create a public function you can annotate it with the `#[public]` attribute. This will make the public context available within the function's execution scope.
127+
To create a public function you can annotate it with the `#[external("public")]` attribute. This will make the public context available within the function's execution scope.
122128

123129
#include_code set_minter /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust
124130

@@ -144,7 +150,7 @@ let storage = Storage::init(&mut context);
144150

145151
## Constrained `view` Functions #[view]
146152

147-
The `#[view]` attribute can be applied to a `#[private]` or a `#[public]` function and it guarantees that the function cannot modify any contract state (just like `view` functions in Solidity).
153+
The `#[view]` attribute can be applied to a `#[external("private")]` or a `#[external("public")]` function and it guarantees that the function cannot modify any contract state (just like `view` functions in Solidity).
148154

149155
## `Initializer` Functions #[initializer]
150156

docs/docs/developers/docs/guides/smart_contracts/advanced/how_to_retrieve_filter_notes.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ let notes = storage.my_notes.at(owner).get_notes(options);
3737
// Assuming MyNote has an 'owner' field
3838
let mut options = NoteGetterOptions::new();
3939
options = options.select(
40-
MyNote::properties().owner,
41-
Comparator.EQ,
40+
MyNote::properties().owner,
41+
Comparator.EQ,
4242
owner
4343
);
4444
```
@@ -84,7 +84,7 @@ fn filter_above_threshold(
8484
) -> [Option<RetrievedNote<Note>>; MAX_NOTES] {
8585
let mut result = [Option::none(); MAX_NOTES];
8686
let mut count = 0;
87-
87+
8888
for note in notes {
8989
if note.is_some() & (note.unwrap().note.value >= min) {
9090
result[count] = note;
@@ -140,7 +140,7 @@ contract.methods.read_notes(Comparator.GTE, 5).simulate({ from: defaultAddress }
140140
```rust
141141
use dep::aztec::note::note_viewer_options::NoteViewerOptions;
142142

143-
#[utility]
143+
#[external("utility")]
144144
unconstrained fn view_notes(comparator: u8, value: Field) -> auto {
145145
let mut options = NoteViewerOptions::new();
146146
options = options.select(MyNote::properties().value, comparator, value);

0 commit comments

Comments
 (0)