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

Adds Preconditions feature w/ example #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MINA_COMMIT
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
The mina commit used to generate the backends for node and chrome is
a1529d0ed3a8bcf2a84e06fe71a7b78244fc0b5c
2830249951ba96947460a6e4872ffb78aaf2b31b
11 changes: 10 additions & 1 deletion src/examples/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { fetchAccount, isReady, setGraphqlEndpoint, shutdown } from 'snarkyjs';
import {
fetchAccount,
isReady,
setGraphqlEndpoint,
shutdown,
fetchLastBlock,
} from 'snarkyjs';

await isReady;
setGraphqlEndpoint('https://proxy.berkeley.minaexplorer.com/graphql');
Expand All @@ -8,4 +14,7 @@ let { account, error } = await fetchAccount(zkappAddress);
console.log('account', JSON.stringify(account, null, 2));
console.log('error', error);

let block = await fetchLastBlock();
console.log('last block', JSON.stringify(block, null, 2));

await shutdown();
2 changes: 1 addition & 1 deletion src/examples/simple_zkapp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ let initialState = Field(1);
let zkapp = new SimpleZkapp(zkappAddress);

console.log('compile');
SimpleZkapp.compile(zkappAddress);
await SimpleZkapp.compile(zkappAddress);

console.log('deploy');
let tx = await Mina.transaction(feePayerKey, () => {
Expand Down
81 changes: 71 additions & 10 deletions src/examples/simple_zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
isReady,
Permissions,
DeployArgs,
UInt32,
Bool,
} from 'snarkyjs';

await isReady;
Expand All @@ -29,43 +31,102 @@ class SimpleZkapp extends SmartContract {
}

@method update(y: Field) {
// update is only valid if the balance is at least 10 MINA
Party.assertBetween(
this.self.body.accountPrecondition.balance,
UInt64.fromNumber(10e9),
UInt64.MAXINT()
);
let x = this.x.get();
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 = Party.createUnsigned(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);
this.balance.subInPlace(halfBalance);
callerParty.balance.addInPlace(halfBalance);
}
}

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

let account1 = Local.testAccounts[0].privateKey;
// 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);

console.log('deploy');
let tx = await Local.transaction(account1, () => {
Party.fundNewAccount(account1, { initialBalance });
let tx = await Local.transaction(feePayer, () => {
Party.fundNewAccount(feePayer, { initialBalance });
zkapp.deploy({ zkappKey });
});
tx.send();

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

console.log('update');
tx = await Local.transaction(account1, () => {
tx = await Local.transaction(feePayer, () => {
zkapp.update(Field(3));
zkapp.sign(zkappKey);
});
tx.send();

console.log('payout');
tx = await Local.transaction(feePayer, () => {
zkapp.payout(privilegedKey);
zkapp.sign(zkappKey);
});
tx.send();

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 Local.transaction(feePayer, () => {
zkapp.payout(privilegedKey);
zkapp.sign(zkappKey);
});
try {
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 Local.transaction(feePayer, () => {
zkapp.payout(Local.testAccounts[2].privateKey);
zkapp.sign(zkappKey);
});
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`
);
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ export * from './lib/circuit_value';
export * from './lib/int';
export * as Mina from './lib/mina';
export * from './lib/zkapp';
export { state, State, declareState } from './lib/state';
// export * from './lib/proof_system';
export * from './lib/party';
export {
fetchAccount,
fetchLastBlock,
parseFetchedAccount,
addCachedAccount,
setGraphqlEndpoint,
Expand Down
63 changes: 63 additions & 0 deletions src/lib/circuit_value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export {
public_,
circuitMain,
cloneCircuitValue,
circuitValueEquals,
};

type Constructor<T> = { new (...args: any[]): T };
Expand Down Expand Up @@ -270,6 +271,7 @@ function circuitMain(
}

let primitives = new Set(['Field', 'Bool', 'Scalar', 'Group']);

function cloneCircuitValue<T>(obj: T): T {
// primitive JS types and functions aren't cloned
if (typeof obj !== 'object' || obj === null) return obj;
Expand Down Expand Up @@ -299,3 +301,64 @@ function cloneCircuitValue<T>(obj: T): T {
}
return Object.create(Object.getPrototypeOf(obj), propertyDescriptors);
}

function circuitValueEquals<T>(a: T, b: T): boolean {
// primitive JS types and functions are checked for exact equality
if (typeof a !== 'object' || a === null) return a === b;

// built-in JS datatypes with custom equality checks
if (Array.isArray(a)) {
return (
Array.isArray(b) &&
a.length === b.length &&
a.every((a_, i) => circuitValueEquals(a_, b[i]))
);
}
if (a instanceof Set) {
return (
b instanceof Set && a.size === b.size && [...a].every((a_) => b.has(a_))
);
}
if (a instanceof Map) {
return (
b instanceof Map &&
a.size === b.size &&
[...a].every(([k, v]) => circuitValueEquals(v, b.get(k)))
);
}
if (ArrayBuffer.isView(a) && !(a instanceof DataView)) {
// typed array
return (
ArrayBuffer.isView(b) &&
!(b instanceof DataView) &&
circuitValueEquals([...(a as any)], [...(b as any)])
);
}

// the two checks below cover snarkyjs primitives and CircuitValues
// if we have an .equals method, try to use it
if ('equals' in a && typeof (a as any).equals === 'function') {
let isEqual = (a as any).equals(b).toBoolean();
if (typeof isEqual === 'boolean') return isEqual;
if (isEqual instanceof Bool) return isEqual.toBoolean();
}
// if we have a .toFields method, try to use it
if (
'toFields' in a &&
typeof (a as any).toFields === 'function' &&
'toFields' in b &&
typeof (b as any).toFields === 'function'
) {
let aFields = (a as any).toFields() as Field[];
let bFields = (b as any).toFields() as Field[];
return aFields.every((a, i) => a.equals(bFields[i]).toBoolean());
}

// equality test that works for plain objects AND classes whose constructor only assigns properties
let aEntries = Object.entries(a).filter(([, v]) => v !== undefined);
let bEntries = Object.entries(b).filter(([, v]) => v !== undefined);
if (aEntries.length !== bEntries.length) return false;
return aEntries.every(
([key, value]) => key in b && circuitValueEquals((b as any)[key], value)
);
}
Loading