Skip to content

Commit

Permalink
Docs for storage and state variables.
Browse files Browse the repository at this point in the history
  • Loading branch information
LeilaWang committed Aug 22, 2023
1 parent 04a59f1 commit 8242210
Show file tree
Hide file tree
Showing 25 changed files with 1,090 additions and 41 deletions.
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

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

## `open` functions

## `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).

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.

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

### Custom value

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.

#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`

Reading a value from a public state is straightforward:

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

### Notes

### Custom 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 confine to the following interface:

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

## `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.

### `.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:

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

### `.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<NoteType>`

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

### `::new`

#include_code state_vars-ImmutableSingleton /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/storage/game_rules.nr rust

### `.initialise`

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

### `.get_note`

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

## `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.

### `::new`

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:

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

### `.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:

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

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.

#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 `Card` that has the following fields:

#include_code state_vars-NoteCard /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/types/card.nr rust

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.

### `::new`

The following map uses singleton as its value:

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

### `.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

#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:

#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"
type = "contract"

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

0 comments on commit 8242210

Please sign in to comment.