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/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 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/nft_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md new file mode 100644 index 00000000000..cdd551fa0c1 --- /dev/null +++ b/docs/docs/tutorials/codealong/contract_tutorials/nft_contract.md @@ -0,0 +1,382 @@ +--- +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`](#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) - 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 + +These functions are useful for getting contract information for use in other contracts, in the public context. + +- [`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`](#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 +- [`transfer_to_public`](#transfer_to_public) - transfers a NFT from private to public context + +#### Private `view` functions + +These functions are useful for getting contract information in another contract in the private context. + +- [`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`](#_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) - finalizes 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) - 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 + +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/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`. + +::: + +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/nft_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 (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. + +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`](#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/nft_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` + +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/nft_contract/src/main.nr rust + +#### `prepare_private_balance_increase` + +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 + +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. + +::: + +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` + +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` + +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 + +#### `transfer_to_public` + +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 + +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` + +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` + +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. + +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. + +#### `get_admin` + +A getter function for reading the public `admin` value. + +#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/nft_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. + +#### `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/nft_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_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/token_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md index 9b93a6e0e72..ba59aae84e2 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/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) 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 109fe6a57f4..b1d9ddacd5d 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; @@ -30,6 +31,7 @@ contract NFT { }; use dep::compressed_string::FieldCompressedString; use std::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. @@ -41,6 +43,7 @@ contract NFT { token_id: Field, } + // docs:start:storage_struct #[storage] struct Storage { // The symbol of the NFT @@ -58,7 +61,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>) { @@ -68,19 +73,25 @@ contract NFT { storage.name.initialize(FieldCompressedString::from_string(name)); storage.symbol.initialize(FieldCompressedString::from_string(symbol)); } + // docs:end:constructor + // 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:set_admin + // 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:set_minter + // docs:start:mint #[public] fn mint(to: AztecAddress, token_id: Field) { assert(token_id != 0, "zero token ID not supported"); @@ -91,6 +102,7 @@ contract NFT { storage.public_owners.at(token_id).write(to); } + // docs:end:mint #[public] #[view] @@ -116,18 +128,23 @@ 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] fn transfer_in_public(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { @@ -141,8 +158,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(); @@ -158,9 +177,11 @@ 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. + // docs:start:prepare_private_balance_increase #[private] fn prepare_private_balance_increase(to: AztecAddress) -> Field { _prepare_private_balance_increase(to, &mut context, storage) @@ -213,12 +234,14 @@ 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 // 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( @@ -229,16 +252,19 @@ 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. + // 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 + // docs:start:finalize_transfer_to_private_unsafe #[public] #[internal] fn _finalize_transfer_to_private_unsafe( @@ -248,6 +274,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( @@ -275,13 +302,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())) { @@ -300,7 +330,9 @@ contract NFT { let mut new_note = NFTNote::new(token_id, to); nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note(&mut context, to, 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())) { @@ -318,12 +350,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] @@ -336,6 +371,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, @@ -354,5 +390,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..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 @@ -10,6 +10,7 @@ use dep::aztec::{ }, }; +// docs:start:nft_note #[partial_note(quote { token_id})] pub struct NFTNote { // ID of the token @@ -19,8 +20,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 +36,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);