From e8bcd037c04e822c4894c156ed9450a5e84d8f15 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:04:27 +0000 Subject: [PATCH] feat: initial storage slots docs (#2842) Adds an initial explanation around storage slots, how they are computed and used between private and public data. --- .../concepts/foundation/accounts/authwit.md | 2 +- docs/docs/concepts/foundation/main.md | 4 +- .../{state_model.md => state_model/main.md} | 2 +- .../foundation/state_model/storage_slots.md | 65 +++++++++++++++++++ docs/docs/dev_docs/contracts/layout.md | 2 +- .../dev_docs/contracts/syntax/context.mdx | 4 +- .../dev_docs/contracts/syntax/functions.md | 2 +- docs/docs/dev_docs/contracts/syntax/main.md | 2 +- .../syntax/{storage.md => storage/main.md} | 47 ++++---------- .../contracts/syntax/storage/storage_slots.md | 64 ++++++++++++++++++ docs/docs/dev_docs/tutorials/testing.md | 6 +- .../writing_dapp/contract_interaction.md | 2 +- .../tutorials/writing_token_contract.md | 4 +- docs/sidebars.js | 24 ++++++- .../src/contracts/token_contract/src/main.nr | 4 ++ 15 files changed, 183 insertions(+), 51 deletions(-) rename docs/docs/concepts/foundation/{state_model.md => state_model/main.md} (97%) create mode 100644 docs/docs/concepts/foundation/state_model/storage_slots.md rename docs/docs/dev_docs/contracts/syntax/{storage.md => storage/main.md} (88%) create mode 100644 docs/docs/dev_docs/contracts/syntax/storage/storage_slots.md diff --git a/docs/docs/concepts/foundation/accounts/authwit.md b/docs/docs/concepts/foundation/accounts/authwit.md index bb0e5ca367f..27e7edec8cc 100644 --- a/docs/docs/concepts/foundation/accounts/authwit.md +++ b/docs/docs/concepts/foundation/accounts/authwit.md @@ -54,7 +54,7 @@ All of these issues have been discussed in the community for a while, and there Adopting ERC20 for Aztec is not as simple as it might seem because of private state. -If you recall from [State model](./../state_model.md), private state is generally only known by its owner and those they have shared it with. Because it relies on secrets, private state might be "owned" by a contract, but it needs someone with knowledge of these secrets to actually spend it. You might see where this is going. +If you recall from [State model](./../state_model/main.md), private state is generally only known by its owner and those they have shared it with. Because it relies on secrets, private state might be "owned" by a contract, but it needs someone with knowledge of these secrets to actually spend it. You might see where this is going. If we were to implement the `approve` with an allowance in private, you might know the allowance, but unless you also know about the individual notes that make up the user's balances, it would be of no use to you! It is private after all. To spend the user's funds you would need to know the decryption key, see [keys for more](../accounts/keys.md). diff --git a/docs/docs/concepts/foundation/main.md b/docs/docs/concepts/foundation/main.md index 80427ba3eba..0791cd279d3 100644 --- a/docs/docs/concepts/foundation/main.md +++ b/docs/docs/concepts/foundation/main.md @@ -6,7 +6,7 @@ As a layer 2 rollup on Ethereum, the Aztec network includes components that look On this page we will introduce the high level network architecture for Aztec with an emphasis on the concepts that are core to understanding Aztec, including: -- [The state model](./state_model.md) +- [The state model](./state_model/main.md) - [Accounts](./accounts/main.md) - [Aztec Smart Contracts](./contracts.md) - [Transactions](./transactions.md) @@ -24,7 +24,7 @@ A user of the Aztec network will interact with the network through Aztec.js. Azt ### Private Execution Environment -The PXE provides a secure environment for the execution of sensitive operations, ensuring private information and decrypted data are not accessible to unauthorized applications. It hides the details of the [state model](./state_model.md) from end users, but the state model is important for Aztec developers to understand as it has implications for [private/public execution](./communication/public_private_calls.md) and [L1/L2 communication](./communication/cross_chain_calls.md). The PXE also includes the [ACIR Simulator](../advanced/acir_simulator.md) for private executions and the KeyStore for secure key management. +The PXE provides a secure environment for the execution of sensitive operations, ensuring private information and decrypted data are not accessible to unauthorized applications. It hides the details of the [state model](./state_model/main.md) from end users, but the state model is important for Aztec developers to understand as it has implications for [private/public execution](./communication/public_private_calls.md) and [L1/L2 communication](./communication/cross_chain_calls.md). The PXE also includes the [ACIR Simulator](../advanced/acir_simulator.md) for private executions and the KeyStore for secure key management. Procedurally, the PXE sends results of private function execution and requests for public function executions to the [sequencer](./nodes_clients/sequencer.md), which will update the state of the rollup. diff --git a/docs/docs/concepts/foundation/state_model.md b/docs/docs/concepts/foundation/state_model/main.md similarity index 97% rename from docs/docs/concepts/foundation/state_model.md rename to docs/docs/concepts/foundation/state_model/main.md index b02aefaf4bb..80ad4f9554b 100644 --- a/docs/docs/concepts/foundation/state_model.md +++ b/docs/docs/concepts/foundation/state_model/main.md @@ -55,4 +55,4 @@ This is achieved with two main features: ## Further reading -Read more about how to leverage the Aztec state model in Aztec contracts [here](../../dev_docs/contracts/syntax/storage.md). +Read more about how to leverage the Aztec state model in Aztec contracts [here](../../../dev_docs/contracts/syntax/storage/main.md). diff --git a/docs/docs/concepts/foundation/state_model/storage_slots.md b/docs/docs/concepts/foundation/state_model/storage_slots.md new file mode 100644 index 00000000000..9941d53fc72 --- /dev/null +++ b/docs/docs/concepts/foundation/state_model/storage_slots.md @@ -0,0 +1,65 @@ +--- +title: Storage Slots +description: How are storage slots derived for public and private state +--- + +In Aztec private data and public data are stored in two trees, a public data tree and a note hashes tree. + +These trees have in common that they store state for *all* accounts on the Aztec network directly as leaves. This is different from Ethereum, where a state trie contains smaller tries that hold the individual accounts' storage. + +It also means that we need to be careful about how we allocate storage to ensure that they don't collide! We say that storage should be *siloed* to its contract. The exact way of siloing differs a little for public and private storage. Which we will see in the following sections. + +## Public State Slots + +As mentioned in [State Model](./main.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. + +The mental model is that we have a key-value store, where the siloed slot is the key, and the value is the data stored in that slot. You can think of the `real_storage_slot` identifying its position in the tree, and the `logical_storage_slot` identifying the position in the contract storage. + +```rust +real_storage_slot = H(contract_address, logical_storage_slot) +``` + +The siloing is performed by the [Kernel circuits](../../advanced/circuits/kernels/main.md). + +For structs and arrays, we are logically using a similar storage slot computation to ethereum, e.g., as a struct with 3 fields would be stored in 3 consecutive slots. However, because the "actual" storage slot is computed as a hash of the contract address and the logical storage slot, the actual storage slot is not consecutive. + + +## Private State Slots - Slots aren't real + +Private storage is a different beast. As you might remember from [State Model](./main.md), private state is stored in encrypted logs and the corresponding private state commitments in append-only tree where each leaf is a commitment. Being append-only, means that leaves are never updated or deleted; instead a nullifier is emitted to signify that some note is no longer valid. A major reason we used this tree, is that lookups at a specific storage slot would leak information in the context of private state. If you could look up a specific address balance just by looking at the storage slot, even if encrypted you would be able to see it changing! That is not good privacy. + +Following this. The storage slot as we know it doesn't really exists. The leaves of the note hashes tree are just commitments to content (think of it as a hash of its content). + +Nevertheless, the concept of a storage slot is very useful when writing applications, since it allows us to reason about distinct and disjoint pieces of data. For example we can say that the balance of an account is stored in a specific slot and that the balance of another account is stored in another slot with the total supply stored in some third slot. By making sure that these slots are disjoint, we can be sure that the balances are not mixed up and that someone cannot use the total supply as their balance. + +### But how? + +If we include the storage slot, as part of the note whose commitment is stored in the note hashes tree, we can *logically link* all the notes that make up the storage slot. For the case of a balance, we can say that the balance is the sum of all the notes that have the same storage slot - in the same way that your physical \$ balance might be the sum of all the notes in your wallet. + +Similarly to how we siloed the public storage slots, we can silo our private storage by hashing the logical storage slot together with the note content. + +```rust +note_hash = H(...note_content); +commitment = H(logical_storage_slot, note_hash); +``` + +This siloing (there will be more) is done in the application circuit, since it is not necessary for security of the network (but only the application). +:::info +The private variable wrappers `Set` and `Singleton` in Aztec.nr include the `logical_storage_slot` in the commitments they compute, to make it easier for developers to write contracts without having to think about how to correctly handle storage slots. +::: + +When reading the values for these notes, the application circuit can then constrain the values to only read notes with a specific logical storage slot. + +To ensure that one contract cannot insert storage that other contracts would believe is theirs, we do a second siloing by hashing the `commitment` with the contract address. + +```rust +siloed_commitment = H(contract_address, commitment); +``` + +By doing this address-siloing at the kernel circuit we *force* the inserted commitments to include and not lie about the `contract_address`. + +:::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 storage slots](./../../../dev_docs/contracts/syntax/storage/storage_slots.md). \ No newline at end of file diff --git a/docs/docs/dev_docs/contracts/layout.md b/docs/docs/dev_docs/contracts/layout.md index 45923293eab..839bf28b8f5 100644 --- a/docs/docs/dev_docs/contracts/layout.md +++ b/docs/docs/dev_docs/contracts/layout.md @@ -2,7 +2,7 @@ title: Structure --- -A contract is a collection of persistent [state variables](./syntax/storage.md), and [functions](./syntax/functions) which may manipulate these variables. Functions and state variables within a contract's scope are said to belong to that contract. A contract can only access and modify its own state. If a contract wishes to access or modify another contract's state, it must make a call to an external function of the other contract. For anything to happen on the Aztec network, an external function of a contract needs to be called. +A contract is a collection of persistent [state variables](./syntax/storage/main.md), and [functions](./syntax/functions) which may manipulate these variables. Functions and state variables within a contract's scope are said to belong to that contract. A contract can only access and modify its own state. If a contract wishes to access or modify another contract's state, it must make a call to an external function of the other contract. For anything to happen on the Aztec network, an external function of a contract needs to be called. # Contract diff --git a/docs/docs/dev_docs/contracts/syntax/context.mdx b/docs/docs/dev_docs/contracts/syntax/context.mdx index bab10efeac7..f73bfbe72ad 100644 --- a/docs/docs/dev_docs/contracts/syntax/context.mdx +++ b/docs/docs/dev_docs/contracts/syntax/context.mdx @@ -23,9 +23,7 @@ On this page, you'll learn - Differences between the private and public contexts, especially the unique features and variables in the public context ## Two context's one API - -The `Aztec` blockchain contains two environments [public and private](../../../concepts/foundation/state_model.md). - +The `Aztec` blockchain contains two environments [public and private](../../../concepts/foundation/state_model/main.md). - Private, for private transactions taking place on user's devices. - Public, for public transactions taking place on the network's sequencers. diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index cd6634f426e..996e369dff1 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -314,7 +314,7 @@ We achieve this by pushing return values to the execution context, which we then **Making the contract's storage available** #include_code storage-example-context /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust -When a [`Storage` struct](./storage.md) is declared within a contract, the `storage` keyword is made available. As shown in the macro expansion above, this calls the init function on the storage struct with the current function's context. +When a [`Storage` struct](./storage/main.md) is declared within a contract, the `storage` keyword is made available. As shown in the macro expansion above, this calls the init function on the storage struct with the current function's context. Any state variables declared in the `Storage` struct can now be accessed as normal struct members. diff --git a/docs/docs/dev_docs/contracts/syntax/main.md b/docs/docs/dev_docs/contracts/syntax/main.md index 1294c599cdf..17a40bf9f42 100644 --- a/docs/docs/dev_docs/contracts/syntax/main.md +++ b/docs/docs/dev_docs/contracts/syntax/main.md @@ -9,7 +9,7 @@ On top of [Noir's stdlib](https://noir-lang.org/standard_library/array_methods), Aztec.nr contains abstractions which remove the need to understand the low-level Aztec protocol. Notably, it provides: -- Public and private [state variable types](./storage.md) +- Public and private [state variable types](./storage/main.md) - Some pre-designed notes - Functions for [emitting](./events.md) encrypted and unencrypted logs - [Oracle functions](./functions.md#oracle-functions) for accessing: diff --git a/docs/docs/dev_docs/contracts/syntax/storage.md b/docs/docs/dev_docs/contracts/syntax/storage/main.md similarity index 88% rename from docs/docs/dev_docs/contracts/syntax/storage.md rename to docs/docs/dev_docs/contracts/syntax/storage/main.md index 3e56d37536b..0a33ce92a6a 100644 --- a/docs/docs/dev_docs/contracts/syntax/storage.md +++ b/docs/docs/dev_docs/contracts/syntax/storage/main.md @@ -23,7 +23,7 @@ On this page, you’ll learn: Public state variables can be read by anyone, while private state variables can only be read by their owner (or people whom the owner has shared the decrypted data or note viewing key with). -Public state follows the Ethereum style account model, where each contract has its own key-value datastore. Private state follows a UTXO model, where note contents (pre-images) are only known by the sender and those able to decrypt them - see ([state model](./../../../concepts/foundation/state_model.md) and [private/public execution](./../../../concepts/foundation/communication/public_private_calls.md)) for more background. +Public state follows the Ethereum style account model, where each contract has its own key-value datastore. Private state follows a UTXO model, where note contents (pre-images) are only known by the sender and those able to decrypt them - see ([state model](../../../../concepts/foundation/state_model/main.md) and [private/public execution](../../../../concepts/foundation/communication/public_private_calls.md)) for more background. ## Storage struct @@ -39,7 +39,7 @@ struct Storage { ``` :::danger -If your contract works with storage (has Storage struct defined), you **MUST** include a `compute_note_hash_and_nullifier` function to allow PXE to process encrypted events. See [encrypted events](./events.md#processing-encrypted-events) for more. +If your contract works with storage (has Storage struct defined), you **MUST** include a `compute_note_hash_and_nullifier` function to allow PXE to process encrypted events. See [encrypted events](../events.md#processing-encrypted-events) for more. If you don't yet have any private state variables defined put there a placeholder function: @@ -65,27 +65,6 @@ If you have defined a `Storage` struct following this naming scheme, then it wil No storage values should be initialized at slot `0` - storage slots begin at `1`. This is a known issue that will be fixed in the future. ::: -## Storage Slots - -Public state in Aztec is implemented as a single global merkle tree of depth 254, with each contract's internal storage slot combined with its contract address to generate its position in the global tree as `global_storage_slot = pedersen_hash(contract_storage_slot, contract_address)`. - -A contract's state is represented by mapping its own storage slots to each variable. For now, storage slot positions for each variable must be explicitly assigned inside the `Storage impl`. Although variables can be arrays or structs that are stored internally as contiguous blocks, each variable in the storage definition takes just 1 block, so you can increment the storage slot by 1 each time you add another variable, whether it is public or private. - -When assigning contract storage slots, `Map`s are also treated as occupying only 1 storage slot (its "base_slot"), because the actual values in the global state tree are stored in derived slots calculated as `map_value_storage_slot = pedersen_hash(base_slot, key)`. - -Private state is stored in a separate UTXO tree, but each private variable is still assigned a storage slot to track the meaning of the note. Each private variable's storage slot is contained in a contract's bytecode, but they do not appear at all in the global storage tree. Each contract private variable can be associated with 0, 1, or multiple notes in the UTXO tree (and some of those may have already been spent, if their nullifier is already present in the nullifier tree). -The position of each note in the UTXO tree does not matter - the relationship to contract state is contained entirely in its note header. - -:::info -Private variables only require one single slot, because all notes are linked their contract variable through the `storage slot` attribute in their note header (which also contains the contract address and nonce). -::: - -We currently do not support any "bit packing" type optimizations as in most EVM languages. - -:::note -The choice of hash function for global slot position is subject to change in later versions. -::: - ## Map A `map` is a state variable that "maps" a key to a value. It can be used with private or public storage variables. @@ -94,13 +73,13 @@ A `map` is a state variable that "maps" a key to a value. It can be used with pr 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 for now. ::: -It includes a [`Context`](./context.mdx) 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. +It includes a [`Context`](../context.mdx) 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](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/state_vars/map.nr). ### `new` -When declaring the storage for a map, we use the `Map::new()` constructor. As seen below, this takes the `storage_slot` and the `start_var_constructor` along with the [`Context`](./context.mdx). +When declaring the storage for a map, we use the `Map::new()` constructor. As seen below, this takes the `storage_slot` and the `start_var_constructor` along with the [`Context`](../context.mdx). We will see examples of map constructors for public and private variables in later sections. @@ -151,7 +130,7 @@ The `PublicState` struct is generic over the variable type `T` and its serialize Currently, the length of the types must be specified when declaring the storage struct but the intention is that this will be inferred in the future. ::: -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](./../../../concepts/foundation/state_model.md) as EVM chains it will look similar from the contract developers point of view. +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](../../../../concepts/foundation/state_model/main.md) as EVM chains it will look similar from the contract developers point of view. Beyond the struct, the `PublicState` also contains `serialization_methods`, which is a struct with methods that instruct the `PublicState` how to serialize and deserialize the variable. You can find the details of `PublicState` in the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr). @@ -167,7 +146,9 @@ An example using a larger struct can be found in the [lending example](https://g ### `new` -When declaring the storage for `T` as a persistent public storage variable, we use the `PublicState::new()` constructor. This takes the `storage_slot` and the `serialization_methods` as arguments along with the [`Context`](./context.mdx), which in this case is used to share interface with other structures. +When declaring the storage for `T` as a persistent public storage variable, we use the `PublicState::new()` constructor. As seen below, this takes the `storage_slot` and the `serialization_methods` as arguments along with the [`Context`](../context.mdx), which in this case is used to share interface with other structures. + +#include_code public_state_struct_new /yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr rust #### Single value example @@ -191,7 +172,7 @@ We know its verbose, and are working on making it less so. #### 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](./../../../concepts/foundation/communication/public_private_calls.md#a-note-on-l2-access-control). In the `Storage` struct we can add it as follows: +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](../../../../concepts/foundation/communication/public_private_calls.md#a-note-on-l2-access-control). In the `Storage` struct we can add it as follows: #include_code storage_minters /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust @@ -239,9 +220,9 @@ We have a `write` method on the `PublicState` struct that takes the value to wri 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](./events.md#encrypted-events), or offchain via web2, or completely offline: it's up to the app developer. +The value of a private state variable can either be shared via an [encrypted log](../events.md#encrypted-events), or offchain via web2, or completely offline: it's up to the app developer. -Aztec private state follows a utxo-based model. That is, a private state's current value is represented as one or many [notes](#notes). Each note is stored as an individual leaf in a utxo-based merkle tree: the [private state tree](./../../../concepts/foundation/state_model.md). +Aztec private state follows a utxo-based model. That is, a private state's current value is represented as one or many [notes](#notes). Each note is stored as an individual leaf in a utxo-based merkle tree: the [private state tree](../../../../concepts/foundation/state_model/main.md). To greatly simplify the experience of writing private state, Aztec.nr provides three different types of private state variable: @@ -391,7 +372,7 @@ Set is used for managing a collection of notes. All notes in a set are of the sa #include_code struct /yarn-project/aztec-nr/aztec/src/state_vars/set.nr rust -And can be added to the `Storage` struct as follows. Here adding a set for a custom note, the TransparentNote (useful for [public -> private communication](./functions.md#public---private)). +And can be added to the `Storage` struct as follows. Here adding a set for a custom note, the TransparentNote (useful for [public -> private communication](../functions.md#public---private)). #include_code storage_pending_shields /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust @@ -409,7 +390,7 @@ Allows us to modify the storage by inserting a note into the set. A commitment from the note will be generated, and inserted into data-tree, allowing us to later use in contract interactions. -The content of the note should be shared with the owner to allow them to use it. This can be done via an [encrypted log](./events.md#encrypted-events), or offchain via web2, or completely offline. +A commitment from the note will be generated, and inserted into data-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](../events.md#encrypted-events), or offchain via web2, or completely offline. #include_code insert /yarn-project/aztec-nr/easy-private-state/src/easy_private_state.nr rust @@ -457,7 +438,7 @@ This function requires a `NoteViewerOptions`. The `NoteViewerOptions` is essenti ### NoteGetterOptions -`NoteGetterOptions` encapsulates a set of configurable options for filtering and retrieving a selection of notes from a [data oracle](./functions.md#oracle-functions). Developers can design instances of `NoteGetterOptions`, to determine how notes should be filtered and returned to the functions of their smart contracts. +`NoteGetterOptions` encapsulates a set of configurable options for filtering and retrieving a selection of notes from a [data oracle](../functions.md#oracle-functions). Developers can design instances of `NoteGetterOptions`, to determine how notes should be filtered and returned to the functions of their smart contracts. #include_code NoteGetterOptions /yarn-project/aztec-nr/aztec/src/note/note_getter_options.nr rust diff --git a/docs/docs/dev_docs/contracts/syntax/storage/storage_slots.md b/docs/docs/dev_docs/contracts/syntax/storage/storage_slots.md new file mode 100644 index 00000000000..17e49df55ca --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/storage/storage_slots.md @@ -0,0 +1,64 @@ +--- +title: Storage slots +--- + +From the description of [storage slot in concepts](./../../../../concepts/foundation/state_model/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 yarn-project/noir-contracts/src/contracts/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 commitment 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 commitment 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 = TokenNote::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, is_transient: true } + LifeCycle->>Utils: compute_inner_note_hash(note) + Utils->>TokenNote: compute_note_hash(note) + TokenNote->>Utils: note_hash = H(amount, to, randomness) + Utils->>NoteHash: compute_inner_hash(derived_slot, note_hash) + NoteHash->>LifeCycle: inner_note_hash = H(derived_slot, note_hash) + LifeCycle->>Context: push_new_note_hash(inner_note_hash) + end + Context->>Kernel: siloed_note_hash = H(contract_address, inner_note_hash) +``` + +Notice the `siloed_note_hash` at the very end. It's a commitment 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, inner_note_hash) +siloed_note_hash = H(contract_address, H(derived_slot, note_hash)) +siloed_note_hash = H(contract_address, H(H(map_slot, to), note_hash)) +siloed_note_hash = H(contract_address, H(H(map_slot, to), H(amount, to, randomness))) +``` + +Where the `map_slot` is the slot specified in `Storage::init`, recall: + +#include_code storage_balances_init yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +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 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). \ No newline at end of file diff --git a/docs/docs/dev_docs/tutorials/testing.md b/docs/docs/dev_docs/tutorials/testing.md index aca41afc899..ae6988dabde 100644 --- a/docs/docs/dev_docs/tutorials/testing.md +++ b/docs/docs/dev_docs/tutorials/testing.md @@ -144,13 +144,13 @@ In the near future, transactions where a public function call fails will get min 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().view()`. Bear in mind that directly accessing contract storage will break any kind of encapsulation. -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](../contracts/syntax/storage.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`](./../testing/cheat_codes.md) utility class: +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](../contracts/syntax/storage/main.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`](./../testing/cheat_codes.md) utility class: #include_code calc-slot /yarn-project/end-to-end/src/guides/dapp_testing.test.ts typescript #### Querying private state -Private state in the Aztec Network is represented via sets of [private notes](../../concepts/foundation/state_model.md#private-state). In our token contract example, the balance of a user is represented as a set of unspent value notes, each with their own corresponding numeric value. +Private state in the Aztec Network is represented via sets of [private notes](../../concepts/foundation/state_model/main.md#private-state). In our token contract example, the balance of a user is represented as a set of unspent value notes, each with their own corresponding numeric value. #include_code value-note-def yarn-project/aztec-nr/value-note/src/value_note.nr rust @@ -160,7 +160,7 @@ We can query the Private eXecution Environment (PXE) for all notes encrypted for #### Querying public state -[Public state](../../concepts/foundation/state_model.md#public-state) behaves as a key-value store, much like in the EVM. This scenario is much more straightforward, in that we can directly query the target slot and get the result back as a buffer. Note that we use the [`TokenContract`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr) in this example, which defines a mapping of public balances on slot 6. +[Public state](../../concepts/foundation/state_model/main.md#public-state) behaves as a key-value store, much like in the EVM. This scenario is much more straightforward, in that we can directly query the target slot and get the result back as a buffer. Note that we use the [`TokenContract`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr) in this example, which defines a mapping of public balances on slot 6. #include_code public-storage /yarn-project/end-to-end/src/guides/dapp_testing.test.ts typescript diff --git a/docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md b/docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md index 6cc2e502d11..45f7ad4bf71 100644 --- a/docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md +++ b/docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md @@ -93,7 +93,7 @@ At the time of this writing, there are no events emitted when new private notes ## Working with public state -While they are [fundamentally differently](../../../concepts/foundation/state_model.md), the API for working with private and public functions and state from `aztec.js` is equivalent. To query the balance in public tokens for our user accounts, we can just call the `balance_of_public` view function in the contract: +While [private and public state](../../../concepts/foundation/state_model/main.md) are fundamentally different, the API for working with private and public functions and state from `aztec.js` is equivalent. To query the balance in public tokens for our user accounts, we can just call the `balance_of_public` view function in the contract: #include_code showPublicBalances yarn-project/end-to-end/src/sample-dapp/index.mjs javascript diff --git a/docs/docs/dev_docs/tutorials/writing_token_contract.md b/docs/docs/dev_docs/tutorials/writing_token_contract.md index 8da71a9cadc..8e8b6297494 100644 --- a/docs/docs/dev_docs/tutorials/writing_token_contract.md +++ b/docs/docs/dev_docs/tutorials/writing_token_contract.md @@ -248,7 +248,7 @@ We are also importing types from a `types.nr` file. The main thing to note from ### Note on private state -Private state in Aztec is all [UTXOs](https://en.wikipedia.org/wiki/Unspent_transaction_output) under the hood. Handling UTXOs is largely abstracted away from developers, but there are some unique things for developers to be aware of when creating and managing private state in an Aztec contract. See [State Variables](../contracts/syntax/storage.md) to learn more about public and private state in Aztec. +Private state in Aztec is all [UTXOs](https://en.wikipedia.org/wiki/Unspent_transaction_output) under the hood. Handling UTXOs is largely abstracted away from developers, but there are some unique things for developers to be aware of when creating and managing private state in an Aztec contract. See [State Variables](../contracts/syntax/storage/main.md) to learn more about public and private state in Aztec. ## Contract Storage @@ -267,7 +267,7 @@ Reading through the storage variables: - `pending_shields` is a `Set` of `TransparentNote`s stored in private state. What is stored publicly is a set of commitments to `TransparentNote`s. - `public_balances` is a mapping field elements in public state and represents the publicly viewable balances of accounts. -You can read more about it [here](../contracts/syntax/storage.md). +You can read more about it [here](../contracts/syntax/storage/main.md). ### Initializing Storage diff --git a/docs/sidebars.js b/docs/sidebars.js index 951351bb584..629d59622e9 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -74,7 +74,17 @@ const sidebars = { id: "concepts/foundation/main", }, items: [ - "concepts/foundation/state_model", + { + label: "State Model", + type: "category", + link: { + type: "doc", + id: "concepts/foundation/state_model/main", + }, + items: [ + "concepts/foundation/state_model/storage_slots" + ] + }, { label: "Accounts", type: "category", @@ -294,7 +304,17 @@ const sidebars = { id: "dev_docs/contracts/syntax/main", }, items: [ - "dev_docs/contracts/syntax/storage", + { + label: "Storage", + type: "category", + link: { + type: "doc", + id: "dev_docs/contracts/syntax/storage/main", + }, + items: [ + "dev_docs/contracts/syntax/storage/storage_slots", + ] + }, "dev_docs/contracts/syntax/events", "dev_docs/contracts/syntax/functions", "dev_docs/contracts/syntax/context", diff --git a/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr index 2767f09f3f2..b6db0a2de8c 100644 --- a/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr @@ -93,7 +93,9 @@ contract Token { }, ), // docs:end:storage_minters_init + // docs:start:storage_balances_init balances: BalancesMap::new(context, 3), + // docs:end:storage_balances_init total_supply: PublicState::new( context, 4, @@ -334,7 +336,9 @@ contract Token { let amount = SafeU120::new(amount); storage.balances.at(from).sub(amount); + // docs:start:increase_private_balance storage.balances.at(to).add(amount); + // docs:end:increase_private_balance 1 }