diff --git a/docs/docs/concepts/foundation/state_model.md b/docs/docs/concepts/foundation/state_model.md index 0e169b4d223..5041e310ca6 100644 --- a/docs/docs/concepts/foundation/state_model.md +++ b/docs/docs/concepts/foundation/state_model.md @@ -40,4 +40,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/state_variables.md). +Read more about how to leverage the Aztec state model in Aztec contracts [here](../../dev_docs/contracts/syntax/storage.md). diff --git a/docs/docs/dev_docs/contracts/layout.md b/docs/docs/dev_docs/contracts/layout.md index c7943e6d817..45923293eab 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/state_variables.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.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/main.md b/docs/docs/dev_docs/contracts/syntax/main.md index 59c7d8a7f7b..1294c599cdf 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](./state_variables.md) +- Public and private [state variable types](./storage.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/state_variables.md b/docs/docs/dev_docs/contracts/syntax/state_variables.md deleted file mode 100644 index 830d3538824..00000000000 --- a/docs/docs/dev_docs/contracts/syntax/state_variables.md +++ /dev/null @@ -1,370 +0,0 @@ ---- -title: State Variables ---- - -State variables come in two flavours: [**public** state](#publicstatet-t_serialized_len) and [**private** state](#private-state-variables). - -## `PublicState` - -Public state is persistent state that is _publicly visible_ to anyone in the world. - -For developers coming from other blockchain ecosystems (such as Ethereum), this will be a familiar concept, because there, _all_ state is _publicly visible_. - -Aztec public state follows an account-based model. That is, each state occupies a leaf in an account-based merkle tree: the public state tree. See [here](/concepts/advanced/data_structures/trees#public-state-tree) for more of the technical details. - -The `PublicState` struct serves as a wrapper around conventional Noir types `T`, allowing these types to be written to and read from the public state tree. - -### `::new` - -To declare a type `T` as a persistent, public state variable, use the `PublicState::new()` constructor. - -In the following example, we define a public state with a boolean type: - -#include_code state_vars-PublicState /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -The BoolSerializationMethods is part of the Aztec stdlib: - -#include_code state_vars-PublicStateBoolImport /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -It contains methods that instruct its PublicState wrapper how to serialize and deserialize a boolean to and from a Field, which is the data type being saved in the public state tree. - -The Aztec stdlib provides serialization methods for various common types. Check [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/types/type_serialization) for the complete list. - -### Custom types - -It's possible to create a public state for any types. Simply define methods that guide the PublicState wrapper in serialising the custom type to field(s) to store in the public state tree, and deserialising the field(s) retrieved from the tree back to the custom type. - -The methods should be implemented in a struct that conforms to the following interface: - -#include_code TypeSerializationInterface /yarn-project/aztec-nr/aztec/src/types/type_serialization.nr rust - -For example, to create a public state for the following type: - -#include_code state_vars-CustomStruct /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/types/queen.nr rust - -First, define how to serialize and deserialize the custom type: - -#include_code state_vars-PublicStateCustomStruct /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/types/queen.nr rust - -And then initialize the PublicState with it: - -#include_code state_vars-PublicStateCustomStruct /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -### `.read` - -Reading the currently-stored value of a public state variable is straightforward: - -#include_code state_vars-PublicStateRead /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -It returns the type the public state was declared with. The above example returns a boolean. And the following example returns a custom struct. - -#include_code state_vars-PublicStateReadCustom /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -Every public state can be read before its value is written. The default value is 0, or { 0, 0, ... } if it's a struct. - -### `.write` - -The currently-stored value of a private state variable can be overwritten with `.write()`. - -Due to the way public states are [declared](#new), a public state knows how to serialize a given value and store it in the protocol's public state tree. - -We can pass the associated type directly to the `write()` method: - -#include_code state_vars-PublicStateWrite /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust -#include_code state_vars-PublicStateWriteCustom /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -#### Read and write - -A common pattern is reading a public state, changing the value, and then writing the new value back to the public state. Remember to always call `.write` to update the value. The value of a public state won't be changed by modifying the return value of `.read`. - -#include_code state_vars-PublicStateReadWriteCustom /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -#### Writing before calling - -**Important note:** -Before making a call to an external function, it is important to remember to `.write` state variables that have been edited, so as to persist their new values. This is particularly important if the call to the external function might result in re-entrancy into your contract, later in the transaction. If state variables aren't written before making such an external call, then upon re-entrancy, the 'current values' of your state variables will equal the values as at the start of the original function call. - -For example, the following function calls the account contract before it updates the public state. This allows the account contract, which can have arbitrary logic defined by the account itself, to call back to this function within its `send_rewards` function. And because this function hasn't updated the public state, the conditions are still true. This means `send_rewards` will be triggered again and again. - -#include_code state_vars-PublicStateWriteBeforeCall /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -## Private State Variables - -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 [events](./events.md), 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/advanced/data_structures/trees#private-state-tree). - -To greatly simplify the experience of writing private state, Aztec.nr provides three different types of private state variable: - -- [Singleton](#singletonnotetype) -- [ImmutableSingleton](#immutablesingletonnotetype) -- [Set](#setnotetype) - -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. - -> Note that 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 should conform to the following interface: - -#include_code NoteInterface /yarn-project/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 `Singleton`, `ImmutableSingleton` or `Set`) may be declared in [Storage](storage). -- Every note contains (as a 'header') the contract address and storage slot of the state variable to which it "belongs". A note is said to "belong" to a private state if the storage slot of the private state matches the storage slot contained in the note's header. - - Management of this 'header' is abstracted-away from developers who use the `ImmutableSingleton`, `Singleton` and `Set` types. -- A private state variable is colloquially said to "point" to one or many notes (depending on the type), if those note(s) all "belong" to that private state, and those note(s) haven't-yet been nullified. -- An `ImmutableSingleton` will point to _one_ note over the lifetime of the contract. ("One", hence "Singleton"). This note is a struct of information that is persisted forever. -- A `Singleton` may point to _one_ note at a time. ("One", hence "Singleton"). But since it's not "immutable", the note that it points to may be [replaced](#replace) by functions of the contract, over time. The "current value" of a `Singleton` is interpreted as the one note which has not-yet been nullified. The act of 'replacing' a Singleton's note is how a `Singleton` state may be modified by functions. - - `Singleton` is a useful type when declaring a private state which may only ever be modified by those who are privy to the current value of that state. -- A `Set` may point to _multiple_ notes at a time. The "current value" of a private state variable of type `Set` is some 'accumulation' of all not-yet nullified notes which "belong" to the `Set`. - - The term "some accumulation" is intentionally vague. The interpretation of the "current value" of a `Set` must be expressed by the smart contract developer. A common use case for a `Set` 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 `Set` state variable, is to [`insert`](#insert) new notes into the `Set`, 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 `Set` is a very useful type. The `insert` method allows new notes to be added to the `Set` 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!). - -## `Singleton` - -Singleton is a private state variable that is unique in a way. When a Singleton 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. - -### `::new` - -Here we define a Singleton for storing a `CardNote`: - -#include_code state_vars-Singleton /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -### `.initialize` - -The initial value of a Singleton is set via calling `initialize`: - -#include_code state_vars-SingletonInit /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -When this function is called, a nullifier of the storage slot is created, preventing this Singleton from being initialized again. - -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 `Singleton`, `ImmutableSingleton` or `Set`) does not have a default initial value. The `initialize` method (or `insert`, in the case of a `Set`) must be called. - -### `.replace` - -The 'current value' of a `Singleton` state variable may be overwritten via the `.replace` method. - -To modify the 'current value' of a Singleton, we may create a new note (a `CardNote` in the following example) containing some new data, and replace the current note with it: - -#include_code state_vars-SingletonReplace /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -This function will destroy the old note under the hood. If two people are trying to modify the Singleton at the same time, only one will succeed. 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 Singleton: - -#include_code state_vars-SingletonGet /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -However, it's possible that at the time this function is called, the system hasn't synched to the block where the latest note was created. Or a malicious user might feed an old state to this function, tricking the proving system into thinking that the value hasn't changed. To avoid an attack around it, this function will destroy the current note, and replace it with a duplicated note that has the same fields. Because the nullifier of the latest note will be emitted, if two people are trying to use this function against the same note, only one will succeed. - -## `ImmutableSingleton` - -ImmutableSingleton represents a unique private state variable that, as the name suggests, is immutable. Once initialized, its value cannot be altered. - -### `::new` - -In the following example, we define an ImmutableSingleton that utilises the `RulesMethods` struct as its underlying note type: - -#include_code state_vars-ImmutableSingleton /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -### `.initialize` - -Set the initial value of an ImmutableSingleton by calling the `initialize` method: - -#include_code state_vars-ImmutableSingletonInit /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -Once initialized, an ImmutableSingleton's value remains unchangeable. This method can only be called once. - -### `.get_note` - -Use this method to retrieve the value of an initialized ImmutableSingleton: - -#include_code state_vars-ImmutableSingletonGet /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -Unlike a [`singleton`](#get_note-1), the `get_note` function for an ImmutableSingleton doesn't destroy 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 ImmutableSingleton hasn't been initialized. - -## `Set` - -Set is used for managing a collection of notes. All notes in a set are of the same `NoteType`. But whether these notes all belong to one entity, or are accessible and editable by different entities, is totally up to the developer. - -### `::new` - -The `new` method creates a Set that employs a specific note type. When a new Set is created, it initially contains no notes. - -In the following example, we define a set whose underlying note type is `CardNote`: - -#include_code state_vars-Set /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -### `.insert` - -We can call `insert` for as many times as we need to add new notes to a `Set`. A `Set` is unbounded in size. - -#include_code state_vars-SetInsert /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -### `.remove` - -We can also remove a note from a set: - -#include_code state_vars-SetRemove /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -Note that the proof will fail if the note we are removing does not exist. To avoid this, it's advisable to first retrieve the notes using [`get_notes`](#get_notes), which does a membership check under the hood to make sure the notes exist, and then we can safely provide these notes as input to the `remove` function. - -### `.get_notes` - -This function returns the notes the account has access to: - -#include_code state_vars-SetGet /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -There's a limit on the maximum number of notes this function can return at a time. Check [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/constants_gen.nr) and look for `MAX_READ_REQUESTS_PER_CALL` for the up-to-date number. - -Because of this limit, we should always consider using the second argument `NoteGetterOptions` to target the notes we need, and to reduce the time required to recursively call this function. - -### 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. - -#include_code NoteGetterOptions /yarn-project/aztec-nr/aztec/src/note/note_getter_options.nr rust - -#### `selects: BoundedVec, N>` - -`selects` is a collection of filtering criteria, specified by `Select { field_index: u8, value: Field }` structs. It instructs the data oracle to find notes whose (`field_index`)th field matches the provided `value`. - -#### `sorts: BoundedVec, N>` - -`sorts` is a set of sorting instructions defined by `Sort { field_index: u8, order: u2 }` structs. This directs the data oracle to sort the matching notes based on the value of the specified field index 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. - -#### `filter: fn ([Option; MAX_READ_REQUESTS_PER_CALL], FILTER_ARGS) -> [Option; MAX_READ_REQUESTS_PER_CALL]` - -Developers have the option to provide a custom filter. This allows specific logic to be applied to notes that meet the criteria outlined above. The filter takes the notes returned from the oracle and `filter_args` as its parameters. - -#### `filter_args: FILTER_ARGS` - -`filter_args` provides a means to furnish additional data or context to the custom filter. - -#### 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_read_requests_per_call-filter_args---optionnote-max_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. - -#### Examples - -The following code snippet creates an instance of `NoteGetterOptions`, which has been configured to find the cards that belong to `account_address`. 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 /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/options.nr rust - -The first value of `.select` and `.sort` is the index of a field in a note type. For the note type `CardNote` that has the following fields: - -#include_code state_vars-CardNote /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/types/card_note.nr rust - -The indices are: 0 for `points`, 1 for `secret`, and 2 for `owner`. - -In the example, `.select(2, account_address)` matches the 2nd field of `CardNote`, which is `owner`, and returns the cards whose `owner` field equals `account_address`. - -`.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 /yarn-project/noir-contracts/src/contracts/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`: - -#include_code state_vars-OptionFilter /yarn-project/noir-contracts/src/contracts/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 /yarn-project/noir-contracts/src/contracts/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. Therefor, it's possible that the actual notes we end up getting are fewer than the limit. - -The limit is `MAX_READ_REQUESTS_PER_CALL` by default. But we can set it to any value "smaller" than that: - -#include_code state_vars-NoteGetterOptionsPickOne /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/options.nr rust - -The process of applying the options to get the final notes is not constrained. It's necessary to always check the returned notes even when some conditions have been specified in the options. - -#include_code state_vars-check_return_notes /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -### `.view_notes` (unconstrained) - -Similar to [`.get_notes`](#get_notes), this method returns the notes accessible to the account: - -#include_code state_vars-SetView /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.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](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/constants_gen.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`: - -#include_code NoteViewerOptions /yarn-project/aztec-nr/aztec/src/note/note_viewer_options.nr rust - -The `NoteViewerOptions` is essentially similar to the [`NoteGetterOptions`](#notegetteroptions), except that it doesn't take a custom filter. - -## `Map` - -`Map` is a state variable that maps a `Field` to another state variable, which can be [`PublicState`](#publicstatet-t_serialized_len), all the [private state variables](#private-state-variables), and even another Map. - -> `Map` can map from `Field` or any native Noir type which is convertible to `Field`. - -### `::new` - -The following declares a mapping from a `Field` to a `Singleton`: - -#include_code state_vars-MapSingleton /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust - -The second argument `|slot| Singleton::new(slot, ProfileMethods)` is a Noir closure function. It teaches this instance of `Map` how to create a new instance of a `Singleton` whenever the `.at` method is called to access a state variable at a particular mapping key. The `slot` argument will be derived when `.at` is called, based on the lookup key provided. - -### `.at` - -The only api of a map is `.at`. It returns the underlying type that occupies a specific storage slot, which is generated by the field passed to `.at`. - -#include_code state_vars-MapAtSingletonInit /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -#include_code state_vars-MapAtSingletonGet /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust - -In both code snippets, `state_var.at(account)` returns a Singleton that is linked to the requested account. diff --git a/docs/docs/dev_docs/contracts/syntax/storage.md b/docs/docs/dev_docs/contracts/syntax/storage.md index 9b04e3efecc..6cfdeb4f3e5 100644 --- a/docs/docs/dev_docs/contracts/syntax/storage.md +++ b/docs/docs/dev_docs/contracts/syntax/storage.md @@ -1,40 +1,533 @@ -# Storage +--- +title: Storage +--- -In an Aztec.nr contract, storage is to be defined as a single struct. (This enables us to declare types composed of nested generics in Noir). +In an Aztec.nr contract, storage is to be defined as a single struct, that contains both public and private state variables. +As their name indicates, 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 data with. + +As mentioned earlier in the foundational concepts ([state model](./../../../concepts/foundation/state_model.md) and [private/public execution](./../../../concepts/foundation/communication/public_private_calls.md)) private state follows a UTXO model. Where note pre-images are only known to those able to decrypt them. + +:::info The struct **must** be called `Storage` for the Aztec.nr library to properly handle it (will be fixed in the future to support more flexibility). -An example of such a struct could be as follows: +::: -#include_code storage-struct-declaration /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust +```rust +struct Storage { + // public state variables + // private state variables +} +``` :::info If your storage includes private state variables it must include a `compute_note_hash_and_nullifier` function to allow the RPC to process encrypted events, see [encrypted events](./events.md#processing-encrypted-events) for more. ::: -In the storage stuct, we set up a mixture of public and private state variables. The public state variables can be read by anyone, and functions manipulating them are executed by the sequencer, (see [functions](./functions.md#public-functions)). Private state variables are only readable by their owner, or people whom the owner has shared the data with. +Since Aztec.nr is written in Noir, which on its own is state-less, we need to specify how the storage struct should be initialized to read and write data correctly. This is done by specifying an `init` function that is run in functions that rely on reading or altering the state variables. This `init` function should declare the storage struct with an actual instantiation defining how variables are accessed and manipulated. The function MUST be called `init` for the Aztec.nr library to properly handle it (will be fixed in the future to support more flexibility). -As mentioned earlier in the foundational concepts ([state model](./../../../concepts/foundation/state_model.md) and [private/public execution](./../../../concepts/foundation/communication/public_private_calls.md)) private state follows a UTXO model. Where note pre-images are only known to those able to decrypt them. +```rust +impl Storage { + fn init(context: Context) -> Self { + Storage { + // (public state variables)::new() + // (private state variables)::new() + } + } +} +``` -It is currently required to specify the length of the types when declaring the storage struct. If your type is a struct, this will be the number of values in your struct ( with arrays flattened ). +To use storage in functions, e.g., functions where you would read or write storage, you need to initialize the struct first, and then you can read and write afterwards. Below are snippets for initializing in private and public functions. -Since Aztec.nr is a library written in Noir, we can use the types defined in Noir, so it can be useful to consult the [Noir documentation](https://noir-lang.org/language_concepts/data_types) for information on types. +#include_code private_init_storage /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust +#include_code public_init_storage /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust -Currently, the sandbox also requires that you specify how this storage struct is "initialized". This is done by specifying an `init` function that is run in functions that rely on reading or altering the state variables. +:::info +https://github.com/AztecProtocol/aztec-packages/pull/2406 is removing the need to explicitly initialize the storage in each function before reading or writing. This will be updated in the docs once the PR is merged. +::: -An example of such a function for the above storage struct would be: +:::warning Using slot `0` is not supported! +No storage values should be initialized at slot `0`. This is a known issue that will be fixed in the future. +::: -#include_code storage-declaration /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust +## Map -:::warning Using slot `0` is not supported! -No storage values should be initialized at slot `0`. If you are using slot `0` for storage, you will not get an error when compiling, but the contract will not be updating the storage! This is a known issue that will be fixed in the future. +A `map` is a state variable that "maps" a key to a value. In Aztec.nr, we keys are `Field`s (or values that are convertible to Fields) and values can be any type - even other maps. The map is a struct defined as follows: + +#include_code map /yarn-project/aztec-nr/aztec/src/state_vars/map.nr rust + +It includes a [`Context`](./context.mdx) to be used when dabbling in the private domain, a `storage_lot` 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 private. + +### `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). + +#include_code new /yarn-project/aztec-nr/aztec/src/state_vars/map.nr rust + +We will see examples of map constructors for public and private variables in later sections. + +### `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. + +#include_code at /yarn-project/aztec-nr/aztec/src/state_vars/map.nr rust + +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 /yarn-project/noir-contracts/src/contracts/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] == 1, "caller is not minter"); +``` + +## Public State Variables + +As we have looked seen earlier we got two kinds of state, public and private. We'll start with a look at the public side of things, as this is what behaves closest to what most people are used to from Ethereum and other blockchains. + +To define that a variable is public, it is wrapped in the `PublicState` struct, as defined below: + +#include_code public_state_struct /yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr rust + +The `PublicState` struct is generic over the variable type `T` and its serialized size `T_SERIALISED_LEN`. +:::info +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. ::: -In [State Variables](./state_variables.md) we will see in more detail what each of these types are, how they work and how to initialize them. +The struct contains a `storage_slot` which similarly to Ethereum is used to figure out *where* in the storage the variable is stored. Notice that while we don't have the exact same [state model](./../../../concepts/foundation/state_model.md) as EVM chains it will from the developers points of view work very similar. -To use storage in functions, e.g., functions where you would read or write storage, you need to initialize the struct first, and then you can read and write afterwards. +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. -#include_code storage-init /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust +#include_code TypeSerialisationInterface /yarn-project/aztec-nr/aztec/src/types/type_serialisation.nr rust :::info -https://github.com/AztecProtocol/aztec-packages/pull/2406 is removing the need to explicitly initialize the storage in each function before reading or writing. This will be updated in the docs once the PR is merged. -::: \ No newline at end of file +The British haven't surrendered fully to US spelling, so serialization is currently inconsistent. +::: + +The Aztec.nr library provides serialization methods for various common types. As an example, the Field serialization methods are defined as follows: + +#include_code field_serialization /yarn-project/aztec-nr/aztec/src/types/type_serialisation/field_serialisation.nr rust + + +:::info +An example using a larger struct can be found in the [lending example](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/noir-contracts/src/contracts/lending_contract)'s use of an [`Asset`](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/noir-contracts/src/contracts/lending_contract/src/asset.nr). +::: + +### `new` +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 +Say that we wish to add `admin` public state variable into our storage struct. In the struct we can add it as follows: + +#include_code storage_admin /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +And then when initializing it in the `Storage::init` function we can do it as follows: + +#include_code storage_admin_init /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +In this case, specifying that we are dealing with a Field, and that it should be put at slot 1. This is just a single value, and would be similar to the following in solidity: +```solidity +address internal admin; +``` +:::info +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: + +#include_code storage_minters /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +And then when initializing it in the `Storage::init` function we can do it as follows: + +#include_code storage_minters_init /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +In this case, specifying that we are dealing with a map of Fields, and that it should be put at slot 2. + +This would be similar to the following in solidity: +```solidity +mapping(address => uint256) internal minters; +``` + +### `read` + +Now we have an idea of how to define storage, but storage is not really useful before we start using it, so how can we access it? + +Reading data from storage is somewhat straightforward, on the `PublicState` structs we got a `read` method to read the value at the location in storage and using the specified deserialization method to deserialize it. + +#include_code public_state_struct_read /yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr rust + +#### 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 /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + + +#### Reading from out `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 /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + + +### `write` + +We figured out how to read values, but how do we write them? + +Like reading, it is actually quite straight-forward, we got a `write` method on the `PublicState` struct that takes the value to write as an input, and then shoved this into storage, it uses the serialization method defined earlier to serialize the value which insert (possibly multiple) values into storage. + +#include_code public_state_struct_write /yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr rust + +#### Writing to our `admin` example + +#include_code write_admin /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +#### Writing to our `minters` example + +#include_code write_minter /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +## Private State Variables +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. + + + +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). + + + +To greatly simplify the experience of writing private state, Aztec.nr provides three different types of private state variable: + +- [Singleton](#singletonnotetype) +- [ImmutableSingleton](#immutablesingletonnotetype) +- [Set](#setnotetype) + +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 +Note that 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 should conform to the following interface: + +#include_code NoteInterface /yarn-project/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 `Singleton`, `ImmutableSingleton` or `Set`) may be declared in [Storage](storage). +- Every note contains (as a 'header') the contract address and storage slot of the state variable to which it "belongs". A note is said to "belong" to a private state if the storage slot of the private state matches the storage slot contained in the note's header. + - Management of this 'header' is abstracted-away from developers who use the `ImmutableSingleton`, `Singleton` and `Set` types. +- A private state variable is colloquially said to "point" to one or many notes (depending on the type), if those note(s) all "belong" to that private state, and those note(s) haven't-yet been nullified. +- An `ImmutableSingleton` will point to _one_ note over the lifetime of the contract. ("One", hence "Singleton"). This note is a struct of information that is persisted forever. +- A `Singleton` may point to _one_ note at a time. ("One", hence "Singleton"). But since it's not "immutable", the note that it points to may be [replaced](#replace) by functions of the contract, over time. The "current value" of a `Singleton` is interpreted as the one note which has not-yet been nullified. The act of 'replacing' a Singleton's note is how a `Singleton` state may be modified by functions. + - `Singleton` is a useful type when declaring a private state which may only ever be modified by those who are privy to the current value of that state. +- A `Set` may point to _multiple_ notes at a time. The "current value" of a private state variable of type `Set` is some 'accumulation' of all not-yet nullified notes which "belong" to the `Set`. + - The term "some accumulation" is intentionally vague. The interpretation of the "current value" of a `Set` must be expressed by the smart contract developer. A common use case for a `Set` 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 `Set` state variable, is to [`insert`](#insert) new notes into the `Set`, 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 `Set` is a very useful type. The `insert` method allows new notes to be added to the `Set` 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!). + + + + +## `Singleton` + +Singleton is a private state variable that is unique in a way. When a Singleton 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, a storage slot and `note_interface` specifying how the note should be constructed and manipulated. + +#include_code struct /yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr rust + +An example of singleton usage in in the account contracts keeping track of public keys. The `Singleton` is added to the `Storage` struct as follows: + +#include_code storage-singleton-declaration /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust + +### `new` + +#include_code new /yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr rust + +As part of the initialization of the `Storage` struct, the `Singleton` is created as follows, here at storage slot 1 and with the `NoteInterface` for `CardNote`. + +#include_code start_vars_singleton /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust + +### `initialise` + +As mention, the singleton is initialized to create the first note and value. + +#include_code initialise /yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr rust + +When this function is called, a nullifier of the storage slot is created, preventing this Singleton from being initialized again. + +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 `Singleton`, `ImmutableSingleton` or `Set`) does not have a default initial value. The `initialise` method (or `insert`, in the case of a `Set`) must be called. + +:::info +Extend on what happens if you try to use non-initialized state. +::: + +### `replace` + +To update the value of a `Singleton`, 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. + +#include_code replace /yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr rust + +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-SingletonReplace /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust + +If two people are trying to modify the Singleton 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 Singleton, essentially reading the value. + +#include_code get_note /yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr rust + +However, it's possible that at the time this function is called, the system hasn't synched to the block where the latest note was created. Or a malicious user might feed an old state to this function, tricking the proving system into thinking that the value hasn't changed. To avoid an attack around it, this function will destroy the current note, and replace it with a duplicated note that has the same fields. Because the nullifier of the latest note will be emitted, if two people are trying to use this function against the same note, only one will succeed (no duplicate nullifiers allowed). + +#include_code state_vars-SingletonGet /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust + +## `ImmutableSingleton` + +ImmutableSingleton represents a unique private state variable that, as the name suggests, is immutable. Once initialized, its value cannot be altered. + +#include_code struct /yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr rust + +### `new` + +#include_code new /yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr rust + +As part of the initialization of the `Storage` struct, the `Singleton` is created as follows, here at storage slot 1 and with the `NoteInterface` for `PublicKeyNote`. + +#include_code storage_init /yarn-project/noir-contracts/src/contracts/schnorr_account_contract/src/main.nr rust + +### `initialise` + +#include_code initialise /yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr rust + +Set the value of an ImmutableSingleton by calling the `initialise` method: + +#include_code initialise /yarn-project/noir-contracts/src/contracts/schnorr_account_contract/src/main.nr rust + +Once initialized, an ImmutableSingleton's value remains unchangeable. This method can only be called once. + +### `get_note` + +Similar to the `Singleton`, we can use the `get_note` method to read the value of an ImmutableSingleton. + +#include_code get_note /yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr rust + +Use this method to retrieve the value of an initialized ImmutableSingleton. + +#include_code get_note /yarn-project/noir-contracts/src/contracts/schnorr_account_contract/src/main.nr rust + +Unlike a [`singleton`](#get_note-1), the `get_note` function for an ImmutableSingleton doesn't destroy 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 ImmutableSingleton hasn't been initialized. + +## `Set` + +Set is used for managing a collection of notes. All notes in a set are of the same `NoteType`. But whether these notes all belong to one entity, or are accessible and editable by different entities, is totally up to the developer. Due to our state model, 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. + +#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)). + +#include_code storage_pending_shields /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +### `new` + +The `new` method tells the contract how to operate on the underlying storage. + +#include_code new /yarn-project/aztec-nr/aztec/src/state_vars/set.nr rust + +Building on before, we can initialize the set as follows: + +#include_code storage_pending_shields_init /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +### `insert` + +Allows us to modify the storage by inserting a note into the set. + +#include_code insert /yarn-project/aztec-nr/aztec/src/state_vars/set.nr rust + +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 + +### `insert_from_public` + +While we don't support private functions to directly alter public storage, the opposite direction is possible. 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 must have been initiated in public, such as shielding or the like. + +#include_code insert_from_public /yarn-project/aztec-nr/aztec/src/state_vars/set.nr rust + +The usage is rather straight-forward and very similar to using the `insert` method with the difference that this one is called in public functions. + +#include_code insert_from_public /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +### `assert_contains_and_remove` + +This function is used to check existence of a note and then remove it without having read the note ahead of time. This can be useful for cases where the user would is providing all the information needed, such as cases where the note was never emitted to the network and thereby available to the wallet. + +#include_code assert_contains_and_remove /yarn-project/aztec-nr/aztec/src/state_vars/set.nr rust + + + +### `assert_contains_and_remove_publicly_created` + +Like above, this is used to ensure that the message exists in the data tree and then consume it. However, it differs slightly since there is currently a difference between notes that have been inserted from public and private execution. This means that you currently must use this function to consume and nullify a note that was created in a public function. This will be fixed in the future. + +#include_code assert_contains_and_remove_publicly_created /yarn-project/aztec-nr/aztec/src/state_vars/set.nr rust + +While this might look intimidating, the use of the function is rather easy, and is used in the following way: + +#include_code assert_contains_and_remove_publicly_created /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust + +The reason we are not reading this note ahead of time is that no [encrypted log](./events.md#encrypted-events) was emitted for this note, since it was created in public thereby making the encrypted log useless (everyone saw the content ahead of time). + +### `remove` + +Will remove a note from the set if it previously have been read from storage. That being that 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 to not have to read it again. If you recall from earlier, we are emitting a nullifier when reading values to make sure that they are up to date. + +#include_code remove /yarn-project/aztec-nr/aztec/src/state_vars/set.nr rust + +An example of how to use this operation is visible in the `easy_private_state`: + +#include_code remove /yarn-project/aztec-nr/easy-private-state/src/easy_private_state.nr rust + +### `get_notes` + +This function returns the notes the account has access to: + +#include_code get_notes /yarn-project/aztec-nr/aztec/src/state_vars/set.nr rust + +There's a limit on the maximum number of notes this function can return at a time. Check [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/constants_gen.nr) and look for `MAX_READ_REQUESTS_PER_CALL` for the up-to-date number. + +Because of this limit, we should always consider using the second argument `NoteGetterOptions` to target the notes we need, and to reduce the time required to recursively call this function. + +An example of this is is using the [filter_notes_min_sum](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/value-note/src/filter.nr) to get "enough" notes to cover a given value. + +#include_code get_notes /yarn-project/aztec-nr/easy-private-state/src/easy_private_state.nr rust + +### `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 /yarn-project/aztec-nr/aztec/src/state_vars/set.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](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/constants_gen.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`: + +#include_code NoteViewerOptions /yarn-project/aztec-nr/aztec/src/note/note_viewer_options.nr rust + +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. + +#include_code NoteGetterOptions /yarn-project/aztec-nr/aztec/src/note/note_getter_options.nr rust + +#### `selects: BoundedVec, N>` + +`selects` is a collection of filtering criteria, specified by `Select { field_index: u8, value: Field }` structs. It instructs the data oracle to find notes whose (`field_index`)th field matches the provided `value`. + +#### `sorts: BoundedVec, N>` + +`sorts` is a set of sorting instructions defined by `Sort { field_index: u8, order: u2 }` structs. This directs the data oracle to sort the matching notes based on the value of the specified field index 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. + +#### `filter: fn ([Option; MAX_READ_REQUESTS_PER_CALL], FILTER_ARGS) -> [Option; MAX_READ_REQUESTS_PER_CALL]` + +Developers have the option to provide a custom filter. This allows specific logic to be applied to notes that meet the criteria outlined above. The filter takes the notes returned from the oracle and `filter_args` as its parameters. + +#### `filter_args: FILTER_ARGS` + +`filter_args` provides a means to furnish additional data or context to the custom filter. + +#### Methods + +Several methods are available on `NoteGetterOptions` to construct the options in a more readable manner: + +#### `fn new() -> NoteGetterOptions` + +This function initialises a `NoteGetterOptions` that simply returns the maximum number of notes allowed in a call. + +#### `fn with_filter(filter, filter_args) -> NoteGetterOptions` + +This function initialises a `NoteGetterOptions` with a [`filter`](#filter-fn-optionnote-max_read_requests_per_call-filter_args---optionnote-max_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. + +#### Examples + +The following code snippet creates an instance of `NoteGetterOptions`, which has been configured to find the cards that belong to `account_address`. 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 /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/options.nr rust + +The first value of `.select` and `.sort` is the index of a field in a note type. For the note type `CardNote` that has the following fields: + +#include_code state_vars-CardNote /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/types/card_note.nr rust + +The indices are: 0 for `points`, 1 for `secret`, and 2 for `owner`. + +In the example, `.select(2, account_address)` matches the 2nd field of `CardNote`, which is `owner`, and returns the cards whose `owner` field equals `account_address`. + +`.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 /yarn-project/noir-contracts/src/contracts/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`: + +#include_code state_vars-OptionFilter /yarn-project/noir-contracts/src/contracts/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 /yarn-project/noir-contracts/src/contracts/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. Therefor, it's possible that the actual notes we end up getting are fewer than the limit. + +The limit is `MAX_READ_REQUESTS_PER_CALL` by default. But we can set it to any value "smaller" than that: + +#include_code state_vars-NoteGetterOptionsPickOne /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/options.nr rust + +The process of applying the options to get the final notes is not constrained. It's necessary to always check the returned notes even when some conditions have been specified in the options. + +#include_code state_vars-check_return_notes /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust diff --git a/docs/docs/dev_docs/tutorials/writing_token_contract.md b/docs/docs/dev_docs/tutorials/writing_token_contract.md index 44acee30adb..c92a3b1ae44 100644 --- a/docs/docs/dev_docs/tutorials/writing_token_contract.md +++ b/docs/docs/dev_docs/tutorials/writing_token_contract.md @@ -242,7 +242,7 @@ We are also importing types from a `types.nr` file. Copy [this file](https://git ### 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/state_variables) 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.md) to learn more about public and private state in Aztec. Copy [`util.nr`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_contract/src/util.nr) into `token_contract_tutorial/contracts/src` as well. The function defined in `util.nr` will be helpful for generating message hashes that are used when communicating between contracts. diff --git a/docs/sidebars.js b/docs/sidebars.js index 1ec97eda8ea..deadcccb50b 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -248,7 +248,6 @@ const sidebars = { }, items: [ "dev_docs/contracts/syntax/storage", - "dev_docs/contracts/syntax/state_variables", "dev_docs/contracts/syntax/events", "dev_docs/contracts/syntax/functions", "dev_docs/contracts/syntax/context", diff --git a/yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr b/yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr index 5581c60868e..4e0c71e241c 100644 --- a/yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr +++ b/yarn-project/aztec-nr/aztec/src/state_vars/immutable_singleton.nr @@ -10,13 +10,16 @@ use crate::oracle; use dep::std::hash::pedersen_with_separator; use dep::std::option::Option; +// docs:start:struct struct ImmutableSingleton { context: Context, storage_slot: Field, note_interface: NoteInterface, } +// docs:end:struct impl ImmutableSingleton { + // docs:start:new fn new( context: Context, storage_slot: Field, @@ -29,12 +32,14 @@ impl ImmutableSingleton { note_interface, } } + // docs:end:new unconstrained fn is_initialized(self) -> bool { let nullifier = self.compute_initialisation_nullifier(); oracle::notes::is_nullifier_emitted(nullifier) } + // docs:start:initialize fn initialize(self, note: &mut Note) { // Nullify the storage slot. let nullifier = self.compute_initialisation_nullifier(); @@ -49,6 +54,7 @@ impl ImmutableSingleton { self.note_interface, ); } + // docs:end:initialize fn compute_initialisation_nullifier(self) -> Field { pedersen_with_separator( @@ -57,10 +63,12 @@ impl ImmutableSingleton { )[0] } + // docs:start:get_note fn get_note(self) -> Note { let storage_slot = self.storage_slot; get_note(self.context.private.unwrap(), storage_slot, self.note_interface) } + // docs:end:get_note unconstrained fn view_note(self) -> Note { let options = NoteViewerOptions::new().set_limit(1); diff --git a/yarn-project/aztec-nr/aztec/src/state_vars/map.nr b/yarn-project/aztec-nr/aztec/src/state_vars/map.nr index 10df31dd31a..5ee97e8b681 100644 --- a/yarn-project/aztec-nr/aztec/src/state_vars/map.nr +++ b/yarn-project/aztec-nr/aztec/src/state_vars/map.nr @@ -1,13 +1,16 @@ use crate::context::{PrivateContext, PublicContext, Context}; use dep::std::option::Option; +// docs:start:map struct Map { context: Context, storage_slot: Field, state_var_constructor: fn(Context, Field) -> V, } +// docs:end:map impl Map { + // docs:start:new fn new( context: Context, storage_slot: Field, @@ -20,7 +23,9 @@ impl Map { state_var_constructor, } } + // docs:end:new + // docs:start:at fn at(self, key: Field) -> V { // TODO(#1204): use a generator index for the storage slot let derived_storage_slot = dep::std::hash::pedersen([self.storage_slot, key])[0]; @@ -28,4 +33,5 @@ impl Map { let state_var_constructor = self.state_var_constructor; state_var_constructor(self.context, derived_storage_slot) } + // docs:end:at } diff --git a/yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr b/yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr index 41dfdaf261e..fb1962227a7 100644 --- a/yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr +++ b/yarn-project/aztec-nr/aztec/src/state_vars/public_state.nr @@ -4,12 +4,15 @@ use crate::oracle::storage::storage_write; use crate::types::type_serialization::TypeSerializationInterface; use dep::std::option::Option; +// docs:start:public_state_struct struct PublicState { storage_slot: Field, serialization_methods: TypeSerializationInterface, } +// docs:end:public_state_struct impl PublicState { + // docs:start:public_state_struct_new fn new( // Note: Passing the contexts to new(...) just to have an interface compatible with a Map. _: Context, @@ -22,14 +25,19 @@ impl PublicState { serialization_methods, } } + // docs:end:public_state_struct_new + // docs:start:public_state_struct_read fn read(self) -> T { storage_read(self.storage_slot, self.serialization_methods.deserialize) } + // docs:end:public_state_struct_read + // docs:start:public_state_struct_write fn write(self, value: T) { let serialize = self.serialization_methods.serialize; let fields = serialize(value); storage_write(self.storage_slot, fields); } + // docs:end:public_state_struct_write } diff --git a/yarn-project/aztec-nr/aztec/src/state_vars/set.nr b/yarn-project/aztec-nr/aztec/src/state_vars/set.nr index 698af2ef11c..2aa9023aa41 100644 --- a/yarn-project/aztec-nr/aztec/src/state_vars/set.nr +++ b/yarn-project/aztec-nr/aztec/src/state_vars/set.nr @@ -12,13 +12,16 @@ use crate::note::{ utils::compute_note_hash_for_read_or_nullify, }; +// docs:start:struct struct Set { context: Context, storage_slot: Field, note_interface: NoteInterface, } +// docs:end:struct impl Set { + // docs:start:new fn new( context: Context, storage_slot: Field, @@ -31,7 +34,9 @@ impl Set { note_interface, } } + // docs:end:new + // docs:start:insert fn insert(self, note: &mut Note) { create_note( self.context.private.unwrap(), @@ -40,7 +45,9 @@ impl Set { self.note_interface, ); } + // docs:end:insert + // docs:start:insert_from_public fn insert_from_public(self, note: &mut Note) { create_note_hash_from_public( self.context.public.unwrap(), @@ -49,7 +56,9 @@ impl Set { self.note_interface, ); } - + // docs:end:insert_from_public + + // docs:start:assert_contains_and_remove fn assert_contains_and_remove(self, note: &mut Note, nonce: Field) { // Initialize header of note. Must be done before computing note hashes as it initializes the: // - storage slot (used in inner note hash) @@ -77,7 +86,9 @@ impl Set { self.note_interface, ); } + // docs:end:assert_contains_and_remove + // docs:start:assert_contains_and_remove_publicly_created // NOTE: this function should ONLY be used for PUBLICLY-CREATED note hashes! // WARNING: function will be deprecated/removed eventually once public kernel applies nonces. fn assert_contains_and_remove_publicly_created(self, note: &mut Note) { @@ -114,7 +125,9 @@ impl Set { self.note_interface, ); } + // docs:end:assert_contains_and_remove_publicly_created + // docs:start:remove fn remove(self, note: Note) { let context = self.context.private.unwrap(); let note_hash = compute_note_hash_for_read_or_nullify(self.note_interface, note); @@ -127,7 +140,9 @@ impl Set { self.note_interface, ); } + // docs:end:remove + // docs:start:get_notes fn get_notes( self, options: NoteGetterOptions, @@ -141,11 +156,14 @@ impl Set { ); opt_notes } + // docs:end:get_notes + // docs:start:view_notes unconstrained fn view_notes( self, options: NoteViewerOptions, ) -> [Option; MAX_NOTES_PER_PAGE] { view_notes(self.storage_slot, self.note_interface, options) } + // docs:end:view_notes } diff --git a/yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr b/yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr index dd12110fab9..f1d49617ed1 100644 --- a/yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr +++ b/yarn-project/aztec-nr/aztec/src/state_vars/singleton.nr @@ -10,13 +10,16 @@ use crate::oracle; use dep::std::hash::pedersen_with_separator; use dep::std::option::Option; +// docs:start:struct struct Singleton { context: Option<&mut PrivateContext>, storage_slot: Field, note_interface: NoteInterface, } +// docs:end:struct impl Singleton { + // docs:start:new fn new( context: Context, storage_slot: Field, @@ -29,12 +32,14 @@ impl Singleton { note_interface, } } + // docs:end:new unconstrained fn is_initialized(self) -> bool { let nullifier = self.compute_initialisation_nullifier(); oracle::notes::is_nullifier_emitted(nullifier) } + // docs:start:initialize fn initialize(self, note: &mut Note) { let context = self.context.unwrap(); // Nullify the storage slot. @@ -43,6 +48,7 @@ impl Singleton { create_note(context, self.storage_slot, note, self.note_interface); } + // docs:end:initialize fn compute_initialisation_nullifier(self) -> Field { pedersen_with_separator( @@ -51,6 +57,7 @@ impl Singleton { )[0] } + // docs:start:replace fn replace(self, new_note: &mut Note) { let context = self.context.unwrap(); let prev_note = get_note(context, self.storage_slot, self.note_interface); @@ -61,7 +68,9 @@ impl Singleton { // Add replacement note. create_note(context, self.storage_slot, new_note, self.note_interface); } + // docs:end:replace + // docs:start:get_note fn get_note(self) -> Note { let context = self.context.unwrap(); let mut note = get_note(context, self.storage_slot, self.note_interface); @@ -75,6 +84,7 @@ impl Singleton { note } + // docs:end:get_note unconstrained fn view_note(self) -> Note { let options = NoteViewerOptions::new().set_limit(1); diff --git a/yarn-project/aztec-nr/aztec/src/types/type_serialization/field_serialization.nr b/yarn-project/aztec-nr/aztec/src/types/type_serialization/field_serialization.nr index 4fd3ee5d350..3c13de1112e 100644 --- a/yarn-project/aztec-nr/aztec/src/types/type_serialization/field_serialization.nr +++ b/yarn-project/aztec-nr/aztec/src/types/type_serialization/field_serialization.nr @@ -1,5 +1,6 @@ use crate::types::type_serialization::TypeSerializationInterface; +// docs:start:field_serialization global FIELD_SERIALIZED_LEN: Field = 1; fn deserializeField(fields: [Field; FIELD_SERIALIZED_LEN]) -> Field { @@ -13,4 +14,5 @@ fn serializeField(value: Field) -> [Field; FIELD_SERIALIZED_LEN] { global FieldSerializationMethods = TypeSerializationInterface { deserialize: deserializeField, serialize: serializeField, -}; \ No newline at end of file +}; +// docs:end:field_serialization \ No newline at end of file diff --git a/yarn-project/aztec-nr/easy-private-state/src/easy_private_state.nr b/yarn-project/aztec-nr/easy-private-state/src/easy_private_state.nr index 005c8990f6f..69ec2eea87f 100644 --- a/yarn-project/aztec-nr/easy-private-state/src/easy_private_state.nr +++ b/yarn-project/aztec-nr/easy-private-state/src/easy_private_state.nr @@ -42,7 +42,9 @@ impl EasyPrivateUint { let mut addend_note = ValueNote::new(addend as Field, owner); // Insert the new note to the owner's set of notes. + // docs:start:insert self.set.insert(&mut addend_note); + // docs:end:insert // Emit the newly created encrypted note preimages via oracle calls. let owner_key = get_public_key(owner); @@ -58,8 +60,10 @@ impl EasyPrivateUint { // Very similar to `value_note::utils::decrement`. fn sub(self, subtrahend: u120, owner: Field) { + // docs:start:get_notes let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend as Field); let maybe_notes = self.set.get_notes(options); + // docs:end:get_notes let mut minuend: u120 = 0; for i in 0..maybe_notes.len() { @@ -71,7 +75,9 @@ impl EasyPrivateUint { assert(note.owner == owner); // Removes the note from the owner's set of notes. + // docs:start:remove self.set.remove(note); + // docs:end:remove minuend += note.value as u120; } diff --git a/yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr index 1bf53dc922d..a4dd67ff48d 100644 --- a/yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr @@ -32,7 +32,9 @@ contract DocsExample { locked: PublicState, queen: PublicState, game_rules: ImmutableSingleton, + // docs:start:storage-singleton-declaration legendary_card: Singleton, + // docs:end:storage-singleton-declaration cards: Set, profiles: Map>, } @@ -59,7 +61,9 @@ contract DocsExample { // highlight-next-line:state_vars-ImmutableSingleton game_rules: ImmutableSingleton::new(context, 3, RulesNoteMethods), // highlight-next-line:state_vars-Singleton + // docs:start:start_vars_singleton legendary_card: Singleton::new(context, 4, CardNoteMethods), + // docs:end:start_vars_singleton // highlight-next-line:state_vars-Set cards: Set::new(context, 5, CardNoteMethods), // highlight-next-line:state_vars-MapSingleton diff --git a/yarn-project/noir-contracts/src/contracts/schnorr_account_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/schnorr_account_contract/src/main.nr index a26d6fe6de1..5a5b1e5c780 100644 --- a/yarn-project/noir-contracts/src/contracts/schnorr_account_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/schnorr_account_contract/src/main.nr @@ -18,13 +18,17 @@ contract SchnorrAccount { use crate::public_key_note::{PublicKeyNote, PublicKeyNoteMethods, PUBLIC_KEY_NOTE_LEN}; struct Storage { + // docs:start:storage signing_public_key: ImmutableSingleton, + // docs:end:storage } impl Storage { fn init(context: Context) -> pub Self { Storage { + // docs:start:storage_init signing_public_key: ImmutableSingleton::new(context, 1, PublicKeyNoteMethods), + // docs:end:storage_init } } } @@ -40,8 +44,10 @@ contract SchnorrAccount { let storage = Storage::init(Context::private(&mut context)); let this = context.this_address(); + // docs:start:initialize let mut pub_key_note = PublicKeyNote::new(signing_pub_key_x, signing_pub_key_y, this); storage.signing_public_key.initialize(&mut pub_key_note); + // docs:end:initialize emit_encrypted_log( &mut context, @@ -81,8 +87,9 @@ contract SchnorrAccount { // docs:start:entrypoint // Load public key from storage let storage = Storage::init(Context::private(context)); + // docs:start:get_note let public_key = storage.signing_public_key.get_note(); - + // docs:end:get_note // Load auth witness let witness: [Field; 64] = get_auth_witness(message_hash); let mut signature: [u8; 64] = [0; 64]; 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 7b67527fd68..0439a1ab042 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 @@ -43,11 +43,20 @@ contract Token { // docs:start:storage_struct struct Storage { + + // docs:start:storage_admin admin: PublicState, + // docs:end:storage_admin + // docs:start:storage_minters minters: Map>, + // docs:end:storage_minters + // docs:start:storage_balances balances: Map>, + // docs:end:storage_balances total_supply: PublicState, + // docs:start:storage_pending_shields pending_shields: Set, + // docs:end:storage_pending_shields public_balances: Map>, } // docs:end:storage_struct @@ -56,11 +65,14 @@ contract Token { impl Storage { fn init(context: Context) -> pub Self { Storage { + // docs:start:storage_admin_init admin: PublicState::new( context, 1, FieldSerializationMethods, ), + // docs:end:storage_admin_init + // docs:start:storage_minters_init minters: Map::new( context, 2, @@ -72,6 +84,7 @@ contract Token { ) }, ), + // docs:end:storage_minters_init balances: Map::new( context, 3, @@ -84,7 +97,9 @@ contract Token { 4, FieldSerializationMethods, ), + // docs:start:storage_pending_shields_init pending_shields: Set::new(context, 5, TransparentNoteMethods), + // docs:end:storage_pending_shields_init public_balances: Map::new( context, 6, @@ -115,9 +130,13 @@ contract Token { fn set_admin( new_admin: AztecAddress, ) { + // docs:start:public_init_storage let storage = Storage::init(Context::public(&mut context)); + // docs:end:public_init_storage assert(storage.admin.read() == context.msg_sender(), "caller is not admin"); + // docs:start:write_admin storage.admin.write(new_admin.address); + // docs:end:write_admin } // docs:end:set_admin @@ -128,8 +147,12 @@ contract Token { approve: bool, ) { let storage = Storage::init(Context::public(&mut context)); + // docs:start:read_admin assert(storage.admin.read() == context.msg_sender(), "caller is not admin"); + // docs:end:read_admin + // docs:start:write_minter storage.minters.at(minter.address).write(approve as Field); + // docs:end:write_minter } // docs:end:set_minter @@ -140,7 +163,9 @@ contract Token { amount: Field, ) -> Field { let storage = Storage::init(Context::public(&mut context)); + // docs:start:read_minter assert(storage.minters.at(context.msg_sender()).read() == 1, "caller is not minter"); + // docs:end:read_minter let amount = SafeU120::new(amount); let new_balance = SafeU120::new(storage.public_balances.at(to.address).read()).add(amount); let supply = SafeU120::new(storage.total_supply.read()).add(amount); @@ -164,7 +189,9 @@ contract Token { let supply = SafeU120::new(storage.total_supply.read()).add(SafeU120::new(amount)); storage.total_supply.write(supply.value as Field); + // docs:start:insert_from_public pending_shields.insert_from_public(&mut note); + // docs:end:insert_from_public 1 } // docs:end:mint_private @@ -264,12 +291,16 @@ contract Token { amount: Field, secret: Field, ) -> Field { + // docs:start:private_init_storage let storage = Storage::init(Context::private(&mut context)); + // docs:end:private_init_storage let pending_shields = storage.pending_shields; let balance = storage.balances.at(to.address); let mut public_note = TransparentNote::new_from_secret(amount, secret); + // docs:start:assert_contains_and_remove_publicly_created pending_shields.assert_contains_and_remove_publicly_created(&mut public_note); + // docs:end:assert_contains_and_remove_publicly_created increment(balance, amount, to.address); 1