diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-js/how_to_pay_fees.md b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-js/how_to_pay_fees.md index 92e43b3c27a..5157c1ff659 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-js/how_to_pay_fees.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-js/how_to_pay_fees.md @@ -181,7 +181,7 @@ const { receipt: tx } = await bananaCoin.methods For transactions where the fee payment itself should be private, you can use a fully private FPC, one that holds Fee Juice claimed from L1 as an internal private balance, works on every network, and never needs an onchain deployment. See [Pay Fees Privately](./how_to_use_private_fee_juice.md) for how this pattern works and a walkthrough using a community-built example. :::tip Shared salt for privacy -When multiple apps derive the same private FPC address (using the same artifact and salt), every private fee payment joins a single, larger privacy set. See [recommended salt](./how_to_use_private_fee_juice.md#recommended-salt-0) for details. +When multiple apps derive the same private FPC address (using the same artifact and salt), every private fee payment joins a single, larger privacy set. See [recommended salt](./how_to_use_private_fee_juice.md#recommended-salt-1) for details. ::: ### Third-party FPCs on testnet and mainnet diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-js/how_to_use_private_fee_juice.md b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-js/how_to_use_private_fee_juice.md index 3771aa82388..d71fd18a618 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-js/how_to_use_private_fee_juice.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-js/how_to_use_private_fee_juice.md @@ -34,7 +34,7 @@ This section describes the design pattern using the `private-fee-juice` package' Two different salt values show up in this flow; it's worth naming them up front so they don't get confused: -- **Deployment salt.** Used to derive the FPC's contract address. Once a community agrees on the bytecode and this salt, everyone can derive the same address locally without an onchain deployment tx. The convention for the `private-fee-juice` `PrivateFPC` is `Fr.ZERO` (see [Recommended salt](#recommended-salt-0)). +- **Deployment salt.** Used to derive the FPC's contract address. Once a community agrees on the bytecode and this salt, everyone can derive the same address locally without an onchain deployment tx. The canonical value for the `private-fee-juice` `PrivateFPC` is `1` (see [Recommended salt](#recommended-salt-1)). - **Bridge salt.** A random value the user chooses per L1 deposit. Combined with the user's Aztec address, it derives the _bridge secret_ (`secret = poseidon2([salt, claimer], DOM_SEP__FPC_BRIDGE_SECRET)`), whose hash is passed as the `secretHash` on the L1 deposit. Only the user knows the preimage, so only the user can later produce the `secret` that `FeeJuice.claim` requires to consume the L1-to-L2 message. `PrivateFPC.mint(amount, salt, leaf_index)` and `PrivateFPC.mint_and_pay_fee(amount, salt, leaf_index)` take the **bridge** salt (along with the leaf index and the user's claimer address, which is `msg_sender`) to reconstruct the Fee Juice claim nullifier and verify the bridge was consumed. @@ -70,9 +70,9 @@ Fee payments add one extra observable: the transaction's fee payer address, set This is the whole point of a fully private FPC. Because you don't have to deploy it on L2, there is no race to "be the deployer": the only thing that matters is that everyone agrees on the address. -## Recommended salt: `0` +## Recommended salt: `1` -Two parties derive the same contract address if and only if they use the same compiled artifact and the same deployment salt. For any fully private FPC, using a common salt maximizes the shared privacy set. The community convention for the `private-fee-juice` `PrivateFPC` is `Fr.ZERO`. +Two parties derive the same contract address if and only if they use the same compiled artifact and the same deployment salt. For any fully private FPC, using a common salt maximizes the shared privacy set. The canonical salt for the `private-fee-juice` `PrivateFPC` is `1` (`Fr.ONE`). This is a convention, not a protocol-enforced default. It is up to each developer to pass the salt when registering the contract with their PXE, just as they choose any other deployment parameter. Following the convention means your users' private fee payments join the same privacy set as every other app that follows it. diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-20.md b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-20.md index 95cfbcc08f1..d9a22bc5c3a 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-20.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-20.md @@ -4,7 +4,7 @@ sidebar_position: 1 description: Fungible token standard with private balances, partial-note transfers, and recursive note consumption. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v5.2.0/src/token_contract) AIP-20 defines a fungible token with support for private balances (stored as notes in the note hash tree), public balances (stored in contract public storage), and a hybrid transfer path between the two. diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-4626.md b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-4626.md index 7db638cf283..13f590ebbef 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-4626.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-4626.md @@ -4,7 +4,7 @@ sidebar_position: 3 description: Yield-bearing vault standard with share conversion across private and public contexts. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/vault_contract) (extends the AIP-20 token contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v5.2.0/src/vault_contract) (extends the AIP-20 token contract) AIP-4626 extends [AIP-20](./aip-20.md) to describe a tokenized vault: a contract that holds an underlying asset and issues shares representing a proportional claim on that asset. It mirrors the design of ERC-4626 but adapts the share conversion arithmetic for Aztec's `u128` integer type. diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-721.md b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-721.md index d661cde80af..8385731195c 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-721.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/aip-721.md @@ -4,7 +4,7 @@ sidebar_position: 2 description: Non-fungible token standard with private ownership, partial-note support, and commitment-based transfers. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/nft_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v5.2.0/src/nft_contract) AIP-721 defines a non-fungible token (NFT). Each token is identified by a unique `token_id` field. Tokens can be held privately in the note hash tree or publicly in a map from `token_id` to owner address. diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/dripper.md b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/dripper.md index 7032fcd9076..4a2b7e04343 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/dripper.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/dripper.md @@ -4,6 +4,6 @@ sidebar_position: 6 description: Convenience faucet for minting tokens into private or public balances during development. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/dripper) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v5.2.0/src/dripper) The `aztec-standards` repository also ships a **Dripper** contract — a convenience faucet for minting tokens into private or public balances during development. It is not a formal AIP standard and should not be used in production. diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/escrow.md b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/escrow.md index 00761e65fea..980876b1105 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/escrow.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/escrow.md @@ -4,7 +4,7 @@ sidebar_position: 4 description: Minimal token and NFT custody contract with salt-based authorization. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/escrow_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v5.2.0/src/escrow_contract) The Escrow standard provides a minimal contract for holding tokens or NFTs on behalf of a single owner. Rather than storing the owner in mutable private state — which would require note discovery and decryption on every authorization check — the owner is encoded in the contract's own `salt` and thus baked into the contract address at deploy time. This makes authorization a simple field comparison against immutable deployment parameters: cheaper, simpler, and impossible to front-run. diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/index.md b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/index.md index 076e7577523..ec9543ca215 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/index.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/aztec-nr/standards/index.md @@ -1,17 +1,17 @@ --- title: Aztec Contract Standards sidebar_position: 0 -description: Overview of Aztec Improvement Proposal (AIP) contract standards maintained by DeFi Wonderland. +description: Overview of Aztec Improvement Proposal (AIP) contract standards maintained by the Aztec Foundation. --- Aztec contract standards define shared interfaces and behaviors for common onchain primitives. They serve the same role that ERC standards play on Ethereum: establishing conventions that allow contracts, wallets, and tooling to interoperate without prior coordination. -The standards described in this section are maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards) in the `aztec-standards` repository. Each standard is identified by an **Aztec Improvement Proposal (AIP)** number that mirrors its Ethereum counterpart where applicable (AIP-20 corresponds to ERC-20, AIP-721 to ERC-721, AIP-4626 to ERC-4626). +The standards described in this section are maintained by the Aztec Foundation in the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards). Each standard is identified by an **Aztec Improvement Proposal (AIP)** number that mirrors its Ethereum counterpart where applicable (AIP-20 corresponds to ERC-20, AIP-721 to ERC-721, AIP-4626 to ERC-4626). Because Aztec contracts have both private and public execution contexts, the standards are more involved than their Ethereum equivalents. Transfers can move value between private notes and public balances, and many operations require coordination between encrypted state and transparent state within a single transaction. :::note -The code examples in this section are taken from the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. They will differ from the reference contract implementations shipped in the [aztec-packages repo](https://github.com/AztecProtocol/aztec-packages) under `noir-projects/noir-contracts/contracts/`. When in doubt, consult the aztec-standards github repo for the canonical standard interfaces. +The code examples in this section are taken from the [aztec-standards repository](https://github.com/AztecProtocol/aztec-standards) maintained by the Aztec Foundation. They will differ from the reference contract implementations shipped in the [aztec-packages repo](https://github.com/AztecProtocol/aztec-packages) under `noir-projects/noir-contracts/contracts/`. When in doubt, consult the aztec-standards github repo for the canonical standard interfaces. ::: ## Standards @@ -30,4 +30,4 @@ The code examples in this section are taken from the [aztec-standards repository - [Deploying a Token Contract](../../tutorials/js_tutorials/aztecjs-getting-started.md) — deploy and interact with the reference token contract using Aztec.js - [Counter Contract](../../tutorials/contract_tutorials/counter_contract.md) — introduces private state, notes, and balance management -For the canonical implementations and latest interface specifications, refer to the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. +For the canonical implementations and latest interface specifications, refer to the [aztec-standards repository](https://github.com/AztecProtocol/aztec-standards) maintained by the Aztec Foundation. diff --git a/docs/developer_versioned_docs/version-v5.2.0/docs/tutorials/js_tutorials/aztecjs-getting-started.md b/docs/developer_versioned_docs/version-v5.2.0/docs/tutorials/js_tutorials/aztecjs-getting-started.md index 6584b3ccff7..5b1144ff2bc 100644 --- a/docs/developer_versioned_docs/version-v5.2.0/docs/tutorials/js_tutorials/aztecjs-getting-started.md +++ b/docs/developer_versioned_docs/version-v5.2.0/docs/tutorials/js_tutorials/aztecjs-getting-started.md @@ -197,7 +197,7 @@ Have a look at the [contract source](https://github.com/AztecProtocol/aztec-pack The pre-compiled token contract used in this tutorial is Aztec's reference implementation. It covers the core operations you need to get started: minting, private transfers, and public balance management. -For production applications, consider the **AIP-20 Token Standard** maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract). AIP-20 formalizes the same patterns used in the reference contract and adds: +For production applications, consider the **AIP-20 Token Standard** maintained by the Aztec Foundation in the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards/tree/v5.2.0/src/token_contract). AIP-20 formalizes the same patterns used in the reference contract and adds: - **Commitment-based transfers** for DeFi protocols where the recipient is determined asynchronously - **Recursive note consumption** for handling large balances that span many notes diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-js/how_to_pay_fees.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-js/how_to_pay_fees.md index fce730774ab..bde5e9feb97 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-js/how_to_pay_fees.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-js/how_to_pay_fees.md @@ -182,7 +182,7 @@ const { receipt: tx } = await bananaCoin.methods For transactions where the fee payment itself should be private, you can use a fully private FPC, one that holds Fee Juice claimed from L1 as an internal private balance, works on every network, and never needs an onchain deployment. See [Pay Fees Privately](./how_to_use_private_fee_juice.md) for how this pattern works and a walkthrough using a community-built example. :::tip Shared salt for privacy -When multiple apps derive the same private FPC address (using the same artifact and salt), every private fee payment joins a single, larger privacy set. See [recommended salt](./how_to_use_private_fee_juice.md#recommended-salt-0) for details. +When multiple apps derive the same private FPC address (using the same artifact and salt), every private fee payment joins a single, larger privacy set. See [recommended salt](./how_to_use_private_fee_juice.md#recommended-salt-1) for details. ::: ### Third-party FPCs on testnet and mainnet diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-js/how_to_use_private_fee_juice.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-js/how_to_use_private_fee_juice.md index a8d0dd2ad33..2fd0f21ac86 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-js/how_to_use_private_fee_juice.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-js/how_to_use_private_fee_juice.md @@ -35,7 +35,7 @@ This section describes the design pattern using the `private-fee-juice` package' Two different salt values show up in this flow; it's worth naming them up front so they don't get confused: -- **Deployment salt.** Used to derive the FPC's contract address. Once a community agrees on the bytecode and this salt, everyone can derive the same address locally without an onchain deployment tx. The convention for the `private-fee-juice` `PrivateFPC` is `Fr.ZERO` (see [Recommended salt](#recommended-salt-0)). +- **Deployment salt.** Used to derive the FPC's contract address. Once a community agrees on the bytecode and this salt, everyone can derive the same address locally without an onchain deployment tx. The canonical value for the `private-fee-juice` `PrivateFPC` is `1` (see [Recommended salt](#recommended-salt-1)). - **Bridge salt.** A random value the user chooses per L1 deposit. Combined with the user's Aztec address, it derives the _bridge secret_ (`secret = poseidon2([salt, claimer], DOM_SEP__FPC_BRIDGE_SECRET)`), whose hash is passed as the `secretHash` on the L1 deposit. Only the user knows the preimage, so only the user can later produce the `secret` that `FeeJuice.claim` requires to consume the L1-to-L2 message. `PrivateFPC.mint(amount, salt, leaf_index)` and `PrivateFPC.mint_and_pay_fee(amount, salt, leaf_index)` take the **bridge** salt (along with the leaf index and the user's claimer address, which is `msg_sender`) to reconstruct the Fee Juice claim nullifier and verify the bridge was consumed. @@ -71,9 +71,9 @@ Fee payments add one extra observable: the transaction's fee payer address, set This is the whole point of a fully private FPC. Because you don't have to deploy it on L2, there is no race to "be the deployer": the only thing that matters is that everyone agrees on the address. -## Recommended salt: `0` +## Recommended salt: `1` -Two parties derive the same contract address if and only if they use the same compiled artifact and the same deployment salt. For any fully private FPC, using a common salt maximizes the shared privacy set. The community convention for the `private-fee-juice` `PrivateFPC` is `Fr.ZERO`. +Two parties derive the same contract address if and only if they use the same compiled artifact and the same deployment salt. For any fully private FPC, using a common salt maximizes the shared privacy set. The `private-fee-juice` package fixes the canonical salt for its `PrivateFPC` at `1` (`Fr.ONE`) and records it, with the expected address for the Aztec version the package targets, in [`canonical-deployment.json`](https://github.com/alejoamiras/ecosystem-tooling/blob/main/packages/private-fee-juice/canonical-deployment.json). This is a convention, not a protocol-enforced default. It is up to each developer to pass the salt when registering the contract with their PXE, just as they choose any other deployment parameter. Following the convention means your users' private fee payments join the same privacy set as every other app that follows it. diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-20.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-20.md index 95cfbcc08f1..57469a9fa3e 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-20.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-20.md @@ -4,7 +4,7 @@ sidebar_position: 1 description: Fungible token standard with private balances, partial-note transfers, and recursive note consumption. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0/src/token_contract) AIP-20 defines a fungible token with support for private balances (stored as notes in the note hash tree), public balances (stored in contract public storage), and a hybrid transfer path between the two. @@ -22,13 +22,11 @@ struct Storage { total_supply: PublicMutable, public_balances: Map, Context>, minter: PublicImmutable, - upgrade_authority: PublicImmutable, - asset: PublicImmutable, - vault_offset: PublicImmutable, + auth_contract: PublicImmutable, } ``` -The `asset` and `vault_offset` fields exist to support the [AIP-4626 vault pattern](./aip-4626.md). A standalone AIP-20 token that is not used as a vault underlying asset does not need to populate these fields. +`auth_contract` is an optional authorization hook from ARC-403, a draft extension. When it is not the zero address, every transfer and burn calls it before balances change. Vault state such as `asset` and `vault_offset` lives in the separate [AIP-4626 vault contract](./aip-4626.md), not in the token. ## Note count constants @@ -41,7 +39,7 @@ global RECURSIVE_TRANSFER_CALL_MAX_NOTES: u32 = 8; The initial call attempts to settle the transfer with at most two notes. If that is not enough to cover the amount, the contract recurses into itself and tries up to eight notes per recursive call. -A **placeholder address** is used in partial-note flows to signal that a transfer destination is not yet known at the time the sender initiates the operation. This distinguishes "recipient not yet determined" from "recipient is the zero address," and allows offchain indexers to detect [partial-note transfers](#partial-note-transfers) in event logs without decrypting the note contents: +`Transfer` events use a **sentinel address** for the private side of a balance change, whose counterpart cannot be revealed: a public-to-private transfer emits `Transfer(from, PRIVATE_ADDRESS_MAGIC_VALUE, amount)`. As in ERC-20, the zero address marks where a mint comes from and where a burn goes: ```rust global PRIVATE_ADDRESS_MAGIC_VALUE: AztecAddress = @@ -50,11 +48,11 @@ global PRIVATE_ADDRESS_MAGIC_VALUE: AztecAddress = ## Partial-note transfers -AIP-20 supports partial-note (or "commitment-based") transfers. In Aztec, private functions execute on the user's device before the transaction reaches the network, so they cannot read public state (like a DEX order book or auction result). Partial notes solve this by splitting the operation: the sender privately locks funds into a commitment, and later a public function — which *can* read public state — completes the transfer to the correct recipient. This is what makes private DeFi composability possible. +AIP-20 supports partial-note (or "commitment-based") transfers. In Aztec, private functions execute on the user's device before the transaction reaches the network, so they cannot read public state (like a DEX order book or auction result). Partial notes solve this by splitting the operation: the recipient privately creates a commitment to a note it will own, and a completer later fills in the amount, often from a public function that *can* read public state. This is what makes private DeFi composability possible. -Concretely, the sender locks funds in a note whose destination address is not yet known. A completer — typically a contract acting as a relayer or settlement layer — later fills in the recipient and finalizes the note. +Concretely, a commitment is owned by `to` and only `completer` can complete it. The completer, typically a contract acting as a relayer or settlement layer, completes it with `transfer_public_to_commitment`, `transfer_private_to_commitment` or `mint_to_commitment` and chooses the amount. Complete each commitment at most once. -The sender calls `initialize_transfer_commitment` to create the commitment: +The recipient (or a contract acting for it) calls `initialize_transfer_commitment` to create the commitment: ```rust #[external("private")] @@ -64,9 +62,10 @@ fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> } ``` -The returned `Field` is an opaque commitment to the destination and completer. A separate call then subtracts the balance and completes the note: +The returned `Field` identifies the commitment. The completer can then fill it from a private balance: the call spends notes of `from`, returns any change to `from`, and completes the commitment with `amount`, which becomes public. `msg_sender` must be the completer, and the commitment must come from an earlier block: ```rust +#[authorize_once("from", "_nonce")] #[external("private")] fn transfer_private_to_commitment( from: AztecAddress, @@ -74,7 +73,7 @@ fn transfer_private_to_commitment( amount: u128, _nonce: Field, ) { - _validate_from_private::<4>(self.context, from); + self.internal._call_auth_private(from, amount); self.internal._decrease_private_balance(from, amount, INITIAL_TRANSFER_CALL_MAX_NOTES); @@ -82,12 +81,13 @@ fn transfer_private_to_commitment( PartialUintNote::from_field(commitment).complete_from_private( self.context, completer, + self.storage.private_balances.get_storage_slot(), amount, ); } ``` -This two-step design is useful in DeFi protocols where the recipient of funds depends on some offchain or asynchronous computation. +This two-step design is useful in DeFi protocols where the amount a user receives depends on public state or on some offchain or asynchronous computation. ## Recursive balance subtraction diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-4626.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-4626.md index 7db638cf283..53fa4b574d0 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-4626.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-4626.md @@ -4,36 +4,67 @@ sidebar_position: 3 description: Yield-bearing vault standard with share conversion across private and public contexts. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/vault_contract) (extends the AIP-20 token contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0/src/vault_contract) (uses AIP-20 tokens for the asset and the shares) -AIP-4626 extends [AIP-20](./aip-20.md) to describe a tokenized vault: a contract that holds an underlying asset and issues shares representing a proportional claim on that asset. It mirrors the design of ERC-4626 but adapts the share conversion arithmetic for Aztec's `u128` integer type. +AIP-4626 describes a tokenized vault: a contract that holds an underlying asset and issues shares representing a proportional claim on that asset. In the `aztec-standards` implementation, the vault is a standalone contract and the shares are a separate [AIP-20](./aip-20.md) token whose minter is the vault. The design is inspired by ERC-4626, but the separate share token makes it closer to ERC-7575, and it adapts the share conversion arithmetic for Aztec's `u128` integer type. + +:::warning +The `aztec-standards` repository marks the vault as not production-ready. Its ordering-based reentrancy protection does not hold when the asset token or the share token has an ARC-403 authorization hook configured, and its conversions stop working near a `u128` capacity ceiling. Read the [vault README](https://github.com/AztecProtocol/aztec-standards/blob/v6.0.0/src/vault_contract/README.md) before wrapping a token. +::: ## Share conversion -The vault tracks the total supply of shares and a `vault_offset` that prevents inflation attacks on the initial deposit. The conversion functions use integer arithmetic with configurable rounding direction: +The vault reads the total supply from the share token and keeps a `vault_offset` that prevents inflation attacks on the initial deposit. The conversion functions take both as arguments, use integer arithmetic with a configurable rounding direction, and compute the intermediate product in 256 bits so it does not overflow `u128`: ```rust -#[internal("public")] -fn _convert_to_shares(assets: u128, total_assets: u128, rounding: bool) -> u128 { - let mul_term = - assets * (self.storage.total_supply.read() + self.storage.vault_offset.read()); - let denominator = (total_assets + 1); - let mut shares = mul_term / denominator; - if (rounding == ROUND_UP) & (mul_term % denominator > 0) { - shares = shares + 1; - } - shares +/// @notice Converts an amount of assets to shares using the given exchange rate parameters +/// @param assets The amount of assets to convert +/// @param total_assets The total amount of assets in the vault +/// @param total_supply The total supply of shares +/// @param vault_offset The vault offset +/// @param rounding The rounding direction (ROUND_UP or ROUND_DOWN) +/// @return The equivalent amount of shares +#[contract_library_method] +fn _convert_to_shares( + assets: u128, + total_assets: u128, + total_supply: u128, + vault_offset: u128, + rounding: bool, +) -> u128 { + // `assets * (total_supply + vault_offset)` overflows u128 for large inputs; compute it in a + // 256-bit intermediate. Rounding is unchanged (see `conversion::mul_div`). + crate::conversion::mul_div( + assets, + total_supply + vault_offset, + total_assets + 1, + rounding == ROUND_UP, + ) } -#[internal("public")] -fn _convert_to_assets(shares: u128, total_assets: u128, rounding: bool) -> u128 { - let mul_term = shares * (total_assets + 1); - let denominator = (self.storage.total_supply.read() + self.storage.vault_offset.read()); - let mut assets = mul_term / denominator; - if (rounding == ROUND_UP) & (mul_term % denominator > 0) { - assets = assets + 1; - } - assets +/// @notice Converts an amount of shares to assets using the given exchange rate parameters +/// @param shares The amount of shares to convert +/// @param total_assets The total amount of assets in the vault +/// @param total_supply The total supply of shares +/// @param vault_offset The vault offset +/// @param rounding The rounding direction (ROUND_UP or ROUND_DOWN) +/// @return The equivalent amount of assets +#[contract_library_method] +fn _convert_to_assets( + shares: u128, + total_assets: u128, + total_supply: u128, + vault_offset: u128, + rounding: bool, +) -> u128 { + // `shares * (total_assets + 1)` overflows u128 for large inputs; compute it in a 256-bit + // intermediate. Rounding is unchanged (see `conversion::mul_div`). + crate::conversion::mul_div( + shares, + total_assets + 1, + total_supply + vault_offset, + rounding == ROUND_UP, + ) } ``` @@ -43,24 +74,35 @@ Deposits round shares down (in favor of the vault), while redemptions round asse ## Deposit flow -A public-to-public deposit transfers assets from the caller to the vault, computes the shares due, and mints them to the recipient: +A public-to-public deposit computes the shares due, takes the assets from `from`, and has the share token mint the shares to the recipient: ```rust #[external("public")] -fn deposit_public_to_public(from: AztecAddress, to: AztecAddress, assets: u128, _nonce: Field) { +fn deposit_public_to_public(from: AztecAddress, to: AztecAddress, assets: u128, nonce: Field) { + let asset_token = self.storage.asset.read(); + let shares_token = self.storage.shares.read(); self.internal._validate_from_public(from); + // Calculate shares to mint let total_assets = self.internal._total_assets(); - let shares = self.internal._convert_to_shares(assets, total_assets, ROUND_DOWN); + let total_supply = self.internal._total_supply_of_shares(); + let vault_offset = self.storage.vault_offset.read(); + let shares = + _convert_to_shares(assets, total_assets, total_supply, vault_offset, ROUND_DOWN); + assert(shares > 0, "Zero shares, insufficient assets"); - // Transfer assets from sender to vault - self.call(Token::at(self.storage.asset.read()).transfer_public_to_public( - from, self.address, assets, _nonce, + // Order matters: transfer before minting to order effects safely (necessary but NOT sufficient against a hooked token — see the ARC-403 reentrancy note at the top of this contract). + // Take the assets from the sender + self.call(Token::at(asset_token).transfer_public_to_public( + from, + self.address, + assets, + nonce, )); // Mint shares to the recipient - self.internal._mint_to_public(to, shares); + self.call(Token::at(shares_token).mint_to_public(to, shares)); } ``` -The vault exposes similar entry points for the other combinations of private and public contexts (`deposit_private_to_public`, `deposit_public_to_private`, `deposit_private_to_private`). Each variant transfers assets using the corresponding AIP-20 transfer function and then mints shares into the chosen output context. +The vault exposes similar entry points for the other combinations of private and public contexts (`deposit_private_to_public`, `deposit_public_to_private`, `deposit_private_to_private`). The variants that mint shares into a private balance take the number of shares as an argument and check in public that `assets` covers it, since the exchange rate is public state. diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-721.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-721.md index d661cde80af..a149cce124a 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-721.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/aip-721.md @@ -4,7 +4,7 @@ sidebar_position: 2 description: Non-fungible token standard with private ownership, partial-note support, and commitment-based transfers. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/nft_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0/src/nft_contract) AIP-721 defines a non-fungible token (NFT). Each token is identified by a unique `token_id` field. Tokens can be held privately in the note hash tree or publicly in a map from `token_id` to owner address. @@ -19,7 +19,6 @@ struct Storage { nft_exists: Map, Context>, public_owners: Map, Context>, minter: PublicImmutable, - upgrade_authority: PublicImmutable, } ``` @@ -30,32 +29,69 @@ struct Storage { Each private NFT is represented as an `NFTNote` containing only the `token_id`: ```rust +/// A private note representing a token id associated to an account. #[derive(Eq, Serialize, Packable)] #[custom_note] pub struct NFTNote { + /// The ID of the token represented by this note. pub token_id: Field, } +``` + +`NFTNote::partial` creates a `PartialNFTNote`, an incomplete note whose owner stays private: + +```rust +pub fn partial( + owner: AztecAddress, + context: &mut PrivateContext, + recipient: AztecAddress, + completer: AztecAddress, +) -> PartialNFTNote { + // Safety: We use the randomness to preserve the privacy of the note recipient by preventing brute-forcing, + // so a malicious sender could use non-random values to make the note less private. But they already know + // the full note pre-image anyway, and so the recipient already trusts them to not disclose this + // information. We can therefore assume that the sender will cooperate in the random value generation. + let randomness = unsafe { random() }; + + // We create a commitment to the private data, which we then use to construct the log we send to the recipient. + let commitment = compute_partial_commitment(owner, randomness); + + // Our partial note log encoding scheme includes a field with the tag of the public completion log, and we use + // the commitment as the tag. This is good for multiple reasons: + // - the commitment is uniquely tied to this partial note + // - the commitment is already public information, so we're not revealing anything else + // - we don't need to create any additional information, private or public, for the tag + // - other contracts cannot impersonate us and emit logs with the same tag due to public log siloing + let private_log_content = NFTPartialNotePrivateLogContent {}; + + do_private_message_delivery( + context, + || { + encode_partial_note_private_message( + private_log_content, + owner, + randomness, + commitment, + ) + }, + Option::none(), + recipient, + MessageDelivery::onchain_unconstrained(), + ); + + let partial_note = PartialNFTNote { commitment }; + + // Now we compute the validity commitment and push it to the nullifier tree. It can be safely pushed to + // the nullifier tree since it uses its own separator, making collisions with actual note nullifiers + // practically impossible. + let validity_commitment = partial_note.compute_validity_commitment(completer); + context.push_nullifier_unsafe(validity_commitment); -impl NFTNote { - pub fn partial( - owner: AztecAddress, - storage_slot: Field, - context: &mut PrivateContext, - recipient: AztecAddress, - completer: AztecAddress, - ) -> PartialNFTNote { - let randomness = unsafe { random() }; - let commitment = compute_partial_commitment(owner, storage_slot, randomness); - // ... creates encrypted log and validity commitment - let partial_note = PartialNFTNote { commitment }; - let validity_commitment = partial_note.compute_validity_commitment(completer); - context.push_nullifier_unsafe(validity_commitment); - partial_note - } + partial_note } ``` -The `partial` constructor creates a `PartialNFTNote` whose `commitment` field commits to the future owner and storage slot. Without some form of access control, any party could call the completion function and claim the NFT for themselves. The validity commitment prevents this — it is pushed as a nullifier, and only the designated completer can produce the matching preimage needed to finalize the note. This mirrors the partial-note pattern in [AIP-20](./aip-20.md) but applies it to NFT transfers. +The `commitment` commits to the future owner and a random value, so completing the note cannot change who owns it. The storage slot and `token_id` become public when the note is completed. What needs protecting is who completes it: without some form of access control, any party could fill the commitment before the intended completer, for example with a different token. The validity commitment prevents this: it binds the partial note to the designated completer and is pushed as a nullifier, and completion checks that it exists for the `completer` passed in. The note does not check who the caller is, so the NFT contract passes `msg_sender()` as the completer, and only the designated completer can finalize the note. This mirrors the partial-note pattern in [AIP-20](./aip-20.md) but applies it to NFT transfers. ## Partial-note transfer commitment @@ -65,8 +101,8 @@ The external entry point for initiating a partial NFT transfer is: #[external("private")] fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> Field { let commitment = self.internal._initialize_transfer_commitment(to, completer); - commitment.commitment() + commitment.commitment } ``` -This function returns `commitment.commitment()` — an opaque `Field` representing the commitment. The AIP-20 equivalent returns `commitment.to_field()` for `PartialUintNote`. +This function returns `commitment.commitment`, the `Field` stored in the `PartialNFTNote`. The AIP-20 equivalent returns `commitment.to_field()` for `PartialUintNote`. diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/dripper.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/dripper.md index 7032fcd9076..d8e0686e49e 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/dripper.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/dripper.md @@ -4,6 +4,6 @@ sidebar_position: 6 description: Convenience faucet for minting tokens into private or public balances during development. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/dripper) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0/src/dripper) The `aztec-standards` repository also ships a **Dripper** contract — a convenience faucet for minting tokens into private or public balances during development. It is not a formal AIP standard and should not be used in production. diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/escrow.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/escrow.md index 00761e65fea..0d0384c73fb 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/escrow.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/escrow.md @@ -4,7 +4,7 @@ sidebar_position: 4 description: Minimal token and NFT custody contract with salt-based authorization. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/escrow_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0/src/escrow_contract) The Escrow standard provides a minimal contract for holding tokens or NFTs on behalf of a single owner. Rather than storing the owner in mutable private state — which would require note discovery and decryption on every authorization check — the owner is encoded in the contract's own `salt` and thus baked into the contract address at deploy time. This makes authorization a simple field comparison against immutable deployment parameters: cheaper, simpler, and impossible to front-run. @@ -16,17 +16,13 @@ pub contract Escrow { #[external("private")] fn withdraw(token: AztecAddress, amount: u128, recipient: AztecAddress) { self.internal._assert_msg_sender(); - self.call(Token::at(token).transfer_private_to_private( - self.address, recipient, amount, 0, - )); + self.call(Token::at(token).transfer_private_to_private(self.address, recipient, amount, 0)); } #[external("private")] fn withdraw_nft(nft: AztecAddress, token_id: Field, recipient: AztecAddress) { self.internal._assert_msg_sender(); - self.call(NFT::at(nft).transfer_private_to_private( - self.address, recipient, token_id, 0, - )); + self.call(NFT::at(nft).transfer_private_to_private(self.address, recipient, token_id, 0)); } #[internal("private")] @@ -42,38 +38,55 @@ The authorization check in `_assert_msg_sender` reads the `salt` field of the es ## Escrow logic library -A DeFi protocol (like a lending market or DEX) often needs to give each user a personal escrow to hold collateral or pending settlements. The standard ships a companion library that lets the parent contract deterministically compute escrow addresses from its own address and the user's keys — no onchain deployment transaction required: +A DeFi protocol (like a lending market or DEX) often needs to give each user a personal escrow to hold collateral or pending settlements. The standard ships a companion library that lets the parent contract deterministically compute escrow addresses from its own address and a secret key, with no onchain deployment transaction required: ```rust +/// @notice Returns the escrow address that corresponds to the given secret key and class ID. +/// @param context The private context +/// @param escrow_class_id The contract class id of the escrow contract +/// @param secret_key The secret key used to derive master secret keys and public keys +/// @return The escrow address #[contract_library_method] pub fn _get_escrow( context: &mut PrivateContext, escrow_class_id: Field, - master_secret_keys: MasterSecretKeys, + secret_key: Field, ) -> AztecAddress { - let computed_public_keys: PublicKeys = _secret_keys_to_public_keys(master_secret_keys); + // Compute the public keys from the secret key (sk -> msks -> pks) + let computed_public_keys: PublicKeys = secret_key_to_public_keys(secret_key); + let escrow_instance = ContractInstance { salt: context.this_address().to_field(), deployer: AztecAddress::from_field(0), original_contract_class_id: ContractClassId::from_field(escrow_class_id), initialization_hash: 0, + immutables_hash: 0, public_keys: computed_public_keys, }; + escrow_instance.to_address() } +/// @notice Shares the escrow details needed to find and use the escrow contract +/// @dev Emits a private log with the escrow details +/// @param context The private context +/// @param account The address of the account that will use the escrow +/// @param escrow The address of the escrow +/// @param secret_key The secret key used to derive master secret keys and public keys #[contract_library_method] pub fn _share_escrow( context: &mut PrivateContext, account: AztecAddress, escrow: AztecAddress, - master_secret_keys: MasterSecretKeys, + secret_key: Field, ) { - let event_struct = EscrowDetailsLogContent { escrow, master_secret_keys }; + let event_struct = EscrowDetailsLogContent { escrow, secret_key }; + emit_event_in_private(context, event_struct).deliver_to( - account, MessageDelivery::onchain_constrained(), + account, + MessageDelivery::onchain_constrained(), ); } ``` -`_get_escrow` reconstructs the escrow address deterministically from the calling contract's address (used as the salt) and a set of master secret keys. `_share_escrow` emits an encrypted log so that the designated `account` can discover the escrow address and the keys needed to access its notes. Without this notification, the user's PXE would have no way to find the escrow or decrypt notes held there. Constrained delivery ensures the log is validated against the note hash tree before the recipient's PXE trusts it. +`_get_escrow` reconstructs the escrow address deterministically from the calling contract's address (used as the salt) and a secret key, from which it derives the escrow's public keys. `_share_escrow` emits an encrypted log so that the designated `account` can discover the escrow address and the secret key needed to access its notes. Without this notification, the user's PXE would have no way to find the escrow or decrypt notes held there. Constrained delivery proves the log's encryption and tagging in the circuit, so the calling contract cannot emit a log that the recipient's PXE fails to find or decrypt. diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/generic-proxy.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/generic-proxy.md index 06c3c696c8b..5da2d8e789f 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/generic-proxy.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/generic-proxy.md @@ -4,6 +4,10 @@ sidebar_position: 5 description: Forwarding layer for account abstraction that routes calls by argument count. --- +:::note +In the `aztec-standards` repository, [`GenericProxy`](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0/src/generic_proxy) is an internal test utility, not a standard. Its Noir test suites use it when a call must come from a contract, and it carries no stability or compatibility guarantees, so do not build on it. +::: + In Aztec, account contracts authorize every transaction the user sends and must be able to forward calls to any contract. However, Noir requires function signatures to be known at compile time, so an account contract cannot call an arbitrary function with an arbitrary number of arguments in a single generic entrypoint. The Generic Proxy contract solves this by providing a fixed set of forwarding functions — one per argument count — that the account contract can call. This avoids hard-coding every possible target function signature while keeping the account contract simple. @@ -31,7 +35,7 @@ pub contract GenericProxy { self.context.call_private_function(target, selector, args).get_preimage(); returns } - // ... forward_private_1 through forward_private_8 + // ... forward_private_1 through forward_private_8, and forward_public_4 } ``` diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/index.md b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/index.md index 076e7577523..c31d53a4933 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/index.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/aztec-nr/standards/index.md @@ -1,33 +1,35 @@ --- title: Aztec Contract Standards sidebar_position: 0 -description: Overview of Aztec Improvement Proposal (AIP) contract standards maintained by DeFi Wonderland. +description: Overview of Aztec Improvement Proposal (AIP) contract standards maintained by the Aztec Foundation. --- Aztec contract standards define shared interfaces and behaviors for common onchain primitives. They serve the same role that ERC standards play on Ethereum: establishing conventions that allow contracts, wallets, and tooling to interoperate without prior coordination. -The standards described in this section are maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards) in the `aztec-standards` repository. Each standard is identified by an **Aztec Improvement Proposal (AIP)** number that mirrors its Ethereum counterpart where applicable (AIP-20 corresponds to ERC-20, AIP-721 to ERC-721, AIP-4626 to ERC-4626). +The standards described in this section are maintained by the Aztec Foundation in the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards). Each standard is identified by an **Aztec Improvement Proposal (AIP)** number that mirrors its Ethereum counterpart where applicable (AIP-20 corresponds to ERC-20, AIP-721 to ERC-721, AIP-4626 to ERC-4626). + +The contracts' compiled artifacts and TypeScript wrappers are published on npm as [`@aztec-foundation/aztec-standards`](https://www.npmjs.com/package/@aztec-foundation/aztec-standards). Because Aztec contracts have both private and public execution contexts, the standards are more involved than their Ethereum equivalents. Transfers can move value between private notes and public balances, and many operations require coordination between encrypted state and transparent state within a single transaction. :::note -The code examples in this section are taken from the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. They will differ from the reference contract implementations shipped in the [aztec-packages repo](https://github.com/AztecProtocol/aztec-packages) under `noir-projects/noir-contracts/contracts/`. When in doubt, consult the aztec-standards github repo for the canonical standard interfaces. +The code examples in this section are taken from the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0) at `v6.0.0`. They differ from the reference contract implementations under `noir-projects/noir-contracts/contracts/` in the [aztec-node repository](https://github.com/aztec-labs-eng/aztec-node). When in doubt, consult the `aztec-standards` repository for the canonical standard interfaces. The repository marks these contracts as unaudited: do not use them in production or to secure assets of value. ::: ## Standards -- [AIP-20: Fungible Token](./aip-20.md) — private and public balances, partial-note transfers, recursive note consumption -- [AIP-721: Non-Fungible Token](./aip-721.md) — private NFT ownership, partial-note support, commitment-based transfers -- [AIP-4626: Tokenized Vault](./aip-4626.md) — yield-bearing vaults with share conversion across private and public contexts -- [Escrow](./escrow.md) — minimal token/NFT custody with salt-based authorization -- [Generic Proxy](./generic-proxy.md) — forwarding layer for account abstraction patterns -- [Dripper](./dripper.md) — development faucet for testing +- [AIP-20: Fungible Token](./aip-20.md): private and public balances, partial-note transfers, recursive note consumption +- [AIP-721: Non-Fungible Token](./aip-721.md): private NFT ownership, partial-note support, commitment-based transfers +- [AIP-4626: Tokenized Vault](./aip-4626.md): yield-bearing vaults with share conversion across private and public contexts +- [Escrow](./escrow.md): minimal token/NFT custody with salt-based authorization +- [Generic Proxy](./generic-proxy.md): forwarding contract, an internal test utility in `aztec-standards` rather than a standard +- [Dripper](./dripper.md): development faucet for testing ## Related tutorials -- [Private Token Contract](../../tutorials/contract_tutorials/token_contract.md) — build a privacy-preserving fungible token that closely parallels AIP-20 -- [NFT Bridge](../../tutorials/js_tutorials/token_bridge.md) — build a private NFT with custom `NFTNote` and `PrivateSet`, covering patterns extended by AIP-721 -- [Deploying a Token Contract](../../tutorials/js_tutorials/aztecjs-getting-started.md) — deploy and interact with the reference token contract using Aztec.js -- [Counter Contract](../../tutorials/contract_tutorials/counter_contract.md) — introduces private state, notes, and balance management +- [Private Token Contract](../../tutorials/contract_tutorials/token_contract.md): build a privacy-preserving fungible token that closely parallels AIP-20 +- [NFT Bridge](../../tutorials/js_tutorials/token_bridge.md): build a private NFT with custom `NFTNote` and `PrivateSet`, covering patterns extended by AIP-721 +- [Deploying a Token Contract](../../tutorials/js_tutorials/aztecjs-getting-started.md): deploy and interact with the reference token contract using Aztec.js +- [Counter Contract](../../tutorials/contract_tutorials/counter_contract.md): introduces private state, notes, and balance management -For the canonical implementations and latest interface specifications, refer to the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. +For the canonical implementations and latest interface specifications, refer to the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards), maintained by the Aztec Foundation. diff --git a/docs/developer_versioned_docs/version-v6.0.0/docs/tutorials/js_tutorials/aztecjs-getting-started.md b/docs/developer_versioned_docs/version-v6.0.0/docs/tutorials/js_tutorials/aztecjs-getting-started.md index 5c06f8f8103..d5f5a5340c5 100644 --- a/docs/developer_versioned_docs/version-v6.0.0/docs/tutorials/js_tutorials/aztecjs-getting-started.md +++ b/docs/developer_versioned_docs/version-v6.0.0/docs/tutorials/js_tutorials/aztecjs-getting-started.md @@ -197,10 +197,12 @@ Have a look at the [contract source](https://github.com/aztec-labs-eng/aztec-nod The pre-compiled token contract used in this tutorial is Aztec's reference implementation. It covers the core operations you need to get started: minting, private transfers, and public balance management. -For production applications, consider the **AIP-20 Token Standard** maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract). AIP-20 formalizes the same patterns used in the reference contract and adds: +For more complete token features, consider the **AIP-20 Token Standard**, maintained by the Aztec Foundation in the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards/tree/v6.0.0/src/token_contract). AIP-20 formalizes the same patterns used in the reference contract and adds: -- **Commitment-based transfers** for DeFi protocols where the recipient is determined asynchronously +- **Commitment-based transfers** for DeFi protocols where the amount is determined asynchronously - **Recursive note consumption** for handling large balances that span many notes -- **Tokenized vault support (AIP-4626)** for yield-bearing tokens that issue shares against an underlying asset +- **Tokenized vaults (AIP-4626)**, where a separate vault contract issues AIP-20 shares against an AIP-20 asset + +The repository marks these contracts as unaudited: do not use them in production or to secure assets of value. To learn how to write a token contract from scratch rather than deploying a pre-compiled one, see the [Private Token Contract tutorial](../contract_tutorials/token_contract.md). For the full specifications of all Aztec contract standards, see the [Aztec Contract Standards](../../aztec-nr/standards/index.md) reference. diff --git a/docs/docs-developers/docs/aztec-js/how_to_pay_fees.md b/docs/docs-developers/docs/aztec-js/how_to_pay_fees.md index 089e9e0f839..057f827b180 100644 --- a/docs/docs-developers/docs/aztec-js/how_to_pay_fees.md +++ b/docs/docs-developers/docs/aztec-js/how_to_pay_fees.md @@ -109,7 +109,7 @@ Here's a simpler example from the test suite: For transactions where the fee payment itself should be private, you can use a fully private FPC, one that holds Fee Juice claimed from L1 as an internal private balance, works on every network, and never needs an onchain deployment. See [Pay Fees Privately](./how_to_use_private_fee_juice.md) for how this pattern works and a walkthrough using a community-built example. :::tip Shared salt for privacy -When multiple apps derive the same private FPC address (using the same artifact and salt), every private fee payment joins a single, larger privacy set. See [recommended salt](./how_to_use_private_fee_juice.md#recommended-salt-0) for details. +When multiple apps derive the same private FPC address (using the same artifact and salt), every private fee payment joins a single, larger privacy set. See [recommended salt](./how_to_use_private_fee_juice.md#recommended-salt-1) for details. ::: ### Third-party FPCs on testnet and mainnet diff --git a/docs/docs-developers/docs/aztec-js/how_to_use_private_fee_juice.md b/docs/docs-developers/docs/aztec-js/how_to_use_private_fee_juice.md index 2d3076517a9..0c8b40c566f 100644 --- a/docs/docs-developers/docs/aztec-js/how_to_use_private_fee_juice.md +++ b/docs/docs-developers/docs/aztec-js/how_to_use_private_fee_juice.md @@ -35,7 +35,7 @@ This section describes the design pattern using the `private-fee-juice` package' Two different salt values show up in this flow; it's worth naming them up front so they don't get confused: -- **Deployment salt.** Used to derive the FPC's contract address. Once a community agrees on the bytecode and this salt, everyone can derive the same address locally without an onchain deployment tx. The convention for the `private-fee-juice` `PrivateFPC` is `Fr.ZERO` (see [Recommended salt](#recommended-salt-0)). +- **Deployment salt.** Used to derive the FPC's contract address. Once a community agrees on the bytecode and this salt, everyone can derive the same address locally without an onchain deployment tx. The canonical value for the `private-fee-juice` `PrivateFPC` is `1` (see [Recommended salt](#recommended-salt-1)). - **Bridge salt.** A random value the user chooses per L1 deposit. Combined with the user's Aztec address, it derives the _bridge secret_ (`secret = poseidon2([salt, claimer], DOM_SEP__FPC_BRIDGE_SECRET)`), whose hash is passed as the `secretHash` on the L1 deposit. Only the user knows the preimage, so only the user can later produce the `secret` that `FeeJuice.claim` requires to consume the L1-to-L2 message. `PrivateFPC.mint(amount, salt, leaf_index)` and `PrivateFPC.mint_and_pay_fee(amount, salt, leaf_index)` take the **bridge** salt (along with the leaf index and the user's claimer address, which is `msg_sender`) to reconstruct the Fee Juice claim nullifier and verify the bridge was consumed. @@ -71,9 +71,9 @@ Fee payments add one extra observable: the transaction's fee payer address, set This is the whole point of a fully private FPC. Because you don't have to deploy it on L2, there is no race to "be the deployer": the only thing that matters is that everyone agrees on the address. -## Recommended salt: `0` +## Recommended salt: `1` -Two parties derive the same contract address if and only if they use the same compiled artifact and the same deployment salt. For any fully private FPC, using a common salt maximizes the shared privacy set. The community convention for the `private-fee-juice` `PrivateFPC` is `Fr.ZERO`. +Two parties derive the same contract address if and only if they use the same compiled artifact and the same deployment salt. For any fully private FPC, using a common salt maximizes the shared privacy set. The `private-fee-juice` package fixes the canonical salt for its `PrivateFPC` at `1` (`Fr.ONE`) and records it, with the expected address for the Aztec version the package targets, in [`canonical-deployment.json`](https://github.com/alejoamiras/ecosystem-tooling/blob/main/packages/private-fee-juice/canonical-deployment.json). This is a convention, not a protocol-enforced default. It is up to each developer to pass the salt when registering the contract with their PXE, just as they choose any other deployment parameter. Following the convention means your users' private fee payments join the same privacy set as every other app that follows it. diff --git a/docs/docs-developers/docs/aztec-nr/standards/aip-20.md b/docs/docs-developers/docs/aztec-nr/standards/aip-20.md index 95cfbcc08f1..a7dad6d0a97 100644 --- a/docs/docs-developers/docs/aztec-nr/standards/aip-20.md +++ b/docs/docs-developers/docs/aztec-nr/standards/aip-20.md @@ -4,7 +4,7 @@ sidebar_position: 1 description: Fungible token standard with private balances, partial-note transfers, and recursive note consumption. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/main/src/token_contract) AIP-20 defines a fungible token with support for private balances (stored as notes in the note hash tree), public balances (stored in contract public storage), and a hybrid transfer path between the two. @@ -22,13 +22,11 @@ struct Storage { total_supply: PublicMutable, public_balances: Map, Context>, minter: PublicImmutable, - upgrade_authority: PublicImmutable, - asset: PublicImmutable, - vault_offset: PublicImmutable, + auth_contract: PublicImmutable, } ``` -The `asset` and `vault_offset` fields exist to support the [AIP-4626 vault pattern](./aip-4626.md). A standalone AIP-20 token that is not used as a vault underlying asset does not need to populate these fields. +`auth_contract` is an optional authorization hook from ARC-403, a draft extension. When it is not the zero address, every transfer and burn calls it before balances change. Vault state such as `asset` and `vault_offset` lives in the separate [AIP-4626 vault contract](./aip-4626.md), not in the token. ## Note count constants @@ -41,7 +39,7 @@ global RECURSIVE_TRANSFER_CALL_MAX_NOTES: u32 = 8; The initial call attempts to settle the transfer with at most two notes. If that is not enough to cover the amount, the contract recurses into itself and tries up to eight notes per recursive call. -A **placeholder address** is used in partial-note flows to signal that a transfer destination is not yet known at the time the sender initiates the operation. This distinguishes "recipient not yet determined" from "recipient is the zero address," and allows offchain indexers to detect [partial-note transfers](#partial-note-transfers) in event logs without decrypting the note contents: +`Transfer` events use a **sentinel address** for the private side of a balance change, whose counterpart cannot be revealed: a public-to-private transfer emits `Transfer(from, PRIVATE_ADDRESS_MAGIC_VALUE, amount)`. As in ERC-20, the zero address marks where a mint comes from and where a burn goes: ```rust global PRIVATE_ADDRESS_MAGIC_VALUE: AztecAddress = @@ -50,11 +48,11 @@ global PRIVATE_ADDRESS_MAGIC_VALUE: AztecAddress = ## Partial-note transfers -AIP-20 supports partial-note (or "commitment-based") transfers. In Aztec, private functions execute on the user's device before the transaction reaches the network, so they cannot read public state (like a DEX order book or auction result). Partial notes solve this by splitting the operation: the sender privately locks funds into a commitment, and later a public function — which *can* read public state — completes the transfer to the correct recipient. This is what makes private DeFi composability possible. +AIP-20 supports partial-note (or "commitment-based") transfers. In Aztec, private functions execute on the user's device before the transaction reaches the network, so they cannot read public state (like a DEX order book or auction result). Partial notes solve this by splitting the operation: the recipient privately creates a commitment to a note it will own, and a completer later fills in the amount, often from a public function that *can* read public state. This is what makes private DeFi composability possible. -Concretely, the sender locks funds in a note whose destination address is not yet known. A completer — typically a contract acting as a relayer or settlement layer — later fills in the recipient and finalizes the note. +Concretely, a commitment is owned by `to` and only `completer` can complete it. The completer, typically a contract acting as a relayer or settlement layer, completes it with `transfer_public_to_commitment`, `transfer_private_to_commitment` or `mint_to_commitment` and chooses the amount. Complete each commitment at most once. -The sender calls `initialize_transfer_commitment` to create the commitment: +The recipient (or a contract acting for it) calls `initialize_transfer_commitment` to create the commitment: ```rust #[external("private")] @@ -64,9 +62,10 @@ fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> } ``` -The returned `Field` is an opaque commitment to the destination and completer. A separate call then subtracts the balance and completes the note: +The returned `Field` identifies the commitment. The completer can then fill it from a private balance: the call spends notes of `from`, returns any change to `from`, and completes the commitment with `amount`, which becomes public. `msg_sender` must be the completer, and the commitment must come from an earlier block: ```rust +#[authorize_once("from", "_nonce")] #[external("private")] fn transfer_private_to_commitment( from: AztecAddress, @@ -74,7 +73,7 @@ fn transfer_private_to_commitment( amount: u128, _nonce: Field, ) { - _validate_from_private::<4>(self.context, from); + self.internal._call_auth_private(from, amount); self.internal._decrease_private_balance(from, amount, INITIAL_TRANSFER_CALL_MAX_NOTES); @@ -82,12 +81,13 @@ fn transfer_private_to_commitment( PartialUintNote::from_field(commitment).complete_from_private( self.context, completer, + self.storage.private_balances.get_storage_slot(), amount, ); } ``` -This two-step design is useful in DeFi protocols where the recipient of funds depends on some offchain or asynchronous computation. +This two-step design is useful in DeFi protocols where the amount a user receives depends on public state or on some offchain or asynchronous computation. ## Recursive balance subtraction diff --git a/docs/docs-developers/docs/aztec-nr/standards/aip-4626.md b/docs/docs-developers/docs/aztec-nr/standards/aip-4626.md index 7db638cf283..ee4ad835453 100644 --- a/docs/docs-developers/docs/aztec-nr/standards/aip-4626.md +++ b/docs/docs-developers/docs/aztec-nr/standards/aip-4626.md @@ -4,36 +4,67 @@ sidebar_position: 3 description: Yield-bearing vault standard with share conversion across private and public contexts. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/vault_contract) (extends the AIP-20 token contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/main/src/vault_contract) (uses AIP-20 tokens for the asset and the shares) -AIP-4626 extends [AIP-20](./aip-20.md) to describe a tokenized vault: a contract that holds an underlying asset and issues shares representing a proportional claim on that asset. It mirrors the design of ERC-4626 but adapts the share conversion arithmetic for Aztec's `u128` integer type. +AIP-4626 describes a tokenized vault: a contract that holds an underlying asset and issues shares representing a proportional claim on that asset. In the `aztec-standards` implementation, the vault is a standalone contract and the shares are a separate [AIP-20](./aip-20.md) token whose minter is the vault. The design is inspired by ERC-4626, but the separate share token makes it closer to ERC-7575, and it adapts the share conversion arithmetic for Aztec's `u128` integer type. + +:::warning +The `aztec-standards` repository marks the vault as not production-ready. Its ordering-based reentrancy protection does not hold when the asset token or the share token has an ARC-403 authorization hook configured, and its conversions stop working near a `u128` capacity ceiling. Read the [vault README](https://github.com/AztecProtocol/aztec-standards/blob/main/src/vault_contract/README.md) before wrapping a token. +::: ## Share conversion -The vault tracks the total supply of shares and a `vault_offset` that prevents inflation attacks on the initial deposit. The conversion functions use integer arithmetic with configurable rounding direction: +The vault reads the total supply from the share token and keeps a `vault_offset` that prevents inflation attacks on the initial deposit. The conversion functions take both as arguments, use integer arithmetic with a configurable rounding direction, and compute the intermediate product in 256 bits so it does not overflow `u128`: ```rust -#[internal("public")] -fn _convert_to_shares(assets: u128, total_assets: u128, rounding: bool) -> u128 { - let mul_term = - assets * (self.storage.total_supply.read() + self.storage.vault_offset.read()); - let denominator = (total_assets + 1); - let mut shares = mul_term / denominator; - if (rounding == ROUND_UP) & (mul_term % denominator > 0) { - shares = shares + 1; - } - shares +/// @notice Converts an amount of assets to shares using the given exchange rate parameters +/// @param assets The amount of assets to convert +/// @param total_assets The total amount of assets in the vault +/// @param total_supply The total supply of shares +/// @param vault_offset The vault offset +/// @param rounding The rounding direction (ROUND_UP or ROUND_DOWN) +/// @return The equivalent amount of shares +#[contract_library_method] +fn _convert_to_shares( + assets: u128, + total_assets: u128, + total_supply: u128, + vault_offset: u128, + rounding: bool, +) -> u128 { + // `assets * (total_supply + vault_offset)` overflows u128 for large inputs; compute it in a + // 256-bit intermediate. Rounding is unchanged (see `conversion::mul_div`). + crate::conversion::mul_div( + assets, + total_supply + vault_offset, + total_assets + 1, + rounding == ROUND_UP, + ) } -#[internal("public")] -fn _convert_to_assets(shares: u128, total_assets: u128, rounding: bool) -> u128 { - let mul_term = shares * (total_assets + 1); - let denominator = (self.storage.total_supply.read() + self.storage.vault_offset.read()); - let mut assets = mul_term / denominator; - if (rounding == ROUND_UP) & (mul_term % denominator > 0) { - assets = assets + 1; - } - assets +/// @notice Converts an amount of shares to assets using the given exchange rate parameters +/// @param shares The amount of shares to convert +/// @param total_assets The total amount of assets in the vault +/// @param total_supply The total supply of shares +/// @param vault_offset The vault offset +/// @param rounding The rounding direction (ROUND_UP or ROUND_DOWN) +/// @return The equivalent amount of assets +#[contract_library_method] +fn _convert_to_assets( + shares: u128, + total_assets: u128, + total_supply: u128, + vault_offset: u128, + rounding: bool, +) -> u128 { + // `shares * (total_assets + 1)` overflows u128 for large inputs; compute it in a 256-bit + // intermediate. Rounding is unchanged (see `conversion::mul_div`). + crate::conversion::mul_div( + shares, + total_assets + 1, + total_supply + vault_offset, + rounding == ROUND_UP, + ) } ``` @@ -43,24 +74,35 @@ Deposits round shares down (in favor of the vault), while redemptions round asse ## Deposit flow -A public-to-public deposit transfers assets from the caller to the vault, computes the shares due, and mints them to the recipient: +A public-to-public deposit computes the shares due, takes the assets from `from`, and has the share token mint the shares to the recipient: ```rust #[external("public")] -fn deposit_public_to_public(from: AztecAddress, to: AztecAddress, assets: u128, _nonce: Field) { +fn deposit_public_to_public(from: AztecAddress, to: AztecAddress, assets: u128, nonce: Field) { + let asset_token = self.storage.asset.read(); + let shares_token = self.storage.shares.read(); self.internal._validate_from_public(from); + // Calculate shares to mint let total_assets = self.internal._total_assets(); - let shares = self.internal._convert_to_shares(assets, total_assets, ROUND_DOWN); + let total_supply = self.internal._total_supply_of_shares(); + let vault_offset = self.storage.vault_offset.read(); + let shares = + _convert_to_shares(assets, total_assets, total_supply, vault_offset, ROUND_DOWN); + assert(shares > 0, "Zero shares, insufficient assets"); - // Transfer assets from sender to vault - self.call(Token::at(self.storage.asset.read()).transfer_public_to_public( - from, self.address, assets, _nonce, + // Order matters: transfer before minting to order effects safely (necessary but NOT sufficient against a hooked token — see the ARC-403 reentrancy note at the top of this contract). + // Take the assets from the sender + self.call(Token::at(asset_token).transfer_public_to_public( + from, + self.address, + assets, + nonce, )); // Mint shares to the recipient - self.internal._mint_to_public(to, shares); + self.call(Token::at(shares_token).mint_to_public(to, shares)); } ``` -The vault exposes similar entry points for the other combinations of private and public contexts (`deposit_private_to_public`, `deposit_public_to_private`, `deposit_private_to_private`). Each variant transfers assets using the corresponding AIP-20 transfer function and then mints shares into the chosen output context. +The vault exposes similar entry points for the other combinations of private and public contexts (`deposit_private_to_public`, `deposit_public_to_private`, `deposit_private_to_private`). The variants that mint shares into a private balance take the number of shares as an argument and check in public that `assets` covers it, since the exchange rate is public state. diff --git a/docs/docs-developers/docs/aztec-nr/standards/aip-721.md b/docs/docs-developers/docs/aztec-nr/standards/aip-721.md index d661cde80af..a2cb8c661c9 100644 --- a/docs/docs-developers/docs/aztec-nr/standards/aip-721.md +++ b/docs/docs-developers/docs/aztec-nr/standards/aip-721.md @@ -4,7 +4,7 @@ sidebar_position: 2 description: Non-fungible token standard with private ownership, partial-note support, and commitment-based transfers. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/nft_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/main/src/nft_contract) AIP-721 defines a non-fungible token (NFT). Each token is identified by a unique `token_id` field. Tokens can be held privately in the note hash tree or publicly in a map from `token_id` to owner address. @@ -19,7 +19,6 @@ struct Storage { nft_exists: Map, Context>, public_owners: Map, Context>, minter: PublicImmutable, - upgrade_authority: PublicImmutable, } ``` @@ -30,32 +29,69 @@ struct Storage { Each private NFT is represented as an `NFTNote` containing only the `token_id`: ```rust +/// A private note representing a token id associated to an account. #[derive(Eq, Serialize, Packable)] #[custom_note] pub struct NFTNote { + /// The ID of the token represented by this note. pub token_id: Field, } +``` + +`NFTNote::partial` creates a `PartialNFTNote`, an incomplete note whose owner stays private: + +```rust +pub fn partial( + owner: AztecAddress, + context: &mut PrivateContext, + recipient: AztecAddress, + completer: AztecAddress, +) -> PartialNFTNote { + // Safety: We use the randomness to preserve the privacy of the note recipient by preventing brute-forcing, + // so a malicious sender could use non-random values to make the note less private. But they already know + // the full note pre-image anyway, and so the recipient already trusts them to not disclose this + // information. We can therefore assume that the sender will cooperate in the random value generation. + let randomness = unsafe { random() }; + + // We create a commitment to the private data, which we then use to construct the log we send to the recipient. + let commitment = compute_partial_commitment(owner, randomness); + + // Our partial note log encoding scheme includes a field with the tag of the public completion log, and we use + // the commitment as the tag. This is good for multiple reasons: + // - the commitment is uniquely tied to this partial note + // - the commitment is already public information, so we're not revealing anything else + // - we don't need to create any additional information, private or public, for the tag + // - other contracts cannot impersonate us and emit logs with the same tag due to public log siloing + let private_log_content = NFTPartialNotePrivateLogContent {}; + + do_private_message_delivery( + context, + || { + encode_partial_note_private_message( + private_log_content, + owner, + randomness, + commitment, + ) + }, + Option::none(), + recipient, + MessageDelivery::onchain_unconstrained(), + ); + + let partial_note = PartialNFTNote { commitment }; + + // Now we compute the validity commitment and push it to the nullifier tree. It can be safely pushed to + // the nullifier tree since it uses its own separator, making collisions with actual note nullifiers + // practically impossible. + let validity_commitment = partial_note.compute_validity_commitment(completer); + context.push_nullifier_unsafe(validity_commitment); -impl NFTNote { - pub fn partial( - owner: AztecAddress, - storage_slot: Field, - context: &mut PrivateContext, - recipient: AztecAddress, - completer: AztecAddress, - ) -> PartialNFTNote { - let randomness = unsafe { random() }; - let commitment = compute_partial_commitment(owner, storage_slot, randomness); - // ... creates encrypted log and validity commitment - let partial_note = PartialNFTNote { commitment }; - let validity_commitment = partial_note.compute_validity_commitment(completer); - context.push_nullifier_unsafe(validity_commitment); - partial_note - } + partial_note } ``` -The `partial` constructor creates a `PartialNFTNote` whose `commitment` field commits to the future owner and storage slot. Without some form of access control, any party could call the completion function and claim the NFT for themselves. The validity commitment prevents this — it is pushed as a nullifier, and only the designated completer can produce the matching preimage needed to finalize the note. This mirrors the partial-note pattern in [AIP-20](./aip-20.md) but applies it to NFT transfers. +The `commitment` commits to the future owner and a random value, so completing the note cannot change who owns it. The storage slot and `token_id` become public when the note is completed. What needs protecting is who completes it: without some form of access control, any party could fill the commitment before the intended completer, for example with a different token. The validity commitment prevents this: it binds the partial note to the designated completer and is pushed as a nullifier, and completion checks that it exists for the `completer` passed in. The note does not check who the caller is, so the NFT contract passes `msg_sender()` as the completer, and only the designated completer can finalize the note. This mirrors the partial-note pattern in [AIP-20](./aip-20.md) but applies it to NFT transfers. ## Partial-note transfer commitment @@ -65,8 +101,8 @@ The external entry point for initiating a partial NFT transfer is: #[external("private")] fn initialize_transfer_commitment(to: AztecAddress, completer: AztecAddress) -> Field { let commitment = self.internal._initialize_transfer_commitment(to, completer); - commitment.commitment() + commitment.commitment } ``` -This function returns `commitment.commitment()` — an opaque `Field` representing the commitment. The AIP-20 equivalent returns `commitment.to_field()` for `PartialUintNote`. +This function returns `commitment.commitment`, the `Field` stored in the `PartialNFTNote`. The AIP-20 equivalent returns `commitment.to_field()` for `PartialUintNote`. diff --git a/docs/docs-developers/docs/aztec-nr/standards/dripper.md b/docs/docs-developers/docs/aztec-nr/standards/dripper.md index 7032fcd9076..9c613de2f3c 100644 --- a/docs/docs-developers/docs/aztec-nr/standards/dripper.md +++ b/docs/docs-developers/docs/aztec-nr/standards/dripper.md @@ -4,6 +4,6 @@ sidebar_position: 6 description: Convenience faucet for minting tokens into private or public balances during development. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/dripper) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/main/src/dripper) The `aztec-standards` repository also ships a **Dripper** contract — a convenience faucet for minting tokens into private or public balances during development. It is not a formal AIP standard and should not be used in production. diff --git a/docs/docs-developers/docs/aztec-nr/standards/escrow.md b/docs/docs-developers/docs/aztec-nr/standards/escrow.md index 00761e65fea..9aa05a3d676 100644 --- a/docs/docs-developers/docs/aztec-nr/standards/escrow.md +++ b/docs/docs-developers/docs/aztec-nr/standards/escrow.md @@ -4,7 +4,7 @@ sidebar_position: 4 description: Minimal token and NFT custody contract with salt-based authorization. --- -[Source](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/escrow_contract) +[Source](https://github.com/AztecProtocol/aztec-standards/tree/main/src/escrow_contract) The Escrow standard provides a minimal contract for holding tokens or NFTs on behalf of a single owner. Rather than storing the owner in mutable private state — which would require note discovery and decryption on every authorization check — the owner is encoded in the contract's own `salt` and thus baked into the contract address at deploy time. This makes authorization a simple field comparison against immutable deployment parameters: cheaper, simpler, and impossible to front-run. @@ -16,17 +16,13 @@ pub contract Escrow { #[external("private")] fn withdraw(token: AztecAddress, amount: u128, recipient: AztecAddress) { self.internal._assert_msg_sender(); - self.call(Token::at(token).transfer_private_to_private( - self.address, recipient, amount, 0, - )); + self.call(Token::at(token).transfer_private_to_private(self.address, recipient, amount, 0)); } #[external("private")] fn withdraw_nft(nft: AztecAddress, token_id: Field, recipient: AztecAddress) { self.internal._assert_msg_sender(); - self.call(NFT::at(nft).transfer_private_to_private( - self.address, recipient, token_id, 0, - )); + self.call(NFT::at(nft).transfer_private_to_private(self.address, recipient, token_id, 0)); } #[internal("private")] @@ -42,38 +38,55 @@ The authorization check in `_assert_msg_sender` reads the `salt` field of the es ## Escrow logic library -A DeFi protocol (like a lending market or DEX) often needs to give each user a personal escrow to hold collateral or pending settlements. The standard ships a companion library that lets the parent contract deterministically compute escrow addresses from its own address and the user's keys — no onchain deployment transaction required: +A DeFi protocol (like a lending market or DEX) often needs to give each user a personal escrow to hold collateral or pending settlements. The standard ships a companion library that lets the parent contract deterministically compute escrow addresses from its own address and a secret key, with no onchain deployment transaction required: ```rust +/// @notice Returns the escrow address that corresponds to the given secret key and class ID. +/// @param context The private context +/// @param escrow_class_id The contract class id of the escrow contract +/// @param secret_key The secret key used to derive master secret keys and public keys +/// @return The escrow address #[contract_library_method] pub fn _get_escrow( context: &mut PrivateContext, escrow_class_id: Field, - master_secret_keys: MasterSecretKeys, + secret_key: Field, ) -> AztecAddress { - let computed_public_keys: PublicKeys = _secret_keys_to_public_keys(master_secret_keys); + // Compute the public keys from the secret key (sk -> msks -> pks) + let computed_public_keys: PublicKeys = secret_key_to_public_keys(secret_key); + let escrow_instance = ContractInstance { salt: context.this_address().to_field(), deployer: AztecAddress::from_field(0), original_contract_class_id: ContractClassId::from_field(escrow_class_id), initialization_hash: 0, + immutables_hash: 0, public_keys: computed_public_keys, }; + escrow_instance.to_address() } +/// @notice Shares the escrow details needed to find and use the escrow contract +/// @dev Emits a private log with the escrow details +/// @param context The private context +/// @param account The address of the account that will use the escrow +/// @param escrow The address of the escrow +/// @param secret_key The secret key used to derive master secret keys and public keys #[contract_library_method] pub fn _share_escrow( context: &mut PrivateContext, account: AztecAddress, escrow: AztecAddress, - master_secret_keys: MasterSecretKeys, + secret_key: Field, ) { - let event_struct = EscrowDetailsLogContent { escrow, master_secret_keys }; + let event_struct = EscrowDetailsLogContent { escrow, secret_key }; + emit_event_in_private(context, event_struct).deliver_to( - account, MessageDelivery::onchain_constrained(), + account, + MessageDelivery::onchain_constrained(), ); } ``` -`_get_escrow` reconstructs the escrow address deterministically from the calling contract's address (used as the salt) and a set of master secret keys. `_share_escrow` emits an encrypted log so that the designated `account` can discover the escrow address and the keys needed to access its notes. Without this notification, the user's PXE would have no way to find the escrow or decrypt notes held there. Constrained delivery ensures the log is validated against the note hash tree before the recipient's PXE trusts it. +`_get_escrow` reconstructs the escrow address deterministically from the calling contract's address (used as the salt) and a secret key, from which it derives the escrow's public keys. `_share_escrow` emits an encrypted log so that the designated `account` can discover the escrow address and the secret key needed to access its notes. Without this notification, the user's PXE would have no way to find the escrow or decrypt notes held there. Constrained delivery proves the log's encryption and tagging in the circuit, so the calling contract cannot emit a log that the recipient's PXE fails to find or decrypt. diff --git a/docs/docs-developers/docs/aztec-nr/standards/generic-proxy.md b/docs/docs-developers/docs/aztec-nr/standards/generic-proxy.md index 06c3c696c8b..0d4a6c1b2a0 100644 --- a/docs/docs-developers/docs/aztec-nr/standards/generic-proxy.md +++ b/docs/docs-developers/docs/aztec-nr/standards/generic-proxy.md @@ -4,6 +4,10 @@ sidebar_position: 5 description: Forwarding layer for account abstraction that routes calls by argument count. --- +:::note +In the `aztec-standards` repository, [`GenericProxy`](https://github.com/AztecProtocol/aztec-standards/tree/main/src/generic_proxy) is an internal test utility, not a standard. Its Noir test suites use it when a call must come from a contract, and it carries no stability or compatibility guarantees, so do not build on it. +::: + In Aztec, account contracts authorize every transaction the user sends and must be able to forward calls to any contract. However, Noir requires function signatures to be known at compile time, so an account contract cannot call an arbitrary function with an arbitrary number of arguments in a single generic entrypoint. The Generic Proxy contract solves this by providing a fixed set of forwarding functions — one per argument count — that the account contract can call. This avoids hard-coding every possible target function signature while keeping the account contract simple. @@ -31,7 +35,7 @@ pub contract GenericProxy { self.context.call_private_function(target, selector, args).get_preimage(); returns } - // ... forward_private_1 through forward_private_8 + // ... forward_private_1 through forward_private_8, and forward_public_4 } ``` diff --git a/docs/docs-developers/docs/aztec-nr/standards/index.md b/docs/docs-developers/docs/aztec-nr/standards/index.md index 076e7577523..2648eb4392b 100644 --- a/docs/docs-developers/docs/aztec-nr/standards/index.md +++ b/docs/docs-developers/docs/aztec-nr/standards/index.md @@ -1,33 +1,35 @@ --- title: Aztec Contract Standards sidebar_position: 0 -description: Overview of Aztec Improvement Proposal (AIP) contract standards maintained by DeFi Wonderland. +description: Overview of Aztec Improvement Proposal (AIP) contract standards maintained by the Aztec Foundation. --- Aztec contract standards define shared interfaces and behaviors for common onchain primitives. They serve the same role that ERC standards play on Ethereum: establishing conventions that allow contracts, wallets, and tooling to interoperate without prior coordination. -The standards described in this section are maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards) in the `aztec-standards` repository. Each standard is identified by an **Aztec Improvement Proposal (AIP)** number that mirrors its Ethereum counterpart where applicable (AIP-20 corresponds to ERC-20, AIP-721 to ERC-721, AIP-4626 to ERC-4626). +The standards described in this section are maintained by the Aztec Foundation in the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards). Each standard is identified by an **Aztec Improvement Proposal (AIP)** number that mirrors its Ethereum counterpart where applicable (AIP-20 corresponds to ERC-20, AIP-721 to ERC-721, AIP-4626 to ERC-4626). + +The contracts' compiled artifacts and TypeScript wrappers are published on npm as [`@aztec-foundation/aztec-standards`](https://www.npmjs.com/package/@aztec-foundation/aztec-standards). Because Aztec contracts have both private and public execution contexts, the standards are more involved than their Ethereum equivalents. Transfers can move value between private notes and public balances, and many operations require coordination between encrypted state and transparent state within a single transaction. :::note -The code examples in this section are taken from the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. They will differ from the reference contract implementations shipped in the [aztec-packages repo](https://github.com/AztecProtocol/aztec-packages) under `noir-projects/noir-contracts/contracts/`. When in doubt, consult the aztec-standards github repo for the canonical standard interfaces. +The code examples in this section are taken from the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards/tree/main) at `main`. They differ from the reference contract implementations under `noir-projects/noir-contracts/contracts/` in the [aztec-node repository](https://github.com/aztec-labs-eng/aztec-node). When in doubt, consult the `aztec-standards` repository for the canonical standard interfaces. The repository marks these contracts as unaudited: do not use them in production or to secure assets of value. ::: ## Standards -- [AIP-20: Fungible Token](./aip-20.md) — private and public balances, partial-note transfers, recursive note consumption -- [AIP-721: Non-Fungible Token](./aip-721.md) — private NFT ownership, partial-note support, commitment-based transfers -- [AIP-4626: Tokenized Vault](./aip-4626.md) — yield-bearing vaults with share conversion across private and public contexts -- [Escrow](./escrow.md) — minimal token/NFT custody with salt-based authorization -- [Generic Proxy](./generic-proxy.md) — forwarding layer for account abstraction patterns -- [Dripper](./dripper.md) — development faucet for testing +- [AIP-20: Fungible Token](./aip-20.md): private and public balances, partial-note transfers, recursive note consumption +- [AIP-721: Non-Fungible Token](./aip-721.md): private NFT ownership, partial-note support, commitment-based transfers +- [AIP-4626: Tokenized Vault](./aip-4626.md): yield-bearing vaults with share conversion across private and public contexts +- [Escrow](./escrow.md): minimal token/NFT custody with salt-based authorization +- [Generic Proxy](./generic-proxy.md): forwarding contract, an internal test utility in `aztec-standards` rather than a standard +- [Dripper](./dripper.md): development faucet for testing ## Related tutorials -- [Private Token Contract](../../tutorials/contract_tutorials/token_contract.md) — build a privacy-preserving fungible token that closely parallels AIP-20 -- [NFT Bridge](../../tutorials/js_tutorials/token_bridge.md) — build a private NFT with custom `NFTNote` and `PrivateSet`, covering patterns extended by AIP-721 -- [Deploying a Token Contract](../../tutorials/js_tutorials/aztecjs-getting-started.md) — deploy and interact with the reference token contract using Aztec.js -- [Counter Contract](../../tutorials/contract_tutorials/counter_contract.md) — introduces private state, notes, and balance management +- [Private Token Contract](../../tutorials/contract_tutorials/token_contract.md): build a privacy-preserving fungible token that closely parallels AIP-20 +- [NFT Bridge](../../tutorials/js_tutorials/token_bridge.md): build a private NFT with custom `NFTNote` and `PrivateSet`, covering patterns extended by AIP-721 +- [Deploying a Token Contract](../../tutorials/js_tutorials/aztecjs-getting-started.md): deploy and interact with the reference token contract using Aztec.js +- [Counter Contract](../../tutorials/contract_tutorials/counter_contract.md): introduces private state, notes, and balance management -For the canonical implementations and latest interface specifications, refer to the [aztec-standards repository](https://github.com/defi-wonderland/aztec-standards) maintained by DeFi Wonderland. +For the canonical implementations and latest interface specifications, refer to the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards), maintained by the Aztec Foundation. diff --git a/docs/docs-developers/docs/tutorials/js_tutorials/aztecjs-getting-started.md b/docs/docs-developers/docs/tutorials/js_tutorials/aztecjs-getting-started.md index 4ca94044426..381f95a5583 100644 --- a/docs/docs-developers/docs/tutorials/js_tutorials/aztecjs-getting-started.md +++ b/docs/docs-developers/docs/tutorials/js_tutorials/aztecjs-getting-started.md @@ -126,10 +126,12 @@ Have a look at the [contract source](https://github.com/aztec-labs-eng/aztec-nod The pre-compiled token contract used in this tutorial is Aztec's reference implementation. It covers the core operations you need to get started: minting, private transfers, and public balance management. -For production applications, consider the **AIP-20 Token Standard** maintained by [DeFi Wonderland](https://github.com/defi-wonderland/aztec-standards/tree/dev/src/token_contract). AIP-20 formalizes the same patterns used in the reference contract and adds: +For more complete token features, consider the **AIP-20 Token Standard**, maintained by the Aztec Foundation in the [`aztec-standards` repository](https://github.com/AztecProtocol/aztec-standards/tree/main/src/token_contract). AIP-20 formalizes the same patterns used in the reference contract and adds: -- **Commitment-based transfers** for DeFi protocols where the recipient is determined asynchronously +- **Commitment-based transfers** for DeFi protocols where the amount is determined asynchronously - **Recursive note consumption** for handling large balances that span many notes -- **Tokenized vault support (AIP-4626)** for yield-bearing tokens that issue shares against an underlying asset +- **Tokenized vaults (AIP-4626)**, where a separate vault contract issues AIP-20 shares against an AIP-20 asset + +The repository marks these contracts as unaudited: do not use them in production or to secure assets of value. To learn how to write a token contract from scratch rather than deploying a pre-compiled one, see the [Private Token Contract tutorial](../contract_tutorials/token_contract.md). For the full specifications of all Aztec contract standards, see the [Aztec Contract Standards](../../aztec-nr/standards/index.md) reference. diff --git a/docs/docs-words.txt b/docs/docs-words.txt index fd9a014c605..618d78f28b7 100644 --- a/docs/docs-words.txt +++ b/docs/docs-words.txt @@ -261,6 +261,7 @@ mitigiation moreso MSGEXISTS msgexists +msks multiscalar multisends mycontract