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

Examples and cleanup #59

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
134 changes: 134 additions & 0 deletions examples/airdrop.eg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* Example implementation of a token airdrop that allows concurrent withdrawals.
*/
import {
AccountUpdate,
AccountUpdateForest,
AccountUpdateTree,
Bool,
Experimental,
Field,
method,
Mina,
Poseidon,
Provable,
PublicKey,
SmartContract,
State,
state,
Struct,
UInt64,
} from "o1js"
import { token, tokenId } from "./token.eg"
const { IndexedMerkleMap, BatchReducer } = Experimental

Check failure on line 23 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Property 'IndexedMerkleMap' does not exist on type 'typeof Experimental'.

Check failure on line 23 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Property 'BatchReducer' does not exist on type 'typeof Experimental'.

class MerkleMap extends IndexedMerkleMap(10) {}

// a couple of test accounts
const accounts = Mina.TestPublicKey.random(20)

// create a map of eligible accounts
const eligible = createEligibleMap(accounts)

/**
* An action to claim your airdrop.
*/
class Claim extends Struct({ account: PublicKey, amount: UInt64 }) {}

// set up reducer
let batchReducer = new BatchReducer({ actionType: Claim, batchSize: 5 })
class Batch extends batchReducer.Batch {}
class BatchProof extends batchReducer.BatchProof {}

/**
* Contract that manages airdrop claims.
*/
class Airdrop extends SmartContract {
// merkle map related state
@state(Field)
eligibleRoot = State(eligible.root)

Check failure on line 49 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Expected 0 arguments, but got 1.

Check failure on line 49 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Property 'root' does not exist on type 'MerkleMap'.
@state(Field)
eligibleLength = State(eligible.length)

Check failure on line 51 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Expected 0 arguments, but got 1.

Check failure on line 51 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Property 'length' does not exist on type 'MerkleMap'.

// batch reducer state
@state(Field)
actionState = State(BatchReducer.initialActionState)

Check failure on line 55 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Expected 0 arguments, but got 1.
@state(Field)
actionStack = State(BatchReducer.initialActionStack)

Check failure on line 57 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Expected 0 arguments, but got 1.

@method
async claim(amount: UInt64) {
let account = this.sender.getUnconstrained()
account.isEmpty().assertFalse() // TODO remove when this is prevented in `createSigned`

// ensure that the token account already exists and that the sender knows its private key
let au = AccountUpdate.createSigned(account, tokenId)
au.body.useFullCommitment = Bool(true) // ensures the signature attests to the entire transaction

batchReducer.dispatch(new Claim({ account, amount }))
}

@method.returns(MerkleMap.provable)

Check failure on line 71 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Property 'provable' does not exist on type 'typeof MerkleMap'.
async settleClaims(batch: Batch, proof: BatchProof) {
// fetch merkle map and assert that it matches the onchain root
let eligible = await Provable.witnessAsync(MerkleMap.provable, fetchEligibleMap)

Check failure on line 74 in examples/airdrop.eg.ts

View workflow job for this annotation

GitHub Actions / Ensure typecheck and tests pass

Property 'provable' does not exist on type 'typeof MerkleMap'.
this.eligibleRoot.requireEquals(eligible.root)

let accountUpdates = AccountUpdateForest.empty()

// process claims by reducing actions
batchReducer.processBatch({ batch, proof }, (claim, isDummy) => {
// check whether the claim is valid = exactly contained in the map
let accountKey = key(claim.account)
let amountOption = eligible.getOption(accountKey)
let amount = UInt64.Unsafe.fromField(amountOption.orElse(0n)) // not unsafe, because only uint64s can be claimed
let isValid = amountOption.isSome.and(amount.equals(claim.amount)).and(isDummy.not())

// if the claim is valid, set the amount in the map to zero
eligible.setIf(isValid, accountKey, 0n)

// if the claim is valid, add a token account update to our forest of approved updates
let update = AccountUpdate.default(claim.account, tokenId)
update.balance.addInPlace(amount)
this.balance.subInPlace(amount) // this is 0 if the claim is invalid
accountUpdates.pushIf(isValid, AccountUpdateTree.from(update))
})

// approve the created account updates
token.approveBase(accountUpdates)

// update the onchain eligible map
this.eligibleRoot.set(eligible.root)
this.eligibleLength.set(eligible.length)

// return the updated eligible map
return eligible
}
}

/**
* Helper function to create a map of eligible accounts.
*/
function createEligibleMap(accounts: PublicKey[]) {
// predefined MerkleMap of eligible accounts
const eligible = new MerkleMap()

// every account gets 100 tokens
accounts.forEach((account) => eligible.insert(key(account), 100n))

return eligible
}

/**
* How to map an address to a map key.
*/
function key(address: PublicKey) {
return Poseidon.hashPacked(PublicKey, address)
}

/**
* Mock for fetching the (partial) Merkle Map from a service endpoint.
*/
async function fetchEligibleMap() {
return eligible
}
2 changes: 1 addition & 1 deletion examples/e2e.eg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Mina.setActiveInstance(localChain)

const fee = 1e8

const [deployer, owner, alexa, billy] = Mina.TestPublicKey.random(4)
const [deployer, owner, alexa, billy] = localChain.testAccounts
const contract = PrivateKey.randomKeypair()
const admin = PrivateKey.randomKeypair()

Expand Down
2 changes: 1 addition & 1 deletion examples/escrow.eg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Mina.setActiveInstance(localChain)

const fee = 1e8

const [deployer, owner, alexa, billy, jackie] = Mina.TestPublicKey.random(5)
const [deployer, owner, alexa, billy, jackie] = localChain.testAccounts
const tokenContract = PrivateKey.randomKeypair()
const escrowContract = PrivateKey.randomKeypair()
const admin = PrivateKey.randomKeypair()
Expand Down
11 changes: 11 additions & 0 deletions examples/token.eg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* This file contains parameters for an example token
*/
import { FungibleToken } from "FungibleToken.js"
import { Mina } from "o1js"

export { token, tokenAccount, tokenId }

let tokenAccount = Mina.TestPublicKey.random()
let token = new FungibleToken(tokenAccount)
let tokenId = token.deriveTokenId()