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
9 changes: 5 additions & 4 deletions docs/docs/aztec/concepts/storage/partial_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,16 @@ Those `G_x` are generators that generated [here](https://github.com/AztecProtoco

We can see the complete implementation of creating and completing partial notes in an Aztec contract in the `setup_refund` and `complete_refund` functions.

#### `setup_refund`
#### `fee_entrypoint_private`

#include_code setup_refund noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust
#include_code fee_entrypoint_private noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr rust

The `setup_refund` function sets the `complete_refund` function to be called at the end of the public function execution (`set_public_teardown_function`). This ensures that the partial notes will be completed and the fee payer will be paid and the user refund will be issued.
The `fee_entrypoint_private` function sets the `complete_refund` function to be called at the end of the public function execution (`set_public_teardown_function`).
This ensures that the refund partial note will be completed for the user.

#### `complete_refund`

#include_code complete_refund noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust
#include_code complete_refund noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr rust

## Future work

Expand Down
28 changes: 28 additions & 0 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ Aztec is in full-speed development. Literally every version breaks compatibility

## TBD

### [Token, FPC] Moving fee-related complexity from the Token to the FPC

There was a complexity leak of fee-related functionality in the token contract.
We've came up with a way how to achieve the same objective with the general functionality of the Token contract.
This lead to the removal of `setup_refund` and `complete_refund` functions from the Token contract and addition of `complete_refund` function to the FPC.

### [Aztec.nr] Improved storage slot allocation

State variables are no longer assumed to be generic over a type that implements the `Serialize` trait: instead, they must implement the `Storage` trait with an `N` value equal to the number of slots they need to reserve.

For the vast majority of state variables, this simply means binding the serialization length to this trait:
Expand All @@ -18,6 +25,7 @@ For the vast majority of state variables, this simply means binding the serializ
```

### [Aztec.nr] Introduction of `Packable` trait

We have introduced a `Packable` trait that allows types to be serialized and deserialized with a focus on minimizing the size of the resulting Field array.
This is in contrast to the `Serialize` and `Deserialize` traits, which follows Noir's intrinsic serialization format.
This is a breaking change because we now require `Packable` trait implementation for any type that is to be stored in contract storage.
Expand All @@ -43,6 +51,7 @@ impl Packable<U128_PACKED_LEN> for U128 {
### Logs for notes, partial notes, and events have been refactored.

We're preparing to make log assembly more customisable. These paths have changed.

```diff
- use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note,
+ use dep::aztec::encrypted_logs::log_assembly_strategies::default_aes128::note::encode_and_encrypt_note,
Expand All @@ -57,6 +66,7 @@ The way in which logs are assembled in this "default_aes128" strategy is has als
You can remove this method from any custom notes or events that you've implemented.

### [Aztec.nr] Packing notes resulting in changes in `NoteInterface`

Note interface implementation generated by our macros now packs note content instead of serializing it
With this change notes are being less costly DA-wise to emit when some of the note struct members implements the `Packable` trait (this is typically the `UintNote` which represents `value` as `U128` that gets serialized as 2 fields but packed as 1).
This results in the following changes in the `NoteInterface`:
Expand Down Expand Up @@ -97,7 +107,9 @@ have been merged into getContractMetadata
These functions have been merged into `pxe.getContractMetadata` and `pxe.getContractClassMetadata`.

## 0.72.0

### Some functions in `aztec.js` and `@aztec/accounts` are now async

In our efforts to make libraries more browser-friendly and providing with more bundling options for `bb.js` (like a non top-level-await version), some functions are being made async, in particular those that access our cryptographic functions.

```diff
Expand All @@ -109,7 +121,9 @@ In our efforts to make libraries more browser-friendly and providing with more b
```

### Public logs replace unencrypted logs

Any log emitted from public is now known as a public log, rather than an unencrypted log. This means methods relating to these logs have been renamed e.g. in the pxe, archiver, txe:

```diff
- getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse>
- getUnencryptedEvents<T>(eventMetadata: EventMetadataDefinition, from: number, limit: number): Promise<T[]>
Expand All @@ -118,54 +132,66 @@ Any log emitted from public is now known as a public log, rather than an unencry
```

The context method in aztec.nr is now:

```diff
- context.emit_unencrypted_log(log)
+ context.emit_public_log(log)
```

These logs were treated as bytes in the node and as hashes in the protocol circuits. Now, public logs are treated as fields everywhere:

```diff
- unencryptedLogs: UnencryptedTxL2Logs
- unencrypted_logs_hashes: [ScopedLogHash; MAX_UNENCRYPTED_LOGS_PER_TX]
+ publicLogs: PublicLog[]
+ public_logs: [PublicLog; MAX_PUBLIC_LOGS_PER_TX]
```

A `PublicLog` contains the log (as an array of fields) and the app address.

This PR also renamed encrypted events to private events:

```diff
- getEncryptedEvents<T>(eventMetadata: EventMetadataDefinition, from: number, limit: number, vpks: Point[]): Promise<T[]>
+ getPrivateEvents<T>(eventMetadata: EventMetadataDefinition, from: number, limit: number, vpks: Point[]): Promise<T[]>
```

## 0.70.0

### [Aztec.nr] Removal of `getSiblingPath` oracle

Use `getMembershipWitness` oracle instead that returns both the sibling path and index.

## 0.68.0

### [archiver, node, pxe] Remove contract artifacts in node and archiver and store function names instead

Contract artifacts were only in the archiver for debugging purposes. Instead function names are now (optionally) emitted
when registering contract classes

Function changes in the Node interface and Contract Data source interface:

```diff
- addContractArtifact(address: AztecAddress, artifact: ContractArtifact): Promise<void>;
+ registerContractFunctionNames(address: AztecAddress, names: Record<string, string>): Promise<void>;
```

So now the PXE registers this when calling `registerContract()`

```
await this.node.registerContractFunctionNames(instance.address, functionNames);
```

Function changes in the Archiver

```diff
- addContractArtifact(address: AztecAddress, artifact: ContractArtifact)
- getContractArtifact(address: AztecAddress)
+ registerContractFunctionNames(address: AztecAddress, names: Record<string, string>): Promise<void>
```

### [fees, fpc] Changes in setting up FPC as fee payer on AztecJS and method names in FPC

On AztecJS, setting up `PrivateFeePaymentMethod` and `PublicFeePaymentMethod` are now the same. The don't need to specify a sequencer address or which coin to pay in. The coins are set up in the FPC contract!

```diff
Expand All @@ -177,6 +203,7 @@ On AztecJS, setting up `PrivateFeePaymentMethod` and `PublicFeePaymentMethod` ar
```

Changes in `FeePaymentMethod` class in AztecJS

```diff
- getAsset(): AztecAddress;
+ getAsset(): Promise<AztecAddress>;
Expand All @@ -192,6 +219,7 @@ Also created a public function `pull_funds()` for admin to clawback any money in
Expect more changes in FPC in the coming releases!

### Name change from `contact` to `sender` in PXE API

`contact` has been deemed confusing because the name is too similar to `contract`.
For this reason we've decided to rename it:

Expand Down
12 changes: 0 additions & 12 deletions docs/docs/tutorials/codealong/contract_tutorials/token_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,12 +312,6 @@ After initializing storage, the function checks that the `msg_sender` is authori

#include_code burn_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

#### `setup_refund`

This private function may be called by a Fee Paying Contract (FPC) in order to allow users to pay transaction fees privately on the network. This function ensures that the user has enough funds in their account to pay the transaction fees for the transaction, sets up partial notes for paying the fees to the `fee_payer` and sending any unspent fees back to the user, and enqueues a call to the internal, public [`complete_refund`](#complete_refund) function to be run as part of the public execution step.

#include_code setup_refund /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

#### `prepare_private_balance_increase`

TODO: update from `prepare_transfer_to_private`
Expand All @@ -342,12 +336,6 @@ This function is called from [`burn`](#burn). The account's private balance is d

#include_code reduce_total_supply /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

#### `complete_refund`

This public function is intended to be called during the public teardown at the end of public transaction execution. The call to this function is staged in [`setup_refund`](#setup_refund). This function ensures that the user has sufficient funds to cover the transaction costs and emits encrypted notes to the fee payer and the remaining, unused transaction fee back to the user.

#include_code complete_refund /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

#### `_finalize_transfer_to_private_unsafe`

This public internal function decrements the public balance of the `from` account and finalizes the partial note for the recipient, which is hidden in the `hiding_point_slot`.
Expand Down
91 changes: 65 additions & 26 deletions noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,14 @@ contract FPC {
/// ## Overview
/// Uses partial notes to implement a refund flow which works as follows:
/// Setup Phase:
/// 1. This `setup_refund` function:
/// - Calls the AA token contract, which:
/// - subtracts the `max_fee` from the user's balance;
/// - prepares a partial note for the user (which will be used to later refund the user any unspent fee);
/// - sets a public teardown function (within the same AA token contract), where at the end of the tx
/// a fee (denominated in AA) will be transferred to the FPC in public, and a partial note will be finalized
/// with the refund amount (also denominated in AA).
/// - Sets itself as the `fee_payer` of the tx; meaning this contract will be responsible for ultimately
/// transferring the `tx_fee` -- denominated in fee juice -- to the protocol, during the later "teardown"
/// phase of this tx.
/// 1. This `fee_entrypoint_private` function:
/// - Transfers `max_fee` of AA from private balance of the user to the public balance of this contract,
/// - prepares a partial note for the user (which will later on be used to refund the user any unspent fee),
/// - sets a public teardown function, where at the end of the tx a partial note will be finalized
/// with the refund amount (also denominated in AA),
/// - sets itself as the `fee_payer` of the tx; meaning this contract will be responsible for ultimately
/// transferring the `tx_fee` -- denominated in fee juice -- to the protocol, during the later "teardown"
/// phase of this tx.
///
/// Execution Phase:
/// 2. Then the private and public functions of the tx get executed.
Expand All @@ -56,38 +54,79 @@ contract FPC {
/// 3. By this point, the protocol has computed the `tx_fee` (denominated in "fee juice"). So now we can
/// execute the "teardown function" which was lined-up during the earlier "setup phase".
/// Within the teardown function, we:
/// - compute how much of the `max_fee` (denominated in AA) the user needs to pay to the FPC,
/// and how much of it will be refunded back to the user. Since the protocol-calculated `tx_fee` is
/// denominated in fee juice, and not in this FPC's AA, an equivalent value of AA is computed based
/// on an exchange rate between AA and fee juice.
/// - finalize the refund note with a value of `max_fee - tx_fee` for the user;
/// - send the tx fee to the FPC in public.
/// - Compute how much of the `max_fee` (denominated in AA) will be refunded back to the user. Since
/// the protocol-calculated `tx_fee` is denominated in fee juice, and not in this FPC's AA, an equivalent
/// value of AA is computed based on the exchange rate between AA and fee juice,
/// - finalize the refund note with a value of `max_fee - tx_fee` for the user.
///
/// Protocol-enshrined fee-payment phase:
/// 4. The protocol deducts the protocol-calculated `tx_fee` (denominated in fee juice) from the `fee_payer`'s
/// balance (which in this case is this FPC's balance), which is a special storage slot in a protocol-controlled
/// "fee juice" contract.
///
/// With this scheme a user has privately paid for the tx fee with an arbitrary AA (e.g. could be a stablecoin),
/// by paying this FPC. This FPC has in turn paid the protocol-mandated `tx_fee` (denominated in fee
/// juice).
/// by paying this FPC. This FPC has in turn paid the protocol-mandated `tx_fee` (denominated in fee juice).
///
/// ***Note:***
/// This flow allows us to pay for the tx with msg_sender's private balance of AA and hence msg_sender's identity
/// is not revealed. We do, however, reveal:
/// - the `max_fee`;
/// - which FPC has been used to make the payment;
/// - the `max_fee`,
/// - which FPC has been used to make the payment,
/// - the asset which was used to make the payment.
// docs:start:fee_entrypoint_private
#[private]
fn fee_entrypoint_private(max_fee: U128, nonce: Field) {
// TODO(PR #8022): Once PublicImmutable performs only 1 merkle proof here, we'll save ~4k gates
let config = storage.config.read();
let accepted_asset = storage.config.read().accepted_asset;

Token::at(config.accepted_asset).setup_refund(context.msg_sender(), max_fee, nonce).call(
&mut context,
let user = context.msg_sender();
let token = Token::at(accepted_asset);

// TODO(#10805): Here we should check that `max_fee` converted to fee juice is enough to cover the tx
// fee juice/mana/gas limit. Currently the fee juice/AA exchange rate is fixed 1:1.

// Pull the max fee from the user's balance of the accepted asset to the public balance of this contract.
token.transfer_to_public(user, context.this_address(), max_fee, nonce).call(&mut context);

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.

It's not this PR's fault, but it's kind of ugly that this nonce has to pass-through this contract to get to the token contract. Do we have any ideas to improve that interface?

@benesjan benesjan Jan 31, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal of the nonce there is to make the authwit valid only for the call with that specific nonce and prevent authwit re-use.

If we would like to get rid of it from the API we would have to achieve this some other way.

Given that authwits are so common maybe we could move the nonce from the function arguments to the context itself and then inject it into the authwit inner hash here?

Seems quite natural to have a tx_nonce available within the context.

@iAmMichaelConnor WDYT?

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.

Yeah that might work. I've not paid much attention to authwits or capsules. I remember we wanted to refactor capsules, and maybe even that authwits would be replaced by capsules (because capsules are a generalisation of authwits: injecting data into the function separately from params). Maybe we wait until the capsules refactor, if it's still planned?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I am looking at the code the usage of capsules was mostly removed (it's not even called from Noir anymore) so it seems like they have no future.

Tagging @nventuro as it would be nice to get a feedback on the nonce in context.


// Prepare a partial note for the refund for the user.
let refund_slot = token.prepare_private_balance_increase(user, user).call(&mut context);

// Set a public teardown function in which the refund will be paid back to the user by finalizing the partial note.
let max_fee_serialized = max_fee.serialize();
context.set_public_teardown_function(
context.this_address(),
comptime {
FunctionSelector::from_signature("complete_refund((Field),Field,(Field,Field))")
},
[accepted_asset.to_field(), refund_slot, max_fee_serialized[0], max_fee_serialized[1]],
);

// Set the FPC as the fee payer of the tx.
context.set_as_fee_payer();
}
// docs:end:fee_entrypoint_private

/// Executed as a public teardown function and is responsible for completing the refund in the private fee payment
/// flow.
// docs:start:complete_refund
#[public]
#[internal]
fn complete_refund(accepted_asset: AztecAddress, refund_slot: Field, max_fee: U128) {
let tx_fee = U128::from_integer(context.transaction_fee());

// 1. Check that user funded the fee payer contract with at least the transaction fee.
// TODO(#10805): Nuke this check once we have a proper max_fee check in the fee_entrypoint_private.
assert(max_fee >= tx_fee, "max fee not enough to cover tx fee");

// 2. Compute the refund amount as the difference between funded amount and the tx fee.
// TODO(#10805): Introduce a real exchange rate
let refund_amount = max_fee - tx_fee;

Token::at(accepted_asset).finalize_transfer_to_private(refund_amount, refund_slot).call(
&mut context,
);
}
// docs:end:complete_refund

/// Pays for the tx fee with msg_sender's public balance of accepted asset (AA). The maximum fee a user is willing
/// to pay is defined by `max_fee` and is denominated in AA.
Expand Down Expand Up @@ -139,9 +178,9 @@ contract FPC {
);
}

/// Pays the refund to the `refund_recipient`. The refund is the difference between the `max_fee` and
/// the actual fee. `accepted_asset` is the asset in which the refund is paid. It's passed as an argument
/// to avoid the need for another read from public storage.
/// Pays the refund to the `refund_recipient` as part of the public fee payment flow. The refund is the difference
/// between the `max_fee` and the actual fee. `accepted_asset` is the asset in which the refund is paid.
/// It's passed as an argument to avoid the need for another read from public storage.
#[public]
#[internal]
fn pay_refund(refund_recipient: AztecAddress, max_fee: U128, accepted_asset: AztecAddress) {
Expand Down
Loading