Skip to content

Commit

Permalink
Merge branch 'main' into test/tokens-playground
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinMinkov committed Jul 21, 2022
2 parents d870ae1 + f2c3360 commit b9919ab
Show file tree
Hide file tree
Showing 16 changed files with 739 additions and 392 deletions.
40 changes: 32 additions & 8 deletions src/examples/simple_zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
UInt32,
Bool,
PublicKey,
Circuit,
Experimental,
} from 'snarkyjs';

await isReady;
Expand All @@ -32,6 +34,7 @@ class SimpleZkapp extends SmartContract {
this.setPermissions({
...Permissions.default(),
editState: Permissions.proofOrSignature(),
send: Permissions.proofOrSignature(),
});
this.balance.addInPlace(UInt64.fromNumber(initialBalance));
this.x.set(initialState);
Expand All @@ -54,14 +57,17 @@ class SimpleZkapp extends SmartContract {
callerAddress.assertEquals(privilegedAddress);

// assert that the caller nonce is 0, and increment the nonce - this way, payout can only happen once
let callerParty = Party.createUnsigned(callerAddress);
let callerParty = Experimental.createChildParty(this.self, callerAddress);
callerParty.account.nonce.assertEquals(UInt32.zero);
callerParty.body.incrementNonce = Bool(true);

// pay out half of the zkapp balance to the caller
let balance = this.account.balance.get();
this.account.balance.assertEquals(balance);
let halfBalance = balance.div(2);
// FIXME UInt64.div() doesn't work on variables
let halfBalance = Circuit.witness(UInt64, () =>
balance.toConstant().div(2)
);
this.balance.subInPlace(halfBalance);
callerParty.balance.addInPlace(halfBalance);

Expand All @@ -71,6 +77,8 @@ class SimpleZkapp extends SmartContract {
}
}

const doProofs = true;

let Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);

Expand All @@ -89,6 +97,11 @@ let initialBalance = 10_000_000_000;
let initialState = Field(1);
let zkapp = new SimpleZkapp(zkappAddress);

if (doProofs) {
console.log('compile');
await SimpleZkapp.compile(zkappAddress);
}

console.log('deploy');
let tx = await Mina.transaction(feePayer, () => {
Party.fundNewAccount(feePayer, { initialBalance });
Expand All @@ -102,28 +115,38 @@ console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`);
console.log('update');
tx = await Mina.transaction(feePayer, () => {
zkapp.update(Field(3));
zkapp.sign(zkappKey);
if (!doProofs) zkapp.sign(zkappKey);
});
if (doProofs) await tx.prove();
tx.send();

// pay more into the zkapp -- this doesn't need a proof
console.log('receive');
tx = await Mina.transaction(feePayer, () => {
let payerParty = Party.createSigned(feePayer);
payerParty.balance.subInPlace(8e9);
zkapp.balance.addInPlace(8e9);
});
tx.send();

console.log('payout');
tx = await Mina.transaction(feePayer, () => {
zkapp.payout(privilegedKey);
zkapp.sign(zkappKey);
if (!doProofs) zkapp.sign(zkappKey);
});
if (doProofs) await tx.prove();
tx.send();

console.log(tx.toJSON());

console.log('final state: ' + zkapp.x.get());
console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`);

console.log('try to payout a second time..');
tx = await Mina.transaction(feePayer, () => {
zkapp.payout(privilegedKey);
zkapp.sign(zkappKey);
if (!doProofs) zkapp.sign(zkappKey);
});
try {
if (doProofs) await tx.prove();
tx.send();
} catch (err: any) {
console.log('Transaction failed with error', err.message);
Expand All @@ -133,8 +156,9 @@ console.log('try to payout to a different account..');
try {
tx = await Mina.transaction(feePayer, () => {
zkapp.payout(Local.testAccounts[2].privateKey);
zkapp.sign(zkappKey);
if (!doProofs) zkapp.sign(zkappKey);
});
if (doProofs) await tx.prove();
tx.send();
} catch (err: any) {
console.log('Transaction failed with error', err.message);
Expand Down
249 changes: 249 additions & 0 deletions src/examples/zkapps/simple_and_counter_zkapp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/**
* This is just two zkapps mixed together in one file, with their respective interactions bundled
* in the same transaction, to check that this actually works.
* -) "simple zkapp", testing state updates + events + account preconditions + child parties
* -) "counter rollup", testing state updates + sequence events / reducer
*/

import {
Field,
state,
State,
method,
UInt64,
PrivateKey,
SmartContract,
Mina,
Party,
isReady,
Permissions,
DeployArgs,
UInt32,
Bool,
PublicKey,
Circuit,
Experimental,
} from 'snarkyjs';

const doProofs = true;

await isReady;

const INCREMENT = Field.one;

let offchainStorage = {
pendingActions: [] as Field[][],
};

class CounterZkapp extends SmartContract {
// the "reducer" field describes a type of action that we can dispatch, and reduce later
reducer = Experimental.Reducer({ actionType: Field });

// on-chain version of our state. it will typically lag behind the
// version that's implicitly represented by the list of actions
@state(Field) counter = State<Field>();
// helper field to store the point in the action history that our on-chain state is at
@state(Field) actionsHash = State<Field>();

deploy(args: DeployArgs) {
super.deploy(args);
this.setPermissions({
...Permissions.default(),
editState: Permissions.proofOrSignature(),
editSequenceState: Permissions.proofOrSignature(),
});
this.actionsHash.set(Experimental.Reducer.initialActionsHash);
}

@method incrementCounter() {
this.reducer.dispatch(INCREMENT);
}

@method rollupIncrements() {
// get previous counter & actions hash, assert that they're the same as on-chain values
let counter = this.counter.get();
this.counter.assertEquals(counter);
let actionsHash = this.actionsHash.get();
this.actionsHash.assertEquals(actionsHash);

// compute the new counter and hash from pending actions
// remark: it's not feasible to pass in the pending actions as method arguments, because they have dynamic size
let { state: newCounter, actionsHash: newActionsHash } =
this.reducer.reduce(
offchainStorage.pendingActions,
// state type
Field,
// function that says how to apply an action
(state: Field, _action: Field) => {
return state.add(1);
},
{ state: counter, actionsHash }
);

// update on-chain state
this.counter.set(newCounter);
this.actionsHash.set(newActionsHash);
}
}

class SimpleZkapp extends SmartContract {
@state(Field) x = State<Field>();

events = {
update: Field,
payout: UInt64,
payoutReceiver: PublicKey,
};

deploy(args: DeployArgs) {
super.deploy(args);
this.setPermissions({
...Permissions.default(),
editState: Permissions.proofOrSignature(),
send: Permissions.proofOrSignature(),
});
this.balance.addInPlace(UInt64.fromNumber(initialBalance));
this.x.set(initialState);
}

@method update(y: Field) {
this.emitEvent('update', y);
let x = this.x.get();
this.x.assertEquals(x);
this.x.set(x.add(y));
}

/**
* This method allows a certain privileged account to claim half of the zkapp balance, but only once
* @param caller the privileged account
*/
@method payout(caller: PrivateKey) {
// check that caller is the privileged account
let callerAddress = caller.toPublicKey();
callerAddress.assertEquals(privilegedAddress);

// assert that the caller nonce is 0, and increment the nonce - this way, payout can only happen once
let callerParty = Experimental.createChildParty(this.self, callerAddress);
callerParty.account.nonce.assertEquals(UInt32.zero);
callerParty.body.incrementNonce = Bool(true);

// pay out half of the zkapp balance to the caller
let balance = this.account.balance.get();
this.account.balance.assertEquals(balance);
// FIXME UInt64.div() doesn't work on variables
let halfBalance = Circuit.witness(UInt64, () =>
balance.toConstant().div(2)
);
this.balance.subInPlace(halfBalance);
callerParty.balance.addInPlace(halfBalance);

// emit some events
this.emitEvent('payoutReceiver', callerAddress);
this.emitEvent('payout', halfBalance);
}
}

let Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);

// a test account that pays all the fees, and puts additional funds into the zkapp
let feePayer = Local.testAccounts[0].privateKey;

// the zkapp account
let zkappKey = PrivateKey.random();
let zkappAddress = zkappKey.toPublicKey();

// a special account that is allowed to pull out half of the zkapp balance, once
let privilegedKey = Local.testAccounts[1].privateKey;
let privilegedAddress = privilegedKey.toPublicKey();

let initialBalance = 10_000_000_000;
let initialState = Field(1);
let zkapp = new SimpleZkapp(zkappAddress);

let counterZkappKey = PrivateKey.random();
let counterZkappAddress = counterZkappKey.toPublicKey();
let counterZkapp = new CounterZkapp(counterZkappAddress);

if (doProofs) {
console.log('compile');
await SimpleZkapp.compile(zkappAddress);
await CounterZkapp.compile(counterZkappAddress);
}

console.log('deploy');
let tx = await Mina.transaction(feePayer, () => {
Party.fundNewAccount(feePayer, {
initialBalance: Mina.accountCreationFee().add(initialBalance),
});
zkapp.deploy({ zkappKey });
counterZkapp.deploy({ zkappKey: counterZkappKey });
});
tx.send();

console.log('initial state: ' + zkapp.x.get());
console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`);

console.log('update & dispatch increment');
tx = await Mina.transaction(feePayer, () => {
zkapp.update(Field(3));
counterZkapp.incrementCounter();
if (!doProofs) {
zkapp.sign(zkappKey);
counterZkapp.sign(counterZkappKey);
}
});
if (doProofs) await tx.prove();
tx.send();
offchainStorage.pendingActions.push([INCREMENT]);
console.log('state (on-chain): ' + counterZkapp.counter.get());
console.log('pending actions:', JSON.stringify(offchainStorage.pendingActions));

console.log('payout & rollup');
tx = await Mina.transaction(feePayer, () => {
zkapp.payout(privilegedKey);
counterZkapp.rollupIncrements();
if (!doProofs) {
zkapp.sign(zkappKey);
counterZkapp.sign(counterZkappKey);
}
});
if (doProofs) await tx.prove();
console.log(tx.toJSON());
tx.send();
offchainStorage.pendingActions = [];

console.log('final state: ' + zkapp.x.get());
console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`);
console.log('state (on-chain): ' + counterZkapp.counter.get());
console.log('pending actions:', JSON.stringify(offchainStorage.pendingActions));

console.log('try to payout a second time..');
tx = await Mina.transaction(feePayer, () => {
zkapp.payout(privilegedKey);
if (!doProofs) zkapp.sign(zkappKey);
});
try {
if (doProofs) await tx.prove();
tx.send();
} catch (err: any) {
console.log('Transaction failed with error', err.message);
}

console.log('try to payout to a different account..');
try {
tx = await Mina.transaction(feePayer, () => {
zkapp.payout(Local.testAccounts[2].privateKey);
if (!doProofs) zkapp.sign(zkappKey);
});
if (doProofs) await tx.prove();
tx.send();
} catch (err: any) {
console.log('Transaction failed with error', err.message);
}

console.log(
`should still be the same final balance: ${zkapp.account.balance
.get()
.div(1e9)} MINA`
);
16 changes: 12 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export * from './lib/int';
export * as Mina from './lib/mina';
export {
SmartContract,
Experimental,
method,
deploy,
DeployArgs,
Expand All @@ -37,16 +36,25 @@ export {
} from './lib/zkapp';
export { state, State, declareState } from './lib/state';
export { Proof, SelfProof, ZkProgram, verify } from './lib/proof_system';
export * from './lib/party';
export { Party, Permissions, ZkappPublicInput } from './lib/party';
export {
fetchAccount,
fetchLastBlock,
parseFetchedAccount,
addCachedAccount,
setGraphqlEndpoint,
sendZkappQuery,
sendZkapp,
} from './lib/fetch';
export * as Encryption from './lib/encryption';
export * as Encoding from './lib/encoding';
export { Character, CircuitString } from './lib/string';

// experimental APIs
import { Reducer } from './lib/zkapp';
import { createChildParty } from './lib/party';
export { Experimental };

/**
* This module exposes APIs that are unstable, in the sense that the API surface is expected to change.
* (Not unstable in the sense that they are less functional or tested than other parts.)
*/
const Experimental = { Reducer, createChildParty };
Loading

0 comments on commit b9919ab

Please sign in to comment.