Skip to content

Commit

Permalink
some corrections and additional explanations for offchain state
Browse files Browse the repository at this point in the history
  • Loading branch information
mitschabaude committed May 16, 2024
1 parent 1ac0e70 commit 4742537
Showing 1 changed file with 35 additions and 11 deletions.
46 changes: 35 additions & 11 deletions docs/zkapps/writing-a-zkapp/feature-overview/offchain-storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ This approach maintains a provably secure connection between the on-chain smart

## Design

Presently, Offchain storage offers support for two types of state: `OffchainState.Field`, representing a single field element, and `OffchainState.Map`, akin to the key-value maps found in JavaScript, like `let myMap = new Map<string, number>();`.
Presently, Offchain storage offers support for two types of state: `OffchainState.Field`, representing a single field of state, and `OffchainState.Map`, akin to the key-value maps found in JavaScript, like `let myMap = new Map<string, number>();`.

All offchain state resides within a single Merkle Map, which is essentially a wrapper around a [Merkle Tree](/zkapps/o1js/merkle-tree.mdx). Practically speaking, there are no constraints on the number of state fields and maps that a developer can store in a smart contract using Offchain storage.

Expand Down Expand Up @@ -84,7 +84,7 @@ await offchainState.compile();
await ExampleContract.compile();
```

When the offchain state requires settlement, an Offchain storage proof must be generated and provided to the smart contract's `settle()` method. This method automatically retrieves all pending actions (state changes) and resolves them using a recursive reducer.
To settle the offchain state, an Offchain storage proof must be generated and provided to the smart contract's `settle()` method. This method automatically retrieves all pending actions (state changes) and resolves them using a recursive reducer.
Finally, the proof is passed to the `settle()` method.

```ts
Expand All @@ -101,7 +101,7 @@ await Mina.transaction(sender, () => {

### Configuring Your Smart Contract

The smart contract requires a field indicating the previously defined commitment to the offchain state. This field allows accessing offchain state similar to any other on-chain value.
The smart contract requires a field containing a commitment to the offchain state. This field is used internally by the `OffchainState` methods and should not be written to by your smart contract logic.

```ts
class MyContract extends SmartContract {
Expand Down Expand Up @@ -138,15 +138,26 @@ class MyContract extends SmartContract {
// ...
@method
async useOffchainStorage(playerA: PublicKey) {
let totalScoreOption = await offchainState.fields.totalScore.get(); // retrieve totalScore
let totalScore = totalScoreOption.orElse(0n); // unwrap the Option and return a default value if the entry if empty
// retrieve totalScore, returning an Option
let totalScoreOption = await offchainState.fields.totalScore.get();

// unwrap the Option and return a default value if the entry if empty
let totalScore = totalScoreOption.orElse(0n);

// increment totalScore, set a precondition on the state
// (if `from` is undefined, the precondition is that the field is empty)
offchainState.fields.totalScore.update({
from: totalScoreOption,
to: totalScore.add(1),
}); // increment totalScore, set a precondition on the state, if `from` is undefined apply the update unconditionally
});

let playerOption = await offchainState.fields.players.get(playerA); // retrieve an entry from the map, returning an Option
let score = playerOption.orElse(0n); // unwrap the player's score Option and return a default value if the entry is empty
// retrieve an entry from the map, returning an Option
let playerOption = await offchainState.fields.players.get(playerA);

// unwrap the player's score Option and return a default value if the entry is empty
let score = playerOption.orElse(0n);

// increment the player's score, set a precondition on the previous score
offchainState.fields.players.update(playerA, {
from: fromOption,
to: score.add(1),
Expand All @@ -155,11 +166,24 @@ class MyContract extends SmartContract {
}
```

Currently, Offchain storage of type Field support `field.get()` and `field.overwrite(newValue)`, while maps support `map.get(key)` and `map.overwrite(key, newValue)`.
The `.overwrite()` method sets the value without taking into account the previous value. If the value is modified by multiple zkkApps concurrently, interactions that are applied later will simply be overwritten!
Currently, Offchain states of type Field support `field.get()` and `field.overwrite(newValue)`, while maps support `map.get(key)` and `map.overwrite(key, newValue)`.
The `.overwrite()` method sets the value without taking into account the previous value. If the value is modified by multiple zkkApps concurrently, interactions that were applied earlier will simply be overwritten!

All Offchain storage types also provide an `.update()` method which is a safe version of `.overwrite()`.
The `.update()` method lets you define preconditions on the state that you want to update. If the precondition of the previous value does not match, the update will not be applied.
The `.update()` method lets you define a precondition on the state that you want to update. If the precondition of the previous value does not match, the update will not be applied:

```ts
field.update(config: {
// `from` is the precondition on the previous state
from: Option<T>,
// `to` is the new state to set
to: T,
});
```

Note that the precondition is an `Option` type: setting it to `None` means that you require the field to not exist, while `Some(value)` requires that it exists and contains the `value`. The return value of `get()` is an `Option` with the same semantics, and can be passed to `update()` directly.

Important: When `update()` fails due a mismatching precondition, _none_ of the state updates made in the same method call will be applied. This lets you safely write logic where multiple fields are linked and have to be updated in a consistent way, like in the example above where the total score has to be the sum of all player's scores.

## Additional Resources

Expand Down

0 comments on commit 4742537

Please sign in to comment.