From 86c1df8d2f2ccce63b7e313ecbce0f2d71a24f11 Mon Sep 17 00:00:00 2001 From: josh crites Date: Fri, 22 Nov 2024 16:58:06 -0500 Subject: [PATCH 01/14] wip --- .../contract_tutorials/nft_contract.md | 331 ++++++++++++++++++ .../contract_tutorials/token_contract.md | 2 +- .../contracts/nft_contract/src/main.nr | 31 +- .../nft_contract/src/types/nft_note.nr | 10 +- 4 files changed, 361 insertions(+), 13 deletions(-) create mode 100644 docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md new file mode 100644 index 00000000000..d2d3a6f992a --- /dev/null +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -0,0 +1,331 @@ +--- +title: "NFT contract" +sidebar_position: 5 +--- + +In this tutorial we will go through writing an L2 native NFT token contract +for the Aztec Network, using the Aztec.nr contract libraries. + +This tutorial is intended to help you get familiar with the Aztec.nr library, Aztec contract syntax and some of the underlying structure of the Aztec network. + +In this tutorial you will learn how to: + +- Write public functions that update public state +- Write private functions that update private state +- Implement access control on public and private functions +- Handle math operations safely +- Handle different private note types +- Pass data between private and public state + +We are going to start with a blank project and fill in the token contract source code defined [here (GitHub Link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr), and explain what is being added as we go. + +## Requirements + +You will need to have `aztec-nargo` installed in order to compile Aztec.nr contracts. + +## Project setup + +Create a new project with: + +```bash +aztec-nargo new --contract nft_contract +``` + +Your file structure should look something like this: + +```tree +. +|--nft_contract +| |--src +| | |--main.nr +| |--Nargo.toml +``` + +Inside `Nargo.toml` paste the following: + +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/aztec" } +authwit={ git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/authwit"} +compressed_string = {git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/compressed-string"} +``` + +We will be working within `main.nr` for the rest of the tutorial. + +### Execution contexts + +Before we go further, a quick note about execution contexts. + +Transactions are initiated in the private context (executed client-side), then move to the L2 public context (executed remotely by an Aztec sequencer), then to the Ethereum L1 context (executed by an Ethereum node). + +Step 1. Private Execution + +Users provide inputs and execute locally on their device for privacy reasons. Outputs of the private execution are commitment and nullifier updates, a proof of correct execution and any return data to pass to the public execution context. + +Step 2. Public Execution + +This happens remotely by the sequencer, which takes inputs from the private execution and runs the public code in the network virtual machine, similar to any other public blockchain. + +Step 3. Ethereum execution + +Aztec transactions can pass messages to Ethereum contracts through the rollup via the outbox. The data can be consumed by Ethereum contracts at a later time, but this is not part of the transaction flow for an Aztec transaction. The technical details of this are beyond the scope of this tutorial, but we will cover them in an upcoming piece. + +## How this will work + +Before writing the functions, let's go through them to see how this contract will work: + +### Initializer + +There is one `initializer` function in this contract, and it will be selected and executed once when the contract is deployed, similar to a constructor in Solidity. This is marked `public`, so the function logic will be transparent. + +### Public functions + +These are functions that have transparent logic, will execute in a publicly verifiable context and can update public storage. + +- [`constructor`](#constructor) - executed when the contract instance is deployed +- [`set_admin`]() - updates the `admin` of the contract +- [`set_minter`]() - adds a minter to the `minters` mapping +- [`mint`]() - mints an NFT with a specified `token_id` to the recipient +- [`transfer_in_public`]() - publicly transfer the specified token +- [`finalize_transfer_to_private`]() - finalized a transfer to from the public context to the private context (more on this below) + +#### Public `view` functions + +These functions are useful for getting contract information for use in other contracts, in the public context. + +- [`public_get_name`]() - returns name of the NFT contract +- [`public_get_symbol`]() - returns the symbols of the NFT contract +- [`get_admin`]() - returns the `admin` account address +- [`is_minter`]() - returns a boolean, indicating whether the provided address is a minter +- [`owner_of`]() - returns the owner of the provided `token_id` + +### Private functions + +These are functions that have private logic and will be executed on user devices to maintain privacy. The only data that is submitted to the network is a proof of correct execution, new data commitments and nullifiers, so users will not reveal which contract they are interacting with or which function they are executing. The only information that will be revealed publicly is that someone executed a private transaction on Aztec. + +- [`transfer_to_private`]() - initiates the transfer of an NFT from the public context to the private context +- [`prepare_private_balance_increase`]() - creates a [partial note](../../../aztec/concepts/storage/partial_notes.md) to transfer an NFT from the public context to the private context. +- [`cancel_authwit`]() - emits a nullifier to cancel a private authwit +- [`transfer_in_private`]() - transfers an NFT to another account, privately +- [`transfer_to_public`]() - transfers and NFT from a private accounts' balance to a specified accounts' public balance + +#### Private `view` functions + +These functions are useful for getting contract information in the private context. + +- [`private_get_symbol`]() - returns the NFT contract symbol +- [`private_get_name`]() - returns the NFT contract name + +### Internal functions + +Internal functions are functions that can only be called by the contract itself. These can be used when the contract needs to call one of it's public functions from one of it's private functions. + +- [`_store_payload_in_transient_storage_unsafe`]() - +- [`_finalize_transfer_to_private_unsafe`]() - +- [`_finish_transfer_to_public`]() - + +### Unconstrained functions + +Unconstrained functions can be thought of as view functions from Solidity--they only return information from the contract storage or compute and return data without modifying contract storage. They are distinguished from functions with the `#[view]` annotation in that unconstrained functions cannot be called by other contracts. + +- [`get_private_nfts`]() - + +## Contract dependencies + +Before we can implement the functions, we need set up the contract storage, and before we do that we need to import the appropriate dependencies. + +:::info Copy required files + +We will be going over the code in `main.nr` [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src). If you are following along and want to compile `main.nr` yourself, you need to add the other files in the directory as they contain imports that are used in `main.nr`. + +::: + +Paste these imports: + +```rust +#include_code imports /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr raw +} +``` + +We are importing: + +- `CompressedString` to hold the token symbol +- Types from `aztec::prelude` +- Types for storing note types + +### Types files + +We are also importing types from a `types.nr` file, which imports types from the `types` folder. You can view them [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/noir-contracts/contracts/nft_contract/src). + +:::note + +Private state in Aztec is all [UTXOs](../../../aztec/concepts/storage/index.md). + +::: + +## Contract Storage + +Now that we have dependencies imported into our contract we can define the storage for the contract. + +Below the dependencies, paste the following Storage struct: + +#include_code storage_struct /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust + +## Custom Notes + +The contract storage uses a [custom note](../../../guides/developer_guides/smart_contracts/writing_contracts/notes/custom_note.md) implementation. Custom notes are useful for defining your own data types. You can think of a custom note as a "chunk" of private data, the entire thing is added, updated or nullified (deleted) together. This NFT note is very simple and stores only the owner and the `token_id` and uses `randomness` to hide its contents. + +Randomness is required because notes are stored as commitments (hashes) in the note hash tree. Without randomness, the contents of a note may be derived through brute force (e.g. without randomness, if you know my Aztec address, you may be able to figure out which note hash in the tree is mine by hashing my address with many potential `token_id`s). + +#include_code nft_note /noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr rust + +The custom note implementation also includes the nullifier computation function. This tells the protocol how the note should be nullified. + +#include_code compute_nullifier /noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr rust + +## Functions + +Copy and paste the body of each function into the appropriate place in your project if you are following along. + +### Constructor + +This function sets the admin and makes them a minter, and sets the name and symbol. + +#include_code constructor /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +### Public function implementations + +Public functions are declared with the `#[public]` macro above the function name. + +As described in the [execution contexts section above](#execution-contexts), public function logic and transaction information is transparent to the world. Public functions update public state, but can be used to finalize notes prepared in a private context ([partial notes flow](../../../aztec/concepts/storage/partial_notes.md)). + +Storage is referenced as `storage.variable`. + +#### `set_admin` + +The function checks that the `msg_sender` is the `admin`. If not, the transaction will fail. If it is, the `new_admin` is saved as the `admin`. + +#include_code set_admin /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust + +#### `set_minter` + +This function allows the `admin` to add or a remove a `minter` from the public `minters` mapping. It checks that `msg_sender` is the `admin` and finally adds the `minter` to the `minters` mapping. + +#include_code set_minter /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust + +#### `mint` + +This public function checks that the `token_id` is not 0 and does not already exist and the `msg_sender` is authorized to mint. Then it indicates that the `token_id` exists, which is useful for verifying its existence if it gets transferred to private, and updates the owner in the `public_owners` mapping. + +#include_code mint /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust + +#### `transfer_in_public` + +#include_code transfer_in_public /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust + +##### Authorizing token spends + +If the `msg_sender` is **NOT** the same as the account to debit from, the function checks that the account has authorized the `msg_sender` contract to debit tokens on its behalf. This check is done by computing the function selector that needs to be authorized, computing the hash of the message that the account contract has approved. This is a hash of the contract that is approved to spend (`context.msg_sender`), the token contract that can be spent from (`context.this_address()`), the `selector`, the account to spend from (`from`), the `amount` and a `nonce` to prevent multiple spends. This hash is passed to `assert_inner_hash_valid_authwit_public` to ensure that the Account Contract has approved tokens to be spent on it's behalf. + +If the `msg_sender` is the same as the account to debit from, the authorization check is bypassed and the function proceeds to update the public owner. + +#### `finalize_transfer_to_private` + +This public function finalizes a transfer that has been set up by a call to `prepare_private_balance_increase` by reducing the public balance of the associated account and emitting the note for the intended recipient. + +#include_code finalize_transfer_to_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +### Private function implementations + +Private functions are declared with the `#[private]` macro above the function name like so: + +```rust + #[private] + fn transfer_in_private( +``` + +As described in the [execution contexts section above](#execution-contexts), private function logic and transaction information is hidden from the world and is executed on user devices. Private functions update private state, but can pass data to the public execution context (e.g. see the [`transfer_to_public`](#transfer_to_public) function). + +Storage is referenced as `storage.variable`. + +#### `transfer_to_private` + +#include_code transfer_to_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +#### `prepare_private_balance_increase` + +TODO + +#### `cancel_authwit` + +TODO + +#### `transfer_in_private` + +TODO + +#### `transfer_to_public` + +TODO + +### Internal function implementations + +Internal functions are functions that can only be called by this contract. The following 3 functions are public functions that are called from the [private execution context](#execution-contexts). Marking these as `internal` ensures that only the desired private functions in this contract are able to call them. Private functions defer execution to public functions because private functions cannot update public state directly. + +#### `_store_payload_in_transient_storage_unsafe` + +#### `_finalize_transfer_to_private_unsafe` + +#### `_finish_transfer_to_public` + +### View function implementations + +View functions in Aztec are similar to `view` functions in Solidity in that they only return information from the contract storage or compute and return data without modifying contract storage. These functions are different from unconstrained functions in that the return values are constrained by their definition in the contract. + +Public view calls that are part of a transaction will be executed by the sequencer when the transaction is being executed, so they are not private and will reveal information about the transaction. Private view calls can be safely used in private transactions for getting the same information. + +#### `admin` + +A getter function for reading the public `admin` value. + +#include_code admin /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +#### `is_minter` + +A getter function for checking the value of associated with a `minter` in the public `minters` mapping. + +#include_code is_minter /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +### Unconstrained function implementations + +Unconstrained functions are similar to `view` functions in Solidity in that they only return information from the contract storage or compute and return data without modifying contract storage. They are different from view functions in that the values are returned from the user's PXE and are not constrained by the contract's definition--if there is bad data in the user's PXE, they will get bad data back. + +#### `get_private_nfts` + +A getter function for checking the private balance of the provided Aztec account. Returns an array of token IDs owned by `owner` in private and a flag indicating whether a page limit was reached. + +#include_code get_private_nfts /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +## Compiling + +Now that the contract is complete, you can compile it with `aztec-nargo`. See the [Sandbox reference page](../../../reference/developer_references/sandbox_reference/index.md) for instructions on setting it up. + +Run the following command in the directory where your `Nargo.toml` file is located: + +```bash +aztec-nargo compile +``` + +Once your contract is compiled, optionally generate a typescript interface with the following command: + +```bash +aztec codegen target -o src/artifacts +``` + +### Optional: Dive deeper into this contract and concepts mentioned here + +- Review [the end to end tests (Github link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/yarn-project/end-to-end/src/e2e_nft.test.ts) for reference. +- [Nullifiers](../../../aztec/concepts/storage/trees/index.md#nullifier-tree) +- [Public / Private function calls](../../../aztec/smart_contracts/functions/public_private_calls.md). +- [Contract Storage](../../../aztec/concepts/storage/index.md) +- [Authwit](../../../aztec/concepts/accounts/authwit.md) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md index e650379aee4..2ac31b1ac46 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md @@ -1,6 +1,6 @@ --- title: "Private & Public token contract" -sidebar_position: 5 +draft: true --- In this tutorial we will go through writing an L2 native token contract diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index ccefcbef97f..c72da073d62 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -1,3 +1,4 @@ +// docs:start:imports mod types; mod test; @@ -16,21 +17,17 @@ contract NFT { encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, keys::getters::get_public_keys, macros::{ - events::event, - functions::{initializer, internal, private, public, view}, + events::event, functions::{initializer, internal, private, public, view}, storage::storage, - }, - note::constants::MAX_NOTES_PER_PAGE, - oracle::random::random, + }, note::constants::MAX_NOTES_PER_PAGE, oracle::random::random, prelude::{ AztecAddress, Map, NoteGetterOptions, NoteViewerOptions, PrivateContext, PrivateSet, PublicContext, PublicMutable, SharedImmutable, - }, - protocol_types::{point::Point, traits::Serialize}, - utils::comparison::Comparator, + }, protocol_types::{point::Point, traits::Serialize}, utils::comparison::Comparator, }; use dep::compressed_string::FieldCompressedString; use std::{embedded_curve_ops::EmbeddedCurvePoint, meta::derive}; + // docs:end:imports // TODO(#8467): Rename this to Transfer - calling this NFTTransfer to avoid export conflict with the Transfer event // in the Token contract. @@ -42,6 +39,7 @@ contract NFT { token_id: Field, } + // docs:start:storage_struct #[storage] struct Storage { // The symbol of the NFT @@ -59,7 +57,9 @@ contract NFT { // A map from token ID to the public owner of the NFT. public_owners: Map, Context>, } + // docs:end:storage_struct + // docs:start:constructor #[public] #[initializer] fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>) { @@ -69,19 +69,25 @@ contract NFT { storage.name.initialize(FieldCompressedString::from_string(name)); storage.symbol.initialize(FieldCompressedString::from_string(symbol)); } + // docs:end:constructor + // docs:start:admin #[public] fn set_admin(new_admin: AztecAddress) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); storage.admin.write(new_admin); } + // docs:end:admin + // docs:start:minter #[public] fn set_minter(minter: AztecAddress, approve: bool) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); storage.minters.at(minter).write(approve); } + // docs:end:minter + // docs:start:mint #[public] fn mint(to: AztecAddress, token_id: Field) { assert(token_id != 0, "zero token ID not supported"); @@ -92,6 +98,7 @@ contract NFT { storage.public_owners.at(token_id).write(to); } + // docs:end:mint #[public] #[view] @@ -129,6 +136,7 @@ contract NFT { storage.minters.at(minter).read() } + // docs:start:transfer_in_public #[public] fn transfer_in_public(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { @@ -142,8 +150,10 @@ contract NFT { public_owners_storage.write(to); } + // docs:end:transfer_in_public // Transfers token with `token_id` from public balance of message sender to a private balance of `to`. + // docs:start:transfer_to_private #[private] fn transfer_to_private(to: AztecAddress, token_id: Field) { let from = context.msg_sender(); @@ -159,6 +169,7 @@ contract NFT { &mut context, ); } + // docs:end:transfer_to_private /// Prepares an increase of private balance of `to` (partial note). The increase needs to be finalized by calling /// `finalize_transfer_to_private. Returns a hiding point slot. @@ -237,11 +248,13 @@ contract NFT { /// Finalizes a transfer of NFT with `token_id` from public balance of `from` to a private balance of `to`. /// The transfer must be prepared by calling `prepare_private_balance_increase` first and the resulting /// `hiding_point_slot` must be passed as an argument to this function. + // docs:start:finalize_transfer_to_private #[public] fn finalize_transfer_to_private(token_id: Field, hiding_point_slot: Field) { let from = context.msg_sender(); _finalize_transfer_to_private(from, token_id, hiding_point_slot, &mut context, storage); } + // docs:end:finalize_transfer_to_private #[public] #[internal] @@ -347,6 +360,7 @@ contract NFT { /// Returns an array of token IDs owned by `owner` in private and a flag indicating whether a page limit was /// reached. Starts getting the notes from page with index `page_index`. Zero values in the array are placeholder /// values for non-existing notes. + // docs:start:get_private_nfts unconstrained fn get_private_nfts( owner: AztecAddress, page_index: u32, @@ -365,5 +379,6 @@ contract NFT { let page_limit_reached = notes.len() == options.limit; (owned_nft_ids, page_limit_reached) } + // docs:end:get_private_nfts } diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr index 8a7f25e0374..f50352b1ef2 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr @@ -1,8 +1,6 @@ use dep::aztec::{ - keys::getters::{get_nsk_app, get_public_keys}, - macros::notes::partial_note, - note::utils::compute_note_hash_for_nullify, - oracle::random::random, + keys::getters::{get_nsk_app, get_public_keys}, macros::notes::partial_note, + note::utils::compute_note_hash_for_nullify, oracle::random::random, prelude::{NoteHeader, NullifiableNote, PrivateContext}, protocol_types::{ address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, @@ -10,6 +8,7 @@ use dep::aztec::{ }, }; +// docs:start:nft_note #[partial_note(quote { token_id})] pub struct NFTNote { // ID of the token @@ -19,8 +18,10 @@ pub struct NFTNote { // Randomness of the note to hide its contents randomness: Field, } +// docs:end:nft_note impl NullifiableNote for NFTNote { + // docs:start:compute_nullifier fn compute_nullifier( self, context: &mut PrivateContext, @@ -33,6 +34,7 @@ impl NullifiableNote for NFTNote { GENERATOR_INDEX__NOTE_NULLIFIER as Field, ) } + // docs:end:compute_nullifier unconstrained fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_nullify(self); From 4173649c44e8ab5c3180c87faecb738f3bd2df26 Mon Sep 17 00:00:00 2001 From: josh crites Date: Fri, 22 Nov 2024 17:02:24 -0500 Subject: [PATCH 02/14] fix typos --- .../docs/tutorials/codealong/contract_tutorials/nft_contract.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md index d2d3a6f992a..cbbf65ceee8 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -304,7 +304,7 @@ Unconstrained functions are similar to `view` functions in Solidity in that they A getter function for checking the private balance of the provided Aztec account. Returns an array of token IDs owned by `owner` in private and a flag indicating whether a page limit was reached. -#include_code get_private_nfts /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code get_private_nfts /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust ## Compiling From e93b518317ba73ecf364c039bdf7643580176641 Mon Sep 17 00:00:00 2001 From: josh crites Date: Tue, 26 Nov 2024 11:57:06 -0500 Subject: [PATCH 03/14] edit docs tags --- .../noir-contracts/contracts/nft_contract/src/main.nr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index c72da073d62..cadb31ab6dc 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -71,21 +71,21 @@ contract NFT { } // docs:end:constructor - // docs:start:admin + // docs:start:set_admin #[public] fn set_admin(new_admin: AztecAddress) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); storage.admin.write(new_admin); } - // docs:end:admin + // docs:end:set_admin - // docs:start:minter + // docs:start:set_minter #[public] fn set_minter(minter: AztecAddress, approve: bool) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); storage.minters.at(minter).write(approve); } - // docs:end:minter + // docs:end:set_minter // docs:start:mint #[public] From 561947878094c031f42b64469339d7d52e83274d Mon Sep 17 00:00:00 2001 From: josh crites Date: Mon, 9 Dec 2024 15:11:23 -0500 Subject: [PATCH 04/14] add internal links and public/private getters --- .../contract_tutorials/nft_contract.md | 69 ++++++++++++------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md index cbbf65ceee8..a2e36c4b45a 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -83,52 +83,51 @@ There is one `initializer` function in this contract, and it will be selected an These are functions that have transparent logic, will execute in a publicly verifiable context and can update public storage. - [`constructor`](#constructor) - executed when the contract instance is deployed -- [`set_admin`]() - updates the `admin` of the contract -- [`set_minter`]() - adds a minter to the `minters` mapping -- [`mint`]() - mints an NFT with a specified `token_id` to the recipient -- [`transfer_in_public`]() - publicly transfer the specified token -- [`finalize_transfer_to_private`]() - finalized a transfer to from the public context to the private context (more on this below) +- [`set_admin`](#set_admin) - updates the `admin` of the contract +- [`set_minter`](#set_minter) - adds a minter to the `minters` mapping +- [`mint`](#mint) - mints an NFT with a specified `token_id` to the recipient +- [`transfer_in_public`](#transfer_in_public) - publicly transfer the specified token +- [`finalize_transfer_to_private`](#finalize_transfer_to_private) - finalized a transfer to from the public context to the private context (more on this below) #### Public `view` functions These functions are useful for getting contract information for use in other contracts, in the public context. -- [`public_get_name`]() - returns name of the NFT contract -- [`public_get_symbol`]() - returns the symbols of the NFT contract -- [`get_admin`]() - returns the `admin` account address -- [`is_minter`]() - returns a boolean, indicating whether the provided address is a minter -- [`owner_of`]() - returns the owner of the provided `token_id` +- [`public_get_name`](#public_get_name) - returns name of the NFT contract +- [`public_get_symbol`](#public_get_symbol) - returns the symbols of the NFT contract +- [`get_admin`](#get_admin) - returns the `admin` account address +- [`is_minter`](#is_minter) - returns a boolean, indicating whether the provided address is a minter +- [`owner_of`](#owner_of) - returns the owner of the provided `token_id` ### Private functions These are functions that have private logic and will be executed on user devices to maintain privacy. The only data that is submitted to the network is a proof of correct execution, new data commitments and nullifiers, so users will not reveal which contract they are interacting with or which function they are executing. The only information that will be revealed publicly is that someone executed a private transaction on Aztec. -- [`transfer_to_private`]() - initiates the transfer of an NFT from the public context to the private context -- [`prepare_private_balance_increase`]() - creates a [partial note](../../../aztec/concepts/storage/partial_notes.md) to transfer an NFT from the public context to the private context. -- [`cancel_authwit`]() - emits a nullifier to cancel a private authwit -- [`transfer_in_private`]() - transfers an NFT to another account, privately -- [`transfer_to_public`]() - transfers and NFT from a private accounts' balance to a specified accounts' public balance +- [`transfer_to_private`](#transfer_to_private) - initiates the transfer of an NFT from the public context to the private context +- [`prepare_private_balance_increase`](#prepare_private_balance_increase) - creates a [partial note](../../../aztec/concepts/storage/partial_notes.md) to transfer an NFT from the public context to the private context. +- [`cancel_authwit`](#cancel_authwit) - emits a nullifier to cancel a private authwit +- [`transfer_in_private`](#transfer_in_private) - transfers an NFT to another account, privately +- [`transfer_to_public`](#transfer_to_public) - transfers and NFT from a private accounts' balance to a specified accounts' public balance #### Private `view` functions -These functions are useful for getting contract information in the private context. +These functions are useful for getting contract information in another contract in the private context. -- [`private_get_symbol`]() - returns the NFT contract symbol -- [`private_get_name`]() - returns the NFT contract name +- [`private_get_symbol`](#private_get_symbol) - returns the NFT contract symbol +- [`private_get_name`](#private_get_name) - returns the NFT contract name ### Internal functions Internal functions are functions that can only be called by the contract itself. These can be used when the contract needs to call one of it's public functions from one of it's private functions. -- [`_store_payload_in_transient_storage_unsafe`]() - -- [`_finalize_transfer_to_private_unsafe`]() - -- [`_finish_transfer_to_public`]() - +- [`_store_payload_in_transient_storage_unsafe`](#_store_payload_in_transient_storage_unsafe) - a public function that is called when preparing a private balance increase. This function handles the needed public state updates. +- [`finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) - finalized a transfer from public to private state. ### Unconstrained functions Unconstrained functions can be thought of as view functions from Solidity--they only return information from the contract storage or compute and return data without modifying contract storage. They are distinguished from functions with the `#[view]` annotation in that unconstrained functions cannot be called by other contracts. -- [`get_private_nfts`]() - +- [`get_private_nfts`](#get_private_nfts) - Returns an array of token IDs owned by the passed `AztecAddress` in private and a flag indicating whether a page limit was reached. ## Contract dependencies @@ -274,8 +273,12 @@ Internal functions are functions that can only be called by this contract. The f #### `_store_payload_in_transient_storage_unsafe` +It is labeled unsafe because the public function does not check the value of the storage slot before writing, but it is safe because of the private execution preceding this call. + #### `_finalize_transfer_to_private_unsafe` +This function is labeled as unsafe because the sender is not enforced in this function, but it is safe because the sender is enforced in the execution of the private function that calls this function. + #### `_finish_transfer_to_public` ### View function implementations @@ -284,7 +287,7 @@ View functions in Aztec are similar to `view` functions in Solidity in that they Public view calls that are part of a transaction will be executed by the sequencer when the transaction is being executed, so they are not private and will reveal information about the transaction. Private view calls can be safely used in private transactions for getting the same information. -#### `admin` +#### `get_admin` A getter function for reading the public `admin` value. @@ -296,6 +299,26 @@ A getter function for checking the value of associated with a `minter` in the pu #include_code is_minter /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#### `owner_of` + +Returns the owner of the provided `token_id`. Reverts if the `token_id` does not exist. Returns the zero address if the `token_id` does not have a public owner. + +#### `public_get_name` + +Returns the name of the NFT contract in the public context. + +#### `public_get_symbol` + +Returns the symbol of the NFT contract in the public context. + +#### `private_get_name` + +Returns the name of the NFT contract in the private context. + +#### `private_get_symbol` + +Returns the symbol of the NFT contract in the private context. + ### Unconstrained function implementations Unconstrained functions are similar to `view` functions in Solidity in that they only return information from the contract storage or compute and return data without modifying contract storage. They are different from view functions in that the values are returned from the user's PXE and are not constrained by the contract's definition--if there is bad data in the user's PXE, they will get bad data back. From 1e7a61baeb3c31824e7c888072d4f1515bab4442 Mon Sep 17 00:00:00 2001 From: josh crites Date: Mon, 9 Dec 2024 20:25:51 -0500 Subject: [PATCH 05/14] edits --- .../codealong/contract_tutorials/nft_contract.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md index a2e36c4b45a..6c857406ded 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -121,7 +121,7 @@ These functions are useful for getting contract information in another contract Internal functions are functions that can only be called by the contract itself. These can be used when the contract needs to call one of it's public functions from one of it's private functions. - [`_store_payload_in_transient_storage_unsafe`](#_store_payload_in_transient_storage_unsafe) - a public function that is called when preparing a private balance increase. This function handles the needed public state updates. -- [`finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) - finalized a transfer from public to private state. +- [`finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) - finalizes a transfer from public to private state ### Unconstrained functions @@ -249,6 +249,8 @@ Storage is referenced as `storage.variable`. #### `transfer_to_private` +Transfers token with `token_id` from public balance of the sender to a private balance of `to`. Calls [`_prepare_private_balance_increase`](#prepare_private_balance_increase) to get the hiding point slot and [`_finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) to finalize the transfer in the public context. + #include_code transfer_to_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust #### `prepare_private_balance_increase` @@ -275,12 +277,20 @@ Internal functions are functions that can only be called by this contract. The f It is labeled unsafe because the public function does not check the value of the storage slot before writing, but it is safe because of the private execution preceding this call. +#include_code \_store_payload_in_transient_storage_unsafe /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust + #### `_finalize_transfer_to_private_unsafe` This function is labeled as unsafe because the sender is not enforced in this function, but it is safe because the sender is enforced in the execution of the private function that calls this function. +#include_code \_finalize_transfer_to_private_unsafe /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust + #### `_finish_transfer_to_public` +Updates the public owner of the `token_id` to the `to` address. + +#include_code \_finish_transfer_to_public /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust + ### View function implementations View functions in Aztec are similar to `view` functions in Solidity in that they only return information from the contract storage or compute and return data without modifying contract storage. These functions are different from unconstrained functions in that the return values are constrained by their definition in the contract. From 3c7fe5c3e167401fef3dc343c8a838f7be8c568b Mon Sep 17 00:00:00 2001 From: josh crites Date: Tue, 10 Dec 2024 16:11:48 -0500 Subject: [PATCH 06/14] fix links --- docs/docs/aztec/smart_contracts/contract_structure.md | 2 +- docs/docs/aztec/smart_contracts/functions/index.md | 2 +- docs/docs/aztec/smart_contracts/functions/visibility.md | 4 ++-- .../writing_contracts/common_patterns/index.md | 2 +- .../smart_contracts/writing_contracts/storage/notes.md | 2 +- .../smart_contract_reference/dependencies.md | 2 +- .../contract_tutorials/token_bridge/3_withdrawing_to_l1.md | 4 ++-- .../codealong/contract_tutorials/token_bridge/index.md | 2 +- .../codealong/contract_tutorials/write_accounts_contract.md | 4 ---- 9 files changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/docs/aztec/smart_contracts/contract_structure.md b/docs/docs/aztec/smart_contracts/contract_structure.md index b625256ee56..79c7428faff 100644 --- a/docs/docs/aztec/smart_contracts/contract_structure.md +++ b/docs/docs/aztec/smart_contracts/contract_structure.md @@ -37,4 +37,4 @@ Here's a common layout for a basic Aztec.nr Contract project: ``` - See the vanilla Noir docs for [more info on packages](https://noir-lang.org/docs/noir/modules_packages_crates/crates_and_packages). -- You can review the structure of a complete contract in the token contract tutorial [here](../../tutorials/codealong/contract_tutorials/token_contract.md). +- You can review the structure of a complete contract in the NFT contract tutorial [here](../../tutorials/codealong/contract_tutorials/nft_contract.md). diff --git a/docs/docs/aztec/smart_contracts/functions/index.md b/docs/docs/aztec/smart_contracts/functions/index.md index 8427c5e7684..72a502f8050 100644 --- a/docs/docs/aztec/smart_contracts/functions/index.md +++ b/docs/docs/aztec/smart_contracts/functions/index.md @@ -5,7 +5,7 @@ tags: [functions] Functions serve as the building blocks of smart contracts. Functions can be either **public**, ie they are publicly available for anyone to see and can directly interact with public state, or **private**, meaning they are executed completely client-side in the [PXE](../../concepts/pxe/index.md). Read more about how private functions work [here](./attributes.md#private-functions). -For a more practical guide of using multiple types of functions, follow the [token tutorial](../../../tutorials/codealong/contract_tutorials/token_contract.md). +For a more practical guide of using multiple types of functions, follow the [NFT tutorial](../../../tutorials/codealong/contract_tutorials/nft_contract.md). Currently, any function is "mutable" in the sense that it might alter state. However, we also support static calls, similarly to EVM. A static call is essentially a call that does not alter state (it keeps state static). diff --git a/docs/docs/aztec/smart_contracts/functions/visibility.md b/docs/docs/aztec/smart_contracts/functions/visibility.md index 1fc206cd6fa..599ce9818b0 100644 --- a/docs/docs/aztec/smart_contracts/functions/visibility.md +++ b/docs/docs/aztec/smart_contracts/functions/visibility.md @@ -6,11 +6,11 @@ tags: [functions] In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. This page explains these types of visibility. -For a practical guide of using multiple types of data and function visibility, follow the [token tutorial](../../../tutorials/codealong/contract_tutorials/token_contract.md). +For a practical guide of using multiple types of data and function visibility, follow the [NFT tutorial](../../../tutorials/codealong/contract_tutorials/nft_contract.md). ### Data Visibility -Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). +Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). ### Function visibility diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md index aacd44df9d8..6ee1b680d16 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md @@ -77,7 +77,7 @@ In this situation, try to mark the public function as `internal`. This ensures y ### Moving public data into the private domain -See [partial notes](../../../../../aztec/concepts/storage/partial_notes.md). Partial notes are how public balances are transferred to private [in the token contract](../../../../../tutorials/codealong/contract_tutorials/token_contract.md). +See [partial notes](../../../../../aztec/concepts/storage/partial_notes.md). Partial notes are how public balances are transferred to private [in the NFT contract](../../../../../tutorials/codealong/contract_tutorials/nft_contract.md). ### Discovering my notes diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/storage/notes.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/storage/notes.md index b4f737c7857..2e8100bb9f8 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/storage/notes.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/storage/notes.md @@ -150,7 +150,7 @@ Notice how the `add` function shows the simplicity of appending a new note to al ### Apply -Try the [Token tutorial](../../../../../tutorials/codealong/contract_tutorials/token_contract.md) to see what notes can achieve. In this section you will also find other tutorials using notes in different ways. +Try the [NFT tutorial](../../../../../tutorials/codealong/contract_tutorials/nft_contract.md) to see what notes can achieve. In this section you will also find other tutorials using notes in different ways. ### Further reading diff --git a/docs/docs/reference/developer_references/smart_contract_reference/dependencies.md b/docs/docs/reference/developer_references/smart_contract_reference/dependencies.md index d2d7842ca61..1a2bf74b08c 100644 --- a/docs/docs/reference/developer_references/smart_contract_reference/dependencies.md +++ b/docs/docs/reference/developer_references/smart_contract_reference/dependencies.md @@ -52,4 +52,4 @@ This library contains types that are used in the Aztec protocol. Find it on [Git value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/value-note" } ``` -This is a library for a note that stores one arbitrary value. You can see an example of how it might be used in the [token contract codealong tutorial](../../../tutorials/codealong/contract_tutorials/token_contract.md). +This is a library for a note that stores one arbitrary value. You can see an example of how it might be used in the [crowdfunding contract codealong tutorial](../../../tutorials/codealong/contract_tutorials/crowdfunding_contract.md). diff --git a/docs/docs/tutorials/codealong/contract_tutorials/token_bridge/3_withdrawing_to_l1.md b/docs/docs/tutorials/codealong/contract_tutorials/token_bridge/3_withdrawing_to_l1.md index ae28246bde3..daf6967280c 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/token_bridge/3_withdrawing_to_l1.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/token_bridge/3_withdrawing_to_l1.md @@ -19,7 +19,7 @@ The `exit_to_l1_public` function enables anyone to withdraw their L2 tokens back 1. Like with our deposit function, we need to create the L2 to L1 message. The content is the _amount_ to burn, the recipient address, and who can execute the withdraw on the L1 portal on behalf of the user. It can be `0x0` for anyone, or a specified address. 2. `context.message_portal()` passes this content to the kernel circuit which creates the proof for the transaction. The kernel circuit then adds the sender (the L2 address of the bridge + version of aztec) and the recipient (the portal to the L2 address + the chain ID of L1) under the hood, to create the message which gets added as part of the transaction data published by the sequencer and is stored in the outbox for consumption. 3. The `context.message_portal()` takes the recipient and content as input, and will insert a message into the outbox. We set the recipient to be the portal address read from storage of the contract. -4. Finally, you also burn the tokens on L2! Note that it burning is done at the end to follow the check effects interaction pattern. Note that the caller has to first approve the bridge contract to burn tokens on its behalf. Refer to [burn_public function on the token contract](../token_contract.md#burn_public). +4. Finally, you also burn the tokens on L2! Note that it burning is done at the end to follow the check effects interaction pattern. Note that the caller has to first approve the bridge contract to burn tokens on its behalf. Refer to `burn_public` function on the [token contract](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src/main.nr). - We burn the tokens from the `msg_sender()`. Otherwise, a malicious user could burn someone else’s tokens and mint tokens on L1 to themselves. One could add another approval flow on the bridge but that might make it complex for other applications to call the bridge. ## Withdrawing Privately @@ -87,6 +87,6 @@ And generate the TypeScript interface for the contract and add it to the test di aztec codegen target -o ../../src/test/fixtures ``` -This will create a TS interface inside `fixtures` dir in our `src/test` folder! +This will create a TS interface inside `fixtures` dir in our `src/test` folder! In the next step we will write the TypeScript code to deploy our contracts and call on both L1 and L2 so we can see how everything works together. diff --git a/docs/docs/tutorials/codealong/contract_tutorials/token_bridge/index.md b/docs/docs/tutorials/codealong/contract_tutorials/token_bridge/index.md index 72a0c7061df..2853f11bf5a 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/token_bridge/index.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/token_bridge/index.md @@ -42,7 +42,7 @@ The goal for this tutorial is to create functionality such that a token can be b This is just a reference implementation for educational purposes only. It has not been through an in-depth security audit. -Let’s assume a token exists on Ethereum and Aztec (see a [the token tutorial](../token_contract.md)). +Let’s assume a token exists on Ethereum and Aztec (see [the example token contract](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src/main.nr)). We will build: diff --git a/docs/docs/tutorials/codealong/contract_tutorials/write_accounts_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/write_accounts_contract.md index 064365bb3dd..4f9c242c993 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/write_accounts_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/write_accounts_contract.md @@ -109,10 +109,6 @@ Lo and behold, we get `Error: Assertion failed: 'verification == true'` when run ## Next Steps -### Build a hybrid token tutorial - -Follow the token contract tutorial on the [next page](./token_contract.md) and learn more about hybrid state. - ### Optional: Learn more about concepts mentioned here - [ECDSA signer account contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr) From 961667b33cd014ec4d9d04936a83e74f951e28a6 Mon Sep 17 00:00:00 2001 From: josh crites Date: Tue, 10 Dec 2024 16:11:57 -0500 Subject: [PATCH 07/14] fix link --- docs/docs/guides/developer_guides/js_apps/deploy_contract.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/developer_guides/js_apps/deploy_contract.md b/docs/docs/guides/developer_guides/js_apps/deploy_contract.md index 2b59f7588e8..15c54059cec 100644 --- a/docs/docs/guides/developer_guides/js_apps/deploy_contract.md +++ b/docs/docs/guides/developer_guides/js_apps/deploy_contract.md @@ -15,7 +15,7 @@ You can read about contract artifacts [here](../../../aztec/smart_contracts/cont ## Import the contract artifact -In this guide we are using a Token contract artifact. This comes from the [token contract tutorial](../../../tutorials/codealong/contract_tutorials/token_contract.md). +In this guide we are using a Token contract artifact. #include_code import_token_contract yarn-project/end-to-end/src/composed/docs_examples.test.ts typescript From 325ff4160fe07cdd7dcb6aac3712211ff04a1923 Mon Sep 17 00:00:00 2001 From: josh crites Date: Tue, 10 Dec 2024 16:12:46 -0500 Subject: [PATCH 08/14] final details --- .../contract_tutorials/nft_contract.md | 28 ++++++++++++++----- .../contracts/nft_contract/src/main.nr | 23 ++++++++++++--- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md index 6c857406ded..1a344e1ccae 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -255,19 +255,33 @@ Transfers token with `token_id` from public balance of the sender to a private b #### `prepare_private_balance_increase` -TODO +This function prepares a partial note to transfer an NFT from the public context to the private context. The caller specifies an `AztecAddress` that will receive the NFT in private storage. + +:::note + +This function calls `_prepare_private_balance_increase` which is marked as `#[contract_library_method]`, which means the compiler will inline the `_prepare_private_balance_increase` function. Click through to the source to see the implementation. + +::: + +#include_code prepare_private_balance_increase /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `cancel_authwit` -TODO +Cancels a private authwit by emitting the corresponding nullifier. + +#include_code cancel_authwit /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `transfer_in_private` -TODO +Transfers an NFT in the private context. Uses [authwits](../../../aztec/concepts/accounts/authwit.md) to allow contracts to transfer NFTs on behalf of other accounts. + +#include_code transfer_in_private /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `transfer_to_public` -TODO +Transfers and NFT from private storage to public storage. The private call enqueues a call to [`_finish_transfer_to_public`](#_finish_transfer_to_public) which updates the public owner of the `token_id`. + +#include_code transfer_to_public /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust ### Internal function implementations @@ -277,19 +291,19 @@ Internal functions are functions that can only be called by this contract. The f It is labeled unsafe because the public function does not check the value of the storage slot before writing, but it is safe because of the private execution preceding this call. -#include_code \_store_payload_in_transient_storage_unsafe /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust +#include_code store_payload_in_transient_storage_unsafe /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `_finalize_transfer_to_private_unsafe` This function is labeled as unsafe because the sender is not enforced in this function, but it is safe because the sender is enforced in the execution of the private function that calls this function. -#include_code \_finalize_transfer_to_private_unsafe /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust +#include_code finalize_transfer_to_private_unsafe /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `_finish_transfer_to_public` Updates the public owner of the `token_id` to the `to` address. -#include_code \_finish_transfer_to_public /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust +#include_code finish_transfer_to_public /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust ### View function implementations diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index f24d8a06fe5..969edd30e00 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -17,9 +17,12 @@ contract NFT { encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, keys::getters::get_public_keys, macros::{ - events::event, functions::{initializer, internal, private, public, view}, + events::event, + functions::{initializer, internal, private, public, view}, storage::storage, - }, note::constants::MAX_NOTES_PER_PAGE, oracle::random::random, + }, + note::constants::MAX_NOTES_PER_PAGE, + oracle::random::random, prelude::{ AztecAddress, Map, NoteGetterOptions, NoteViewerOptions, PrivateContext, PrivateSet, PublicContext, PublicImmutable, PublicMutable, @@ -175,11 +178,12 @@ contract NFT { /// Prepares an increase of private balance of `to` (partial note). The increase needs to be finalized by calling /// `finalize_transfer_to_private. Returns a hiding point slot. + // docs:start:prepare_private_balance_increase #[private] fn prepare_private_balance_increase(to: AztecAddress) -> Field { _prepare_private_balance_increase(to, &mut context, storage) } - + // docs:end:prepare_private_balance_increase /// This function exists separately from `prepare_private_balance_increase` solely as an optimization as it allows /// us to have it inlined in the `transfer_to_private` function which results in one less kernel iteration. /// @@ -236,6 +240,7 @@ contract NFT { // the public call and instead we would do something like `context.transient_storage_write(slot, payload)` and that // will allow us to use generics and hence user will not need to define it explicitly. We cannot use generics here // as it is an entrypoint function. + // docs:start:store_payload_in_transient_storage_unsafe #[public] #[internal] fn _store_payload_in_transient_storage_unsafe( @@ -246,7 +251,7 @@ contract NFT { context.storage_write(slot, point); context.storage_write(slot + aztec::protocol_types::point::POINT_LENGTH as Field, setup_log); } - + // docs:end:store_payload_in_transient_storage_unsafe /// Finalizes a transfer of NFT with `token_id` from public balance of `from` to a private balance of `to`. /// The transfer must be prepared by calling `prepare_private_balance_increase` first and the resulting /// `hiding_point_slot` must be passed as an argument to this function. @@ -258,6 +263,7 @@ contract NFT { } // docs:end:finalize_transfer_to_private + // docs:start:finalize_transfer_to_private_unsafe #[public] #[internal] fn _finalize_transfer_to_private_unsafe( @@ -267,6 +273,7 @@ contract NFT { ) { _finalize_transfer_to_private(from, token_id, hiding_point_slot, &mut context, storage); } + // docs:end:finalize_transfer_to_private_unsafe #[contract_library_method] fn _finalize_transfer_to_private( @@ -294,13 +301,16 @@ contract NFT { * Cancel a private authentication witness. * @param inner_hash The inner hash of the authwit to cancel. */ + // docs:start:cancel_authwit #[private] fn cancel_authwit(inner_hash: Field) { let on_behalf_of = context.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); context.push_nullifier(nullifier); } + // docs:end:cancel_authwit + // docs:start:transfer_in_private #[private] fn transfer_in_private(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { @@ -326,7 +336,9 @@ contract NFT { from, )); } + // docs:end:transfer_in_private + // docs:start:transfer_to_public #[private] fn transfer_to_public(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { @@ -344,12 +356,15 @@ contract NFT { &mut context, ); } + // docs:end:transfer_to_public + // docs:start:finish_transfer_to_public #[public] #[internal] fn _finish_transfer_to_public(to: AztecAddress, token_id: Field) { storage.public_owners.at(token_id).write(to); } + // docs:end:finish_transfer_to_public // Returns zero address when the token does not have a public owner. Reverts if the token does not exist. #[public] From 53863de0b36aa84bdef5f1ef98bf35861c723123 Mon Sep 17 00:00:00 2001 From: josh crites Date: Mon, 16 Dec 2024 16:14:37 -0500 Subject: [PATCH 09/14] Update docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md Co-authored-by: Rahul Kothari --- .../docs/tutorials/codealong/contract_tutorials/nft_contract.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md index 1a344e1ccae..74580b46b47 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -87,7 +87,7 @@ These are functions that have transparent logic, will execute in a publicly veri - [`set_minter`](#set_minter) - adds a minter to the `minters` mapping - [`mint`](#mint) - mints an NFT with a specified `token_id` to the recipient - [`transfer_in_public`](#transfer_in_public) - publicly transfer the specified token -- [`finalize_transfer_to_private`](#finalize_transfer_to_private) - finalized a transfer to from the public context to the private context (more on this below) +- [`finalize_transfer_to_private`](#finalize_transfer_to_private) - finalize the transfer of the NFT from public to private context by completing the [partial note](../../../aztec/concepts/storage/partial_notes.md)(more on this below) #### Public `view` functions From 22025d90e54bb9c1b1ef232f805aeb8ae77e7f35 Mon Sep 17 00:00:00 2001 From: josh crites Date: Mon, 16 Dec 2024 16:14:52 -0500 Subject: [PATCH 10/14] Update docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md Co-authored-by: Rahul Kothari --- .../docs/tutorials/codealong/contract_tutorials/nft_contract.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md index 74580b46b47..0fbfc99f7d0 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -103,7 +103,7 @@ These functions are useful for getting contract information for use in other con These are functions that have private logic and will be executed on user devices to maintain privacy. The only data that is submitted to the network is a proof of correct execution, new data commitments and nullifiers, so users will not reveal which contract they are interacting with or which function they are executing. The only information that will be revealed publicly is that someone executed a private transaction on Aztec. -- [`transfer_to_private`](#transfer_to_private) - initiates the transfer of an NFT from the public context to the private context +- [`transfer_to_private`](#transfer_to_private) - privately initiates the transfer of an NFT from the public context to the private context by creating a [partial note](../../../aztec/concepts/storage/partial_notes.md) - [`prepare_private_balance_increase`](#prepare_private_balance_increase) - creates a [partial note](../../../aztec/concepts/storage/partial_notes.md) to transfer an NFT from the public context to the private context. - [`cancel_authwit`](#cancel_authwit) - emits a nullifier to cancel a private authwit - [`transfer_in_private`](#transfer_in_private) - transfers an NFT to another account, privately From ee6ff5454ca8f9c7bbf2cb792a011de7f1b26e5f Mon Sep 17 00:00:00 2001 From: josh crites Date: Mon, 16 Dec 2024 16:15:13 -0500 Subject: [PATCH 11/14] Update docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md Co-authored-by: Rahul Kothari --- .../docs/tutorials/codealong/contract_tutorials/nft_contract.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md index 0fbfc99f7d0..e0fe9fad7f4 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -107,7 +107,7 @@ These are functions that have private logic and will be executed on user devices - [`prepare_private_balance_increase`](#prepare_private_balance_increase) - creates a [partial note](../../../aztec/concepts/storage/partial_notes.md) to transfer an NFT from the public context to the private context. - [`cancel_authwit`](#cancel_authwit) - emits a nullifier to cancel a private authwit - [`transfer_in_private`](#transfer_in_private) - transfers an NFT to another account, privately -- [`transfer_to_public`](#transfer_to_public) - transfers and NFT from a private accounts' balance to a specified accounts' public balance +- [`transfer_to_public`](#transfer_to_public) - transfers a NFT from private to public context #### Private `view` functions From f55687210e020a346b76c26ce6ebce9d6ebe6f16 Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Mon, 23 Dec 2024 10:00:24 +0000 Subject: [PATCH 12/14] fix links, add partial note explaination --- .../contract_tutorials/nft_contract.md | 26 +++++++++++-------- .../contracts/nft_contract/src/main.nr | 9 +++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md index e0fe9fad7f4..cdd551fa0c1 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -135,7 +135,7 @@ Before we can implement the functions, we need set up the contract storage, and :::info Copy required files -We will be going over the code in `main.nr` [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src). If you are following along and want to compile `main.nr` yourself, you need to add the other files in the directory as they contain imports that are used in `main.nr`. +We will be going over the code in `main.nr` [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/noir-contracts/contracts/nft_contract/src). If you are following along and want to compile `main.nr` yourself, you need to add the other files in the directory as they contain imports that are used in `main.nr`. ::: @@ -190,7 +190,7 @@ Copy and paste the body of each function into the appropriate place in your proj This function sets the admin and makes them a minter, and sets the name and symbol. -#include_code constructor /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code constructor /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust ### Public function implementations @@ -222,7 +222,7 @@ This public function checks that the `token_id` is not 0 and does not already ex #include_code transfer_in_public /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust -##### Authorizing token spends +##### Authorizing token spends (via authwits) If the `msg_sender` is **NOT** the same as the account to debit from, the function checks that the account has authorized the `msg_sender` contract to debit tokens on its behalf. This check is done by computing the function selector that needs to be authorized, computing the hash of the message that the account contract has approved. This is a hash of the contract that is approved to spend (`context.msg_sender`), the token contract that can be spent from (`context.this_address()`), the `selector`, the account to spend from (`from`), the `amount` and a `nonce` to prevent multiple spends. This hash is passed to `assert_inner_hash_valid_authwit_public` to ensure that the Account Contract has approved tokens to be spent on it's behalf. @@ -230,9 +230,9 @@ If the `msg_sender` is the same as the account to debit from, the authorization #### `finalize_transfer_to_private` -This public function finalizes a transfer that has been set up by a call to `prepare_private_balance_increase` by reducing the public balance of the associated account and emitting the note for the intended recipient. +This public function finalizes a transfer that has been set up by a call to [`prepare_private_balance_increase`](#prepare_private_balance_increase) by reducing the public balance of the associated account and emitting the note for the intended recipient. -#include_code finalize_transfer_to_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code finalize_transfer_to_private /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust ### Private function implementations @@ -249,13 +249,13 @@ Storage is referenced as `storage.variable`. #### `transfer_to_private` -Transfers token with `token_id` from public balance of the sender to a private balance of `to`. Calls [`_prepare_private_balance_increase`](#prepare_private_balance_increase) to get the hiding point slot and [`_finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) to finalize the transfer in the public context. +Transfers token with `token_id` from public balance of the sender to a private balance of `to`. Calls [`_prepare_private_balance_increase`](#prepare_private_balance_increase) to get the hiding point slot (a transient storage slot where we can keep the partial note) and then calls [`_finalize_transfer_to_private_unsafe`](#_finalize_transfer_to_private_unsafe) to finalize the transfer in the public context. -#include_code transfer_to_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code transfer_to_private /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `prepare_private_balance_increase` -This function prepares a partial note to transfer an NFT from the public context to the private context. The caller specifies an `AztecAddress` that will receive the NFT in private storage. +This function prepares a [partial note](../../../aztec/concepts/storage/partial_notes.md) to transfer an NFT from the public context to the private context. The caller specifies an `AztecAddress` that will receive the NFT in private storage. :::note @@ -263,6 +263,8 @@ This function calls `_prepare_private_balance_increase` which is marked as `#[co ::: +It also calls [`_store_payload_in_transient_storage_unsafe`](#_store_payload_in_transient_storage_unsafe) to store the partial note in "transient storage" (more below) + #include_code prepare_private_balance_increase /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `cancel_authwit` @@ -273,7 +275,7 @@ Cancels a private authwit by emitting the corresponding nullifier. #### `transfer_in_private` -Transfers an NFT in the private context. Uses [authwits](../../../aztec/concepts/accounts/authwit.md) to allow contracts to transfer NFTs on behalf of other accounts. +Transfers an NFT between two addresses in the private context. Uses [authwits](../../../aztec/concepts/accounts/authwit.md) to allow contracts to transfer NFTs on behalf of other accounts. #include_code transfer_in_private /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust @@ -291,6 +293,8 @@ Internal functions are functions that can only be called by this contract. The f It is labeled unsafe because the public function does not check the value of the storage slot before writing, but it is safe because of the private execution preceding this call. +This is transient storage since the storage is not permanent, but is scoped to the current transaction only, after which it will be reset. The partial note is stored the "hiding point slot" value (computed in `_prepare_private_balance_increase()`) in public storage. However subseqeuent enqueued call to `_finalize_transfer_to_private_unsafe()` will read the partial note in this slot, complete it and emit it. Since the note is completed, there is no use of storing the hiding point slot anymore so we will reset to empty. This saves a write to public storage too. + #include_code store_payload_in_transient_storage_unsafe /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `_finalize_transfer_to_private_unsafe` @@ -315,13 +319,13 @@ Public view calls that are part of a transaction will be executed by the sequenc A getter function for reading the public `admin` value. -#include_code admin /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code admin /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `is_minter` A getter function for checking the value of associated with a `minter` in the public `minters` mapping. -#include_code is_minter /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code is_minter /noir-projects/noir-contracts/contracts/nft_contract/src/main.nr rust #### `owner_of` diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index e23ffaae728..5720bb1e167 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -128,17 +128,21 @@ contract NFT { storage.symbol.read() } + // docs:start:admin #[public] #[view] fn get_admin() -> Field { storage.admin.read().to_field() } + // docs:end:admin + // docs:start:is_minter #[public] #[view] fn is_minter(minter: AztecAddress) -> bool { storage.minters.at(minter).read() } + // docs:end:is_minter // docs:start:transfer_in_public #[public] @@ -158,7 +162,7 @@ contract NFT { // Transfers token with `token_id` from public balance of message sender to a private balance of `to`. // docs:start:transfer_to_private - #[private] + #[private] fn transfer_to_private(to: AztecAddress, token_id: Field) { let from = context.msg_sender(); @@ -182,7 +186,7 @@ contract NFT { fn prepare_private_balance_increase(to: AztecAddress) -> Field { _prepare_private_balance_increase(to, &mut context, storage) } - // docs:end:prepare_private_balance_increase + /// This function exists separately from `prepare_private_balance_increase` solely as an optimization as it allows /// us to have it inlined in the `transfer_to_private` function which results in one less kernel iteration. /// @@ -230,6 +234,7 @@ contract NFT { hiding_point_slot } + // docs:end:prepare_private_balance_increase // TODO(#9375): Having to define the note log length here is very unfortunate as it's basically impossible for // users to derive manually. This will however go away once we have a real transient storage since we will not need From 23c3a436c9521e468179006eebc11d33627e3b2a Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Mon, 23 Dec 2024 14:21:00 +0400 Subject: [PATCH 13/14] Update noir-projects/noir-contracts/contracts/nft_contract/src/main.nr --- noir-projects/noir-contracts/contracts/nft_contract/src/main.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index 5720bb1e167..b1d9ddacd5d 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -162,7 +162,7 @@ contract NFT { // Transfers token with `token_id` from public balance of message sender to a private balance of `to`. // docs:start:transfer_to_private - #[private] + #[private] fn transfer_to_private(to: AztecAddress, token_id: Field) { let from = context.msg_sender(); From 2d3547864c9d4738863a0bab9fe74648548c8c22 Mon Sep 17 00:00:00 2001 From: josh crites Date: Mon, 23 Dec 2024 12:12:22 -0500 Subject: [PATCH 14/14] fix fmt --- .../contracts/nft_contract/src/types/nft_note.nr | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr index f50352b1ef2..36a691be67f 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr @@ -1,6 +1,8 @@ use dep::aztec::{ - keys::getters::{get_nsk_app, get_public_keys}, macros::notes::partial_note, - note::utils::compute_note_hash_for_nullify, oracle::random::random, + keys::getters::{get_nsk_app, get_public_keys}, + macros::notes::partial_note, + note::utils::compute_note_hash_for_nullify, + oracle::random::random, prelude::{NoteHeader, NullifiableNote, PrivateContext}, protocol_types::{ address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER,