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
6 changes: 3 additions & 3 deletions boxes/boxes/react/src/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ contract BoxReact {
#[external("private")]
#[initializer]
fn constructor(number: Field, owner: AztecAddress) {
let numbers = storage.numbers;
let numbers = self.storage.numbers;
let new_number = ValueNote::new(number, owner);

numbers.at(owner).initialize(new_number).emit(
Expand All @@ -29,7 +29,7 @@ contract BoxReact {

#[external("private")]
fn setNumber(number: Field, owner: AztecAddress) {
let numbers = storage.numbers;
let numbers = self.storage.numbers;

numbers.at(owner)
.replace(|_old| {
Expand All @@ -44,7 +44,7 @@ contract BoxReact {

#[external("utility")]
unconstrained fn getNumber(owner: AztecAddress) -> ValueNote {
let numbers = storage.numbers;
let numbers = self.storage.numbers;
numbers.at(owner).view_note()
}
}
30 changes: 15 additions & 15 deletions boxes/boxes/vanilla/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,39 @@ pub contract PrivateVoting {
#[external("public")]
#[initializer]
fn constructor(admin: AztecAddress) {
storage.admin.write(admin);
storage.vote_ended.write(false);
storage.active_at_block.initialize(context.block_number());
self.storage.admin.write(admin);
self.storage.vote_ended.write(false);
self.storage.active_at_block.initialize(self.context.block_number());
}

#[external("private")]
fn cast_vote(candidate: Field) {
let msg_sender_npk_m_hash = get_public_keys(context.msg_sender().unwrap()).npk_m.hash();
let msg_sender_npk_m_hash = get_public_keys(self.context.msg_sender().unwrap()).npk_m.hash();

let secret = context.request_nsk_app(msg_sender_npk_m_hash); // get secret key of caller of function
let nullifier = std::hash::pedersen_hash([context.msg_sender().unwrap().to_field(), secret]); // derive nullifier from sender and secret
context.push_nullifier(nullifier);
PrivateVoting::at(context.this_address()).add_to_tally_public(candidate).enqueue(
&mut context,
let secret = self.context.request_nsk_app(msg_sender_npk_m_hash); // get secret key of caller of function
let nullifier = std::hash::pedersen_hash([self.msg_sender().unwrap().to_field(), secret]); // derive nullifier from sender and secret
self.context.push_nullifier(nullifier);
PrivateVoting::at(self.address).add_to_tally_public(candidate).enqueue(
self.context,
);
}

#[external("public")]
#[internal]
fn add_to_tally_public(candidate: Field) {
assert(storage.vote_ended.read() == false, "Vote has ended"); // assert that vote has not ended
let new_tally = storage.tally.at(candidate).read() + 1;
storage.tally.at(candidate).write(new_tally);
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;
self.storage.tally.at(candidate).write(new_tally);
}

#[external("public")]
fn end_vote() {
assert(storage.admin.read().eq(context.msg_sender().unwrap()), "Only admin can end votes"); // assert that caller is admin
storage.vote_ended.write(true);
assert(self.storage.admin.read().eq(self.msg_sender().unwrap()), "Only admin can end votes"); // assert that caller is admin
self.storage.vote_ended.write(true);
}

#[external("utility")]
unconstrained fn get_vote(candidate: Field) -> Field {
storage.tally.at(candidate).read()
self.storage.tally.at(candidate).read()
}
}
6 changes: 3 additions & 3 deletions boxes/boxes/vite/src/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ contract BoxReact {
#[external("private")]
#[initializer]
fn constructor(number: Field, owner: AztecAddress) {
let numbers = storage.numbers;
let numbers = self.storage.numbers;
let mut new_number = ValueNote::new(number, owner);

numbers.at(owner).initialize(new_number).emit(
Expand All @@ -29,7 +29,7 @@ contract BoxReact {

#[external("private")]
fn setNumber(number: Field, owner: AztecAddress) {
let numbers = storage.numbers;
let numbers = self.storage.numbers;


numbers.at(owner)
Expand All @@ -43,7 +43,7 @@ contract BoxReact {

#[external("utility")]
unconstrained fn getNumber(owner: AztecAddress) -> ValueNote {
let numbers = storage.numbers;
let numbers = self.storage.numbers;
numbers.at(owner).view_note()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,27 +53,27 @@ The contract function must return information about the execution back to the ke

This structure contains a host of information about the executed program. It will contain any newly created nullifiers, any messages to be sent to l2 and most importantly it will contain the return values of the function.

**Hashing the function inputs.**
#include_code context-example-hasher /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust

_What is the hasher and why is it needed?_

Inside the kernel circuits, the inputs to functions are reduced to a single value; the inputs hash. This prevents the need for multiple different kernel circuits; each supporting differing numbers of inputs. The hasher abstraction that allows us to create an array of all of the inputs that can be reduced to a single value.
**Creating the function's `self.`**
#include_code contract_self_creation /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust

**Creating the function's context.**
#include_code context-example-context /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust

Each Aztec function has access to a [context](context) object. This object, although labelled a global variable, is created locally on a users' device. It is initialized from the inputs provided by the kernel, and a hash of the function's inputs.
Each Aztec function has access to a `self` object. Upon creation it accepts storage and context. Context is initialized from the inputs provided by the kernel, and a hash of the function's inputs.

#include_code context-example-context-return /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust

We use the kernel to pass information between circuits. This means that the return values of functions must also be passed to the kernel (where they can be later passed on to another function).
We achieve this by pushing return values to the execution context, which we then pass to the kernel.

**Hashing the function inputs.**

Inside the kernel circuits, the inputs to functions are reduced to a single value; the inputs hash. This prevents the need for multiple different kernel circuits; each supporting differing numbers of inputs. Hashing the inputs allows to reduce all of the inputs to a single value.

**Making the contract's storage available**
#include_code storage-example-context /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust

When a `Storage` struct is declared within a contract, the `storage` keyword is made available. As shown in the macro expansion above, this calls the init function on the storage struct with the current function's context.
Each `self` has a `storage` variable exposed on it.
When a `Storage` struct is declared within a contract, the `self.storage` contains real variables.

If Storage is note defined `self.storage` contains only a placeholder value.

Any state variables declared in the `Storage` struct can now be accessed as normal struct members.

Expand Down
134 changes: 134 additions & 0 deletions docs/docs/developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,140 @@ The following commands were dropped from the `aztec` command:

## [Aztec.nr]

### Introducing `self` in contracts

Aztec contracts now automatically inject a `self` parameter into every contract function, providing a unified interface for accessing the contract's address, storage, and execution context.

#### What is `self`?

`self` is an instance of `ContractSelf<Context, Storage>` that provides:

- `self.address` - The contract's own address
- `self.storage` - Access to your contract's storage
- `self.context` - The execution context (private, public, or utility)
- `self.msg_sender()` - Get the address of the caller
- `self.emit(...)` - Emit events

And soon to be implemented also:
- `self.call(...)` - Make contract calls

#### How it works

The `#[external(...)]` macro automatically injects `self` into your function. When you write:

```noir
#[external("private")]
fn transfer(amount: u128, recipient: AztecAddress) {
let sender = self.msg_sender().unwrap();
self.storage.balances.at(sender).sub(amount);
self.storage.balances.at(recipient).add(amount);
}
```

The macro transforms it to initialize `self` with the context and storage before your code executes.

#### Migration guide

**Before:** Access context and storage as separate parameters

```noir
#[external("private")]
fn old_transfer(amount: u128, recipient: AztecAddress) {
let storage = Storage::init(context);
let sender = context.msg_sender().unwrap();
storage.balances.at(sender).sub(amount);
}
```

**After:** Use `self` to access everything

```noir
#[external("private")]
fn new_transfer(amount: u128, recipient: AztecAddress) {
let sender = self.msg_sender().unwrap();
self.storage.balances.at(sender).sub(amount);
}
```

#### Key changes

1. **Storage and context access:**

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
- 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).

2. **Getting caller address:** Use `self.msg_sender()` instead of `context.msg_sender()`

```diff
- let caller = context.msg_sender().unwrap();
+ let caller = self.msg_sender().unwrap();
```

3. **Getting contract address:** Use `self.address` instead of `context.this_address()`

```diff
- let this_contract = context.this_address();
+ let this_contract = self.address;
```

4. **Emitting events:**

In private functions:

```diff
- emit_event_in_private(event, context, recipient, delivery_mode);
+ self.emit(event, recipient, delivery_mode);
```

In public functions:

```diff
- emit_event_in_public(event, context);
+ self.emit(event);
```

#### Example: Full contract migration

**Before:**

```noir
#[external("private")]
fn withdraw(amount: u128, recipient: AztecAddress) {
let storage = Storage::init(context);
let sender = context.msg_sender().unwrap();
let token = storage.donation_token.get_note().get_address();

// ... withdrawal logic

emit_event_in_private(Withdraw { withdrawer, amount }, context, withdrawer, MessageDelivery.UNCONSTRAINED_ONCHAIN);
}
```

**After:**

```noir
#[external("private")]
fn withdraw(amount: u128, recipient: AztecAddress) {
let sender = self.msg_sender().unwrap();
let token = self.storage.donation_token.get_note().get_address();

// ... withdrawal logic

self.emit(Withdraw { withdrawer, amount }, withdrawer, MessageDelivery.UNCONSTRAINED_ONCHAIN);
}
```

### 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
Loading
Loading