Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: storage and state variables #1725

Merged
merged 9 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions docs/docs/dev_docs/contracts/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@

- A special `constructor` function MUST be declared within a contract's scope.
- A constructor doesn't have a name, because its purpose is clear: to initialise state.
- In Aztec terminology, a constructor is always a 'private function' (i.e. it cannot be an `open` function).
- In Aztec terminology, a constructor is always a 'secret function' (i.e. it cannot be an `open` function).
- A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract.

## secret functions

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
#include_code functions-SecretFunction /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust

## `open` functions

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
## `unconstrained` functions
#include_code functions-OpenFunction /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust

## `unconstrained` functions

#include_code functions-UncontrainedFunction /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust

# Calling functions

Expand Down Expand Up @@ -47,4 +51,4 @@ E.g. `get()`

## Delegatecall

Talk a about the dangers of delegatecall too!
Talk a about the dangers of delegatecall too!
219 changes: 202 additions & 17 deletions docs/docs/dev_docs/contracts/state_variables.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,220 @@
# State Variables

## `PublicState`
State variables come in two flavours: [**public** state](#publicstatet-t_serialised_len) and [**private** state](#private-state-variables).

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
Public state is persistent state which is _publicly visible_, by anyone in the world.
## `PublicState<T, T_SERIALISED_LEN>`

For developers coming from other blockchain ecosystems (such as Ethereum) this will be a familiar concept, because there, _all_ state is _publicly visible_.
Public state is persistent state that is _publicly visible_ to anyone in the world.

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_ (LINK). See here (LINK) for more of the technical details.
For developers coming from other blockchain ecosystems (such as Ethereum), this will be a familiar concept, because there, _all_ state is _publicly visible_.

The `PublicState<T, T_SERIALISED_LEN>` struct, provides a wrapper around conventional Noir types `T`, allowing such types to be written-to and read-from the public state tree.
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_ (LINK). See here (LINK) for more of the technical details.

#include_code PublicState /yarn-project/noir-contracts/src/contracts/public_token_contract/src/storage.nr rust
The `PublicState<T, T_SERIALISED_LEN>` struct serves as a wrapper around conventional Noir types `T`, allowing these types to be written to and read from the public state tree.

:::danger TODO
Examples which:
- initialise a `PublicState<T>` by itself (without being wrapped in a `Map`)
- initialise a `PublicState<T>` where `T` is a custom struct.
:::
The Aztec stdlib provides serialization methods for some common types. Check [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation) for the complete list.

### `::new`

## `Map<T>`
In the following example, we define a public state with a boolean value.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#include_code state_vars-PublicState /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/storage/locked.nr rust

### Custom value
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

It's possible to create a public state for any types. For example, to create a public state for the following struct:

#include_code state_vars-CustomStruct /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/types/queen.nr rust

First, define how to serialise and deserialise the struct. And then initialise the PublicState with it:

#include_code state_vars-PublicStateCustomStruct /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/storage/queen.nr rust

### `.write`

We can pass the associated type directly to a public state. It knows how to serialise the given value to store in the public state tree.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#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

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
### `.read`
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

Reading a value from a public state is straightforward:
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#include_code state_vars-PublicStateRead /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust

## Private State
It returns the type the public state was declared with. The above example returns a boolean. And the following example returns a custom struct.

### UTXO trees
#include_code state_vars-PublicStateReadCustom /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust

## Private State Variables

There are 3 different types of private state variables:

- [Singleton<NoteType\>](#singletonnotetype)
- [ImmutableSingleton<NoteType\>](#immutablesingletonnotetype)
- [Set<NoteType\>](#setnotetype)

In contrast to public state variables, private state variables are only visible to specific relevant parties. The value of a private state variable can either be shared via (log)[INSERT_LINK_HERE] or completely offline.

Note that an app can choose to emit the data via unencrypted log, or define a note whose data is easy to figure out, then the information is technically not private and could be visible to anyone.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

### Notes

### Custom Notes
Unlike public state variables, which can be arbitrary types. Private state variables operate on `NoteType`.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

Notes are the fundamental elements in the private world.

A note should confine to the following interface:
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#include_code NoteInterface /yarn-project/noir-libs/noir-aztec/src/note/note_interface.nr rust

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
## `Singleton<NoteType>`

Singleton is a private state variable that is unique in a way. When a singleton is initialised, 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 Card note:

#include_code state_vars-Singleton /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/storage/legendary_card.nr rust

### `.initialise`

The initial value of a singleton is set via calling `initialise`:

#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 initialised again.

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
### `.replace`

To modify the value of a singleton, we will create a note (a Card in the following example) that has the new data, and replace the current note with it:
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#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.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

### `.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.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

## `ImmutableSingleton<NoteType>`

Immutable singleton is unique and, as the name suggests, immutable. Once it has been initialised, its value can't be changed anymore.

### `::new`

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
#include_code state_vars-ImmutableSingleton /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/storage/game_rules.nr rust

### `.initialise`

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
#include_code state_vars-ImmutableSingletonInit /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust

### `.get_note`

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
#include_code state_vars-ImmutableSingletonGet /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust

Different to singleton, calling the `get_note` function on an immutable singleton to read the value doesn't destroy the current note behind the scene. Which means this function can be called simultaneously.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

## `Set<NoteType>`

Set is used for managing a collection of notes. All notes in a set should be the same type. But whether they belong to one single account, or are accessible by different entities, is totally up to the developer.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

### `::new`

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
In the following example, we define a set of cards:

#include_code state_vars-Set /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/storage/cards.nr rust

### `.insert`

We can call `insert` for as many times as we need to add new notes to a set:
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#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 transaction won't fail if the note we are removing does not exist in the set. As a best practice, we should fetch the notes by calling [`get_notes`](#get_notes), which does a membership check under the hood to make sure the notes exist, and then feed the returned notes to the `remove` function.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

### `.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 maxinum number of notes this function can return at a time. Check [here](INSERT_LINK_HERE) 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 retrieving notes from a database:
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#include_code NoteGetterOptions /yarn-project/noir-libs/noir-aztec/src/note/note_getter_options.nr rust

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
For example, the following configs let us 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.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#include_code state_vars-NoteGetterOptionsSelectSortOffset /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/options.nr rust
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

The first value of `.select` and `.sort` is the index of a field in a note type. For the note type `Card` that has the following fields:
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#include_code state_vars-NoteCard /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/types/card.nr rust
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

The indices are: 0 for `points`, 1 for `secret`, and 2 for `owner`.

In the previous example,

`.select(2, account_address)` matches the 2nd field of `Card`, which is `owner`, and returns the cards whose `owner` field equals `account_address`.

`.sort(0, SortOrder.DESC)` sorts the 0th field of `Card`, 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

## `Map<T>`

`Map` is a state variable that maps a field to another state variable, which can be [`PublicState`](#publicstatet-t_serialised_len), all the [private state variables](#private-state-variables), and even another Map.
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

### `::new`

The following map uses singleton as its value:
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#include_code state_vars-MapSingleton /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/storage/profiles.nr rust

LeilaWang marked this conversation as resolved.
Show resolved Hide resolved
### `.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

### `UTXO<NoteType>`
#include_code state_vars-MapAtSingletonGet /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/actions.nr rust

### `UTXOSet<NoteType>`
In both code snippets, `state_var.at(account)` returns a singleton that is linked to the requested account.
14 changes: 10 additions & 4 deletions docs/docs/dev_docs/contracts/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@

State variables must be declared inside a struct. (This enables us to declare types composed of nested generics in Noir - see [types](./types.md)).

By way of example, we could define a private state variable `balances`, mapping user addresses to their token balances:
We could define any kinds of state variables in the Storage struct:

#include_code storage-declaration /yarn-project/noir-contracts/src/contracts/private_token_contract/src/storage.nr rust
#include_code storage-declaration /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/storage.nr rust
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll start reviewing this now (in between other meetings). It's looking lovely!

My first comment: If the example contract is going to be used throughout the syntax explanations (which is a very nice idea), should we have a page or paragraph which introduces the purpose of this contract? What is the game? What is the objective of the game? What are the rules? What do players do?

It feels like this explanation could be both a README within the contract's directory, but also as a dedicated page or paragraph of these docs?


#include_code storage-import /yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr rust
See [State Variables](./state_variables.md) for how to initialise them.

State variables come in two flavours: **public** state and **private** state. <INSERT LINK TO DOC EXPLAINING PRIVATE STATE & UTXOS>.
Using Storage in a contract is like using any other variables. First, import them to the `main.nr` file:
LeilaWang marked this conversation as resolved.
Show resolved Hide resolved

#include_code storage-import /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust

For each function that needs access to the storage, initialise the storage inside the function, and call the state variables in it:

#include_code storage-init /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust
12 changes: 6 additions & 6 deletions docs/docs/dev_docs/contracts/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@

## Aztec stdlib

On top of ['Vanialla Noir's' stdlib](https://noir-lang.org/standard_library/array_methods), we provide an [Aztec stdlib](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/noir-libs) for writing Noir Contracts. The Aztec stdlib contains abstractions which remove the need to understand the low-level Aztec protocol. Notably, it provides:
- Public and private [state variable types](./types.md)
On top of ['Vanilla Noir's' stdlib](https://noir-lang.org/standard_library/array_methods), we provide an [Aztec stdlib](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/noir-libs) for writing Noir Contracts. The Aztec stdlib contains abstractions which remove the need to understand the low-level Aztec protocol. Notably, it provides:

- Public and private [state variable types](./types.md).
- Ready-made notes.
- Functions for [emitting](./events.md) encrypted and unencrypted logs
- Functions for [emitting](./events.md) encrypted and unencrypted logs.
- [Oracle functions](./functions.md#oracle-calls) for accessing:
- private state
- secrets
- Functions for communicating with Ethereum L1

- Functions for communicating with Ethereum L1.

To import the Aztec stdlib into your Noir Contract project, simply include it as a dependency:

:::danger TODO
https://github.com/AztecProtocol/aztec-packages/issues/1335
:::

#include_code importing-aztec /yarn-project/noir-contracts/src/contracts/private_token_contract/Nargo.toml toml
#include_code importing-aztec /yarn-project/noir-contracts/src/contracts/private_token_contract/Nargo.toml toml
2 changes: 1 addition & 1 deletion yarn-project/noir-contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
artifacts/
target/
proofs/
types/
/src/types/
Prover.toml
Verifier.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "docs_example_contract"
authors = [""]
compiler_version = "0.1"
iAmMichaelConnor marked this conversation as resolved.
Show resolved Hide resolved
type = "contract"

[dependencies]
aztec = { path = "../../../../noir-libs/noir-aztec" }
Loading