From ab8eff029018f4005c12ec225287994a76f49964 Mon Sep 17 00:00:00 2001 From: AztecBot <49558828+AztecBot@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:22:56 +0000 Subject: [PATCH] fix(docs): updating docs structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :warning: this PR builds off of the changes in https://github.com/AztecProtocol/aztec-packages/pull/16442, so merge that one first This PR makes several edits to the docs. The main impetus behind the changes being made is to make a more linear learning journey for developers as they move down the sidebar. - moves the guides section above the tutorials - flattens the pages inside the "Developer smart contracts" section - makes the "developing smart contracts" section a more linear flow for how a developer should be moving through this content - moves some of the technical explanations to the concepts section - adds a several pages to the Guides section including: - project sturcturing - data types - custom types - defining functions (initializer page moved into here) - removes some explainers and reference about storage from the references section into the guides section closes: https://github.com/AztecProtocol/dev-rel/issues/598 Co-authored-by: Alex Gherghisan Co-authored-by: Charlie Lye <5764343+charlielye@users.noreply.github.com> Co-authored-by: Facundo Co-authored-by: IlyasRidhuan Co-authored-by: Jonathan Hao Co-authored-by: Josh Crites Co-authored-by: José Pedro Sousa Co-authored-by: LHerskind <16536249+LHerskind@users.noreply.github.com> Co-authored-by: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Co-authored-by: Raju Krishnamoorthy Co-authored-by: fcarreiro Co-authored-by: federicobarbacovi <171914500+federicobarbacovi@users.noreply.github.com> Co-authored-by: jeanmon Co-authored-by: maramihali Co-authored-by: sergei iakovenko <105737703+iakovenkos@users.noreply.github.com> Co-authored-by: sirasistant Co-authored-by: thunkar --- docs/docs-words.txt | 1 + docs/docs/aztec/concepts/accounts/keys.md | 2 +- docs/docs/aztec/concepts/advanced/authwit.md | 2 +- .../advanced/storage/storage_slots.md | 60 ++- docs/docs/aztec/concepts/call_types.md | 2 +- docs/docs/aztec/concepts/pxe/index.md | 2 +- docs/docs/aztec/concepts/storage/notes.md | 143 +++++- .../aztec/concepts/storage/state_model.md | 4 - .../smart_contracts/contract_creation.md | 2 +- .../aztec/smart_contracts/functions/index.md | 2 +- .../aztec/smart_contracts/oracles/index.md | 4 +- .../docs/aztec/writing_efficient_contracts.md | 27 +- .../guides/getting_started_on_testnet.md | 15 +- .../developers/guides/js_apps/_category_.json | 8 +- .../docs/developers/guides/js_apps/authwit.md | 8 +- docs/docs/developers/guides/js_apps/test.md | 2 +- .../developers/guides/local_env/sandbox.md | 2 +- .../guides/smart_contracts/_category_.json | 8 +- .../smart_contracts/advanced}/_category_.json | 4 +- .../index.md => advanced/common_patterns.md} | 11 +- .../smart_contracts/advanced/get_notes.md | 131 ++++++ .../how_to_use_capsules.md | 1 - .../{ => advanced}/profiling_transactions.md | 10 +- .../{writing_contracts => }/authwit.md | 21 +- .../{writing_contracts => }/call_contracts.md | 10 +- ...portal.md => cross_chain_communication.md} | 15 +- .../smart_contracts/define_functions.md | 101 +++++ .../how_to_compile_contract.md | 6 +- .../how_to_emit_event.md | 4 +- .../how_to_prove_history.md | 4 +- .../guides/smart_contracts/index.md | 13 +- .../guides/smart_contracts/note_types.md | 140 ++++++ .../guides/smart_contracts/storage.md | 28 ++ .../guides/smart_contracts/storage_types.md | 428 ++++++++++++++++++ .../smart_contracts/structure_project.md | 63 +++ .../guides/smart_contracts/testing.md | 8 +- .../writing_contracts/_category_.json | 6 - .../writing_contracts/index.mdx | 41 -- .../writing_contracts/initializers.md | 49 -- .../writing_contracts/notes/address_note.md | 43 -- .../notes/implementing_a_note.md | 42 -- .../writing_contracts/notes/index.md | 11 - .../writing_contracts/notes/value_note.md | 63 --- .../writing_contracts/portals/index.md | 7 - .../writing_contracts/storage/index.md | 22 - .../writing_contracts/storage/notes.md | 169 ------- .../storage/storage_slots.md | 61 --- .../cli_wallet_reference.md | 4 +- .../storage/delayed_public_mutable.md | 96 ---- .../smart_contract_reference/storage/index.md | 92 ---- .../storage/private_state.md | 369 --------------- .../storage/public_state.md | 105 ----- .../contract_tutorials/counter_contract.md | 2 +- .../crowdfunding_contract.md | 4 +- .../contract_tutorials/nft_contract.md | 2 +- .../js_tutorials/aztecjs-getting-started.md | 67 ++- docs/netlify.toml | 10 +- docs/package.json | 2 +- docs/sidebars.js | 8 +- .../advanced/storage/storage_slots.md | 2 - .../common_patterns/index.md | 50 +- .../contracts/app/escrow_contract/src/main.nr | 2 + .../contracts/app/nft_contract/src/main.nr | 2 + .../app/simple_token_contract/src/main.nr | 2 + .../src/types/transparent_note.nr | 2 + 65 files changed, 1296 insertions(+), 1331 deletions(-) rename docs/docs/developers/{reference/smart_contract_reference/storage => guides/smart_contracts/advanced}/_category_.json (50%) rename docs/docs/developers/guides/smart_contracts/{writing_contracts/common_patterns/index.md => advanced/common_patterns.md} (90%) create mode 100644 docs/docs/developers/guides/smart_contracts/advanced/get_notes.md rename docs/docs/developers/guides/smart_contracts/{writing_contracts => advanced}/how_to_use_capsules.md (99%) rename docs/docs/developers/guides/smart_contracts/{ => advanced}/profiling_transactions.md (94%) rename docs/docs/developers/guides/smart_contracts/{writing_contracts => }/authwit.md (93%) rename docs/docs/developers/guides/smart_contracts/{writing_contracts => }/call_contracts.md (91%) rename docs/docs/developers/guides/smart_contracts/{writing_contracts/portals/communicate_with_portal.md => cross_chain_communication.md} (96%) create mode 100644 docs/docs/developers/guides/smart_contracts/define_functions.md rename docs/docs/developers/guides/smart_contracts/{writing_contracts => }/how_to_emit_event.md (98%) rename docs/docs/developers/guides/smart_contracts/{writing_contracts => }/how_to_prove_history.md (98%) create mode 100644 docs/docs/developers/guides/smart_contracts/note_types.md create mode 100644 docs/docs/developers/guides/smart_contracts/storage.md create mode 100644 docs/docs/developers/guides/smart_contracts/storage_types.md create mode 100644 docs/docs/developers/guides/smart_contracts/structure_project.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/_category_.json delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/index.mdx delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/initializers.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/notes/address_note.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/notes/implementing_a_note.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/notes/index.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/notes/value_note.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/portals/index.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/storage/index.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/storage/notes.md delete mode 100644 docs/docs/developers/guides/smart_contracts/writing_contracts/storage/storage_slots.md delete mode 100644 docs/docs/developers/reference/smart_contract_reference/storage/delayed_public_mutable.md delete mode 100644 docs/docs/developers/reference/smart_contract_reference/storage/index.md delete mode 100644 docs/docs/developers/reference/smart_contract_reference/storage/private_state.md delete mode 100644 docs/docs/developers/reference/smart_contract_reference/storage/public_state.md diff --git a/docs/docs-words.txt b/docs/docs-words.txt index 971aee9ea590..00004900c1d0 100644 --- a/docs/docs-words.txt +++ b/docs/docs-words.txt @@ -327,6 +327,7 @@ undeflow Undercollateralized underconstrained unleaked +unlinkability unncessary unreverted unrleated diff --git a/docs/docs/aztec/concepts/accounts/keys.md b/docs/docs/aztec/concepts/accounts/keys.md index 61000e2a74a8..19e46b10d2a6 100644 --- a/docs/docs/aztec/concepts/accounts/keys.md +++ b/docs/docs/aztec/concepts/accounts/keys.md @@ -106,7 +106,7 @@ When it comes to storing the signing key in a private note, there are several de #### Using Delayed Public Mutable state -By [Delayed Public Mutable](../../../developers/reference/smart_contract_reference/storage/delayed_public_mutable.md#delayedpublicmutable) we mean privately readable publicly mutable state. +By [Delayed Public Mutable](../../../developers/guides/smart_contracts/storage_types.md#delayed-public-mutable) we mean privately readable publicly mutable state. To make public state accessible privately, there is a delay window in public state updates. One needs this window to be able to generate proofs client-side. This approach would not generate additional nullifiers and commitments for each transaction while allowing the user to rotate their key. However, this causes every transaction to now have a time-to-live determined by the frequency of the delayed mutable state, as well as imposing restrictions on how fast keys can be rotated due to minimum delays. diff --git a/docs/docs/aztec/concepts/advanced/authwit.md b/docs/docs/aztec/concepts/advanced/authwit.md index abba5debb634..0a672a77ce45 100644 --- a/docs/docs/aztec/concepts/advanced/authwit.md +++ b/docs/docs/aztec/concepts/advanced/authwit.md @@ -141,4 +141,4 @@ We don't need to limit ourselves to the `transfer` function, we can use the same ### Next Steps -Check out the [developer documentation](../../../developers/guides/smart_contracts/writing_contracts/authwit.md) to see how to implement this in your own contracts. +Check out the [developer documentation](../../../developers/guides/smart_contracts/authwit.md) to see how to implement this in your own contracts. diff --git a/docs/docs/aztec/concepts/advanced/storage/storage_slots.md b/docs/docs/aztec/concepts/advanced/storage/storage_slots.md index bc5ef8221c35..7e236f998ef9 100644 --- a/docs/docs/aztec/concepts/advanced/storage/storage_slots.md +++ b/docs/docs/aztec/concepts/advanced/storage/storage_slots.md @@ -5,8 +5,6 @@ sidebar_position: 1 description: Understand how storage slots work in Aztec for both public and private state, including siloing mechanisms and note hash commitments. --- -# Storage Slots - ## Public State Slots As mentioned in [State Model](../../storage/state_model.md), Aztec public state behaves similarly to public state on Ethereum from the point of view of the developer. Behind the scenes however, the storage is managed differently. As mentioned, public state has just one large sparse tree in Aztec - so we silo slots of public data by hashing it together with its contract address. @@ -58,4 +56,60 @@ By doing this address-siloing at the kernel circuit we _force_ the inserted comm To ensure that nullifiers don't collide across contracts we also force this contract siloing at the kernel level. ::: -For an example of this see [developer documentation on storage](../../../../developers/reference/smart_contract_reference/storage/index.md). +## Example + +In this section we will go into more detail and walk through an entire example of how storage slots are computed for private state to improve our storage slot intuition. Recall, that storage slots in the private domain is just a logical construct, and are not "actually" used for lookups, but rather just as a value to constrain against. + +For the case of the example, we will look at what is inserted into the note hashes tree when adding a note in the Token contract. Specifically, we are looking at the last part of the `transfer` function: + +#include_code increase_private_balance noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +This function is creating a new note and inserting it into the balance set of the recipient `to`. Recall that to ensure privacy, only the note hash is really inserted into the note hashes tree. To share the contents of the note with `to` the contract can emit an encrypted log (which this one does), or it can require an out-of-band data transfer sharing the information. Below, we will walk through the steps of how the note hash is computed and inserted into the tree. For this, we don't care about the encrypted log, so we are going to ignore that part of the function call for now. + +Outlining it in more detail below as a sequence diagram, we can see how the calls make their way down the stack. +In the end a siloed note hash is computed in the kernel. + +:::info +Some of the syntax below is a little butchered to make it easier to follow variables without the full code. +::: + +```mermaid +sequenceDiagram + alt Call + Token->>BalanceMap: Map::new(map_slot); + Token->>Token: to_bal = storage.balances.at(to) + Token->>BalanceMap: BalanceMap.at(to) + BalanceMap->>BalanceMap: derived_slot = H(map_slot, to) + BalanceMap->>BalanceSet: BalanceSet::new(to, derived_slot) + Token->>BalanceSet: to_bal.add(amount) + BalanceSet->>BalanceSet: note = UintNote::new(amount, to) + BalanceSet->>Set: insert(note) + Set->>LifeCycle: create_note(derived_slot, note) + LifeCycle->>LifeCycle: note.header = NoteHeader { contract_address,
storage_slot: derived_slot, nonce: 0, note_hash_counter } + UintPartialNotePrivateContent->>UintNote: note_hash = compute_partial_commitment(storage_slot).x + LifeCycle->>Context: push_note_hash(note_hash) + end + Context->>Kernel: unique_note_hash = H(nonce, note_hash) + Context->>Kernel: siloed_note_hash = H(contract_address, unique_note_hash) +``` + +Notice the `siloed_note_hash` at the very end. It's a hash that will be inserted into the note hashes tree. To clarify what this really is, we "unroll" the values to their simplest components. This gives us a better idea around what is actually inserted into the tree. + +```rust +siloed_note_hash = H(contract_address, unique_note_hash) +siloed_note_hash = H(contract_address, H(nonce, note_hash)) +siloed_note_hash = H(contract_address, H(H(tx_hash, note_index_in_tx), note_hash)) +siloed_note_hash = H(contract_address, H(H(tx_hash, note_index_in_tx), MSM([G_amt, G_to, G_rand, G_slot], [amount, to, randomness, derived_slot]).x)) +``` + +MSM is a multi scalar multiplication on a grumpkin curve and G\_\* values are generators. + +And `to` is the actor who receives the note, `amount` of the note and `randomness` is the randomness used to make the note hiding. Without the `randomness` the note could just as well be plaintext (computational cost of a preimage attack would be trivial in such a case). + +:::info +Beware that this hash computation is what the aztec.nr library is doing, and not strictly required by the network (only the kernel computation is). +::: + +With this note structure, the contract can require that only notes sitting at specific storage slots can be used by specific operations, e.g., if transferring funds from `from` to `to`, the notes to destroy should be linked to `H(map_slot, from)` and the new notes (except the change-note) should be linked to `H(map_slot, to)`. + +That way, we can have logical storage slots, without them really existing. This means that knowing the storage slot for a note is not enough to actually figure out what is in there (whereas it would be for looking up public state). diff --git a/docs/docs/aztec/concepts/call_types.md b/docs/docs/aztec/concepts/call_types.md index 53d277003148..ccf9e5de3f93 100644 --- a/docs/docs/aztec/concepts/call_types.md +++ b/docs/docs/aztec/concepts/call_types.md @@ -149,7 +149,7 @@ aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "#incl Even with the router contract achieving good privacy is hard. For example, if the value being checked against is unique and stored in the contract's public storage, it's then simple to find private transactions that are using that value in the enqueued public reads, and therefore link them to this contract. -For this reason it is encouraged to try to avoid public function calls and instead privately read [Shared State](../../developers/reference/smart_contract_reference/storage/delayed_public_mutable.md) when possible. +For this reason it is encouraged to try to avoid public function calls and instead privately read [Shared State](../../developers/guides/smart_contracts/storage_types.md#delayed-public-mutable) when possible. ### Public Execution diff --git a/docs/docs/aztec/concepts/pxe/index.md b/docs/docs/aztec/concepts/pxe/index.md index e47edceefbfd..0670987780fc 100644 --- a/docs/docs/aztec/concepts/pxe/index.md +++ b/docs/docs/aztec/concepts/pxe/index.md @@ -106,4 +106,4 @@ Oracles are pieces of data that are injected into a smart contract function from To learn how to develop on top of the PXE, refer to these guides: - [Run more than one PXE on your local machine](../../../developers/guides/local_env/sandbox.md#running-multiple-pxes-in-the-sandbox) -- [Use in-built oracles including oracles for arbitrary data](../../../developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md) +- [Use in-built oracles including oracles for arbitrary data](../../../developers/guides/smart_contracts/advanced/how_to_use_capsules.md) diff --git a/docs/docs/aztec/concepts/storage/notes.md b/docs/docs/aztec/concepts/storage/notes.md index b16b7c30715e..782b988391f2 100644 --- a/docs/docs/aztec/concepts/storage/notes.md +++ b/docs/docs/aztec/concepts/storage/notes.md @@ -43,4 +43,145 @@ When using the Aztec protocol, users may not be aware of the specific notes that This is accomplished through the smart contract library, Aztec.nr, which abstracts notes by allowing developers to specify custom note types. This means they can specify how notes are interacted with, nullified, transferred, and displayed. Aztec.nr also helps users discover all of the notes that have been encrypted to their account and posted to the chain, known as [note discovery](../advanced/storage/note_discovery.md). -To understand note abstraction in Aztec.nr, you can read the [Build section](../../../developers/guides/smart_contracts/writing_contracts/notes/index.md). +## Technical details + +### Some context + +- Public functions and storage work much like other blockchains in terms of having dedicated storage slots and being publicly visible +- Private functions are executed locally with proofs generated for sound execution, and commitments to private variable updates are stored using append-only trees +- "Note" types are part of Aztec.nr, a framework that facilitates use of Aztec's different storage trees to achieve things such as private variables + +This page will focus on how private variables are implemented with Notes and storage trees. + +#### Side-note about execution + +Under the hood, the Aztec protocol handles some important details around public and private function calls. Calls between them are asynchronous due to different execution contexts (local execution vs. node execution). +A detailed explanation of the transaction lifecycle can be found [here](../transactions.md#simple-example-of-the-private-transaction-lifecycle). + +## Private state variables in Aztec + +State variables in an Aztec contract are defined inside a struct specifically named `Storage`, and must satisfy the [Note Interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_interface.nr) and contain a [Note header (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_header.nr). + +The Note header struct contains the contract address which the value is effectively siloed to, a nonce to ensure unique Note hashes, and a storage "slot" (or ID) to associate multiple notes. + +A couple of things to unpack here: + +#### Storage "slot" + +Storage slots are more literal for public storage, a place where a value is stored. For private storage, a storage slot is logical (more [here](../advanced/storage/storage_slots.md)). + +#### Silos + +The address of the contract is included in a Note's data to ensure that different contracts don't arrive at the same hash with an identical variable. This is handled in the protocol's execution. + +### Note types + +There is more than one Note type, such as the `PrivateSet` type is used for private variables. There are also `PrivateMutable` and `PrivateImmutable` types. + +Furthermore, notes can be completely custom types, storing any value or set of values that are desired by an application. + +### Initialization + +Private state variables are stored locally when the contract is created. Depending on the application, values may be privately shared by the creator with others via encrypted logs onchain. +A hash of a note is stored in the append-only note hash tree on the network so as to prove existence of the current state of the note in a privacy preserving way. + +#### Note Hash Tree + +By virtue of being append only, notes are not edited. If two transactions amend a private value, multiple notes will be inserted into the tree to the note hash tree and the nullifier tree. The header will contain the same logical storage slot. + +### Reading Notes + +:::info + +Only those with appropriate keys/information will be able to successfully read private values that they have permission to. Notes can be read outside of a transaction or "off-chain" with no changes to data structures on-chain. + +::: + +When a note is read in a transaction, a subsequent read from another transaction of the same note would reveal a link between the two. So to preserve privacy, notes that are read in a transaction are said to be "consumed" (defined below), and new note(s) are then created with a unique hash. + +With type `PrviateSet`, a private variable's value is interpreted as the sum of values of notes with the same logical storage slot. + +Consuming, deleting, or otherwise "nullifying" a note is NOT done by deleting the Note hash; this would leak information. Rather a nullifier is created deterministically linked to the value. This nullifier is inserted into another the nullifier storage tree. + +When reading a value, the local private execution checks that its notes (of the corresponding storage slot/ID) have not been nullified. + +### Updating + +:::note +Only those with appropriate keys/information will be able to successfully nullify a value that they have permission to. +::: + +To update a value, its previous note hash(es) are nullified. The new note value is updated in the user's private execution environment (PXE), and the updated note hash inserted into the note hash tree. + +## Supplementary components + +Some optional background resources on notes can be found here: + +- [High level network architecture](../../index.md), specifically the Private Execution Environment +- [Transaction lifecycle (simple diagram)](../transactions.md#simple-example-of-the-private-transaction-lifecycle) +- [Public and Private state](./state_model.md) + +Notes touch several core components of the protocol, but we will focus on a the essentials first. + +### Some code context + +The way Aztec benefits from the Noir language is via three important components: + +- `Aztec.nr` - a Noir framework enabling contracts on Aztec, written in Noir. Includes useful Note implementations +- `noir contracts` - example Aztec contracts +- `noir-protocol-circuits` - a crate containing essential circuits for the protocol (public circuits and private wrappers) + +A lot of what we will look at will be in [aztec-nr/aztec/src/note (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note), specifically the lifecycle and note interface. + +Looking at the noir circuits in these components, you will see references to the distinction between public/private execution and state. + +### Lifecycle functions + +Inside the [lifecycle (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr) circuits we see the functions to create and destroy a note, implemented as insertions of note hashes and nullifiers respectively. This is helpful for regular private variables. + +We also see a function to create a note hash from the public context, a way of creating a private variable from a public call (run in the sequencer). This could be used in application contracts to give private digital assets to users. + +### Note Interface functions + +To see a [note_interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_interface.nr) implementation, we will look at a simple [ValueNote GitHub link](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/value-note/src/value_note.nr). + +The interface is required to work within an Aztec contract's storage, and a ValueNote is a specific type of note to hold a number (as a `Field`). + +#### Computing hashes and nullifiers + +A few key functions in the note interface are around computing the note hash and nullifier, with logic to get/use secret keys from the private context. + +In the ValueNote implementation you'll notice that it uses the `pedersen_hash` function. This is currently required by the protocol, but may be updated to another hashing function, like poseidon. + +As a convenience, the outer [note/utils.nr (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/utils.nr) contains implementations of functions that may be needed in Aztec contracts, for example computing note hashes. + +#### Serialization and deserialization + +Serialization/deserialization of content is used to convert between the Note's variables and a generic array of Field elements. The Field type is understood and used by lower level crypographic libraries. +This is analogous to the encoding/decoding between variables and bytes in solidity. + +For example in ValueNote, the `serialize_content` function simply returns: the value, nullifying public key hash (as a field) and the note randomness; as an array of Field elements. + +### Value as a sum of Notes + +We recall that multiple notes are associated with a "slot" (or ID), and so the value of a numerical note (like ValueNote) is the sum of each note's value. +The helper function in [balance_utils (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_/noir-projects/aztec-nr/value-note/src/balance_utils.nr) implements this logic taking a `PrivateSet` of `ValueNotes`. + +A couple of things worth clarifying: + +- A `PrivateSet` takes a Generic type, specified here as `ValueNote`, but can be any `Note` type (for all notes in the set) +- A `PrivateSet` of notes also specifies _the_ slot of all Notes that it holds + +### Example - Notes in action + +The Aztec.nr framework includes examples of high-level states [easy_private_uint (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr) for use in contracts. + +The struct (`EasyPrivateUint`) contains a Context, Set of ValueNotes, and storage_slot (used when setting the Set). + +Notice how the `add` function shows the simplicity of appending a new note to all existing ones. On the other hand, `sub` (subtraction), needs to first add up all existing values (consuming them in the process), and then insert a single new value of the difference between the sum and parameter. + +--- + +### References + +- ["Stable" state variable (GitHub link)](https://github.com/AztecProtocol/aztec-packages/issues/4130) diff --git a/docs/docs/aztec/concepts/storage/state_model.md b/docs/docs/aztec/concepts/storage/state_model.md index e30376f438e1..3f455d7981a7 100644 --- a/docs/docs/aztec/concepts/storage/state_model.md +++ b/docs/docs/aztec/concepts/storage/state_model.md @@ -22,7 +22,3 @@ Private state is represented in an append-only database since updating a record The act of "deleting" a private state variable can be represented by adding an associated nullifier to a nullifier set. The nullifier is generated such that, without knowing the decryption key of the owner, an observer cannot link a state record with a nullifier. Modification of state variables can be emulated by nullifying the state record and creating a new record to represent the variable. Private state has an intrinsic UTXO structure. - -## Further reading - -Read more about how to leverage the Aztec state model in Aztec contracts [here](../../../developers/reference/smart_contract_reference/storage/index.md). diff --git a/docs/docs/aztec/smart_contracts/contract_creation.md b/docs/docs/aztec/smart_contracts/contract_creation.md index 54e49e85731f..e9914cf4cede 100644 --- a/docs/docs/aztec/smart_contracts/contract_creation.md +++ b/docs/docs/aztec/smart_contracts/contract_creation.md @@ -61,7 +61,7 @@ Aztec makes an important distinction between initialization and public deploymen ### Initialization -Contract constructors are not enshrined in the protocol, but handled at the application circuit level. Constructors are methods used for initializing a contract, either private or public, and contract classes may declare more than a single constructor. They can be declared by the `#[initializer]` macro. You can read more about how to use them on the [Defining Initializer Functions](../../developers/guides/smart_contracts/writing_contracts/initializers.md) page. +Contract constructors are not enshrined in the protocol, but handled at the application circuit level. Constructors are methods used for initializing a contract, either private or public, and contract classes may declare more than a single constructor. They can be declared by the `#[initializer]` macro. You can read more about how to use them on the [Defining Initializer Functions](../../developers/guides/smart_contracts/define_functions.md#initializer-functions) page. A contract must ensure: diff --git a/docs/docs/aztec/smart_contracts/functions/index.md b/docs/docs/aztec/smart_contracts/functions/index.md index e09e24f0f9cd..ad04679b85b2 100644 --- a/docs/docs/aztec/smart_contracts/functions/index.md +++ b/docs/docs/aztec/smart_contracts/functions/index.md @@ -24,7 +24,7 @@ There are also special oracle functions, which can get data from outside of the ## Learn more about functions - [How function visibility works in Aztec](./visibility.md) -- How to write an [initializer function](../../../developers/guides/smart_contracts/writing_contracts/initializers.md) +- How to write an [initializer function](../../../developers/guides/smart_contracts/define_functions.md#initializer-functions) - [Oracles](../oracles/index.md) and how Aztec smart contracts might use them - [How functions work under the hood](./attributes.md) diff --git a/docs/docs/aztec/smart_contracts/oracles/index.md b/docs/docs/aztec/smart_contracts/oracles/index.md index f1d3a192cd44..4f9998a04ea0 100644 --- a/docs/docs/aztec/smart_contracts/oracles/index.md +++ b/docs/docs/aztec/smart_contracts/oracles/index.md @@ -7,7 +7,7 @@ description: Learn about oracles in Aztec, which provide external data to smart This page goes over what oracles are in Aztec and how they work. -Looking for a hands-on guide? You can learn how to use oracles in a smart contract [here](../../../developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md). +Looking for a hands-on guide? You can learn how to use oracles in a smart contract [here](../../../developers/guides/smart_contracts/advanced/how_to_use_capsules.md). An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. @@ -33,4 +33,4 @@ Oracles introduce **non-determinism** into a circuit, and thus are `unconstraine Find a full list [on GitHub](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/aztec-nr/aztec/src/oracle). -Please note that it is **not** possible to write a custom oracle for your dapp. Oracles are implemented in the PXE, so all users of your dapp would have to use a PXE service with your custom oracle included. If you want to inject some arbitrary data that does not have a dedicated oracle, you can use [capsules](../../../developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md). +Please note that it is **not** possible to write a custom oracle for your dapp. Oracles are implemented in the PXE, so all users of your dapp would have to use a PXE service with your custom oracle included. If you want to inject some arbitrary data that does not have a dedicated oracle, you can use [capsules](../../../developers/guides/smart_contracts/advanced/how_to_use_capsules.md). diff --git a/docs/docs/aztec/writing_efficient_contracts.md b/docs/docs/aztec/writing_efficient_contracts.md index 0e1c58ad6965..f72a2e2397e7 100644 --- a/docs/docs/aztec/writing_efficient_contracts.md +++ b/docs/docs/aztec/writing_efficient_contracts.md @@ -37,6 +37,7 @@ Since proof generation is a significant local burden, being mindful of the gate- An explanation of efficient use of Noir for circuits should be considered for each subsection under [writing efficient Noir](https://noir-lang.org/docs/explainers/explainer-writing-noir#writing-efficient-noir-for-performant-products) to avoid hitting local limits. The general theme is to use language features that favour the underlying primitives and representation of a circuit from code. A couple of examples: + - Since the underlying cryptography uses an equation made of additions and multiplications, these are more efficient (wrt gate count) in Noir than say bit-shifting. - Unconstrained functions by definition do not constrain their operations/output, so do not contribute to gate count. Using them carefully can bring in some savings, but the results must then be constrained so that proofs are meaningful for your application. @@ -50,7 +51,7 @@ When private functions are called, the overhead of a "kernel circuit" is added e #### Profiling using FlameGraph -Measuring the gate count across a private function can be seen at the end of the counter tutorial [here](../developers/tutorials/contract_tutorials/counter_contract#investigate-the-increment-function). Full profiling and flamegraph commands explained [here](../developers/guides/smart_contracts/profiling_transactions). +Measuring the gate count across a private function can be seen at the end of the counter tutorial [here](../developers/tutorials/contract_tutorials/counter_contract#investigate-the-increment-function). Full profiling and flamegraph commands explained [here](../developers/guides/smart_contracts/advanced/profiling_transactions). ### L2 Data costs @@ -63,7 +64,6 @@ That is, what is stored in an L1 contract is simply a hash. For data availability, blobs are utilized since data storage is often cheaper here than in contracts. Like other L2s such costs are factored into the L2 fee mechanisms. These limits can be seen and iterated on when a transaction is simulated/estimated. - ## Examples for private functions (reducing gate count) After the first section about generating a flamegraph for an Aztec function, each section shows an example of different optimisation techniques. @@ -78,14 +78,14 @@ For example, the resulting flamegraph (as an .svg file) of a counter's increment To get a sense of things, here is a table of gate counts for common operations: -Gates | Operation ------ | ---------- -~75 | Hashing 3 fields with Poseidon2 -3500 | Reading a value from a tree (public data tree, note hash tree, nullifier tree) -4000 | Reading a delayed public mutable read -X000 | Calculating sha256 -X000 | Constrained encryption of a log of Y fields -X000 | Constrained encryption and tag a log of Y fields +| Gates | Operation | +| ----- | ------------------------------------------------------------------------------ | +| ~75 | Hashing 3 fields with Poseidon2 | +| 3500 | Reading a value from a tree (public data tree, note hash tree, nullifier tree) | +| 4000 | Reading a delayed public mutable read | +| X000 | Calculating sha256 | +| X000 | Constrained encryption of a log of Y fields | +| X000 | Constrained encryption and tag a log of Y fields | ### Optimization: use arithmetic instead of non-arithmetic operations @@ -115,7 +115,6 @@ In the same vein bitwise `AND`/`OR`, and inequality relational operators (`>`, ` For example, use boolean equality effectively instead of `>=`: - ```rust { #[private] @@ -147,6 +146,7 @@ For example, use boolean equality effectively instead of `>=`: ``` So for a loop of 1000 iterations, 751 gates were saved by: + - Adding an equivalence check and a boolean assignment - Replacing `>=` with a boolean equivalence check @@ -161,9 +161,11 @@ Since private functions are circuits, their size must be known at compile time, See [this example](https://github.com/noir-lang/noir-examples/blob/master/noir_by_example/loops/noir/src/main.nr#L11) for how to use loops when dynamic execution lengths (ie variable number of loops) is not possible. ### Optimization: considered use of `unconstrained` functions + #### Example - calculating square root Consider the following example of an implementation of the `sqrt` function: + ```rust use dep::aztec::macros::aztec; @@ -226,6 +228,7 @@ The two implementations after the contract differ in one being constrained vs un Measuring the two, we find the `sqrt_inefficient` to require around 1500 extra gates compared to `sqrt_efficient`. To see each flamegraph: + - `SERVE=1 aztec flamegraph target/optimisation_example-OptimisationExample.json sqrt_inefficient` - `SERVE=1 aztec flamegraph target/optimisation_example-OptimisationExample.json sqrt_efficient` - (if you make changes to the code, you will need to compile and regenerate the flamegraph, then refresh in your browser to use the latest svg file) @@ -355,5 +358,5 @@ unconstrained fn refactor_array(array: [u32; ARRAY_SIZE]) -> [u32; ARRAY_SIZE] { If a struct has many fields to be read, we can design an extra variable maintained as the hash of all values within it (like a checksum). When it comes to reading, we can now do an unconstrained read (incurring no read requests), and then check the hash of the result against that stored for the struct. This final check is thus only one read request rather than one per variable. :::note Leverage unconstrained functions -When needing to make use of large private operations (eg private execution or many read requests), use of [unconstrained functions](https://noir-lang.org/docs/explainers/explainer-writing-noir#leverage-unconstrained-execution) wisely to reduce the gate count of private functions. +When needing to make use of large private operations (eg private execution or many read requests), use of [unconstrained functions](https://noir-lang.org/docs/explainers/explainer-writing-noir#leverage-unconstrained-execution) wisely to reduce the gate count of private functions. ::: diff --git a/docs/docs/developers/guides/getting_started_on_testnet.md b/docs/docs/developers/guides/getting_started_on_testnet.md index 6ef010077e18..751c18a232e0 100644 --- a/docs/docs/developers/guides/getting_started_on_testnet.md +++ b/docs/docs/developers/guides/getting_started_on_testnet.md @@ -1,6 +1,6 @@ --- title: Setting up for Testnet -sidebar_position: 1 +sidebar_position: 3 tags: [testnet] description: Guide for developers to get started with the Aztec testnet, including account creation and contract deployment. --- @@ -14,6 +14,7 @@ This guide explains the differences between sandbox and testnet, how to migrate Before diving into the setup, it's important to understand the differences between sandbox and testnet: ### Sandbox (Local Development) + - Runs locally on your machine - No proving by default (faster development) - No fees @@ -22,6 +23,7 @@ Before diving into the setup, it's important to understand the differences betwe - Ideal for rapid development and testing ### Testnet (Remote Network) + - Remote environment with network of sequencers - Always has proving enabled (longer transaction times) - Always has fees enabled (need to pay or sponsor fees) @@ -128,7 +130,6 @@ aztec-wallet send mint_to_private \ --args accounts:my-wallet 10 ``` - ## Migrating from Sandbox to Testnet If you have an existing app running on sandbox, here's how to migrate it to testnet: @@ -152,11 +153,13 @@ aztec-wallet create-account -a main --register-only --node-url $NODE_URL You can connect to testnet directly from your app using AztecJS: In the browser: + ```javascript import { createPXEService } from "@aztec/pxe/client/lazy"; ``` In Node.js: + ```javascript import { createPXEService } from "@aztec/pxe/server"; ``` @@ -219,8 +222,8 @@ Testnet transactions take longer than sandbox. Handle timeouts gracefully: try { const receipt = await tx.wait(); } catch (error) { - if (error.message.includes('Timeout awaiting isMined')) { - console.log('Transaction sent but still being mined'); + if (error.message.includes("Timeout awaiting isMined")) { + console.log("Transaction sent but still being mined"); // Check block explorer for status } } @@ -231,8 +234,8 @@ try { Detect which environment your code is running against: ```javascript -const isTestnet = process.env.NODE_URL?.includes('testnet'); -const nodeUrl = process.env.NODE_URL || 'http://localhost:8080'; +const isTestnet = process.env.NODE_URL?.includes("testnet"); +const nodeUrl = process.env.NODE_URL || "http://localhost:8080"; ``` ## Next Steps diff --git a/docs/docs/developers/guides/js_apps/_category_.json b/docs/docs/developers/guides/js_apps/_category_.json index 855a5401c2ea..a5ec36450b73 100644 --- a/docs/docs/developers/guides/js_apps/_category_.json +++ b/docs/docs/developers/guides/js_apps/_category_.json @@ -1,6 +1,6 @@ { - "position": 3, - "collapsible": true, - "collapsed": true, - "label": "How to Use Aztec in JS" + "position": 2, + "collapsible": true, + "collapsed": true, + "label": "How to Use Aztec in JS" } diff --git a/docs/docs/developers/guides/js_apps/authwit.md b/docs/docs/developers/guides/js_apps/authwit.md index ea09694f82c8..1eb4628d23f7 100644 --- a/docs/docs/developers/guides/js_apps/authwit.md +++ b/docs/docs/developers/guides/js_apps/authwit.md @@ -5,7 +5,7 @@ sidebar_position: 5 description: Learn how to use authentication witnesses in your Aztec.js applications for secure, delegated transactions. --- -This page assumes you have authwit set up correctly in your contract. To learn how to do that, [go here](../smart_contracts/writing_contracts/authwit.md). +This page assumes you have authwit set up correctly in your contract. To learn how to do that, [go here](../smart_contracts/authwit.md). For an introduction to authentication witnesses on Aztec, [read this explainer](../../../aztec/concepts/advanced/authwit.md). @@ -78,7 +78,7 @@ Private execution uses oracles and are executed locally by the PXE, so the authw ### Private -This is expected to be used alongside [private authwits in Aztec.nr contract](../smart_contracts/writing_contracts/authwit.md#private-functions). +This is expected to be used alongside [private authwits in Aztec.nr contract](../smart_contracts/authwit.md#private-functions). Create a private authwit like this: @@ -100,7 +100,7 @@ Then add it to the wallet of the authwit receiver (the caller of the function): ### Public -This is expected to be used alongside [public authwits in Aztec.nr contract](../smart_contracts/writing_contracts/authwit.md#public-functions). +This is expected to be used alongside [public authwits in Aztec.nr contract](../smart_contracts/authwit.md#public-functions). Set a public authwit like this: @@ -120,4 +120,4 @@ If you created an arbitrary message, you would replace the first param struct wi ## Further reading - [An explainer of authentication witnesses](../../../aztec/concepts/advanced/authwit.md) -- [Authwits in Aztec.nr](../smart_contracts/writing_contracts/authwit.md) +- [Authwits in Aztec.nr](../smart_contracts/authwit.md) diff --git a/docs/docs/developers/guides/js_apps/test.md b/docs/docs/developers/guides/js_apps/test.md index 5552ae3a5fe8..0909eb9ece27 100644 --- a/docs/docs/developers/guides/js_apps/test.md +++ b/docs/docs/developers/guides/js_apps/test.md @@ -103,7 +103,7 @@ WARN Error processing tx 06dc87c4d64462916ea58426ffcfaf20017880b353c9ec3e0f0ee5f We can check private or public state directly rather than going through view-only methods, as we did in the initial example by calling `token.methods.balance().simulate()`. -To query storage directly, you'll need to know the slot you want to access. This can be checked in the [contract's `Storage` definition](../../reference/smart_contract_reference/storage/index.md) directly for most data types. However, when it comes to mapping types, as in most EVM languages, we'll need to calculate the slot for a given key. To do this, we'll use the [`CheatCodes`](../../reference/environment_reference/cheat_codes.md) utility class: +To query storage directly, you'll need to know the slot you want to access. However, when it comes to mapping types, as in most EVM languages, we'll need to calculate the slot for a given key. To do this, we'll use the [`CheatCodes`](../../reference/environment_reference/cheat_codes.md) utility class: #include_code calc-slot /yarn-project/end-to-end/src/guides/dapp_testing.test.ts typescript diff --git a/docs/docs/developers/guides/local_env/sandbox.md b/docs/docs/developers/guides/local_env/sandbox.md index 1f27e3ff1068..a865dc33dd4c 100644 --- a/docs/docs/developers/guides/local_env/sandbox.md +++ b/docs/docs/developers/guides/local_env/sandbox.md @@ -206,7 +206,7 @@ Note that you do not need to restart the sandbox in order to start sending prove If this is the first time you are sending transactions with proving enabled, it will take a while to download a CRS file (which is several MBs) that is required for proving. :::note -You can also profile your transactions to get gate count, if you don't want to prove your transactions but check how many constraints it is. Follow the [guide here](../../guides/smart_contracts/profiling_transactions.md) +You can also profile your transactions to get gate count, if you don't want to prove your transactions but check how many constraints it is. Follow the [guide here](../../guides/smart_contracts/advanced/profiling_transactions.md) ::: ## Running Multiple PXEs in the Sandbox diff --git a/docs/docs/developers/guides/smart_contracts/_category_.json b/docs/docs/developers/guides/smart_contracts/_category_.json index 5d662dec8cdf..2dace8cefee0 100644 --- a/docs/docs/developers/guides/smart_contracts/_category_.json +++ b/docs/docs/developers/guides/smart_contracts/_category_.json @@ -1,6 +1,6 @@ { - "position": 4, - "collapsible": true, - "collapsed": true, - "label": "Developing Smart Contracts" + "position": 1, + "collapsible": true, + "collapsed": true, + "label": "Developing Smart Contracts" } diff --git a/docs/docs/developers/reference/smart_contract_reference/storage/_category_.json b/docs/docs/developers/guides/smart_contracts/advanced/_category_.json similarity index 50% rename from docs/docs/developers/reference/smart_contract_reference/storage/_category_.json rename to docs/docs/developers/guides/smart_contracts/advanced/_category_.json index 25edf7aa1c2e..0ffccd345bb9 100644 --- a/docs/docs/developers/reference/smart_contract_reference/storage/_category_.json +++ b/docs/docs/developers/guides/smart_contracts/advanced/_category_.json @@ -1,6 +1,6 @@ { - "position": 1, + "position": 11, "collapsible": true, "collapsed": true, - "label": "Storage" + "label": "Advanced Topics" } diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md b/docs/docs/developers/guides/smart_contracts/advanced/common_patterns.md similarity index 90% rename from docs/docs/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md rename to docs/docs/developers/guides/smart_contracts/advanced/common_patterns.md index 97dd0df03a5b..162790616b27 100644 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md +++ b/docs/docs/developers/guides/smart_contracts/advanced/common_patterns.md @@ -1,6 +1,5 @@ --- title: Common Patterns -sidebar_position: 7 description: Explore common design patterns for writing secure and efficient Aztec smart contracts. --- @@ -40,7 +39,7 @@ Note - you could also create a note and send it to the user. The problem is ther ### Reading public storage in private -You can read public storage in private domain by leveraging the private getters of `PublicImmutable` (for values that never change) and `DelayedPublicMutable` (for values that change infrequently, see [delayed public mutable state](../../../../reference/smart_contract_reference/storage/delayed_public_mutable.md) for details) state variables. +You can read public storage in private domain by leveraging the private getters of `PublicImmutable` (for values that never change) and `DelayedPublicMutable` (for values that change infrequently, see [delayed public mutable state](../storage_types.md#delayed-public-mutable) for details) state variables. Values that change frequently (`PublicMutable`) cannot be read in private as for those we need access to the tip of the chain and only a sequencer has access to that (and sequencer executes only public functions). E.g. when using `PublicImmutable` @@ -76,7 +75,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/advanced/storage/partial_notes.md). Partial notes are how public balances are transferred to private [in the NFT contract](../../../../tutorials/contract_tutorials/nft_contract.md). +See [partial notes](../../../../aztec/concepts/advanced/storage/partial_notes.md). Partial notes are how public balances are transferred to private [in the NFT contract](../../../tutorials/contract_tutorials/nft_contract.md). ### Discovering my notes @@ -111,7 +110,7 @@ Hence, it's necessary to add a "randomness" field to your note to prevent such a ### L1 -- L2 interactions -Refer to [Token Portal codealong tutorial on bridging tokens between L1 and L2](../../../../tutorials/js_tutorials/token_bridge.md) and/or [Uniswap smart contract example that shows how to swap on L1 using funds on L2](../../../../tutorials/js_tutorials/uniswap/index.md). Both examples show how to: +Refer to [Token Portal codealong tutorial on bridging tokens between L1 and L2](../../../tutorials/js_tutorials/token_bridge.md) and/or [Uniswap smart contract example that shows how to swap on L1 using funds on L2](../../../tutorials/js_tutorials/uniswap/index.md). Both examples show how to: 1. L1 -> L2 message flow 2. L2 -> L1 message flow @@ -125,7 +124,7 @@ To send a note to someone, they need to have a key which we can encrypt the note There are several patterns here: 1. Give the contract a key and share it amongst all participants. This leaks privacy, as anyone can see all the notes in the contract. -2. `transfer_to_public` funds into the contract - this is used in the [Uniswap smart contract example where a user sends private funds into a Uniswap Portal contract which eventually withdraws to L1 to swap on L1 Uniswap](../../../../tutorials/js_tutorials/uniswap/index.md). This works like Ethereum - to achieve contract composability, you move funds into the public domain. This way the contract doesn't even need keys. +2. `transfer_to_public` funds into the contract - this is used in the [Uniswap smart contract example where a user sends private funds into a Uniswap Portal contract which eventually withdraws to L1 to swap on L1 Uniswap](../../../tutorials/js_tutorials/uniswap/index.md). This works like Ethereum - to achieve contract composability, you move funds into the public domain. This way the contract doesn't even need keys. There are several other designs we are discussing through [in this discourse post](https://discourse.aztec.network/t/how-to-handle-private-escrows-between-two-parties/2440) but they need some changes in the protocol or in our demo contract. If you are interested in this discussion, please participate in the discourse post! @@ -147,7 +146,7 @@ PS: when calling from private to public, `msg_sender` is the contract address wh In the [Prevent the same user flow from happening twice using nullifier](#prevent-the-same-user-flow-from-happening-twice-using-nullifiers), we recommended using nullifiers. But what you put in the nullifier is also as important. -E.g. for a voting contract, if your nullifier simply emits just the `user_address`, then privacy can easily be leaked via a preimage attack as nullifiers are deterministic (have no randomness), especially if there are few users of the contract. So you need some kind of randomness. You can add the user's secret key into the nullifier to add randomness. We call this "nullifier secrets" as explained [here](../../../../../aztec/concepts/accounts/keys.md#nullifier-keys). +E.g. for a voting contract, if your nullifier simply emits just the `user_address`, then privacy can easily be leaked via a preimage attack as nullifiers are deterministic (have no randomness), especially if there are few users of the contract. So you need some kind of randomness. You can add the user's secret key into the nullifier to add randomness. We call this "nullifier secrets" as explained [here](../../../../aztec/concepts/accounts/keys.md#nullifier-keys). Here is an example from the voting contract: diff --git a/docs/docs/developers/guides/smart_contracts/advanced/get_notes.md b/docs/docs/developers/guides/smart_contracts/advanced/get_notes.md new file mode 100644 index 000000000000..9ff65bd557f5 --- /dev/null +++ b/docs/docs/developers/guides/smart_contracts/advanced/get_notes.md @@ -0,0 +1,131 @@ +--- +title: Get Notes +tags: [private-state, smart-contracts, notes] +description: Learn how to get notes from a data oracle in Aztec.nr. +--- + +## Overview + +## `NoteGetterOptions` + +`NoteGetterOptions` encapsulates a set of configurable options for filtering and retrieving a selection of notes from a data oracle. Developers can design instances of `NoteGetterOptions`, to determine how notes should be filtered and returned to the functions of their smart contracts. + +You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr). + +### `selects: BoundedVec, N>` + +`selects` is a collection of filtering criteria, specified by `Select { property_selector: PropertySelector, comparator: u8, value: Field }` structs. It instructs the data oracle to find notes whose serialized field (as specified by the `PropertySelector`) matches the provided `value`, according to the `comparator`. The PropertySelector is in turn specified as having an `index` (nth position of the selected field in the serialized note), an `offset` (byte offset inside the selected serialized field) and `length` (bytes to read of the field from the offset). These values are not expected to be manually computed, but instead specified by passing functions autogenerated from the note definition. + +### `sorts: BoundedVec, N>` + +`sorts` is a set of sorting instructions defined by `Sort { property_selector: PropertySelector, order: u2 }` structs. This directs the data oracle to sort the matching notes based on the value of the specified PropertySelector and in the indicated order. The value of order is **1** for _DESCENDING_ and **2** for _ASCENDING_. + +### `limit: u32` + +When the `limit` is set to a non-zero value, the data oracle will return a maximum of `limit` notes. + +### `offset: u32` + +This setting enables us to skip the first `offset` notes. It's particularly useful for pagination. + +### `preprocessor: fn ([Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], PREPROCESSOR_ARGS) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]` + +Developers have the option to provide a custom preprocessor. +This allows specific logic to be applied to notes that meet the criteria outlined above. +The preprocessor takes the notes returned from the oracle and `preprocessor_args` as its parameters. + +An important distinction from the filter function described below is that preprocessor is applied first and unlike filter it is applied in an unconstrained context. + +### `preprocessor_args: PREPROCESSOR_ARGS` + +`preprocessor_args` provides a means to furnish additional data or context to the custom preprocessor. + +### `filter: fn ([Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], FILTER_ARGS) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]` + +Just like preprocessor just applied in a constrained context (correct execution is proven) and applied after the preprocessor. + +### `filter_args: FILTER_ARGS` + +`filter_args` provides a means to furnish additional data or context to the custom filter. + +### `status: u2` + +`status` allows the caller to retrieve notes that have been nullified, which can be useful to prove historical data. Note that when querying for both active and nullified notes the caller cannot know if each note retrieved has or has not been nullified. + +### Methods + +Several methods are available on `NoteGetterOptions` to construct the options in a more readable manner: + +### `fn new() -> NoteGetterOptions` + +This function initializes a `NoteGetterOptions` that simply returns the maximum number of notes allowed in a call. + +### `fn with_filter(filter, filter_args) -> NoteGetterOptions` + +This function initializes a `NoteGetterOptions` with a [`filter`](#filter-fn-optionnote-max_note_hash_read_requests_per_call-filter_args---optionnote-max_note_hash_read_requests_per_call) and [`filter_args`](#filter_args-filter_args). + +### `.select` + +This method adds a [`Select`](#selects-boundedvecoptionselect-n) criterion to the options. + +### `.sort` + +This method adds a [`Sort`](#sorts-boundedvecoptionsort-n) criterion to the options. + +### `.set_limit` + +This method lets you set a limit for the maximum number of notes to be retrieved. + +### `.set_offset` + +This method sets the offset value, which determines where to start retrieving notes. + +### `.set_status` + +This method sets the status of notes to retrieve (active or nullified). + +### Examples + +#### Example 1 + +The following code snippet creates an instance of `NoteGetterOptions`, which has been configured to find the cards that belong to an account with nullifying key hash equal to `account_npk_m_hash`. The returned cards are sorted by their points in descending order, and the first `offset` cards with the highest points are skipped. + +#include_code state_vars-NoteGetterOptionsSelectSortOffset /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust + +The first value of `.select` and `.sort` indicates the property of the note we're looking for. For this we use helper functions that are autogenerated from the note definition. `CardNote` that has the following fields: + +#include_code state_vars-CardNote /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/types/card_note.nr rust + +`CardNote::properties()` will return a struct with the values to pass for each field, which are related to their indices inside the `CardNote` struct, internal offset and length. + +In the example, `.select(CardNote::properties().npk_m_hash, Comparator.EQ, account_npk_m_hash)` matches notes which have the `npk_m_hash` field set to `account_npk_m_hash`. In this case we're using the equality comparator, but other operations exist in the `Comparator` utility struct. + +`.sort(0, SortOrder.DESC)` sorts the 0th field of `CardNote`, which is `points`, in descending order. + +There can be as many conditions as the number of fields a note type has. The following example finds cards whose fields match the three given values: + +#include_code state_vars-NoteGetterOptionsMultiSelects /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust + +While `selects` lets us find notes with specific values, `filter` lets us find notes in a more dynamic way. The function below picks the cards whose points are at least `min_points`, although this now can be done by using the select function with a GTE comparator: + +#include_code state_vars-OptionFilter /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust + +We can use it as a filter to further reduce the number of the final notes: + +#include_code state_vars-NoteGetterOptionsFilter /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust + +One thing to remember is, `filter` will be applied on the notes after they are picked from the database, so it is more efficient to use select with comparators where possible. Another side effect of this is that it's possible that the actual notes we end up getting are fewer than the limit. + +The limit is `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` by default. But we can set it to any value **smaller** than that: + +#include_code state_vars-NoteGetterOptionsPickOne /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust + +#### Example 2 + +An example of how we can use a Comparator to select notes when calling a Noir contract from aztec.js is below. + +#include_code state_vars-NoteGetterOptionsComparatorExampleTs /yarn-project/end-to-end/src/e2e_note_getter.test.ts typescript + +In this example, we use the above typescript code to invoke a call to our Noir contract below. This Noir contract function takes an input to match with, and a comparator to use when fetching and selecting notes from storage. + +#include_code state_vars-NoteGetterOptionsComparatorExampleNoir /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md b/docs/docs/developers/guides/smart_contracts/advanced/how_to_use_capsules.md similarity index 99% rename from docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md rename to docs/docs/developers/guides/smart_contracts/advanced/how_to_use_capsules.md index 26b9f1ad9e64..20bdac3b25e7 100644 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_use_capsules.md +++ b/docs/docs/developers/guides/smart_contracts/advanced/how_to_use_capsules.md @@ -1,6 +1,5 @@ --- title: Using Capsules -sidebar_position: 5 tags: [functions, oracles] description: Learn how to use capsules to add data to the private execution environment for use in your Aztec smart contracts. --- diff --git a/docs/docs/developers/guides/smart_contracts/profiling_transactions.md b/docs/docs/developers/guides/smart_contracts/advanced/profiling_transactions.md similarity index 94% rename from docs/docs/developers/guides/smart_contracts/profiling_transactions.md rename to docs/docs/developers/guides/smart_contracts/advanced/profiling_transactions.md index 2d4e880cef15..63864e22dd1c 100644 --- a/docs/docs/developers/guides/smart_contracts/profiling_transactions.md +++ b/docs/docs/developers/guides/smart_contracts/advanced/profiling_transactions.md @@ -1,7 +1,7 @@ --- -title: Profiling Transactions -sidebar_position: 3 -tags: [contracts, profiling] +title: Optimize Contracts +sidebar_position: 8 +tags: [contracts, profiling, optimization] description: Learn how to profile your Aztec transactions to optimize performance and gas usage. --- @@ -9,13 +9,13 @@ An Aztec transaction typically consists of a private and a public part. The priv Since proof generation is an expensive operation that needs to be done on the client side, it is important to optimize the private contract logic. It is desirable to keep the gate count of circuits representing the private contract logic as low as possible. -A private transaction can involve multiple function calls. It starts with an account `entrypoint()` which may call several private functions to execute the application logic, which in turn might call other functions. Moreover, every private function call has to go through a round of kernel circuits. Read more about the transaction lifecycle [here](../../../aztec/concepts/transactions.md). +A private transaction can involve multiple function calls. It starts with an account `entrypoint()` which may call several private functions to execute the application logic, which in turn might call other functions. Moreover, every private function call has to go through a round of kernel circuits. Read more about the transaction lifecycle [here](../../../../aztec/concepts/transactions.md). In this guide, we will look at how to profile the private execution of a transaction, allowing you to get the gate count of each private function within the transaction, including the kernel circuits. ## Prerequisites -- `aztec-nargo` installed (go to [Sandbox section](../../reference/environment_reference/sandbox-reference.md) for installation instructions) +- `aztec-nargo` installed (go to [Sandbox section](../../../reference/environment_reference/sandbox-reference.md) for installation instructions) - `aztec-wallet` installed (installed as part of the Sandbox) ## Profiling using aztec-wallet diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/authwit.md b/docs/docs/developers/guides/smart_contracts/authwit.md similarity index 93% rename from docs/docs/developers/guides/smart_contracts/writing_contracts/authwit.md rename to docs/docs/developers/guides/smart_contracts/authwit.md index ff744c827cef..609d3ec45950 100644 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/authwit.md +++ b/docs/docs/developers/guides/smart_contracts/authwit.md @@ -1,22 +1,21 @@ --- -title: Authentication Witness -description: Developer Documentation to use Authentication Witness for authentication actions on Aztec. +title: Permit Contract Actions +description: Developer Documentation to use Authentication Witness for authorizing actions by other contracts. tags: [accounts, authwit] +sidebar_position: 7 --- -This page introduces the authwit library and how you can use it in your Aztec.nr smart contracts. [Skip to the usage](#usage). +This page introduces the authwit library and how you can use it in your Aztec.nr smart contracts. -For a guide on using authwit in Aztec.js, [read this](../../js_apps/authwit.md). +For a conceptual overview of authwits, see [Authentication Witnesses](../../../aztec/concepts/advanced/authwit.md). -## Prerequisite reading - -- [Authwit](../../../../aztec/concepts/advanced/authwit.md) +Read more about how to use authwits in Aztec.js [here](../js_apps/authwit.md). ## Introduction -Authentication Witness (authwit) is a scheme for authentication actions on Aztec, so users can allow third-parties (eg other contracts) to execute an action on their behalf. Authwits can only authorize actions for contracts that your account is calling, they cannot be used to permit other users to take actions on your behalf. +Authentication Witness (authwit) is a scheme for authentication actions on Aztec, so users can allow third-parties (e.g. other contracts) to execute an action on their behalf. Authwits can only authorize actions for contracts that your account is calling, they cannot be used to permit other users to take actions on your behalf. -How it works logically is explained in the [concepts](../../../../aztec/concepts/advanced/authwit.md) but we will do a short recap here. +How it works logically is explained in the [concepts](../../../aztec/concepts/advanced/authwit.md) but we will do a short recap here. An authentication witness is defined for a specific action, such as allowing a Defi protocol to transfer funds on behalf of the user. An action is here something that could be explained as `A is allowed to perform X operation on behalf of B` and we define it as a hash computed as such: @@ -152,7 +151,7 @@ Cool, so we have a function that checks if the current call is authenticated, bu #include_code authwit_transfer_example /yarn-project/end-to-end/src/e2e_token_contract/transfer_in_private.test.ts typescript -Learn more about authwits in Aztec.js by [following this guide](../../js_apps/authwit.md). +Learn more about authwits in Aztec.js by [following this guide](../js_apps/authwit.md). ### Public Functions @@ -166,7 +165,7 @@ With private functions covered, how can we use this in a public function? Well, Authenticating an action in the public domain is slightly different from the private domain, since we are executing a function on the auth registry contract to add the witness flag. As you might recall, this was to ensure that we don't need to call into the account contract from public, which is a potential DOS vector. -In the snippet below, this is done as a separate contract call, but can also be done as part of a batch as mentioned in the [Accounts concepts](../../../../aztec/concepts/advanced/authwit.md#what-about-public). +In the snippet below, this is done as a separate contract call, but can also be done as part of a batch as mentioned in the [Accounts concepts](../../../aztec/concepts/advanced/authwit.md#what-about-public). #include_code authwit_public_transfer_example /yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts typescript diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/call_contracts.md b/docs/docs/developers/guides/smart_contracts/call_contracts.md similarity index 91% rename from docs/docs/developers/guides/smart_contracts/writing_contracts/call_contracts.md rename to docs/docs/developers/guides/smart_contracts/call_contracts.md index 58a8ab387797..edbcc70f5005 100644 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/call_contracts.md +++ b/docs/docs/developers/guides/smart_contracts/call_contracts.md @@ -1,7 +1,7 @@ --- -title: Calling Other Contracts -sidebar_position: 4 -tags: [functions, contracts] +title: Contract Composability +sidebar_position: 5 +tags: [functions, contracts, composability] description: Learn how to call other contracts from your Aztec smart contracts. --- @@ -54,8 +54,8 @@ To call a public function from private, you will need to enqueue it like this: #include_code enqueue_public /noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr rust -Public functions are always executed after private execution. To learn why, read the [concepts overview](../../../../aztec/index.md). +Public functions are always executed after private execution. To learn why, read the [concepts overview](../../../aztec/index.md). #### Other call types -There are other call types, for example to ensure no state changes are made. You can learn more about them in the [call types glossary](../../../../aztec/concepts/call_types.md). +There are other call types, for example to ensure no state changes are made. You can learn more about them in the [call types glossary](../../../aztec/concepts/call_types.md). diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/portals/communicate_with_portal.md b/docs/docs/developers/guides/smart_contracts/cross_chain_communication.md similarity index 96% rename from docs/docs/developers/guides/smart_contracts/writing_contracts/portals/communicate_with_portal.md rename to docs/docs/developers/guides/smart_contracts/cross_chain_communication.md index 86e33a48094e..01ecddd0a515 100644 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/portals/communicate_with_portal.md +++ b/docs/docs/developers/guides/smart_contracts/cross_chain_communication.md @@ -1,10 +1,11 @@ --- -title: Communicating with L1 +title: Cross-Chain Communication tags: [contracts, portals] +sidebar_position: 7 description: Learn how to communicate with L1 contracts through portals in your Aztec smart contracts. --- -Follow the [token bridge tutorial](../../../../../developers/tutorials/js_tutorials/token_bridge.md) for hands-on experience writing and deploying a Portal contract. +Follow the [token bridge tutorial](../../tutorials/js_tutorials/token_bridge.md) for hands-on experience writing and deploying a Portal contract. ## Passing data to the rollup @@ -42,7 +43,7 @@ Note that while the `secret` and the `content` are both hashed, they are actuall ### Token bridge example -Computing the `content` must currently be done manually, as we are still adding a number of bytes utilities. A good example exists within the [Token bridge example (codealong tutorial)](../../../../../developers/tutorials/js_tutorials/token_bridge.md). +Computing the `content` must currently be done manually, as we are still adding a number of bytes utilities. A good example exists within the [Token bridge example (codealong tutorial)](../../tutorials/js_tutorials/token_bridge.md). #include_code claim_public /noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr rust @@ -141,7 +142,7 @@ bytes memory message abi.encodeWithSignature( Handling error when moving cross chain can quickly get tricky. Since the L1 and L2 calls are async and independent of each other, the L1 part of a deposit might execute just fine, with the L2 part failing. If this is not handled well, the funds may be lost forever! Developers should consider ways their application can fail cross chain, and handle all cases explicitly. -First, entries in the outboxes **SHOULD** only be consumed if the execution is successful. For an L2 -> L1 call, the L1 execution can revert the transaction completely if anything fails. As the tx is atomic, the failure also reverts consumption. +First, entries in the outboxes **SHOULD** only be consumed if the execution is successful. For an L2 to L1 call, the L1 execution can revert the transaction completely if anything fails. As the tx is atomic, the failure also reverts consumption. If it is possible to enter a state where the second part of the execution fails forever, the application builder should consider including additional failure mechanisms (for token withdraws this could be depositing them again etc). @@ -181,6 +182,10 @@ Designated callers are enforced at the contract level for contracts that are not ## Examples of portals -- Token bridge (Portal contract built for L1 -> L2, i.e., a non-native L2 asset) +- Token bridge (Portal contract built for L1 to L2, i.e., a non-native L2 asset) - [Portal contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/l1-contracts/test/portals/TokenPortal.sol) - [Aztec contract (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/app/token_bridge_contract/src/main.nr) + +## Further reading + +Follow the [token bridge tutorial](../../tutorials/js_tutorials/token_bridge.md) for hands-on experience writing and deploying a Portal contract. diff --git a/docs/docs/developers/guides/smart_contracts/define_functions.md b/docs/docs/developers/guides/smart_contracts/define_functions.md new file mode 100644 index 000000000000..b903b4ae6c48 --- /dev/null +++ b/docs/docs/developers/guides/smart_contracts/define_functions.md @@ -0,0 +1,101 @@ +--- +title: Define Functions +sidebar_position: 4 +tags: [functions, smart-contracts] +description: Learn how to define functions in your Aztec smart contracts. +--- + +There are several types of functions in Aztec contracts that correspond the the different execution environments in which they run. These include: + +- private functions +- public functions +- utility functions +- view functions +- internal functions +- initializer functions +- contract library methods + +## Private Functions + +Private functions execute client-side on user devices to maintain private of user inputs and execution. Specify a private function in your contract using the `#[private]` function annotation. + +#include_code withdraw noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr rust + +## Public Functions + +A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. + +Read more about the concept of public functions [here](../../../aztec/smart_contracts/functions/attributes.md#public-functions). + +Declare a public function in your contract using the `#[public]` function annotation. + +#include_code mint noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr rust + +## Utility Functions + +Contract functions marked with `#[utility]` are used to perform state queries from an off-chain client (from both private and public state!) or to modify local contract-related PXE state (e.g. when processing logs in Aztec.nr), and are never included in any transaction. No guarantees are made on the correctness of the result since the entire execution is unconstrained and heavily reliant on [oracle calls](https://noir-lang.org/docs/explainers/explainer-oracle). Read more about the concept of utility functions [here](../../../aztec/smart_contracts/functions/attributes.md#utility-functions). + +#include_code get_private_nfts noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr rust + +## View Functions + +The #[view] attribute can be applied to a #[private] or a #[public] function and it guarantees that the function cannot modify any contract state (just like view functions in Solidity). This allows you to read private or public state by calling the function from another contract. + +For examples, to get the admin address from the NFT contract, you can use the `get_admin` function: + +#include_code admin noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr rust + +## Internal Functions + +Internal functions are functions that are only callable within the same contract. They are not visible to other contracts and cannot be called from outside the contract. + +Mark an internal function with the `#[internal]` attribute. + +#include_code add_to_tally_public noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr rust + +## Initializer Functions + +Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. + +#### Annotate with `#[initializer]` + +Define your initializer like so: + +```rust +#[initializer] +fn constructor(){ + // function logic here +} +``` + +Aztec supports both public and private initializers. Use the appropriate macro, for example for a private initializer: + +```rust +#[private] +#[initializer] +fn constructor(){ + // function logic here +} +``` + +Initializers are commonly used to set an admin, such as this example: + +#include_code constructor /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +Here, the initializer is writing to storage. It can also call another function. Learn more about calling functions from functions [here](./call_contracts.md). + +### Multiple initializers + +You can set multiple functions as an initializer function simply by annotating each of them with `#[initializer]`. You can then decide which one to call when you are deploying the contract. + +Calling any one of the functions annotated with `#[initializer]` will mark the contract as initialized. + +To see an initializer in action, follow the [Counter codealong tutorial](../../tutorials/contract_tutorials/counter_contract.md). + +## Contract Library Methods + +Contract library methods are functions that are used to implement the logic of a contract and reduce code duplication. When called by another function, they are inlined into the calling function. They are not visible to the outside world and are only callable within the same contract. + +For example, the `subtract_balance` function in the simple token contract: + +#include_code subtract_balance noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr rust diff --git a/docs/docs/developers/guides/smart_contracts/how_to_compile_contract.md b/docs/docs/developers/guides/smart_contracts/how_to_compile_contract.md index 40d66baba199..3293896465ba 100644 --- a/docs/docs/developers/guides/smart_contracts/how_to_compile_contract.md +++ b/docs/docs/developers/guides/smart_contracts/how_to_compile_contract.md @@ -1,7 +1,7 @@ --- -title: Compiling a Contract -sidebar_position: 1 +title: Compile Contracts tags: [contracts] +sidebar_position: 9 description: Learn how to compile your Noir smart contracts for deployment on Aztec. --- @@ -50,7 +50,7 @@ The `aztec-postprocess-contract` command will process all contract artifacts it ### Aztec.nr interfaces -An Aztec.nr contract can [call a function](./writing_contracts/call_contracts.md) in another contract via `context.call_private_function` or `context.call_public_function`. However, this requires manually assembling the function selector and manually serializing the arguments, which is not type-safe. +An Aztec.nr contract can [call a function](./call_contracts.md) in another contract via `context.call_private_function` or `context.call_public_function`. However, this requires manually assembling the function selector and manually serializing the arguments, which is not type-safe. To make this easier, the compiler automatically generates interface structs that expose a convenience method for each function listed in a given contract artifact. These structs are intended to be used from another contract project that calls into the current one. diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_emit_event.md b/docs/docs/developers/guides/smart_contracts/how_to_emit_event.md similarity index 98% rename from docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_emit_event.md rename to docs/docs/developers/guides/smart_contracts/how_to_emit_event.md index 899d2be849bb..0e22ba9b9ec9 100644 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_emit_event.md +++ b/docs/docs/developers/guides/smart_contracts/how_to_emit_event.md @@ -1,7 +1,7 @@ --- title: Emitting Events -sidebar_position: 4 -tags: [contracts] +tags: [contracts, events] +sidebar_position: 6 description: Learn how to emit events from your Aztec smart contracts for off-chain applications to consume. --- diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_prove_history.md b/docs/docs/developers/guides/smart_contracts/how_to_prove_history.md similarity index 98% rename from docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_prove_history.md rename to docs/docs/developers/guides/smart_contracts/how_to_prove_history.md index 7f6631da73d8..24c0ee279ad4 100644 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/how_to_prove_history.md +++ b/docs/docs/developers/guides/smart_contracts/how_to_prove_history.md @@ -1,6 +1,6 @@ --- -title: Using the Archive Tree -sidebar_position: 4 +title: Prove Historic State +sidebar_position: 8 tags: [contracts] description: Learn how to prove historical state transitions in your Aztec smart contracts. --- diff --git a/docs/docs/developers/guides/smart_contracts/index.md b/docs/docs/developers/guides/smart_contracts/index.md index 81a99825421b..3628e7145a05 100644 --- a/docs/docs/developers/guides/smart_contracts/index.md +++ b/docs/docs/developers/guides/smart_contracts/index.md @@ -1,6 +1,6 @@ --- -title: Aztec.nr -tags: [aztec.nr] +title: Developing Smart Contracts +tags: [aztec.nr, smart contracts] description: Comprehensive guide to writing smart contracts for the Aztec network using Noir. --- @@ -27,13 +27,12 @@ help you write Noir programs to deploy on the Aztec network. aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/aztec" } ``` -2. [Write your contracts](./writing_contracts/index.mdx). -3. [Profile](./profiling_transactions.md) the private functions in your contract to get +2. [Profile](./advanced/profiling_transactions.md) the private functions in your contract to get a sense of how long generating client side proofs will take -4. Write unit tests [directly in Noir](testing.md) and end-to-end +3. Write unit tests [directly in Noir](testing.md) and end-to-end tests [with TypeScript](../js_apps/test.md) -5. [Compile](how_to_compile_contract.md) your contract -6. [Deploy](../js_apps/deploy_contract.md) your contract with Aztec.js +4. [Compile](how_to_compile_contract.md) your contract +5. [Deploy](../js_apps/deploy_contract.md) your contract with Aztec.js ## Section Contents diff --git a/docs/docs/developers/guides/smart_contracts/note_types.md b/docs/docs/developers/guides/smart_contracts/note_types.md new file mode 100644 index 000000000000..1494cd09af59 --- /dev/null +++ b/docs/docs/developers/guides/smart_contracts/note_types.md @@ -0,0 +1,140 @@ +--- +title: Note Types +tags: [contracts, notes] +sidebar_position: 3 +keywords: [implementing note, note] +description: Learn about note types and how to implement custom note types in your Aztec smart contracts. +--- + +Notes are the fundamental data structure in Aztec when working with private state. Using Aztec.nr, developers can define note types which allow flexibility in how notes are stored and nullified. + +You may want to create your own note type if you need to use a specific type of private data or struct that is not already implemented in Aztec.nr, or if you want to experiment with custom note hashing and nullifier schemes. For custom hashing and nullifier schemes, use the `#[custom_note]` macro instead of `#[note]`, as it does not automatically derive the `NoteHash` trait. + +For example, if you are developing a card game, you may want to store multiple pieces of data in each card. Rather than storing each piece of data in its own note, you can define a card note type that contains all the data, and then nullify (or exchange ownership of) the card when it has been used. + +If you want to work with values, addresses or integers, you can check out [ValueNote](#valuenote), or [AddressNote](#addressnote). + +## Standard Note Type + +A note type can be defined with the macro `#[note]` used on a struct: + +#include_code state_vars-CardNote noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/types/card_note.nr rust + + + +In this example, we are implementing a card note that holds a number of `points` as `u8`. + +`randomness` is not enforced by the protocol and should be implemented by the application developer. If you do not include `randomness`, and the note preimage can be guessed by an attacker, it makes the note vulnerable to preimage attacks. + +`owner` is used when nullifying the note to obtain a nullifier secret key. +It ensures that when a note is spent, only the owner can spend it and the note sender cannot figure out that the note has been spent! +Providing the `owner` with improved privacy. + +Why is it delivering privacy from sender? + +Because a sender cannot derive a note nullifier. +We could derive the nullifier based solely on the note itself (for example, by computing `hash([note.points, note.owner, note.randomness], NULLIFIER_SEPARATOR)`). +This would work since the nullifier would be unique and only the note recipient could spend it (as contract logic typically only allows the note owner to obtain a note, e.g. from a `Map<...>`). +However, if we did this, the sender could also derive the nullifier off-chain and monitor the nullifier tree for its inclusion, allowing them to determine when a note has been spent. +This would leak privacy. + + +## Examples + +Address notes hold one main property of the type `AztecAddress`. It also holds `owner` and `randomness`, similar to other note types. + +### AddressNote + +This is the AddressNote: + +#include_code address_note_def noir-projects/aztec-nr/address-note/src/address_note.nr rust + +#### Importing AddressNote + +##### In Nargo.toml + +```toml +address_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/address-note" } +``` + +##### In your contract + +#include_code addressnote_import noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr rust + +#### Working with AddressNote + +##### Creating a new note + +Creating a new `AddressNote` takes the following args: + +- `address` (`AztecAddress`): the address to store in the AddressNote +- `owner` (`AztecAddress`): owner is the party whose nullifying key can be used to spend the note + +#include_code addressnote_new noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr rust + +In this example, `owner` is the `address` and the `npk_m_hash` of the donor was computed earlier. + +### ValueNote + +This is the ValueNote struct: + +#include_code value-note-def noir-projects/aztec-nr/value-note/src/value_note.nr rust + +#### Importing ValueNote + +##### In Nargo.toml + +```toml +value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/value-note" } +``` + +##### In your contract + +#include_code import_valuenote noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr rust + +#### Working with ValueNote + +##### Creating a new note + +Creating a new `ValueNote` takes the following args: + +- `value` (`Field`): the value of the ValueNote +- `owner` (`AztecAddress`): owner is the party whose nullifying key can be used to spend the note + +#include_code valuenote_new noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr rust + +##### Getting a balance + +A user may have multiple notes in a set that all refer to the same content (e.g. a set of notes representing a single token balance). By using the `ValueNote` type to represent token balances, you do not have to manually add each of these notes and can instead use a helper function `get_balance()`. + +It takes one argument - the set of notes. + +#include_code get_balance noir-projects/noir-contracts/contracts/test/stateful_test_contract/src/main.nr rust + +This can only be used in an unconstrained function. + +##### Incrementing and decrementing + +Both `increment` and `decrement` functions take the same args: + +#include_code increment_args noir-projects/aztec-nr/value-note/src/utils.nr rust + +Note that this will create a new note in the set of notes passed as the first argument. +For example: +#include_code increment_valuenote noir-projects/noir-contracts/contracts/test/benchmarking_contract/src/main.nr rust + +The `decrement` function works similarly except the `amount` is the number that the value will be decremented by, and it will fail if the sum of the selected notes is less than the amount. + +### Custom Note Type + +Using the `#[custom_note]` macro allows you to define your own note hash and nullifier schemes for your notes, rather than using the default poseidon2 hash of the note to generate the note hash or using the note owners nullifier key to generate a nullifier. + +The TransparentNote in an example token contract demonstrates how you can generate a custom note hash and nullifiers. + +#include_code transparent_note_impl noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/types/transparent_note.nr rust + +## Further reading + +- [What is `#[note]` actually doing? + functions such as serialize() and deserialize()](../../../aztec/smart_contracts/functions/attributes.md#implementing-notes) +- [Macros reference](../../../developers/reference/smart_contract_reference/macros.md) +- [Keys, including npk_m_hash (nullifier public key master)](../../../aztec/concepts/accounts/keys.md) diff --git a/docs/docs/developers/guides/smart_contracts/storage.md b/docs/docs/developers/guides/smart_contracts/storage.md new file mode 100644 index 000000000000..7597279e46bd --- /dev/null +++ b/docs/docs/developers/guides/smart_contracts/storage.md @@ -0,0 +1,28 @@ +--- +title: Declare Contract Storage +sidebar_position: 1 +tags: [contracts, storage] +description: Comprehensive guide to storage management in your Aztec smart contracts. +--- + +On this page, you will learn how to define storage in your smart contract. + +To learn more about how storage works in Aztec, read [the concepts](../../../aztec/concepts/storage/index.md). + +Declare storage for your contract by defining a struct and annotating it as `#[storage]`. This will be made available to you through the reserved `storage` keyword within your contract functions. + +You can declare public and private state variables in your storage struct. + +## Example + +For example, the following is the storage struct for the NFT contract: + +#include_code storage_struct /noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr rust + +:::info + +The `Context` parameter is injected into storage and contract functions. It provides information about the current execution mode (e.g. private or public). + +::: + +Read more about the data types available in the [Storage Types](./storage_types.md) page. diff --git a/docs/docs/developers/guides/smart_contracts/storage_types.md b/docs/docs/developers/guides/smart_contracts/storage_types.md new file mode 100644 index 000000000000..830d199700f8 --- /dev/null +++ b/docs/docs/developers/guides/smart_contracts/storage_types.md @@ -0,0 +1,428 @@ +--- +title: Storage Types +sidebar_position: 2 +tags: [data-types, smart-contracts] +description: Learn about how data is stored in Aztec contracts. +--- + +Interacting with the protocol in a reliable, private manner is simplified by using the storage types described below, provided by the Aztec.nr library for writing contracts. + +## Map + +A `map` is a state variable that "maps" a key to a value. It can be used with private or public storage variables. + +:::info +In Aztec.nr, keys are always `Field`s, or types that can be serialized as Fields, and values can be any type - even other maps. `Field`s are finite field elements, but you can think of them as integers. +::: + +It includes a `Context` to specify the private or public domain, a `storage_slot` to specify where in storage the map is stored, and a `start_var_constructor` which tells the map how it should operate on the underlying type. This includes how to serialize and deserialize the type, as well as how commitments and nullifiers are computed for the type if it's private. + +You can view the implementation in the Aztec.nr library [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/aztec-nr). + +You can have multiple `map`s in your contract that each have a different underlying note type, due to note type IDs. These are identifiers for each note type that are unique within a contract. + +#### As private storage + +When declaring a mapping in private storage, we have to specify which type of Note to use. In the example below, we are specifying that we want to use the `PrivateMutable` note which will hold `ValueNote` types. + +In the Storage struct: + +#include_code private_map /noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr rust + +#### Public Example + +When declaring a public mapping in Storage, we have to specify that the type is public by declaring it as `PublicState` instead of specifying a note type like with private storage above. + +#include_code storage_minters /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +### Accessing a value + +When dealing with a Map, we can access the value at a given key using the `::at` method. This takes the key as an argument and returns the value at that key. + +This function behaves similarly for both private and public maps. An example could be if we have a map with `minters`, which is mapping addresses to a flag for whether they are allowed to mint tokens or not. + +#include_code read_minter /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +Above, we are specifying that we want to get the storage in the Map `at` the `msg_sender()`, read the value stored and check that `msg_sender()` is indeed a minter. Doing a similar operation in Solidity code would look like: + +```solidity +require(minters[msg.sender], "caller is not minter"); +``` + +## Private Data Types + +To simplify the experience of writing private state, Aztec.nr provides three different types of private state variable: + +- [PrivateMutable\](#privatemutablenotetype) +- [PrivateImmutable\](#privateimmutablenotetype) +- [PrivateSet\](#privatesetnotetype) + +These three structs abstract-away many of Aztec's protocol complexities, by providing intuitive methods to modify notes in the utxo tree in a privacy-preserving way. + +Unlike public state variables, which can be arbitrary types, private state variables operate on `NoteType`. See [Note Types](./note_types.md) for more information about how to define your own note types. + +Notes are the fundamental elements of private state. + +### PrivateMutable + +PrivateMutable is a private state variable that is unique in a way. When a PrivateMutable is initialized, a note is created to represent its value. And the way to update the value is to destroy the current note, and create a new one with the updated value. + +Like for public state, we define the struct to have context and a storage slot. You can view the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr). + +An example of `PrivateMutable` usage in the account contracts is keeping track of public keys. The `PrivateMutable` is added to the `Storage` struct as follows: + +#include_code storage-private-mutable-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### `new` + +As part of the initialization of the `Storage` struct, the `PrivateMutable` is created as follows at the specified storage slot. + +#include_code start_vars_private_mutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### `initialize` + +As mentioned, the PrivateMutable is initialized to create the first note and value. + +When this function is called, a nullifier of the storage slot is created, preventing this PrivateMutable from being initialized again. + +:::danger Privacy-Leak +Beware that because this nullifier is created only from the storage slot without randomness it leaks privacy. This means that it is possible for an external observer to determine when the note is nullified. + +For example, if the storage slot depends on the an address then it is possible to link the nullifier to the address. If the PrivateMutable is part of a `map` with an `AztecAddress` as the key then the nullifier will be linked to the address. +::: + +Unlike public states, which have a default initial value of `0` (or many zeros, in the case of a struct, array or map), a private state (of type `PrivateMutable`, `PrivateImmutable` or `PrivateSet`) does not have a default initial value. The `initialize` method (or `insert`, in the case of a `PrivateSet`) must be called. + +:::info +Extend on what happens if you try to use non-initialized state. +::: + +#### `is_initialized` + +An unconstrained method to check whether the PrivateMutable has been initialized or not. It takes an optional owner and returns a boolean. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr). + +#include_code private_mutable_is_initialized /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### `replace` + +To update the value of a `PrivateMutable`, we can use the `replace` method. The method takes a new note as input and replaces the current note with the new one. It emits a nullifier for the old value, and inserts the new note into the data tree. + +An example of this is seen in a example card game, where we create a new note (a `CardNote`) containing some new data, and replace the current note with it: + +#include_code state_vars-PrivateMutableReplace /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +:::info + +Calling `emit(encode_and_encrypt_note())` on the `replace` method will encrypt the new note and post it to the data availability layer so that the note information is retrievable by the recipient. + +::: + +If two people are trying to modify the PrivateMutable at the same time, only one will succeed as we don't allow duplicate nullifiers! Developers should put in place appropriate access controls to avoid race conditions (unless a race is intended!). + +#### `get_note` + +This function allows us to get the note of a PrivateMutable, essentially reading the value. + +#include_code state_vars-PrivateMutableGet /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### Nullifying Note reads + +To ensure that a user's private execution always uses the latest value of a PrivateMutable, the `get_note` function will nullify the note that it is reading. This means that if two people are trying to use this function with the same note, only one will succeed (no duplicate nullifiers allowed). + +This also makes read operations indistinguishable from write operations and allows the sequencer to verifying correct execution without learning anything about the value of the note. + +#### `view_note` + +Functionally similar to [`get_note`](#get_note), but executed in unconstrained functions and can be used by the wallet to fetch notes for use by front-ends etc. + +### PrivateImmutable + +`PrivateImmutable` (formerly known as `ImmutableSingleton`) represents a unique private state variable that, as the name suggests, is immutable. Once initialized, its value cannot be altered. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr). + +#### `new` + +As part of the initialization of the `Storage` struct, the `PrivateMutable` is created as follows, here at storage slot 1. + +#include_code storage-private-immutable-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### `initialize` + +When this function is invoked, it creates a nullifier for the storage slot, ensuring that the PrivateImmutable cannot be initialized again. + +:::danger Privacy-Leak +Beware that because this nullifier is created only from the storage slot without randomness it leaks privacy. This means that it is possible for an external observer to determine when the note is nullified. + +For example, if the storage slot depends on the an address then it is possible to link the nullifier to the address. If the PrivateImmutable is part of a `map` with an `AztecAddress` as the key then the nullifier will be linked to the address. +::: + +Set the value of an PrivateImmutable by calling the `initialize` method: + +#include_code initialize-private-mutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +:::info + +Calling `emit(encode_and_encrypt_note())` on `initialize` will encrypt the new note and post it to the data availability layer so that the note information is retrievable by the recipient. + +::: + +Once initialized, an PrivateImmutable's value remains unchangeable. This method can only be called once. + +#### `is_initialized` + +An unconstrained method to check if the PrivateImmutable has been initialized. Takes an optional owner and returns a boolean. You can find the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr). + +#### `get_note` + +Similar to the `PrivateMutable`, we can use the `get_note` method to read the value of an PrivateImmutable. + +Use this method to retrieve the value of an initialized PrivateImmutable. + +#include_code get_note-private-immutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +Unlike a `PrivateMutable`, the `get_note` function for an PrivateImmutable doesn't nullify the current note in the background. This means that multiple accounts can concurrently call this function to read the value. + +This function will throw if the `PrivateImmutable` hasn't been initialized. + +#### `view_note` + +Functionally similar to `get_note`, but executed unconstrained and can be used by the wallet to fetch notes for use by front-ends etc. + +### PrivateSet + +`PrivateSet` is used for managing a collection of notes. All notes in a `PrivateSet` are of the same `NoteType`. But whether these notes all belong to one entity, or are accessible and editable by different entities, is up to the developer. + +For example, adding a mapping of private NFTs to storage, indexed by `AztecAddress`: + +#include_code private_map /noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr rust + +#### `insert` + +Allows us to modify the storage by inserting a note into the `PrivateSet`. + +A hash of the note will be generated, and inserted into the note hash tree, allowing us to later use in contract interactions. Recall that the content of the note should be shared with the owner to allow them to use it, as mentioned this can be done via an encrypted log or offchain via web2, or completely offline. + +#include_code insert /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust + +:::info + +Calling `emit(encode_and_encrypt_note())` on `insert` will encrypt the new note and post it to the data availability layer so that the note information is retrievable by the recipient. + +::: + +#### `insert_from_public` + +The `insert_from_public` allow public function to insert notes into private storage. This is very useful when we want to support private function calls that have been initiated in public. + +The usage is similar to using the `insert` method with the difference that this one is called in public functions. + +#### `pop_notes` + +This function pops (gets, removes and returns) the notes the account has access to based on the provided filter. + +The kernel circuits are constrained to a maximum number of notes this function can return at a time. Check [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr) and look for `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` for the up-to-date number. + +Because of this limit, we should always consider using the second argument `NoteGetterOptions` to limit the number of notes we need to read and constrain in our programs. This is quite important as every extra call increases the time used to prove the program and we don't want to spend more time than necessary. + +An example of such options is using the [filter_notes_min_sum (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/value-note/src/filter.nr) to get "enough" notes to cover a given value. Essentially, this function will return just enough notes to cover the amount specified such that we don't need to read all our notes. For users with a lot of notes, this becomes increasingly important. + +#include_code pop_notes /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust + +#### `get_notes` + +This function has the same behavior as `pop_notes` above but it does not delete the notes. + +#### `remove` + +Will remove a note from the `PrivateSet` if it previously has been read from storage, e.g. you have fetched it through a `get_notes` call. This is useful when you want to remove a note that you have previously read from storage and do not have to read it again. + +Note that if you obtained the note you are about to remove via `get_notes` it's much better to use `pop_notes` as `pop_notes` results in significantly fewer constraints since it doesn't need to check that the note has been previously read, as it reads and deletes at once. + +#### `view_notes` + +Functionally similar to [`get_notes`](#get_notes), but executed unconstrained and can be used by the wallet to fetch notes for use by front-ends etc. + +#include_code view_notes /noir-projects/aztec-nr/value-note/src/balance_utils.nr rust + +There's also a limit on the maximum number of notes that can be returned in one go. To find the current limit, refer to [this file (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/constants.nr) and look for `MAX_NOTES_PER_PAGE`. + +The key distinction is that this method is unconstrained. It does not perform a check to verify if the notes actually exist, which is something the [`get_notes`](#get_notes) method does under the hood. Therefore, it should only be used in an unconstrained contract function. + +This function requires a `NoteViewerOptions`. The `NoteViewerOptions` is essentially similar to the [`NoteGetterOptions`](#notegetteroptions), except that it doesn't take a custom filter. + +## Public Data Types + +### PublicMutable + +The `PublicMutable` struct is generic over the variable type `T`. + +The struct contains a `storage_slot` which, similar to Ethereum, is used to figure out _where_ in storage the variable is located. + +For a version of `PublicMutable` that can also be read in private, head to [`DelayedPublicMutable`](#delayed-public-mutable). + +:::info +An example using a larger struct can be found in the [lending example](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts/app/lending_contract)'s use of an [`Asset`](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/noir-contracts/contracts/app/lending_contract/src/asset.nr). +::: + +##### Single value example + +To add `admin` public state variable into our storage struct, we can define it as: + +#include_code storage-leader-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### Mapping example + +To add a group of `minters` that are able to mint assets in our contract, and we want them in public storage: + +#include_code storage-minters-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### `read` + +On the `PublicMutable` structs we have a `read` method to read the value at the location in storage. + +##### Reading from our `admin` example + +For our `admin` example from earlier, this could be used as follows to check that the stored value matches the `msg_sender()`. + +#include_code read_admin /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +##### Reading from our `minters` example + +As we saw in the Map earlier, a very similar operation can be done to perform a lookup in a map. + +#include_code read_minter /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +#### `write` + +We have a `write` method on the `PublicMutable` struct that takes the value to write as an input and saves this in storage. It uses the serialization method to serialize the value which inserts (possibly multiple) values into storage. + +##### Writing to our `admin` example + +#include_code write_admin /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +##### Writing to our `minters` example + +#include_code write_minter /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +### PublicImmutable + +`PublicImmutable` is a type that is initialized from public once, typically during a contract deployment, but which can later be read from public, private and utility execution contexts. This state variable is useful for stuff that you would usually have in `immutable` values in Solidity, e.g. this can be the name of a token or its number of decimals. + +Just like the `PublicMutable` it is generic over the variable type `T`. The type `MUST` implement the `Serialize` and `Deserialize` traits. + +#include_code storage-public-immutable-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +You can find the details of `PublicImmutable` in the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr). + +#### `new` + +Is done exactly like the `PublicMutable` struct, but with the `PublicImmutable` struct. + +#include_code storage-public-immutable-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### `initialize` + +This function sets the immutable value. It can only be called once. + +#include_code initialize_decimals /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust + +:::warning +A `PublicImmutable`'s storage **must** only be set once via `initialize`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. +::: + +#include_code initialize_public_immutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +#### `read` + +Returns the stored immutable value. This function is available in public, private and utility contexts. + +#include_code read_public_immutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust + +## Delayed Public Mutable + +A typical use case for "Delayed Public Mutable" state is some kind of system configuration, such as a protocol fee or access control permissions. These values are public (known by everyone) and mutable. Reading them in private however is tricky: private execution is always asynchronous and performed over _historical_ state, and hence one cannot easily prove that a given public value is current. + +A naive way to solve this is to enqueue a public call that will assert the current public value, but this leaks _which_ public value is being read, severely reducing privacy. Even if the value itself is already public, the fact that we're using it because we're interacting with some related contract is not. For example, we may leak that we're interacting with a certain DeFi protocol by reading its fee. + +An alternative approach is to create notes in public that are then nullified in private, but this introduces contention: only a single user may use the note and therefore read the state, since nullifying it will prevent all others from doing the same. In some schemes there's only one account that will read the state anyway, but this is not the general case. + +Delayed Public Mutable state works around this by introducing **delays**: while public values are mutable, they cannot change _immediately_. Instead, a value change must be scheduled ahead of time, and some minimum amount of time must pass between the scheduling and the new value taking effect. This means that we can privately prove that a historical public value cannot possibly change before some point in the future (due to the minimum delay), and therefore that our transaction will be valid **as long as it gets included before this future time**. + +This results in the following key properties of `DelayedPublicMutable` state: + +- public values can only be changed after a certain delay has passed, never immediately +- the scheduling of value changes is itself public, including both the new value and the time at which the change will take effect +- transactions that read `DelayedPublicMutable` state become invalid after some time if not included in a block + +### Privacy Considerations + +While `DelayedPublicMutable` state variables are much less leaky than the assertion in public approach, they do reveal some information to external observers by setting the `include_by_timestamp` property of the transaction request. The impact of this can be mitigated with proper selection of the delay value and schedule times. + +### Choosing Delays + +The `include_by_timestamp` transaction property will be set to a value close to the current timestamp plus the duration of the delay in seconds. The exact value depends on the historical block over which the private proof is constructed. For example, if current timestamp is `X` and a `DelayedPublicMutable` state variable has a delay of 3000 seconds, then transactions that read this value privately will set `include_by_timestamp` to a value close to 'X + 3000' (clients building proofs on older state will select a lower `include_by_timestamp`). This implicitly leaks the duration of the delay. + +Applications using similar delays will therefore be part of the same privacy set. It is expected for social coordination to result in small set of predetermined delays that developers choose from depending on their needs, as an example a viable set might be: 12 hours (for time-sensitive operations, such as emergency mechanisms), 5 days (for middle-of-the-road operations) and 2 weeks (for operations that require lengthy public scrutiny). These delays can be changed during the contract lifetime as the application's needs evolve. + +Additionally, users might choose to coordinate and constrain their transactions to set `include_by_timestamp` to a value lower than would be strictly needed by the applications they interact with (if any!) using some common delay, and by doing so prevent privacy leakage. + +### Choosing Epochs + +If a value change is scheduled in the near future, then transactions that access this `DelayedPublicMutable` state will be forced to set a lower `include_by_timestamp` right before the value change. For example, if the current timestamp is 'x' and a `DelayedPublicMutable` state variable with a delay of 3000 seconds has a value change scheduled for timestamp 'x + 50', then transactions that read this value privately will set `include_by_timestamp` to 'x + 50 - 1'. Since the timestamps at which `DelayedPublicMutable` state values change are public, it might be deduced that transactions with an `include_by_timestamp` value close to the current timestamp are reading some state variable with a changed scheduled at `include_by_timestamp + 1`. + +Applications that schedule value changes at the same time will therefore be part of the same privacy set. It is expected for social coordination to result in ways to achieve this, e.g. by scheduling value changes so that they land on timestamps that are multiples of some value - we call these epochs. + +There is a tradeoff between frequent and infrequent epochs: frequent epochs means more of them, and therefore fewer updates on each, shrinking the privacy set. But infrequent epochs result in the effective delay of value changes being potentially larger than desired - though an application can always choose to do an out-of-epoch update if needed. + +Note that wallets can also warn users that a value change will soon take place and that sending a transaction at that time might result in reduced privacy, allowing them to choose to wait until after the epoch. + +### Network Cooperation + +Even though only transactions that interact with `DelayedPublicMutable` state _need_ to set the `include_by_timestamp` property, there is no reason why transactions that do not wouldn't also set this value. If indeed most applications converge on a small set of delays, then wallets could opt to select any of those to populate the `include_by_timestamp` field, as if they were interacting with a `DelayedPublicMutable` state variable with that delay. + +This prevents the network-wide privacy set from being split between transactions that read `DelayedPublicMutable` state and those that don't, which is beneficial to everyone. + +### DelayedPublicMutable + +`DelayedPublicMutable` provides capabilities to read the same state both in private and public, and to schedule value changes after a delay. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/delayed_public_mutable/delayed_public_mutable.nr). + +Unlike other state variables, `DelayedPublicMutable` receives not only a type parameter for the underlying datatype, but also a `DELAY` type parameter with the value change delay as a number of seconds. + +#include_code delayed_public_mutable_storage /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust + +:::note +`DelayedPublicMutable` requires that the underlying type `T` implements both the `ToField` and `FromField` traits, meaning it must fit in a single `Field` value. There are plans to extend support by requiring instead an implementation of the `Serialize` and `Deserialize` traits, therefore allowing for multi-field variables, such as complex structs. +::: + +Since `DelayedPublicMutable` lives in public storage, by default its contents are zeroed-out. Intialization is performed by calling `schedule_value_change`, resulting in initialization itself being delayed. + +### `schedule_value_change` + +This is the means by which a `DelayedPublicMutable` variable mutates its contents. It schedules a value change for the variable at a future timestamp after the `DELAY` has elapsed from the current timestamp, at which point the scheduled value becomes the current value automatically and without any further action, both in public and in private. If a pending value change was scheduled but not yet effective (because insufficient time had elapsed), then the previous schedule value change is replaced with the new one and eliminated. There can only be one pending value change at a time. + +This function can only be called in public, typically after some access control check: + +#include_code delayed_public_mutable_schedule /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust + +If one wishes to schedule a value change from private, simply enqueue a public call to a public `internal` contract function. Recall that **all scheduled value changes, including the new value and scheduled timestamp are public**. + +:::warning +A `DelayedPublicMutable`'s storage **must** only be mutated via `schedule_value_change`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. +::: + +### `get_current_value` + +Returns the current value in a public, private or utility execution context. Once a value change is scheduled via `schedule_value_change` and the delay time passes, this automatically returns the new value. + +#include_code delayed_public_mutable_get_current_public /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust + +Also, calling in private will set the `include_by_timestamp` property of the transaction request, introducing a new validity condition to the entire transaction: it cannot be included in any block with a timestamp larger than `include_by_timestamp`. This could [potentially leak some privacy](#privacy-considerations). + +#include_code delayed_public_mutable_get_current_private /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust + +### `get_scheduled_value` + +Returns the last scheduled value change, along with the timestamp at which the scheduled value becomes the current value. This may either be a pending change, if the timestamp is in the future, or the last executed scheduled change if the timestamp is in the past (in which case there are no pending changes). + +#include_code delayed_public_mutable_get_scheduled_public /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust + +It is not possible to call this function in private: doing so would not be very useful at it cannot be asserted that a scheduled value change will not be immediately replaced if `shcedule_value_change` where to be called. diff --git a/docs/docs/developers/guides/smart_contracts/structure_project.md b/docs/docs/developers/guides/smart_contracts/structure_project.md new file mode 100644 index 000000000000..b742b14f7be8 --- /dev/null +++ b/docs/docs/developers/guides/smart_contracts/structure_project.md @@ -0,0 +1,63 @@ +--- +title: Project Structure +tags: [contracts] +sidebar_position: 0 +description: Learn how to set up and structure your project for Aztec smart contracts. +--- + +This guide explains how to set up and structure your project for Aztec smart contracts. + +## Before you start + +You should have installed the sandbox, which includes local development tools, as described in [the getting started guide](../../getting_started/getting_started.md). + +## Setup + +To create a new project, run the following command: + +```bash +aztec-nargo new new_project --contract +``` + +This will create a new project with a `Nargo.toml` file and a `src` directory with a `main.nr` file where your contract will be written. + +## Dependencies + +Define Aztec.nr as a dependency in your `Nargo.toml` file. Aztec.nr is a package that contains the core functionality for writing Aztec smart contracts. + +```toml +[dependencies] +aztec = { git = "https://github.com/AztecProtocol/aztec-packages", tag = "#include_aztec_version", directory = "noir-projects/aztec-nr/aztec" } +``` + +## Writing a contract + +To write a contract: + +1. Import aztec.nr into your contract in the `src/main.nr` file and declare your contract + +```rust +#include_code setup /noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr raw +``` + +2. Define imports in your contract block + +For example, these are the imports for the example counter contract: + +#include_code imports /noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr rust + +3. Declare your contract storage below your imports + +#include_code storage_struct /noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr rust + +4. Declare a constructor with `#[initializer]`. Constructors can be private or public functions. + +#include_code constructor /noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr rust + +5. Declare your contract functions + +#include_code increment /noir-projects/noir-contracts/contracts/test/counter_contract/src/main.nr rust + +There is a lot more detail and nuance to writing contracts, but this should give you a good starting point. +Read contents of this section for more details about authorizing contract to act on your behalf (authenticaion witnesses), +emitting events, calling functions on other contracts and other common patterns. diff --git a/docs/docs/developers/guides/smart_contracts/testing.md b/docs/docs/developers/guides/smart_contracts/testing.md index c2faac5bce22..3a0d6fa40e5f 100644 --- a/docs/docs/developers/guides/smart_contracts/testing.md +++ b/docs/docs/developers/guides/smart_contracts/testing.md @@ -2,8 +2,7 @@ title: Testing Contracts tags: [contracts, tests, testing, noir] keywords: [tests, testing, noir] -sidebar_position: 2 -importance: 1 +sidebar_position: 10 description: Learn how to write and run tests for your Aztec smart contracts. --- @@ -43,7 +42,7 @@ The complete process for running tests: 3. Run `aztec test` :::warning -Under the hood, `TestEnvironment` expects an oracle resolver called 'TXE' (Test eXecution Environment) to be available. This means that a regular `nargo test` command will not suffice - you *must* use `aztec test` instead. +Under the hood, `TestEnvironment` expects an oracle resolver called 'TXE' (Test eXecution Environment) to be available. This means that a regular `nargo test` command will not suffice - you _must_ use `aztec test` instead. ::: ## Writing Aztec Noir tests @@ -144,7 +143,7 @@ Once accounts have been created they can be used as the `from` parameter to meth ### Private -[Authwits](writing_contracts/authwit.md) are currently added via the `add_private_authwit_from_call_interface` and `add_public_authwit_from_call_interface` experimental functions. Here is an example of testing a private token transfer using authwits: +[Authwits](authwit.md) are currently added via the `add_private_authwit_from_call_interface` and `add_public_authwit_from_call_interface` experimental functions. Here is an example of testing a private token transfer using authwits: #include_code private_authwit /noir-projects/noir-contracts/contracts/app/token_contract/src/test/transfer_in_private.nr rust @@ -179,7 +178,6 @@ For example: You can use `aztec.nr`'s oracles as usual for debug logging, as explained [here](../local_env/how_to_debug.md) - :::warning Remember to set the following environment variables to activate debug logging: diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/_category_.json b/docs/docs/developers/guides/smart_contracts/writing_contracts/_category_.json deleted file mode 100644 index 7e21bf8e7ac4..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "position": 0, - "collapsible": true, - "collapsed": true, - "label": "Writing Contracts" -} diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/index.mdx b/docs/docs/developers/guides/smart_contracts/writing_contracts/index.mdx deleted file mode 100644 index a74561086ccb..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/index.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Writing Contracts -tags: [aztec.nr] -description: Learn the fundamentals of writing smart contracts on Aztec using aztec.nr, including contract structure, storage, constructors, and function declarations. ---- - -import DocCardList from "@theme/DocCardList"; - -## Overview - -To write a contract: - -1. Import aztec.nr and declare your contract - -```rust -#include_code declaration /noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr raw -``` - -2. Define imports in your contract block - -#include_code imports /noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr rust - -3. Declare your contract storage below your imports - -#include_code storage_struct /noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr rust - -4. Declare a constructor with `#[initializer]`. Constructors can be private or public functions. - -#include_code constructor /noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr rust - -5. Declare your contract functions - -#include_code cast_vote /noir-projects/noir-contracts/contracts/app/private_voting_contract/src/main.nr rust - -There is a lot more detail and nuance to writing contracts, but this should give you a good starting point. -Read contents of this section for more details about authorizing contract to act on your behalf (authenticaion witnesses), -emitting events, calling functions on other contracts and other common patterns. - -## Section Contents - - diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/initializers.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/initializers.md deleted file mode 100644 index 5c824689e7ea..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/initializers.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Defining Initializer Functions -sidebar_position: 1 -tags: [functions, contracts] -description: Learn how to write and use initializer functions in your Aztec smart contracts. ---- - -This page explains how to write an initializer function, also known as a constructor. - -Initializers are regular functions that set an "initialized" flag (a nullifier) for the contract. A contract can only be initialized once, and contract functions can only be called after the contract has been initialized, much like a constructor. However, if a contract defines no initializers, it can be called at any time. Additionally, you can define as many initializer functions in a contract as you want, both private and public. - -## Annotate with `#[initializer]` - -Define your initializer like so: - -```rust -#[initializer] -fn constructor(){ - // function logic here -} -``` - -## Public or private - -Aztec supports both public and private initializers. Use the appropriate macro, for example for a private initializer: - -```rust -#[private] -#[initializer] -fn constructor(){ - // function logic here -} -``` - -## Initializer with logic - -Initializers are commonly used to set an admin, such as this example: - -#include_code constructor /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - -Here, the initializer is writing to storage. It can also call another function. Learn more about calling functions from functions [here](./call_contracts.md). - -## Multiple initializers - -You can set multiple functions as an initializer function simply by annotating each of them with `#[initializer]`. You can then decide which one to call when you are deploying the contract. - -Calling any one of the functions annotated with `#[initializer]` will mark the contract as initialized. - -To see an initializer in action, follow the [Counter codealong tutorial](../../../tutorials/contract_tutorials/counter_contract.md). diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/address_note.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/address_note.md deleted file mode 100644 index 0ca2397c8e19..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/address_note.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Using Address Note in Aztec.nr -tags: [contracts, notes] -description: Learn how to use address notes to store and manage addresses in your Aztec smart contracts. ---- - -Address notes hold one main property of the type `AztecAddress`. It also holds `owner` and `randomness`, similar to other note types. - -## AddressNote - -This is the AddressNote: - -#include_code address_note_def noir-projects/aztec-nr/address-note/src/address_note.nr rust - -## Importing AddressNote - -### In Nargo.toml - -```toml -address_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/address-note" } -``` - -### In your contract - -#include_code addressnote_import noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr rust - -## Working with AddressNote - -### Creating a new note - -Creating a new `AddressNote` takes the following args: - -- `address` (`AztecAddress`): the address to store in the AddressNote -- `owner` (`AztecAddress`): owner is the party whose nullifying key can be used to spend the note - -#include_code addressnote_new noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr rust - -In this example, `owner` is the `address` and the `npk_m_hash` of the donor was computed earlier. - -## Learn more - -- [Keys, including nullifier keys and outgoing viewer](../../../../../aztec/concepts/accounts/keys.md) -- [How to implement a note](./implementing_a_note.md) diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/implementing_a_note.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/implementing_a_note.md deleted file mode 100644 index 036a49756760..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/implementing_a_note.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Implementing a note in Aztec.nr -tags: [contracts, notes] -keywords: [implementing note, note] -description: Learn how to implement custom note types in your Aztec smart contracts. ---- - -You may want to create your own note type if you need to use a specific type of private data or struct that is not already implemented in Aztec.nr, or if you want to experiment with custom note hashing and nullifier schemes. For custom hashing and nullifier schemes, use the `#[custom_note]` macro instead of `#[note]`, as it does not automatically derive the `NoteHash` trait. - -For example, if you are developing a card game, you may want to store multiple pieces of data in each card. Rather than storing each piece of data in its own note, you can define a card note type that contains all the data, and then nullify (or exchange ownership of) the card when it has been used. - -If you want to work with values, addresses or integers, you can check out [ValueNote](./value_note.md), [AddressNote](./address_note.md) or `UintNote`. - -## Define a note type - -You will likely want to define your note in a new file and import it into your contract. - -A note type can be defined with the macro `#[note]` used on a struct: - -#include_code state_vars-CardNote noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/types/card_note.nr rust - -In this example, we are implementing a card note that holds a number of `points` as `u8`. - -`randomness` is not enforced by the protocol and should be implemented by the application developer. If you do not include `randomness`, and the note preimage can be guessed by an attacker, it makes the note vulnerable to preimage attacks. - -`owner` is used when nullifying the note to obtain a nullifier secret key. -It ensures that when a note is spent, only the owner can spend it and the note sender cannot figure out that the note has been spent! -Providing the `owner` with improved privacy. - -Why is it delivering privacy from sender? - -Because a sender cannot derive a note nullifier. -We could derive the nullifier based solely on the note itself (for example, by computing `hash([note.points, note.owner, note.randomness], NULLIFIER_SEPARATOR)`). -This would work since the nullifier would be unique and only the note recipient could spend it (as contract logic typically only allows the note owner to obtain a note, e.g. from a `Map<...>`). -However, if we did this, the sender could also derive the nullifier off-chain and monitor the nullifier tree for its inclusion, allowing them to determine when a note has been spent. -This would leak privacy. - -## Further reading - -- [What is `#[note]` actually doing? + functions such as serialize() and deserialize()](../../../../../aztec/smart_contracts/functions/attributes.md#implementing-notes) -- [Macros reference](../../../../reference/smart_contract_reference/macros.md) -- [Keys, including npk_m_hash (nullifier public key master)](../../../../../aztec/concepts/accounts/keys.md) diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/index.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/index.md deleted file mode 100644 index 0486178a2088..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Notes -sidebar_position: 3 -tags: [contracts, notes] -description: Comprehensive guide to using notes for private state management in your Aztec smart contracts. ---- - -Notes are the fundamental data structure in Aztec when working with private state. Using Aztec.nr, developers can define note types which allow flexibility in how notes are stored and nullified. - -In this section there are guides about how to work with `AddressNote`, `ValueNote` from Aztec.nr and how to implement your own notes. -You can learn more about notes in the [concepts section](../../../../../aztec/concepts/storage/notes.md). diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/value_note.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/value_note.md deleted file mode 100644 index 07ffb2f9c1ed..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/notes/value_note.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Using Value Notes in Aztec.nr -tags: [contracts, notes] -description: Learn how to use value notes to store and manage numerical values in your Aztec smart contracts. ---- - -ValueNotes hold one main property - a `value` - and have utils useful for manipulating this value, such as incrementing and decrementing it similarly to an integer. - -## ValueNote - -This is the ValueNote struct: - -#include_code value-note-def noir-projects/aztec-nr/value-note/src/value_note.nr rust - -## Importing ValueNote - -### In Nargo.toml - -```toml -value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/value-note" } -``` - -### In your contract - -#include_code import_valuenote noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr rust - -## Working with ValueNote - -### Creating a new note - -Creating a new `ValueNote` takes the following args: - -- `value` (`Field`): the value of the ValueNote -- `owner` (`AztecAddress`): owner is the party whose nullifying key can be used to spend the note - -#include_code valuenote_new noir-projects/noir-contracts/contracts/test/child_contract/src/main.nr rust - -### Getting a balance - -A user may have multiple notes in a set that all refer to the same content (e.g. a set of notes representing a single token balance). By using the `ValueNote` type to represent token balances, you do not have to manually add each of these notes and can instead use a helper function `get_balance()`. - -It takes one argument - the set of notes. - -#include_code get_balance noir-projects/noir-contracts/contracts/test/stateful_test_contract/src/main.nr rust - -This can only be used in an unconstrained function. - -### Incrementing and decrementing - -Both `increment` and `decrement` functions take the same args: - -#include_code increment_args noir-projects/aztec-nr/value-note/src/utils.nr rust - -Note that this will create a new note in the set of notes passed as the first argument. -For example: -#include_code increment_valuenote noir-projects/noir-contracts/contracts/test/benchmarking_contract/src/main.nr rust - -The `decrement` function works similarly except the `amount` is the number that the value will be decremented by, and it will fail if the sum of the selected notes is less than the amount. - -## Learn more - -- [Keys, including nullifier keys and outgoing viewer](../../../../../aztec/concepts/accounts/keys.md) -- [How to implement a note](./implementing_a_note.md) diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/portals/index.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/portals/index.md deleted file mode 100644 index ba9076299f74..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/portals/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Portals -sidebar_position: 7 -description: Comprehensive guide to using portals for L1-L2 communication in your Aztec smart contracts. ---- - -A portal is a point of contact between L1 and a contract on Aztec. For applications such as token bridges, this is the point where the tokens are held on L1 while used in L2. Note, that a portal doesn't actually need to be a contract, it could be any address on L1. diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/index.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/index.md deleted file mode 100644 index deaa071022f7..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/index.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Declaring Storage -sidebar_position: 2 -tags: [contracts, storage] -description: Comprehensive guide to storage management in your Aztec smart contracts. ---- - -On this page, you will learn how to define storage in your smart contract. - -To learn more about how storage works in Aztec, read [the concepts](./storage_slots.md). - -[See the storage reference](../../../../reference/smart_contract_reference/storage/index.md). - -```rust -#[storage] -struct Storage { - // public state variables - // private state variables -} -``` - -If you have defined a struct and annotated it as `#[storage]`, then it will be made available to you through the reserved `storage` keyword within your contract functions. diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/notes.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/notes.md deleted file mode 100644 index 43f8fdf4f087..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/notes.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -title: Writing Notes -description: Core knowledge of Notes and how they work -useful-for: dev -tags: [contracts, storage, notes] ---- - -Most prominent blockchain networks don't have privacy at the protocol level. Aztec contracts can define public and private functions, that can read/write public and private state. - -To be clear, "private" here is referring to the opposite of how things work on a public blockchain network, not the conventional syntax for visibility within code. - -For private state we need encryption and techniques to hide information about state changes. For private functions, we need local execution and proof of correct execution. - -### Some context - -- Public functions and storage work much like other blockchains in terms of having dedicated storage slots and being publicly visible -- Private functions are executed locally with proofs generated for sound execution, and commitments to private variable updates are stored using append-only trees -- "Note" types are part of Aztec.nr, a framework that facilitates use of Aztec's different storage trees to achieve things such as private variables - -This page will focus on how private variables are implemented with Notes and storage trees. - -#### Side-note about execution - -Under the hood, the Aztec protocol handles some important details around public and private function calls. Calls between them are asynchronous due to different execution contexts (local execution vs. node execution). -A detailed explanation of the transaction lifecycle can be found [here](../../../../../aztec/concepts/transactions.md#simple-example-of-the-private-transaction-lifecycle). - -## Private state variables in Aztec - -State variables in an Aztec contract are defined inside a struct specifically named `Storage`, and must satisfy the [Note Interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_interface.nr) and contain a [Note header (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_header.nr). - -The Note header struct contains the contract address which the value is effectively siloed to, a nonce to ensure unique Note hashes, and a storage "slot" (or ID) to associate multiple notes. - -A couple of things to unpack here: - -#### Storage "slot" - -Storage slots are more literal for public storage, a place where a value is stored. For private storage, a storage slot is logical (more [here](../../../../../aztec/concepts/advanced/storage/storage_slots.md)). - -#### Silos - -The address of the contract is included in a Note's data to ensure that different contracts don't arrive at the same hash with an identical variable. This is handled in the protocol's execution. - -### Note types - -There is more than one Note type, such as the `PrivateSet` type is used for private variables. There are also `PrivateMutable` and `PrivateImmutable` types. - -Furthermore, notes can be completely custom types, storing any value or set of values that are desired by an application. - -### Initialization - -Private state variables are stored locally when the contract is created. Depending on the application, values may be privately shared by the creator with others via encrypted logs onchain. -A hash of a note is stored in the append-only note hash tree on the network so as to prove existence of the current state of the note in a privacy preserving way. - -#### Note Hash Tree - -By virtue of being append only, notes are not edited. If two transactions amend a private value, multiple notes will be inserted into the tree to the note hash tree and the nullifier tree. The header will contain the same logical storage slot. - -### Reading Notes - -:::info - -Only those with appropriate keys/information will be able to successfully read private values that they have permission to. Notes can be read outside of a transaction or "off-chain" with no changes to data structures on-chain. - -::: - -When a note is read in a transaction, a subsequent read from another transaction of the same note would reveal a link between the two. So to preserve privacy, notes that are read in a transaction are said to be "consumed" (defined below), and new note(s) are then created with a unique hash. - -With type `PrviateSet`, a private variable's value is interpreted as the sum of values of notes with the same logical storage slot. - -Consuming, deleting, or otherwise "nullifying" a note is NOT done by deleting the Note hash; this would leak information. Rather a nullifier is created deterministically linked to the value. This nullifier is inserted into another the nullifier storage tree. - -When reading a value, the local private execution checks that its notes (of the corresponding storage slot/ID) have not been nullified. - -### Updating - -:::note -Only those with appropriate keys/information will be able to successfully nullify a value that they have permission to. -::: - -To update a value, its previous note hash(es) are nullified. The new note value is updated in the user's private execution environment (PXE), and the updated note hash inserted into the note hash tree. - -## Supplementary components - -Some optional background resources on notes can be found here: - -- [High level network architecture](../../../../../aztec/index.md), specifically the Private Execution Environment -- [Transaction lifecycle (simple diagram)](../../../../../aztec/concepts/transactions.md#simple-example-of-the-private-transaction-lifecycle) -- [Public and Private state](../../../../../aztec/concepts/storage/state_model.md) - -Notes touch several core components of the protocol, but we will focus on a the essentials first. - -### Some code context - -The way Aztec benefits from the Noir language is via three important components: - -- `Aztec.nr` - a Noir framework enabling contracts on Aztec, written in Noir. Includes useful Note implementations -- `noir contracts` - example Aztec contracts -- `noir-protocol-circuits` - a crate containing essential circuits for the protocol (public circuits and private wrappers) - -A lot of what we will look at will be in [aztec-nr/aztec/src/note (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note), specifically the lifecycle and note interface. - -Looking at the noir circuits in these components, you will see references to the distinction between public/private execution and state. - -### Lifecycle functions - -Inside the [lifecycle (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr) circuits we see the functions to create and destroy a note, implemented as insertions of note hashes and nullifiers respectively. This is helpful for regular private variables. - -We also see a function to create a note hash from the public context, a way of creating a private variable from a public call (run in the sequencer). This could be used in application contracts to give private digital assets to users. - -### Note Interface functions - -To see a [note_interface (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_interface.nr) implementation, we will look at a simple [ValueNote GitHub link](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/value-note/src/value_note.nr). - -The interface is required to work within an Aztec contract's storage, and a ValueNote is a specific type of note to hold a number (as a `Field`). - -#### Computing hashes and nullifiers - -A few key functions in the note interface are around computing the note hash and nullifier, with logic to get/use secret keys from the private context. - -In the ValueNote implementation you'll notice that it uses the `pedersen_hash` function. This is currently required by the protocol, but may be updated to another hashing function, like poseidon. - -As a convenience, the outer [note/utils.nr (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/utils.nr) contains implementations of functions that may be needed in Aztec contracts, for example computing note hashes. - -#### Serialization and deserialization - -Serialization/deserialization of content is used to convert between the Note's variables and a generic array of Field elements. The Field type is understood and used by lower level crypographic libraries. -This is analogous to the encoding/decoding between variables and bytes in solidity. - -For example in ValueNote, the `serialize_content` function simply returns: the value, nullifying public key hash (as a field) and the note randomness; as an array of Field elements. - -### Value as a sum of Notes - -We recall that multiple notes are associated with a "slot" (or ID), and so the value of a numerical note (like ValueNote) is the sum of each note's value. -The helper function in [balance_utils (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_/noir-projects/aztec-nr/value-note/src/balance_utils.nr) implements this logic taking a `PrivateSet` of `ValueNotes`. - -A couple of things worth clarifying: - -- A `PrivateSet` takes a Generic type, specified here as `ValueNote`, but can be any `Note` type (for all notes in the set) -- A `PrivateSet` of notes also specifies _the_ slot of all Notes that it holds - -### Example - Notes in action - -The Aztec.nr framework includes examples of high-level states [easy_private_uint (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr) for use in contracts. - -The struct (`EasyPrivateUint`) contains a Context, Set of ValueNotes, and storage_slot (used when setting the Set). - -Notice how the `add` function shows the simplicity of appending a new note to all existing ones. On the other hand, `sub` (subtraction), needs to first add up all existing values (consuming them in the process), and then insert a single new value of the difference between the sum and parameter. - ---- - -### Apply - -Try the [NFT tutorial](../../../../tutorials/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 - -- [Proof of prior notes](../how_to_prove_history.md) - public/private reading of public/private proof of state (public or private) - -If you're curious about any of the following related topics, search the documentation for... - -- Private and public contexts -- Encryption keys and events -- Oracle's role in using notes -- Value Serialization/Deserialization - -### References - -- ["Stable" state variable (GitHub link)](https://github.com/AztecProtocol/aztec-packages/issues/4130) -- [Code: Aztec-Patterns (GitHub link)](https://github.com/defi-wonderland/aztec-patterns) diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/storage_slots.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/storage_slots.md deleted file mode 100644 index 5a13ee075aad..000000000000 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/storage/storage_slots.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: Storage slots -tags: [contracts, storage] -description: Detailed walkthrough of storage slot computation in Aztec smart contracts, including private state note hash creation and siloing mechanisms. ---- - -From the description of storage slots [in the Concepts](../../../../../aztec/concepts/advanced/storage/storage_slots.md) you will get an idea around the logic of storage slots. In this section we will go into more detail and walk through an entire example of how storage slots are computed for private state to improve our storage slot intuition. Recall, that storage slots in the private domain is just a logical construct, and are not "actually" used for lookups, but rather just as a value to constrain against. - -For the case of the example, we will look at what is inserted into the note hashes tree when adding a note in the Token contract. Specifically, we are looking at the last part of the `transfer` function: - -#include_code increase_private_balance noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - -This function is creating a new note and inserting it into the balance set of the recipient `to`. Recall that to ensure privacy, only the note hash is really inserted into the note hashes tree. To share the contents of the note with `to` the contract can emit an encrypted log (which this one does), or it can require an out-of-band data transfer sharing the information. Below, we will walk through the steps of how the note hash is computed and inserted into the tree. For this, we don't care about the encrypted log, so we are going to ignore that part of the function call for now. - -Outlining it in more detail below as a sequence diagram, we can see how the calls make their way down the stack. -In the end a siloed note hash is computed in the kernel. - -:::info -Some of the syntax below is a little butchered to make it easier to follow variables without the full code. -::: - -```mermaid -sequenceDiagram - alt Call - Token->>BalanceMap: Map::new(map_slot); - Token->>Token: to_bal = storage.balances.at(to) - Token->>BalanceMap: BalanceMap.at(to) - BalanceMap->>BalanceMap: derived_slot = H(map_slot, to) - BalanceMap->>BalanceSet: BalanceSet::new(to, derived_slot) - Token->>BalanceSet: to_bal.add(amount) - BalanceSet->>BalanceSet: note = UintNote::new(amount, to) - BalanceSet->>Set: insert(note) - Set->>LifeCycle: create_note(derived_slot, note) - LifeCycle->>LifeCycle: note.header = NoteHeader { contract_address,
storage_slot: derived_slot, nonce: 0, note_hash_counter } - UintPartialNotePrivateContent->>UintNote: note_hash = compute_partial_commitment(storage_slot).x - LifeCycle->>Context: push_note_hash(note_hash) - end - Context->>Kernel: unique_note_hash = H(nonce, note_hash) - Context->>Kernel: siloed_note_hash = H(contract_address, unique_note_hash) -``` - -Notice the `siloed_note_hash` at the very end. It's a hash that will be inserted into the note hashes tree. To clarify what this really is, we "unroll" the values to their simplest components. This gives us a better idea around what is actually inserted into the tree. - -```rust -siloed_note_hash = H(contract_address, unique_note_hash) -siloed_note_hash = H(contract_address, H(nonce, note_hash)) -siloed_note_hash = H(contract_address, H(H(tx_hash, note_index_in_tx), note_hash)) -siloed_note_hash = H(contract_address, H(H(tx_hash, note_index_in_tx), MSM([G_amt, G_to, G_rand, G_slot], [amount, to, randomness, derived_slot]).x)) -``` - -MSM is a multi scalar multiplication on a grumpkin curve and G_* values are generators. - -And `to` is the actor who receives the note, `amount` of the note and `randomness` is the randomness used to make the note hiding. Without the `randomness` the note could just as well be plaintext (computational cost of a preimage attack would be trivial in such a case). - -:::info -Beware that this hash computation is what the aztec.nr library is doing, and not strictly required by the network (only the kernel computation is). -::: - -With this note structure, the contract can require that only notes sitting at specific storage slots can be used by specific operations, e.g., if transferring funds from `from` to `to`, the notes to destroy should be linked to `H(map_slot, from)` and the new notes (except the change-note) should be linked to `H(map_slot, to)`. - -That way, we can have logical storage slots, without them really existing. This means that knowing the storage slot for a note is not enough to actually figure out what is in there (whereas it would be for looking up public state). diff --git a/docs/docs/developers/reference/environment_reference/cli_wallet_reference.md b/docs/docs/developers/reference/environment_reference/cli_wallet_reference.md index 53c52cc64cae..010d2866e5c5 100644 --- a/docs/docs/developers/reference/environment_reference/cli_wallet_reference.md +++ b/docs/docs/developers/reference/environment_reference/cli_wallet_reference.md @@ -11,8 +11,8 @@ For development, it may be useful to deploy, transact, or create notes in a non- - Deploying contracts - Sending transactions - Bridging L1 "Fee Juice" into Aztec -- Pushing arbitrary [notes](../../guides/smart_contracts/writing_contracts/notes/index.md) to your PXE -- Creating [authwits](../../guides/smart_contracts/writing_contracts/authwit.md) +- Pushing arbitrary [notes](../../guides/smart_contracts/note_types.md) to your PXE +- Creating [authwits](../../guides/smart_contracts/authwit.md) - Aliasing info and secrets for further usage - Proving your transactions and profile gate counts diff --git a/docs/docs/developers/reference/smart_contract_reference/storage/delayed_public_mutable.md b/docs/docs/developers/reference/smart_contract_reference/storage/delayed_public_mutable.md deleted file mode 100644 index e6d138f8741c..000000000000 --- a/docs/docs/developers/reference/smart_contract_reference/storage/delayed_public_mutable.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: Delayed Public Mutable State -description: This page covers an advanced type of state called "Delayed Public Mutable" state, which is public state that can also be read in private. ---- - -This page covers an advanced type of state called "Delayed Public Mutable" state, which is public state that can also be read in private. It is highly recommended that you're familiar with both [private](./private_state.md) and [public](./public_state.md) state before reading this page. - -## Overview and Motivation - -A typical use case for "Delayed Public Mutable" state is some kind of system configuration, such as a protocol fee or access control permissions. These values are public (known by everyone) and mutable. Reading them in private however is tricky: private execution is always asynchronous and performed over _historical_ state, and hence one cannot easily prove that a given public value is current. - -A naive way to solve this is to enqueue a public call that will assert the current public value, but this leaks _which_ public value is being read, severely reducing privacy. Even if the value itself is already public, the fact that we're using it because we're interacting with some related contract is not. For example, we may leak that we're interacting with a certain DeFi protocol by reading its fee. - -An alternative approach is to create notes in public that are then nullified in private, but this introduces contention: only a single user may use the note and therefore read the state, since nullifying it will prevent all others from doing the same. In some schemes there's only one account that will read the state anyway, but this is not the general case. - -Delayed Public Mutable state works around this by introducing **delays**: while public values are mutable, they cannot change _immediately_. Instead, a value change must be scheduled ahead of time, and some minimum amount of time must pass between the scheduling and the new value taking effect. This means that we can privately prove that a historical public value cannot possibly change before some point in the future (due to the minimum delay), and therefore that our transaction will be valid **as long as it gets included before this future time**. - -This results in the following key properties of `DelayedPublicMutable` state: - -- public values can only be changed after a certain delay has passed, never immediately -- the scheduling of value changes is itself public, including both the new value and the time at which the change will take effect -- transactions that read `DelayedPublicMutable` state become invalid after some time if not included in a block - -## Privacy Considerations - -While `DelayedPublicMutable` state variables are much less leaky than the assertion in public approach, they do reveal some information to external observers by setting the `include_by_timestamp` property of the transaction request. The impact of this can be mitigated with proper selection of the delay value and schedule times. - -### Choosing Delays - -The `include_by_timestamp` transaction property will be set to a value close to the current timestamp plus the duration of the delay in seconds. The exact value depends on the historical block over which the private proof is constructed. For example, if current timestamp is `X` and a `DelayedPublicMutable` state variable has a delay of 3000 seconds, then transactions that read this value privately will set `include_by_timestamp` to a value close to 'X + 3000' (clients building proofs on older state will select a lower `include_by_timestamp`). This implicitly leaks the duration of the delay. - -Applications using similar delays will therefore be part of the same privacy set. It is expected for social coordination to result in small set of predetermined delays that developers choose from depending on their needs, as an example a viable set might be: 12 hours (for time-sensitive operations, such as emergency mechanisms), 5 days (for middle-of-the-road operations) and 2 weeks (for operations that require lengthy public scrutiny). These delays can be changed during the contract lifetime as the application's needs evolve. - -Additionally, users might choose to coordinate and constrain their transactions to set `include_by_timestamp` to a value lower than would be strictly needed by the applications they interact with (if any!) using some common delay, and by doing so prevent privacy leakage. - -### Choosing Epochs - -If a value change is scheduled in the near future, then transactions that access this `DelayedPublicMutable` state will be forced to set a lower `include_by_timestamp` right before the value change. For example, if the current timestamp is 'x' and a `DelayedPublicMutable` state variable with a delay of 3000 seconds has a value change scheduled for timestamp 'x + 50', then transactions that read this value privately will set `include_by_timestamp` to 'x + 50 - 1'. Since the timestamps at which `DelayedPublicMutable` state values change are public, it might be deduced that transactions with an `include_by_timestamp` value close to the current timestamp are reading some state variable with a changed scheduled at `include_by_timestamp + 1`. - -Applications that schedule value changes at the same time will therefore be part of the same privacy set. It is expected for social coordination to result in ways to achieve this, e.g. by scheduling value changes so that they land on timestamps that are multiples of some value - we call these epochs. - -There is a tradeoff between frequent and infrequent epochs: frequent epochs means more of them, and therefore fewer updates on each, shrinking the privacy set. But infrequent epochs result in the effective delay of value changes being potentially larger than desired - though an application can always choose to do an out-of-epoch update if needed. - -Note that wallets can also warn users that a value change will soon take place and that sending a transaction at that time might result in reduced privacy, allowing them to choose to wait until after the epoch. - -### Network Cooperation - -Even though only transactions that interact with `DelayedPublicMutable` state _need_ to set the `include_by_timestamp` property, there is no reason why transactions that do not wouldn't also set this value. If indeed most applications converge on a small set of delays, then wallets could opt to select any of those to populate the `include_by_timestamp` field, as if they were interacting with a `DelayedPublicMutable` state variable with that delay. - -This prevents the network-wide privacy set from being split between transactions that read `DelayedPublicMutable` state and those that don't, which is beneficial to everyone. - -## `DelayedPublicMutable` - -`DelayedPublicMutable` provides capabilities to read the same state both in private and public, and to schedule value changes after a delay. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/delayed_public_mutable/delayed_public_mutable.nr). - -Unlike other state variables, `DelayedPublicMutable` receives not only a type parameter for the underlying datatype, but also a `DELAY` type parameter with the value change delay as a number of seconds. - -#include_code delayed_public_mutable_storage /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust - -:::note -`DelayedPublicMutable` requires that the underlying type `T` implements both the `ToField` and `FromField` traits, meaning it must fit in a single `Field` value. There are plans to extend support by requiring instead an implementation of the `Serialize` and `Deserialize` traits, therefore allowing for multi-field variables, such as complex structs. -::: - -Since `DelayedPublicMutable` lives in public storage, by default its contents are zeroed-out. Intialization is performed by calling `schedule_value_change`, resulting in initialization itself being delayed. - -### `schedule_value_change` - -This is the means by which a `DelayedPublicMutable` variable mutates its contents. It schedules a value change for the variable at a future timestamp after the `DELAY` has elapsed from the current timestamp, at which point the scheduled value becomes the current value automatically and without any further action, both in public and in private. If a pending value change was scheduled but not yet effective (because insufficient time had elapsed), then the previous schedule value change is replaced with the new one and eliminated. There can only be one pending value change at a time. - -This function can only be called in public, typically after some access control check: - -#include_code delayed_public_mutable_schedule /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust - -If one wishes to schedule a value change from private, simply enqueue a public call to a public `internal` contract function. Recall that **all scheduled value changes, including the new value and scheduled timestamp are public**. - -:::warning -A `DelayedPublicMutable`'s storage **must** only be mutated via `schedule_value_change`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. -::: - -### `get_current_value` - -Returns the current value in a public, private or utility execution context. Once a value change is scheduled via `schedule_value_change` and the delay time passes, this automatically returns the new value. - -#include_code delayed_public_mutable_get_current_public /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust - -Also, calling in private will set the `include_by_timestamp` property of the transaction request, introducing a new validity condition to the entire transaction: it cannot be included in any block with a timestamp larger than `include_by_timestamp`. This could [potentially leak some privacy](#privacy-considerations). - -#include_code delayed_public_mutable_get_current_private /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust - -### `get_scheduled_value` - -Returns the last scheduled value change, along with the timestamp at which the scheduled value becomes the current value. This may either be a pending change, if the timestamp is in the future, or the last executed scheduled change if the timestamp is in the past (in which case there are no pending changes). - -#include_code delayed_public_mutable_get_scheduled_public /noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr rust - -It is not possible to call this function in private: doing so would not be very useful at it cannot be asserted that a scheduled value change will not be immediately replaced if `shcedule_value_change` where to be called. diff --git a/docs/docs/developers/reference/smart_contract_reference/storage/index.md b/docs/docs/developers/reference/smart_contract_reference/storage/index.md deleted file mode 100644 index 72173c0390d3..000000000000 --- a/docs/docs/developers/reference/smart_contract_reference/storage/index.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: Storage -description: Comprehensive guide to storage management in Aztec smart contracts. ---- - -Smart contracts rely on storage, acting as the persistent memory on the blockchain. In Aztec, because of its hybrid, privacy-first architecture, the management of this storage is more complex than other blockchains like Ethereum. - -To learn how to define a storage struct, read [this guide](../../../guides/smart_contracts/writing_contracts/storage/index.md). -To learn more about storage slots, read [this explainer in the Concepts section](../../../../aztec/concepts/storage/index.md). - -You control this storage in Aztec using a struct annotated with `#[storage]`. This struct serves as the housing unit for all your smart contract's state variables - the data it needs to keep track of and maintain. - -These state variables come in two forms: [public](./public_state.md) and [private](./private_state.md). Public variables are visible to anyone, and private variables remain hidden within the contract. - -Aztec.nr has a few abstractions to help define the type of data your contract holds. These include PrivateMutable, PrivateImmutable, PublicMutable, PublicImmutable, PrivateSet, and DelayedPublicMutable. - -On this and the following pages in this section, you’ll learn: - -- How to manage a smart contract's storage structure -- The distinctions and applications of public and private state variables -- How to use PrivateMutable, PrivateImmutable, PrivateSet, PublicMutable, DelayedPublicMutable and Map -- An overview of 'notes' and the UTXO model -- Practical implications of Storage in real smart contracts - In an Aztec.nr contract, storage is to be defined as a single struct, that contains both public and private state variables. - -## The `Context` parameter - -Aztec contracts have three different modes of execution: private, public, and utility. How storage is accessed depends on the execution mode: for example, `PublicImmutable` can be read in all execution modes but only initialized in public, while `PrivateMutable` is entirely unavailable in public. - -Aztec.nr prevents developers from calling functions unavailable in the current execution mode via the `Context` variable that is injected into all contract functions. Its type indicates the current execution mode: - -- `&mut PrivateContext` for private execution -- `&mut PublicContext` for public execution -- `UtilityContext` for utility execution - -All state variables are generic over this `Context` type, and expose different methods in each execution mode. - -## Map - -A `map` is a state variable that "maps" a key to a value. It can be used with private or public storage variables. - -:::info -In Aztec.nr, keys are always `Field`s, or types that can be serialized as Fields, and values can be any type - even other maps. `Field`s are finite field elements, but you can think of them as integers. -::: - -It includes a `Context` to specify the private or public domain, a `storage_slot` to specify where in storage the map is stored, and a `start_var_constructor` which tells the map how it should operate on the underlying type. This includes how to serialize and deserialize the type, as well as how commitments and nullifiers are computed for the type if it's private. - -You can view the implementation in the Aztec.nr library [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/aztec-nr). - -You can have multiple `map`s in your contract that each have a different underlying note type, due to note type IDs. These are identifiers for each note type that are unique within a contract. - -#### As private storage - -When declaring a mapping in private storage, we have to specify which type of Note to use. In the example below, we are specifying that we want to use the `PrivateMutable` note which will hold `ValueNote` types. - -In the Storage struct: - -```rust -numbers: Map>, -``` - -#### Public Example - -When declaring a public mapping in Storage, we have to specify that the type is public by declaring it as `PublicState` instead of specifying a note type like with private storage above. - -#include_code storage_minters /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - -### `at` - -When dealing with a Map, we can access the value at a given key using the `::at` method. This takes the key as an argument and returns the value at that key. - -This function behaves similarly for both private and public maps. An example could be if we have a map with `minters`, which is mapping addresses to a flag for whether they are allowed to mint tokens or not. - -#include_code read_minter /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - -Above, we are specifying that we want to get the storage in the Map `at` the `msg_sender()`, read the value stored and check that `msg_sender()` is indeed a minter. Doing a similar operation in Solidity code would look like: - -```solidity -require(minters[msg.sender], "caller is not minter"); -``` - -## Further Reading - -- [Public State](./public_state.md) -- [Private State](./private_state.md) -- [Delayed Public Mutable State](./delayed_public_mutable.md) - -## Concepts mentioned - -- [State Model](../../../../aztec/concepts/storage/state_model.md) -- [Public-private execution](../../../../aztec/smart_contracts/functions/public_private_calls.md) -- [Function Contexts](../../../../aztec/smart_contracts/functions/context.md) diff --git a/docs/docs/developers/reference/smart_contract_reference/storage/private_state.md b/docs/docs/developers/reference/smart_contract_reference/storage/private_state.md deleted file mode 100644 index 65eaa078a987..000000000000 --- a/docs/docs/developers/reference/smart_contract_reference/storage/private_state.md +++ /dev/null @@ -1,369 +0,0 @@ ---- -title: Private State -description: Learn how to manage private state in your Aztec smart contracts. ---- - -On this page we will look at how to manage private state in Aztec contracts. We will look at how to declare private state, how to read and write to it, and how to use it in your contracts. - -For a higher level overview of the state model in Aztec, see the [hybrid state model](../../../../aztec/concepts/storage/state_model.md) page. - -## Overview - -In contrast to public state, private state is persistent state that is **not** visible to the whole world. Depending on the logic of the smart contract, a private state variable's current value will only be known to one entity, or a closed group of entities. - -The value of a private state variable can either be shared via an encrypted log, or offchain via web2, or completely offline: it's up to the app developer. - -Aztec private state follows a [UTXO](https://en.wikipedia.org/wiki/Unspent_transaction_output)-based model. That is, a private state's current value is represented as one or many notes. - -To greatly simplify the experience of writing private state, Aztec.nr provides three different types of private state variable: - -- [PrivateMutable\](#privatemutablenotetype) -- [PrivateImmutable\](#privateimmutablenotetype) -- [PrivateSet\](#privatesetnotetype) - -These three structs abstract-away many of Aztec's protocol complexities, by providing intuitive methods to modify notes in the utxo tree in a privacy-preserving way. - -:::info -An app can also choose to emit data via unencrypted log, or to define a note whose data is easy to figure out, then the information is technically not private and could be visible to anyone. -::: - -### Notes - -Unlike public state variables, which can be arbitrary types, private state variables operate on `NoteType`. - -Notes are the fundamental elements in the private world. - -A note has to implement the following traits: - -#include_code note_interfaces /noir-projects/aztec-nr/aztec/src/note/note_interface.nr rust - -The interplay between a private state variable and its notes can be confusing. Here's a summary to aid intuition: - -A private state variable (of type `PrivateMutable`, `PrivateImmutable` or `PrivateSet`) may be declared in storage and the purpose of private state variables is to manage notes (inserting their note hashes into the note hash tree, obtaining the notes, grouping the notes together using the storage slot etc.). - -:::info -Note that storage slots in private state are not real. -They do not point to a specific leaf in a merkle tree (as is the case in public). -Instead, in the case of notes they can be understood only as a tag that is used to associate notes with a private state variable. -The state variable storage slot can commonly represent an owner, as is the case when using the `at(...)` function of a `Map<>` with an `AztecAddress` as the key. -::: - -A private state variable points to one or many notes (depending on the type). The note(s) are all valid private state if the note(s) haven't yet been nullified. - -An `PrivateImmutable` will point to _one_ note over the lifetime of the contract. This note is a struct of information that is persisted forever. - -A `PrivateMutable` may point to _one_ note at a time. But since it's not "immutable", the note that it points to may be [replaced](#replace) by functions of the contract. The current value of a `PrivateMutable` is interpreted as the one note which has not-yet been nullified. The act of replacing a PrivateMutable's note is how a `PrivateMutable` state may be modified by functions. - -`PrivateMutable` is a useful type when declaring a private state variable which may only ever be modified by those who are privy to the current value of that state. - -A `PrivateSet` may point to _multiple_ notes at a time. The "current value" of a private state variable of type `PrivateSet` is some accumulation of all not-yet nullified notes which belong to the `PrivateSet`. - -:::note -The term "some accumulation" is intentionally vague. The interpretation of the "current value" of a `PrivateSet` must be expressed by the smart contract developer. A common use case for a `PrivateSet` is to represent the sum of a collection of values (in which case 'accumulation' is 'summation'). - -Think of a ZCash balance (or even a Bitcoin balance). The "current value" of a user's ZCash balance is the sum of all unspent (not-yet nullified) notes belonging to that user. To modify the "current value" of a `PrivateSet` state variable, is to [`insert`](#insert) new notes into the `PrivateSet`, or [`remove`](#remove) notes from that set. -::: - -Interestingly, if a developer requires a private state to be modifiable by users who _aren't_ privy to the value of that state, a `PrivateSet` is a very useful type. The `insert` method allows new notes to be added to the `PrivateSet` without knowing any of the other notes in the set! (Like posting an envelope into a post box, you don't know what else is in there!). - -## `PrivateMutable` - -PrivateMutable (formerly known as `Singleton`) is a private state variable that is unique in a way. When a PrivateMutable is initialized, a note is created to represent its value. And the way to update the value is to destroy the current note, and create a new one with the updated value. - -Like for public state, we define the struct to have context and a storage slot. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr). - -An example of `PrivateMutable` usage in the account contracts is keeping track of public keys. The `PrivateMutable` is added to the `Storage` struct as follows: - -#include_code storage-private-mutable-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `new` - -As part of the initialization of the `Storage` struct, the `PrivateMutable` is created as follows at the specified storage slot. - -#include_code start_vars_private_mutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `initialize` - -As mentioned, the PrivateMutable is initialized to create the first note and value. - -When this function is called, a nullifier of the storage slot is created, preventing this PrivateMutable from being initialized again. - -:::danger Privacy-Leak -Beware that because this nullifier is created only from the storage slot without randomness it leaks privacy. This means that it is possible for an external observer to determine when the note is nullified. - -For example, if the storage slot depends on the an address then it is possible to link the nullifier to the address. If the PrivateMutable is part of a `map` with an `AztecAddress` as the key then the nullifier will be linked to the address. -::: - -Unlike public states, which have a default initial value of `0` (or many zeros, in the case of a struct, array or map), a private state (of type `PrivateMutable`, `PrivateImmutable` or `PrivateSet`) does not have a default initial value. The `initialize` method (or `insert`, in the case of a `PrivateSet`) must be called. - -:::info -Extend on what happens if you try to use non-initialized state. -::: - -### `is_initialized` - -An unconstrained method to check whether the PrivateMutable has been initialized or not. It takes an optional owner and returns a boolean. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr). - -#include_code private_mutable_is_initialized /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `replace` - -To update the value of a `PrivateMutable`, we can use the `replace` method. The method takes a new note as input, and replaces the current note with the new one. It emits a nullifier for the old value, and inserts the new note into the data tree. - -An example of this is seen in a example card game, where we create a new note (a `CardNote`) containing some new data, and replace the current note with it: - -#include_code state_vars-PrivateMutableReplace /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -If two people are trying to modify the PrivateMutable at the same time, only one will succeed as we don't allow duplicate nullifiers! Developers should put in place appropriate access controls to avoid race conditions (unless a race is intended!). - -### `get_note` - -This function allows us to get the note of a PrivateMutable, essentially reading the value. - -#include_code state_vars-PrivateMutableGet /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -#### Nullifying Note reads - -To ensure that a user's private execution always uses the latest value of a PrivateMutable, the `get_note` function will nullify the note that it is reading. This means that if two people are trying to use this function with the same note, only one will succeed (no duplicate nullifiers allowed). - -This also makes read operations indistinguishable from write operations and allows the sequencer to verifying correct execution without learning anything about the value of the note. - -### `view_note` - -Functionally similar to [`get_note`](#get_note), but executed in unconstrained functions and can be used by the wallet to fetch notes for use by front-ends etc. - -## `PrivateImmutable` - -`PrivateImmutable` (formerly known as `ImmutableSingleton`) represents a unique private state variable that, as the name suggests, is immutable. Once initialized, its value cannot be altered. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr). - -### `new` - -As part of the initialization of the `Storage` struct, the `PrivateMutable` is created as follows, here at storage slot 1. - -#include_code storage-private-immutable-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `initialize` - -When this function is invoked, it creates a nullifier for the storage slot, ensuring that the PrivateImmutable cannot be initialized again. - -:::danger Privacy-Leak -Beware that because this nullifier is created only from the storage slot without randomness it leaks privacy. This means that it is possible for an external observer to determine when the note is nullified. - -For example, if the storage slot depends on the an address then it is possible to link the nullifier to the address. If the PrivateImmutable is part of a `map` with an `AztecAddress` as the key then the nullifier will be linked to the address. -::: - -Set the value of an PrivateImmutable by calling the `initialize` method: - -#include_code initialize-private-mutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -Once initialized, an PrivateImmutable's value remains unchangeable. This method can only be called once. - -### `is_initialized` - -An unconstrained method to check if the PrivateImmutable has been initialized. Takes an optional owner and returns a boolean. You can find the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr). - -### `get_note` - -Similar to the `PrivateMutable`, we can use the `get_note` method to read the value of an PrivateImmutable. - -Use this method to retrieve the value of an initialized PrivateImmutable. - -#include_code get_note-private-immutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -Unlike a `PrivateMutable`, the `get_note` function for an PrivateImmutable doesn't nullify the current note in the background. This means that multiple accounts can concurrently call this function to read the value. - -This function will throw if the `PrivateImmutable` hasn't been initialized. - -### `view_note` - -Functionally similar to `get_note`, but executed unconstrained and can be used by the wallet to fetch notes for use by front-ends etc. - -## `PrivateSet` - -`PrivateSet` is used for managing a collection of notes. All notes in a `PrivateSet` are of the same `NoteType`. But whether these notes all belong to one entity, or are accessible and editable by different entities, is up to the developer. The set is a collection of notes inserted into the data-tree, but notes are never removed from the tree itself, they are only nullified. - -You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr). - -And can be added to the `Storage` struct as follows. Here adding a set for a custom note. - -#include_code storage-set-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `new` - -The `new` method tells the contract how to operate on the underlying storage. - -We can initialize the set as follows: - -#include_code storage-set-init /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `insert` - -Allows us to modify the storage by inserting a note into the `PrivateSet`. - -A hash of the note will be generated, and inserted into the note hash tree, allowing us to later use in contract interactions. Recall that the content of the note should be shared with the owner to allow them to use it, as mentioned this can be done via an encrypted log or offchain via web2, or completely offline. - -#include_code insert /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust - -### `insert_from_public` - -The `insert_from_public` allow public function to insert notes into private storage. This is very useful when we want to support private function calls that have been initiated in public. - -The usage is similar to using the `insert` method with the difference that this one is called in public functions. - -### `pop_notes` - -This function pops (gets, removes and returns) the notes the account has access to based on the provided filter. - -The kernel circuits are constrained to a maximum number of notes this function can return at a time. Check [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr) and look for `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` for the up-to-date number. - -Because of this limit, we should always consider using the second argument `NoteGetterOptions` to limit the number of notes we need to read and constrain in our programs. This is quite important as every extra call increases the time used to prove the program and we don't want to spend more time than necessary. - -An example of such options is using the [filter_notes_min_sum (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/value-note/src/filter.nr) to get "enough" notes to cover a given value. Essentially, this function will return just enough notes to cover the amount specified such that we don't need to read all our notes. For users with a lot of notes, this becomes increasingly important. - -#include_code pop_notes /noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr rust - -### `get_notes` - -This function has the same behavior as `pop_notes` above but it does not delete the notes. - -### `remove` - -Will remove a note from the `PrivateSet` if it previously has been read from storage, e.g. you have fetched it through a `get_notes` call. This is useful when you want to remove a note that you have previously read from storage and do not have to read it again. - -Note that if you obtained the note you are about to remove via `get_notes` it's much better to use `pop_notes` as `pop_notes` results in significantly fewer constraints since it doesn't need to check that the note has been previously read, as it reads and deletes at once. - -### `view_notes` - -Functionally similar to [`get_notes`](#get_notes), but executed unconstrained and can be used by the wallet to fetch notes for use by front-ends etc. - -#include_code view_notes /noir-projects/aztec-nr/value-note/src/balance_utils.nr rust - -There's also a limit on the maximum number of notes that can be returned in one go. To find the current limit, refer to [this file (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/constants.nr) and look for `MAX_NOTES_PER_PAGE`. - -The key distinction is that this method is unconstrained. It does not perform a check to verify if the notes actually exist, which is something the [`get_notes`](#get_notes) method does under the hood. Therefore, it should only be used in an unconstrained contract function. - -This function requires a `NoteViewerOptions`. The `NoteViewerOptions` is essentially similar to the [`NoteGetterOptions`](#notegetteroptions), except that it doesn't take a custom filter. - -## `NoteGetterOptions` - -`NoteGetterOptions` encapsulates a set of configurable options for filtering and retrieving a selection of notes from a data oracle. Developers can design instances of `NoteGetterOptions`, to determine how notes should be filtered and returned to the functions of their smart contracts. - -You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr). - -### `selects: BoundedVec, N>` - -`selects` is a collection of filtering criteria, specified by `Select { property_selector: PropertySelector, comparator: u8, value: Field }` structs. It instructs the data oracle to find notes whose serialized field (as specified by the `PropertySelector`) matches the provided `value`, according to the `comparator`. The PropertySelector is in turn specified as having an `index` (nth position of the selected field in the serialized note), an `offset` (byte offset inside the selected serialized field) and `length` (bytes to read of the field from the offset). These values are not expected to be manually computed, but instead specified by passing functions autogenerated from the note definition. - -### `sorts: BoundedVec, N>` - -`sorts` is a set of sorting instructions defined by `Sort { property_selector: PropertySelector, order: u2 }` structs. This directs the data oracle to sort the matching notes based on the value of the specified PropertySelector and in the indicated order. The value of order is **1** for _DESCENDING_ and **2** for _ASCENDING_. - -### `limit: u32` - -When the `limit` is set to a non-zero value, the data oracle will return a maximum of `limit` notes. - -### `offset: u32` - -This setting enables us to skip the first `offset` notes. It's particularly useful for pagination. - -### `preprocessor: fn ([Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], PREPROCESSOR_ARGS) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]` - -Developers have the option to provide a custom preprocessor. -This allows specific logic to be applied to notes that meet the criteria outlined above. -The preprocessor takes the notes returned from the oracle and `preprocessor_args` as its parameters. - -An important distinction from the filter function described below is that preprocessor is applied first and unlike filter it is applied in an unconstrained context. - -### `preprocessor_args: PREPROCESSOR_ARGS` - -`preprocessor_args` provides a means to furnish additional data or context to the custom preprocessor. - -### `filter: fn ([Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], FILTER_ARGS) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]` - -Just like preprocessor just applied in a constrained context (correct execution is proven) and applied after the preprocessor. - -### `filter_args: FILTER_ARGS` - -`filter_args` provides a means to furnish additional data or context to the custom filter. - -### `status: u2` - -`status` allows the caller to retrieve notes that have been nullified, which can be useful to prove historical data. Note that when querying for both active and nullified notes the caller cannot know if each note retrieved has or has not been nullified. - -### Methods - -Several methods are available on `NoteGetterOptions` to construct the options in a more readable manner: - -### `fn new() -> NoteGetterOptions` - -This function initializes a `NoteGetterOptions` that simply returns the maximum number of notes allowed in a call. - -### `fn with_filter(filter, filter_args) -> NoteGetterOptions` - -This function initializes a `NoteGetterOptions` with a [`filter`](#filter-fn-optionnote-max_note_hash_read_requests_per_call-filter_args---optionnote-max_note_hash_read_requests_per_call) and [`filter_args`](#filter_args-filter_args). - -### `.select` - -This method adds a [`Select`](#selects-boundedvecoptionselect-n) criterion to the options. - -### `.sort` - -This method adds a [`Sort`](#sorts-boundedvecoptionsort-n) criterion to the options. - -### `.set_limit` - -This method lets you set a limit for the maximum number of notes to be retrieved. - -### `.set_offset` - -This method sets the offset value, which determines where to start retrieving notes. - -### `.set_status` - -This method sets the status of notes to retrieve (active or nullified). - -### Examples - -#### Example 1 - -The following code snippet creates an instance of `NoteGetterOptions`, which has been configured to find the cards that belong to an account with nullifying key hash equal to `account_npk_m_hash`. The returned cards are sorted by their points in descending order, and the first `offset` cards with the highest points are skipped. - -#include_code state_vars-NoteGetterOptionsSelectSortOffset /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust - -The first value of `.select` and `.sort` indicates the property of the note we're looking for. For this we use helper functions that are autogenerated from the note definition. `CardNote` that has the following fields: - -#include_code state_vars-CardNote /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/types/card_note.nr rust - -`CardNote::properties()` will return a struct with the values to pass for each field, which are related to their indices inside the `CardNote` struct, internal offset and length. - -In the example, `.select(CardNote::properties().npk_m_hash, Comparator.EQ, account_npk_m_hash)` matches notes which have the `npk_m_hash` field set to `account_npk_m_hash`. In this case we're using the equality comparator, but other operations exist in the `Comparator` utility struct. - -`.sort(0, SortOrder.DESC)` sorts the 0th field of `CardNote`, which is `points`, in descending order. - -There can be as many conditions as the number of fields a note type has. The following example finds cards whose fields match the three given values: - -#include_code state_vars-NoteGetterOptionsMultiSelects /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust - -While `selects` lets us find notes with specific values, `filter` lets us find notes in a more dynamic way. The function below picks the cards whose points are at least `min_points`, although this now can be done by using the select function with a GTE comparator: - -#include_code state_vars-OptionFilter /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust - -We can use it as a filter to further reduce the number of the final notes: - -#include_code state_vars-NoteGetterOptionsFilter /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust - -One thing to remember is, `filter` will be applied on the notes after they are picked from the database, so it is more efficient to use select with comparators where possible. Another side effect of this is that it's possible that the actual notes we end up getting are fewer than the limit. - -The limit is `MAX_NOTE_HASH_READ_REQUESTS_PER_CALL` by default. But we can set it to any value **smaller** than that: - -#include_code state_vars-NoteGetterOptionsPickOne /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/options.nr rust - -#### Example 2 - -An example of how we can use a Comparator to select notes when calling a Noir contract from aztec.js is below. - -#include_code state_vars-NoteGetterOptionsComparatorExampleTs /yarn-project/end-to-end/src/e2e_note_getter.test.ts typescript - -In this example, we use the above typescript code to invoke a call to our Noir contract below. This Noir contract function takes an input to match with, and a comparator to use when fetching and selecting notes from storage. - -#include_code state_vars-NoteGetterOptionsComparatorExampleNoir /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust diff --git a/docs/docs/developers/reference/smart_contract_reference/storage/public_state.md b/docs/docs/developers/reference/smart_contract_reference/storage/public_state.md deleted file mode 100644 index d04042c5d681..000000000000 --- a/docs/docs/developers/reference/smart_contract_reference/storage/public_state.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Public State -description: Learn how to manage public state in your Aztec smart contracts. ---- - -On this page we will look at how to manage public state in Aztec contracts. We will look at how to declare public state, how to read and write to it, and how to use it in your contracts. - -For a higher level overview of the state model in Aztec, see the [state model](../../../../aztec/concepts/storage/state_model.md) concepts page. - -## `PublicMutable` - -The `PublicMutable` (formerly known as `PublicState`) struct is generic over the variable type `T`. The type _must_ implement Serialize and Deserialize traits, as specified here: - -#include_code serialize /noir-projects/noir-protocol-circuits/crates/types/src/traits.nr rust -#include_code deserialize /noir-projects/noir-protocol-circuits/crates/types/src/traits.nr rust - -The struct contains a `storage_slot` which, similar to Ethereum, is used to figure out _where_ in storage the variable is located. Notice that while we don't have the exact same state model as EVM chains it will look similar from the contract developers point of view. - -You can find the details of `PublicMutable` in the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr). - -For a version of `PublicMutable` that can also be read in private, head to [`DelayedPublicMutable`](./delayed_public_mutable.md#delayedpublicmutable). - -:::info -An example using a larger struct can be found in the [lending example (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts/app/lending_contract)'s use of an [`Asset` (GitHub link)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/noir-contracts/contracts/app/lending_contract/src/asset.nr). -::: - -### `new` - -When declaring the storage for `T` as a persistent public storage variable, we use the `PublicMutable::new()` constructor. As seen below, this takes the `storage_slot` and the `serialization_methods` as arguments along with the `Context`, which in this case is used to share interface with other structures. You can view the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr). - -#### Single value example - -Say that we wish to add `admin` public state variable into our storage struct. In the struct we can define it as: - -#include_code storage-leader-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -#### Mapping example - -Say we want to have a group of `minters` that are able to mint assets in our contract, and we want them in public storage, because access control in private is quite cumbersome. In the `Storage` struct we can add it as follows: - -#include_code storage-minters-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `read` - -On the `PublicMutable` structs we have a `read` method to read the value at the location in storage. - -#### Reading from our `admin` example - -For our `admin` example from earlier, this could be used as follows to check that the stored value matches the `msg_sender()`. - -#include_code read_admin /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - -#### Reading from our `minters` example - -As we saw in the Map earlier, a very similar operation can be done to perform a lookup in a map. - -#include_code read_minter /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - -### `write` - -We have a `write` method on the `PublicMutable` struct that takes the value to write as an input and saves this in storage. It uses the serialization method to serialize the value which inserts (possibly multiple) values into storage. - -#### Writing to our `admin` example - -#include_code write_admin /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - -#### Writing to our `minters` example - -#include_code write_minter /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - ---- - -## `PublicImmutable` - -`PublicImmutable` is a type that is initialized from public once, typically during a contract deployment, but which can later be read from public, private and utility execution contexts. This state variable is useful for stuff that you would usually have in `immutable` values in Solidity, e.g. this can be the name of a token or its number of decimals. - -Just like the `PublicMutable` it is generic over the variable type `T`. The type `MUST` implement the `Serialize` and `Deserialize` traits. - -#include_code storage-public-immutable-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -You can find the details of `PublicImmutable` in the implementation [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr). - -### `new` - -Is done exactly like the `PublicMutable` struct, but with the `PublicImmutable` struct. - -#include_code storage-public-immutable-declaration /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `initialize` - -This function sets the immutable value. It can only be called once. - -#include_code initialize_decimals /noir-projects/noir-contracts/contracts/app/token_contract/src/main.nr rust - -:::warning -A `PublicImmutable`'s storage **must** only be set once via `initialize`. Attempting to override this by manually accessing the underlying storage slots breaks all properties of the data structure, rendering it useless. -::: - -#include_code initialize_public_immutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust - -### `read` - -Returns the stored immutable value. This function is available in public, private and utility contexts. - -#include_code read_public_immutable /noir-projects/noir-contracts/contracts/docs/docs_example_contract/src/main.nr rust diff --git a/docs/docs/developers/tutorials/contract_tutorials/counter_contract.md b/docs/docs/developers/tutorials/contract_tutorials/counter_contract.md index a71666bf4499..9db8bd7802e3 100644 --- a/docs/docs/developers/tutorials/contract_tutorials/counter_contract.md +++ b/docs/docs/developers/tutorials/contract_tutorials/counter_contract.md @@ -164,7 +164,7 @@ SERVE=1 aztec flamegraph target/counter-Counter.json increment Note the total gate count at the bottom of the image. The image is interactive; you can hover over different parts of the graph to see the full function name of the execution step and its gate count. This tool also provides insight into the low-level operations that are performed in the private function. Don't worry about the details of the internals of the function right now, just be aware that the more complex the function, the more gates it will use and try out the flamegraph tool on your own functions. -Read more about [profiling transactions with the flamegraph tool](../../guides/smart_contracts/profiling_transactions.md). +Read more about [profiling transactions with the flamegraph tool](../../guides/smart_contracts/advanced/profiling_transactions.md). For more information about writing efficient private functions, see [this page](https://noir-lang.org/docs/explainers/explainer-writing-noir) of the Noir documentation. diff --git a/docs/docs/developers/tutorials/contract_tutorials/crowdfunding_contract.md b/docs/docs/developers/tutorials/contract_tutorials/crowdfunding_contract.md index 894ee385213a..08943344bd1f 100644 --- a/docs/docs/developers/tutorials/contract_tutorials/crowdfunding_contract.md +++ b/docs/docs/developers/tutorials/contract_tutorials/crowdfunding_contract.md @@ -202,7 +202,7 @@ Follow the account contract tutorial on the [next page](./write_accounts_contrac ### Optional: Learn more about concepts mentioned here -- [Initializer functions](../../guides/smart_contracts/writing_contracts/initializers.md) +- [Initializer functions](../../guides/smart_contracts/define_functions.md#initializer-functions) - [Versions and Updating](../../guides/local_env/sandbox.md#updating). - [Authorizing actions](../../../aztec/concepts/advanced/authwit.md) -- [Public logs](../../guides/smart_contracts/writing_contracts/how_to_emit_event.md) +- [Public logs](../../guides/smart_contracts/how_to_emit_event.md) diff --git a/docs/docs/developers/tutorials/contract_tutorials/nft_contract.md b/docs/docs/developers/tutorials/contract_tutorials/nft_contract.md index dfd2a492f4ac..2a05face0978 100644 --- a/docs/docs/developers/tutorials/contract_tutorials/nft_contract.md +++ b/docs/docs/developers/tutorials/contract_tutorials/nft_contract.md @@ -174,7 +174,7 @@ Below the dependencies, paste the following Storage struct: ## Custom Notes -The contract storage uses a [custom note](../../guides/smart_contracts/writing_contracts/notes/implementing_a_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. +The contract storage uses a [custom note](../../guides/smart_contracts/note_types.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). diff --git a/docs/docs/developers/tutorials/js_tutorials/aztecjs-getting-started.md b/docs/docs/developers/tutorials/js_tutorials/aztecjs-getting-started.md index b5364c33b5d0..60c32d4250d6 100644 --- a/docs/docs/developers/tutorials/js_tutorials/aztecjs-getting-started.md +++ b/docs/docs/developers/tutorials/js_tutorials/aztecjs-getting-started.md @@ -46,9 +46,9 @@ Aztec.js assumes your project is using ESM, so make sure you add `"type": "modul We want to [connect to our running sandbox](../../guides/js_apps/connect_to_sandbox.md) and import the test accounts into the local PXE. Let's call them Alice and Bob (of course). Create an `index.ts` with it: ```typescript -import { createPXEClient } from '@aztec/aztec.js'; -import { getDeployedTestAccountsWallets } from '@aztec/accounts/testing' -const pxe = await createPXEClient('http://localhost:8080'); +import { createPXEClient } from "@aztec/aztec.js"; +import { getDeployedTestAccountsWallets } from "@aztec/accounts/testing"; +const pxe = await createPXEClient("http://localhost:8080"); const alice = (await getDeployedTestAccountsWallets(pxe))[0]; const bob = (await getDeployedTestAccountsWallets(pxe))[1]; @@ -61,12 +61,17 @@ Now that we have our accounts loaded, let's move on to deploy our pre-compiled t Let's import the interface directly, and make Alice the admin: ```typescript -import { TokenContract } from '@aztec/noir-contracts.js/Token'; - -const token = await TokenContract.deploy(alice, alice.getAddress(), 'TokenName', 'TKN', 18) - .send({ from: alice.getAddress() }) - .deployed(); - +import { TokenContract } from "@aztec/noir-contracts.js/Token"; + +const token = await TokenContract.deploy( + alice, + alice.getAddress(), + "TokenName", + "TKN", + 18 +) + .send({ from: alice.getAddress() }) + .deployed(); ``` ## Mint and transfer @@ -74,25 +79,36 @@ const token = await TokenContract.deploy(alice, alice.getAddress(), 'TokenName', Let's go ahead and have Alice mint herself some tokens, in private: ```typescript -await token.methods.mint_to_private(alice.getAddress(), 100).send({ from: alice.getAddress() }).wait(); +await token.methods + .mint_to_private(alice.getAddress(), 100) + .send({ from: alice.getAddress() }) + .wait(); ``` Let's check both Alice's and Bob's balances now: ```typescript -let aliceBalance = await token.methods.balance_of_private(alice.getAddress()).simulate({ from: alice.getAddress() }); +let aliceBalance = await token.methods + .balance_of_private(alice.getAddress()) + .simulate({ from: alice.getAddress() }); console.log(`Alice's balance: ${aliceBalance}`); // whoooaa 100 tokens -let bobBalance = await token.methods.balance_of_private(bob.getAddress()).simulate({ from: bob.getAddress() }); -console.log(`Bob's balance: ${bobBalance}`) // you get nothin' 🥹 - +let bobBalance = await token.methods + .balance_of_private(bob.getAddress()) + .simulate({ from: bob.getAddress() }); +console.log(`Bob's balance: ${bobBalance}`); // you get nothin' 🥹 ``` Great! Let's have Alice transfer some tokens to Bob, also in private: ```typescript -await token.methods.transfer(bob.getAddress(), 10).send({ from: alice.getAddress() }).wait(); -bobBalance = await token.methods.balance_of_private(bob.getAddress()).simulate({ from: bob.getAddress() }); -console.log(`Bob's balance: ${bobBalance}`) +await token.methods + .transfer(bob.getAddress(), 10) + .send({ from: alice.getAddress() }) + .wait(); +bobBalance = await token.methods + .balance_of_private(bob.getAddress()) + .simulate({ from: bob.getAddress() }); +console.log(`Bob's balance: ${bobBalance}`); ``` Nice, Bob should now see 10 tokens in his balance! Thanks Alice! @@ -102,15 +118,24 @@ Nice, Bob should now see 10 tokens in his balance! Thanks Alice! Say that Alice is nice and wants to set Bob as a minter. Even though it's a public function, it can be called in a similar way: ```typescript -await token.methods.set_minter(bob.getAddress(), true).send({ from: alice.getAddress() }).wait(); +await token.methods + .set_minter(bob.getAddress(), true) + .send({ from: alice.getAddress() }) + .wait(); ``` Bob is now the minter, so he can mint some tokens to himself, notice that for the time being, you need to bind `token` to Bob's wallet with `withWallet(bob)`: ```typescript -await token.withWallet(bob).methods.mint_to_private(bob.getAddress(), 100).send({ from: bob.getAddress() }).wait(); -bobBalance = await token.methods.balance_of_private(bob.getAddress()).simulate({ from: bob.getAddress() }); -console.log(`Bob's balance: ${bobBalance}`) +await token + .withWallet(bob) + .methods.mint_to_private(bob.getAddress(), 100) + .send({ from: bob.getAddress() }) + .wait(); +bobBalance = await token.methods + .balance_of_private(bob.getAddress()) + .simulate({ from: bob.getAddress() }); +console.log(`Bob's balance: ${bobBalance}`); ``` :::info diff --git a/docs/netlify.toml b/docs/netlify.toml index 5220f3f83666..55b96f29eb30 100644 --- a/docs/netlify.toml +++ b/docs/netlify.toml @@ -4,12 +4,20 @@ [[redirects]] from = "/guides/smart_contracts/writing_contracts/initializers" - to = "/developers/guides/smart_contracts/writing_contracts/initializers" + to = "/developers/guides/smart_contracts/define_functions" + +[[redirects]] + from = "/guides/smart_contracts/writing_contracts/*" + to = "/developers/guides/smart_contracts/" [[redirects]] from = "/reference/aztecjs/accounts" to = "/developers/reference/aztecjs/accounts" +[[redirects]] + from = "/developers/reference/smart_contract_reference/storage/*" + to = "/developers/guides/smart_contracts/storage_types" + [[redirects]] from = "/getting_started" to = "/developers/getting_started" diff --git a/docs/package.json b/docs/package.json index 8c07d545fbfd..9edc9b8ac77e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "HOST=0.0.0.0 ENV=dev yarn preprocess && docusaurus start --host ${HOST:-localhost}", - "build": "yarn clean && yarn preprocess && yarn preprocess:move && docusaurus build", + "build": "yarn clean && yarn spellcheck && yarn preprocess && yarn preprocess:move && docusaurus build", "clean": "./scripts/clean.sh", "serve": "docusaurus serve", "preprocess": "yarn node -r dotenv/config ./src/preprocess/index.js", diff --git a/docs/sidebars.js b/docs/sidebars.js index b0683532c4ff..65ad73f2765c 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -90,12 +90,12 @@ const sidebar = { }, { type: "html", - value: 'Tutorials', + value: 'Guides', className: "sidebar-title", }, { type: "autogenerated", - dirName: "developers/tutorials", + dirName: "developers/guides", }, { type: "html", @@ -103,12 +103,12 @@ const sidebar = { }, { type: "html", - value: 'How-to Guides', + value: 'Tutorials', className: "sidebar-title", }, { type: "autogenerated", - dirName: "developers/guides", + dirName: "developers/tutorials", }, { type: "html", diff --git a/docs/versioned_docs/version-v1.2.0/aztec/concepts/advanced/storage/storage_slots.md b/docs/versioned_docs/version-v1.2.0/aztec/concepts/advanced/storage/storage_slots.md index c24fb3864cad..10364b025371 100644 --- a/docs/versioned_docs/version-v1.2.0/aztec/concepts/advanced/storage/storage_slots.md +++ b/docs/versioned_docs/version-v1.2.0/aztec/concepts/advanced/storage/storage_slots.md @@ -57,5 +57,3 @@ By doing this address-siloing at the kernel circuit we _force_ the inserted comm :::info To ensure that nullifiers don't collide across contracts we also force this contract siloing at the kernel level. ::: - -For an example of this see [developer documentation on storage](../../../../developers/reference/smart_contract_reference/storage/index.md). diff --git a/docs/versioned_docs/version-v1.2.0/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md b/docs/versioned_docs/version-v1.2.0/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md index 70ff4e187661..ccce9958bdcd 100644 --- a/docs/versioned_docs/version-v1.2.0/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md +++ b/docs/versioned_docs/version-v1.2.0/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md @@ -17,31 +17,45 @@ Similarly we have discovered some anti-patterns too (like privacy leakage) that We call this the "authentication witness" pattern or authwit for short. - Approve someone in private domain: -```typescript title="authwit_to_another_sc" showLineNumbers + +```typescript title="authwit_to_another_sc" showLineNumbers // 4. Give approval to bridge to burn owner's funds: const withdrawAmount = 9n; const authwitNonce = Fr.random(); const burnAuthwit = await user1Wallet.createAuthWit({ caller: l2Bridge.address, - action: l2Token.methods.burn_private(ownerAddress, withdrawAmount, authwitNonce), + action: l2Token.methods.burn_private( + ownerAddress, + withdrawAmount, + authwitNonce + ), }); ``` -> Source code: yarn-project/end-to-end/src/e2e_cross_chain_messaging/token_bridge_private.test.ts#L71-L79 +> Source code: yarn-project/end-to-end/src/e2e_cross_chain_messaging/token_bridge_private.test.ts#L71-L79 Here you approve a contract to burn funds on your behalf. - Approve in public domain: -```typescript title="authwit_public_transfer_example" showLineNumbers + +```typescript title="authwit_public_transfer_example" showLineNumbers const action = asset .withWallet(wallets[1]) - .methods.transfer_in_public(accounts[0].address, accounts[1].address, amount, authwitNonce); + .methods.transfer_in_public( + accounts[0].address, + accounts[1].address, + amount, + authwitNonce + ); -const validateActionInteraction = await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true); +const validateActionInteraction = await wallets[0].setPublicAuthWit( + { caller: accounts[1].address, action }, + true +); await validateActionInteraction.send().wait(); ``` -> Source code: yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts#L69-L76 +> Source code: yarn-project/end-to-end/src/e2e_token_contract/transfer_in_public.test.ts#L69-L76 Here you approve someone to transfer funds publicly on your behalf @@ -51,7 +65,7 @@ E.g. you don't want a user to subscribe once they have subscribed already. Or yo Emit a nullifier in your function. By adding this nullifier into the tree, you prevent another nullifier from being added again. This is also why in authwit, we emit a nullifier, to prevent someone from reusing their approval. -```rust title="verify_private_authwit" showLineNumbers +```rust title="verify_private_authwit" showLineNumbers pub fn verify_private_authwit(self, inner_hash: Field) -> Field { // The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can // consume the message. @@ -67,8 +81,8 @@ pub fn verify_private_authwit(self, inner_hash: Field) -> Field { IS_VALID_SELECTOR } ``` -> Source code: noir-projects/aztec-nr/aztec/src/authwit/account.nr#L72-L87 +> Source code: noir-projects/aztec-nr/aztec/src/authwit/account.nr#L72-L87 Note be careful to ensure that the nullifier is not deterministic and that no one could do a preimage analysis attack. More in [the anti pattern section on deterministic nullifiers](#deterministic-nullifiers) @@ -124,7 +138,7 @@ When you send someone a note, the note hash gets added to the note hash tree. To 1. When sending someone a note, emit the note log to the recipient (the function encrypts the log in such a way that only a recipient can decrypt it). PXE then tries to decrypt all the encrypted logs, and stores the successfully decrypted one. [More info here](../how_to_emit_event.md) 2. Manually delivering it via a custom contract method, if you choose to not emit logs to save gas or when creating a note in the public domain and want to consume it in private domain (`encrypt_and_emit_note` shouldn't be called in the public domain because everything is public), like in the previous section where we created a note in public that doesn't have a designated owner. -```typescript title="offchain_delivery" showLineNumbers +```typescript title="offchain_delivery" showLineNumbers const txEffects = await pxe.getTxEffect(txHash); await contract.methods .deliver_transparent_note( @@ -134,16 +148,16 @@ await contract.methods txHash.hash, txEffects!.data.noteHashes, txEffects!.data.nullifiers[0], - recipient, + recipient ) .simulate(); ``` -> Source code: yarn-project/end-to-end/src/composed/e2e_persistence.test.ts#L358-L371 +> Source code: yarn-project/end-to-end/src/composed/e2e_persistence.test.ts#L358-L371 Note that this requires your contract to have a utility function that processes these notes and adds them to PXE. -```rust title="deliver_note_contract_method" showLineNumbers +```rust title="deliver_note_contract_method" showLineNumbers // We cannot replace this function with the standard `process_message` function because the transparent note // originates in public and hence we cannot emit it as an offchain message. We could construct the offchain message // "manually" and then pass it to the `process_message` function, but this doesn't seem to be worth the effort @@ -159,8 +173,8 @@ unconstrained fn deliver_transparent_note( recipient: AztecAddress, ) { ``` -> Source code: noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr#L301-L316 +> Source code: noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr#L301-L316 ### Revealing encrypted logs conditionally @@ -178,7 +192,7 @@ Notes are hashed and stored in the merkle tree. While notes do have a header wit Hence, it's necessary to add a "randomness" field to your note to prevent such attacks. -```rust title="address_note_def" showLineNumbers +```rust title="address_note_def" showLineNumbers #[note] #[derive(Eq)] pub struct AddressNote { @@ -198,8 +212,8 @@ impl AddressNote { } } ``` -> Source code: noir-projects/aztec-nr/address-note/src/address_note.nr#L5-L24 +> Source code: noir-projects/aztec-nr/address-note/src/address_note.nr#L5-L24 ### L1 -- L2 interactions @@ -243,7 +257,7 @@ E.g. for a voting contract, if your nullifier simply emits just the `user_addres Here is an example from the voting contract: -```rust title="cast_vote" showLineNumbers +```rust title="cast_vote" showLineNumbers #[private] // annotation to mark function as private and expose private context fn cast_vote(candidate: Field) { @@ -257,5 +271,5 @@ fn cast_vote(candidate: Field) { ); } ``` -> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L38-L51 +> Source code: noir-projects/noir-contracts/contracts/app/easy_private_voting_contract/src/main.nr#L38-L51 diff --git a/noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr index a8c1ef22dfff..95fd3592851d 100644 --- a/noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/escrow_contract/src/main.nr @@ -29,6 +29,7 @@ pub contract Escrow { storage.owner.initialize(note).emit(encode_and_encrypt_note(&mut context, owner)); } + // docs:start:withdraw // Withdraws balance. Requires that msg.sender is the owner. #[private] fn withdraw(token: AztecAddress, amount: u128, recipient: AztecAddress) { @@ -40,4 +41,5 @@ pub contract Escrow { Token::at(token).transfer(recipient, amount).call(&mut context); // docs:end:call_function } + // docs:end:withdraw } diff --git a/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr index d298749a9ccd..ca8e80cefefc 100644 --- a/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/nft_contract/src/main.nr @@ -49,8 +49,10 @@ pub contract NFT { admin: PublicMutable, // Addresses that can mint minters: Map, Context>, + // docs:start:private_map // Contains the NFTs owned by each address in private. private_nfts: Map, Context>, + // docs:end:private_map // A map from token ID to a boolean indicating if the NFT exists. nft_exists: Map, Context>, // A map from token ID to the public owner of the NFT. diff --git a/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr index 32856233823b..775dafa6fbe1 100644 --- a/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/simple_token_contract/src/main.nr @@ -321,6 +321,7 @@ pub contract SimpleToken { context.push_nullifier(nullifier); } + // docs:start:subtract_balance #[contract_library_method] fn subtract_balance( context: &mut PrivateContext, @@ -338,6 +339,7 @@ pub contract SimpleToken { compute_recurse_subtract_balance_call(*context, account, remaining).call(context) } } + // docs:end:subtract_balance #[no_predicates] #[contract_library_method] diff --git a/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/types/transparent_note.nr index 135acef5091f..9eb6614496a3 100644 --- a/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/types/transparent_note.nr +++ b/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/types/transparent_note.nr @@ -21,6 +21,7 @@ pub struct TransparentNote { secret_hash: Field, } +// docs:start:transparent_note_impl impl NoteHash for TransparentNote { fn compute_note_hash(self, storage_slot: Field) -> Field { let inputs = self.pack().concat([storage_slot]); @@ -51,6 +52,7 @@ impl NoteHash for TransparentNote { self.compute_nullifier(zeroed(), note_hash_for_nullify) } } +// docs:end:transparent_note_impl impl TransparentNote { // CONSTRUCTORS