<!-- all slides will have a font of 30px -->
<style>
.reveal {
font-size: 30px;
}
</style>
# Snapps
---
<!-- .slide: style="font-size: 30px;" -->
## Snapps
Ethereum is a computer that can store user programs. Users execute a program by sending a transaction **specifying one of its methods**.
<img src="https://i.imgur.com/dWBFAXU.jpg" height="200"/>
Mina is a computer that can store user programs. Users execute a program by sending a transaction **containing the execution result of a method (accompanied with a proof of correct execution)**.
---

---
## Snapps
As such, a snapp is eventually compiled down into two programs: one that you can use locally to execute one of its method (and create a proof), and one that others can use to verify that the execution was correct.
<img src="https://i.imgur.com/czNhsNQ.png" height="300px">
*Note*: one account = one snapp
---
## Snapps
Each method of a snapp is actually compiled to a program which:
* takes as argument the method's arguments, as well as a list of preconditions
* returns a list of effects/updates to the state
<img src="https://i.imgur.com/aNGdpQS.png" height="300px">
---
## Snapps
under the hood:
- arguments of the methods are the private inputs of a circuit
- any value read from the state is translated as a precondition, encoded as a public input
- effects/updates are encoded as public inputs to the circuit as well.
The proof is thus of the following statement:
> based on a list of preconditions on the world state, and the snapp's state. Running the snapp's method with some secret inputs leads to the following effects/updates on the world state and the snapp's state.
---
<img src="https://i.imgur.com/TGI2Leb.png" height="300">
Note: of course effects on the world state can only be applied in constrained ways: an external account can only be credited, not debited. Only the current snapp's state can be updated (not other snapps' states).
---
## Setup (NOT READY FOR WORKSHOP)
The Snapp CLI allows you to easily set up, test, and deploy your smart contract.
```console
$ npm install -g snapp-cli
$ snapp project my-proj
```
---
## Setup (NOT READY FOR WORKSHOP)

---
## Setup
```console
$ git clone git@github.com:o1-labs/snarkyjs.git
$ npm install && npm run build
$ ./run src/examples/api_exploration.ts
```
or better:
```console
$ mkdir my-project && cd my-project
$ npm init -y
$ npm install @o1labs/snarkyjs typescript
```
or even better:
```console
$ git clone git@github.com:o1-labs/snarkyjs-workshop.git
$ cd snarkyjs-workshop
```
---
## Hello World
To write a snapp, extend the `SmartContract` class of snarkyjs.
```typescript
import { SmartContract, state, method, init, State, Field } from 'snarkyjs';
class MySmartContract extends SmartContract { }
```
---
## Hello World
A snapp can contain some state (mutable storage).
```typescript
import { SmartContract, state, method, init, State, Field } from 'snarkyjs';
class MySmartContract extends SmartContract {
@state some_var: State<Field>;
@state some_other_var: State<Field>;
}
```
Here, `some_var` is of type `Field`. It is fundamentally the only type supported, and all other types are created from this type. It's almost exactly the same type as Ethereum's `u256` type, except that it's a bit smaller.
---
## Hello World
TODO: explanation about init/constructor...
```typescript
import { SmartContract, state, method, init, State, Field } from 'snarkyjs';
class MySmartContract extends SmartContract {
@state some_var: State<Field>;
@init constructor(some_var: Field) {
let perms = Permissions.default();
perms.receive = Perm.proof();
perms.editState = Perm.proof();
this.self.update.permissions.set(perms)
this.some_var = some_var;
}
}
```
---
## Hello World
a snapp can contain multiple methods, and each method can mutate the storage in different ways
```typescript
import { SmartContract, state, method, init, State, Field } from 'snarkyjs';
class MySmartContract extends SmartContract {
// ...
@method update(some_arg: Field) {
// ...
}
@method add(some_arg: Field, more_arg: Field) {
// ...
}
}
```
note:
* one snapp = multiple circuits/verifying keys (one for each method)
---
## Hello World
The state of your snapp is considered "public", and any method of you snapps' methods can read and update them.
Any argument of a method is considered "private", as no one else but you will be able to learn them. (Unless, for example, a method stores an argument directly in the state.)
```typescript
import { SmartContract, state, method, init, State, Field } from 'snarkyjs';
class MySmartContract extends SmartContract {
@state some_var: State<Field>;
// ...
@method update(squared: Field) {
this.some_var.get().square().assertEqual(squared); // some_var^2 = squared
this.some_var.set(squared); // some_var ← squared
}
}
```
under the hood:
* `get()` -> reads the blockchain and encodes a precondition in the public input of the circuit (e.g. `some_var` must be equal to 4 for the execution to be correct).
* `square()` -> creates a new temporary variable in the circuit that is constrained to be the square of `some_var`.
* `assertEqual()` -> asserts that the temporary variable is equal to the private input `squared`.
* `set()` -> encodes an effect on the snapp's state (e.g. it is correct that executing this method sets `some_var` to 8).
---
## Exercise 1
Create a similar smart contract, that requires that you pass the cubic root of `some_var` as private input. Another private input should set the new value for `some_var`.
---
## Exercise 1
```typescript
import { SmartContract, state, method, init, State, Field } from 'snarkyjs';
class MySmartContract extends SmartContract {
@state some_var: State<Field>;
// TODO: init
@method update(sqrt: Field, new_val: Field) {
let some_var = this.some_var.get();
let cubed = new_val.square().mul(new_val);
cubed.assertEqual(some_var);
this.some_var.set(new_val);
}
}
```
---
## State of the world
What else is public and accessible from a method? Blockchain data, given to you through `this.self`
```typescript
class MySmartContract extends SmartContract {
// ...
@method update(squared: Field) {
this.self.protocolState.blockchainLength.assertGt(500);
}
// ...
}
```
problem: state of the blockchain you see when you execute a method (and produce a transaction that includes a proof) is different from the state of the blockchain a block producer sees when they process your transaction.
solution: ??
---
## State of the world
You can also modify blockchain data as part of the result of an execution, for example to send minas to another account as part of a method's execution
```typescript
this.self.update.X = Y;
```
under the hood: this constrains that specific public output to be equal to Y
---
TODO: continue with updating accounts with payments + do exercise to extend the previous one by returning a reward
---
## Types
<!-- types -->
Consult the documentation to see operations on `Field`, or let auto-complete do that for you.
<img src="https://i.imgur.com/QJcfbGg.png" height="300">
---
## Types
snarkyjs provides a number of useful types based on `Field`:
* `UInt64`
* `UInt32`
* `PublicKey`
* `Optional`
* `Bool`
* `array`
* `Group`
* `EndoScalar`
* `Scalar`
* ...
---
## Types
You can also create custom types by extending snarkyjs' `CircuitValue` and using the `@prop` decorator:
```typescript
class RollupTransaction extends CircuitValue {
@prop amount: UInt64;
@prop nonce: UInt32;
@prop sender: PublicKey;
@prop receiver: PublicKey;
constructor(amount: UInt64, nonce: UInt32, sender: PublicKey, receiver: PublicKey) {
super();
this.amount = amount;
this.nonce = nonce;
this.sender = sender;
this.receiver = receiver;
}
}
```
---
## Functionalities
* just write your code, functions implemented on the snarky types will automatically convert themselves into constraints
* use snarky's assert functions to prevent execution of incorrect logic.
---
## Functionalities
* `Poseidon.hash(input: Field[]): Field` → a hash function.
<!-- .element: class="fragment" data-fragment-index="1" -->
* `Circuit.if(b: Bool, x: T, y: T): T` → there's no `if` for flow control, but you can use it as a ternary operator.
<!-- .element: class="fragment" data-fragment-index="2" -->
* `Signature.verify(publicKey: PublicKey, msg: Field[]): Bool` → verify signature.
* ?
<!-- .element: class="fragment" data-fragment-index="3" -->
---
## Exercise 2
1. Create an `Account` custom type that contains a counter (represented by a field element) (TODO: can we make it a u64?) and a [public key](https://o1-labs.github.io/snarkyjs/classes/PublicKey.html).
2. Create a smart contract with a single account as state, and a method `increment_counter` that allows you to increment the counter if you can provide a valid signature (using the account's public key) on the current counter.
---
## Exercise 2
```typescript=
import { SmartContract, state, method, init, State, Field } from 'snarkyjs';
class Account extends CircuitValue {
@prop: pubkey: State<PublicKey>;
@prop: counter: State<Field>;
}
class Exercise2 extends SmartContract {
@state account: State<Account>;
// TODO: init
@method increment_counter(signature: Signature) {
const counter = this.counter.get();
signature.verify(pubkey, [counter]).assertEquals(true);
this.counter.set(counter + 1);
}
}
function main() {
// keygen
const privkey = PrivateKey.random();
const pubkey = privkey.toPublicKey();
// create signature
const digest = Poseidon.hash(...);
const signature = Signature.create(privkey, [digest]);
}
```
note:
* we cannot use arrays as arguments of methods, we need fixed-size arguments, so tuples will do.
---
## Dealing with large state
Note that the state of a snapp can only contain 8 field values.
```typescript
@state some_var: State<Field>; // 1 field value
@state some_other_var: State<YourOwnType>; // potentially more
```
To manage larger values, a snapp must use accumulator structures like Merkle trees. Snarkyjs standard library provides a number of functions to help you with that.
---
## Dealing with large state
* KeyedAccumulator
* SetAccumulator
* Stack/ListAccumulator
TODO: do we really need to name them "merkle trees" or "accumulators". Is there a more dev-friendly name?
---
## Dealing with large state
pro-tip: you do not need to understand how merkle trees work to use them. Simply use them as blackboxes that provide a storage API.
```typescript
interface AccumulatorI<A, P> {
// adds an element into your set
add: (x: A) => P,
// creates a proof that an element is part of your set
getMembershipProof: (x: A) => P | null,
// this is an authenticator for your set, store it in your snapp
commitment: () => Field,
// this can check a proof that an element is part of a given set
check: (x: A, membershipProof: P) => Bool,
}
```
---
## Dealing with large state
For example:
```typescript
this.my_set.assertEquals(some_set.commitment());
some_set.add(some_value); // can I do this.my_set.add(some_value) ?
this.my_set.set(some_set.commitment());
```
only a field element (a commitment, why?) is stored in the state of a snapp.
---
## Dealing with large state
The data associated to the commitment of a merkle tree needs to live off-chain. There's a data availability problem. You can store this on IPFS, locally, etc. but users of a snapp must have a way to retrieve that data in order to correctly use a snapp.
---
## Exercise 3
create a smart contract that makes use of a Merkle tree
---
## Exercise 3
```typescript
import { SmartContract, state, method, init, State, Field } from 'snarkyjs';
class Exercise3 extends SmartContract {
@state merkle_root: State<Field>;
// TODO: init
@method update_balance(merkle_tree: KeyedAccumulator<PublicKey, Field>, public_key: PublicKey) {
let balance = this.merkle_root.get(public_key).assertEquals(merklee_tree.commitment());
asserts(balance.is_some);
balance.value += val;
this.merkle_root.set(merkle_tree.commitment())
}
}
function main() {
}
```
---
## Making a function payable
```typescript
@method depositFunds(
depositor: Body,
depositAmount: UInt64) {
const self = this.self;
let delta = SignedAmount.ofUnsigned(depositAmount);
self.delta = delta;
depositor.delta = delta.neg();
let deposit = new RollupDeposit(depositor.publicKey, depositAmount);
this.emitEvent(deposit);
const rollupState = this.rollupState.get();
rollupState.pendingDepositsCommitment = MerkleStack.pushCommitment(
deposit, rollupState.pendingDepositsCommitment
);
this.rollupState.set(rollupState);
}
```
```typescript
let depositorBalance = Mina.getBalance(depositorPubkey);
let depositor = Party.createSigned(depositorPrivkey);
return Mina.transaction(() => {
// Deposit some funds into the rollup
RollupInstance.depositFunds(depositor, depositorBalance.div(2));
}).send().wait()
```
---
## Sending money
TKTK
---
## Exercise 5
Do we have time for that?
---
## Events
TKTK
---
## Recursion
```typescript
@proofSystem
class RollupProof extends ProofWithInput<RollupStateTransition> {
@branch static processDeposit(
// ...
}
```
{"metaMigratedAt":"2023-06-16T14:36:28.539Z","metaMigratedFrom":"YAML","title":"Snapps Workshop","breaks":true,"slideOptions":"{\"theme\":\"serif\",\"transition\":\"fade\",\"progress\":true}","contributors":"[{\"id\":\"0cd8f44c-d63a-4fb1-a9c1-a6c6da3e43e3\",\"add\":26094,\"del\":12296}]"}