From b9856604fa4795b93effe2260a2dee85bbbe0b53 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 29 Feb 2024 09:41:35 +0100 Subject: [PATCH 01/16] wip async methods --- src/lib/account-update.ts | 4 +-- src/lib/zkapp.ts | 67 +++++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index fac64ab530..624e20db8b 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -1164,7 +1164,7 @@ class AccountUpdate implements Types.AccountUpdate { static witness( type: FlexibleProvable, - compute: () => { accountUpdate: AccountUpdate; result: T }, + compute: () => Promise<{ accountUpdate: AccountUpdate; result: T }>, { skipCheck = false } = {} ) { // construct the circuit type for a accountUpdate + other result @@ -1175,7 +1175,7 @@ class AccountUpdate implements Types.AccountUpdate { accountUpdate: accountUpdateType, result: type as any, }); - return Provable.witness(combinedType, compute); + return Provable.witnessAsync(combinedType, compute); } static get MayUseToken() { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index b20c25045a..6f0f23f9cc 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -59,7 +59,6 @@ import { inCheckedComputation, inCompile, inProver, - snarkContext, } from './provable-context.js'; import { Cache } from './proof-system/cache.js'; import { assert } from './gadgets/common.js'; @@ -72,11 +71,13 @@ import { } from './mina/smart-contract-context.js'; import { deprecatedToken } from './mina/token/token-methods.js'; import type { TokenContract } from './mina/token/token-contract.js'; +import { assertPromise } from './util/assert.js'; // external API export { SmartContract, method, DeployArgs, declareMethods, Account, Reducer }; const reservedPropNames = new Set(['_methods', '_']); +type AsyncFunction = (...args: any) => Promise; /** * A decorator to use in a zkApp to mark a method as callable by anyone. @@ -88,12 +89,14 @@ const reservedPropNames = new Set(['_methods', '_']); * } * ``` */ -function method( - target: T & { constructor: any }, - methodName: keyof T & string, +function method( + target: T & { + [k in K]: (...args: any) => Promise; + }, + methodName: K & string & keyof T, descriptor: PropertyDescriptor ) { - const ZkappClass = target.constructor; + const ZkappClass = target.constructor as typeof SmartContract; if (reservedPropNames.has(methodName)) { throw Error(`Property name ${methodName} is reserved.`); } @@ -144,19 +147,22 @@ function method( ZkappClass._maxProofsVerified = Math.max( ZkappClass._maxProofsVerified, methodEntry.proofArgs.length - ); - let func = descriptor.value; + ) as 0 | 1 | 2; + let func = descriptor.value as AsyncFunction; descriptor.value = wrapMethod(func, ZkappClass, internalMethodEntry); } // do different things when calling a method, depending on the circumstance function wrapMethod( - method: Function, + method: AsyncFunction, ZkappClass: typeof SmartContract, methodIntf: MethodInterface ) { let methodName = methodIntf.methodName; - return function wrappedMethod(this: SmartContract, ...actualArgs: any[]) { + return async function wrappedMethod( + this: SmartContract, + ...actualArgs: any[] + ) { cleanStatePrecondition(this); // special case: any AccountUpdate that is passed as an argument to a method // is unlinked from its current location, to allow the method to link it to itself @@ -203,7 +209,7 @@ function wrapMethod( let result: unknown; try { let clonedArgs = actualArgs.map(cloneCircuitValue); - result = method.apply(this, clonedArgs); + result = await assertPromise(method.apply(this, clonedArgs)); } finally { memoizationContext.leave(id); } @@ -233,7 +239,7 @@ function wrapMethod( } } else if (!Mina.currentTransaction.has()) { // outside a transaction, just call the method, but check precondition invariants - let result = method.apply(this, actualArgs); + let result = await assertPromise(method.apply(this, actualArgs)); // check the self accountUpdate right after calling the method // TODO: this needs to be done in a unified way for all account updates that are created assertPreconditionInvariants(this.self); @@ -255,16 +261,18 @@ function wrapMethod( let memoId = memoizationContext.enter(memoContext); let result: any; try { - result = method.apply( - this, - actualArgs.map((a, i) => { - let arg = methodIntf.allArgs[i]; - if (arg.type === 'witness') { - let type = methodIntf.witnessArgs[arg.index]; - return Provable.witness(type, () => a); - } - return a; - }) + result = await assertPromise( + method.apply( + this, + actualArgs.map((a, i) => { + let arg = methodIntf.allArgs[i]; + if (arg.type === 'witness') { + let type = methodIntf.witnessArgs[arg.index]; + return Provable.witness(type, () => a); + } + return a; + }) + ) ); } finally { memoizationContext.leave(memoId); @@ -350,7 +358,7 @@ function wrapMethod( // we just reuse the blinding value of the caller for the callee let blindingValue = getBlindingValue(); - let runCalledContract = () => { + let runCalledContract = async () => { let constantArgs = methodArgumentsToConstant(methodIntf, actualArgs); let constantBlindingValue = blindingValue.toConstant(); let accountUpdate = this.self; @@ -364,7 +372,9 @@ function wrapMethod( let memoId = memoizationContext.enter(memoContext); let result: any; try { - result = method.apply(this, constantArgs.map(cloneCircuitValue)); + result = await assertPromise( + method.apply(this, constantArgs.map(cloneCircuitValue)) + ); } finally { memoizationContext.leave(memoId); } @@ -418,7 +428,10 @@ function wrapMethod( let { accountUpdate, result: { result, children }, - } = AccountUpdate.witness<{ result: any; children: AccountUpdateForest }>( + } = await AccountUpdate.witness<{ + result: any; + children: AccountUpdateForest; + }>( provable({ result: returnType ?? provable(null), children: AccountUpdateForest.provable, @@ -605,14 +618,14 @@ class SmartContract extends SmartContractBase { } = {}) { let methodIntfs = this._methods ?? []; let methods = methodIntfs.map(({ methodName }) => { - return ( + return async ( publicInput: unknown, publicKey: PublicKey, tokenId: Field, ...args: unknown[] ) => { let instance = new this(publicKey, tokenId); - (instance as any)[methodName](publicInput, ...args); + await (instance as any)[methodName](publicInput, ...args); }; }); // run methods once to get information that we need already at compile time @@ -1477,7 +1490,7 @@ function declareMethods( let target = SmartContract.prototype; Reflect.metadata('design:paramtypes', argumentTypes)(target, key); let descriptor = Object.getOwnPropertyDescriptor(target, key)!; - method(SmartContract.prototype, key as any, descriptor); + method(SmartContract.prototype as any, key as any, descriptor); Object.defineProperty(target, key, descriptor); } } From f0d31337538266f37f86de73a103773094fb1e46 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 09:37:12 +0100 Subject: [PATCH 02/16] async deploy and fix compilation of unit tests --- src/lib/circuit-value.unit-test.ts | 2 +- .../mina/account-update-layout.unit-test.ts | 12 ++-- src/lib/mina/token/token-contract.ts | 24 ++++---- .../mina/token/token-contract.unit-test.ts | 14 ++--- src/lib/precondition.test.ts | 8 +-- src/lib/token.test.ts | 61 ++++++++++--------- src/lib/util/assert.ts | 4 +- src/lib/zkapp.ts | 27 +++++--- 8 files changed, 79 insertions(+), 73 deletions(-) diff --git a/src/lib/circuit-value.unit-test.ts b/src/lib/circuit-value.unit-test.ts index 206762fd91..2f3505e15c 100644 --- a/src/lib/circuit-value.unit-test.ts +++ b/src/lib/circuit-value.unit-test.ts @@ -107,7 +107,7 @@ class MyContract extends SmartContract { // this works because MyStructPure only contains field elements @state(MyStructPure) x = State(); - @method myMethod( + @method async myMethod( value: MyStruct, tuple: MyTuple, update: AccountUpdate, diff --git a/src/lib/mina/account-update-layout.unit-test.ts b/src/lib/mina/account-update-layout.unit-test.ts index ee82774383..e126f1ef88 100644 --- a/src/lib/mina/account-update-layout.unit-test.ts +++ b/src/lib/mina/account-update-layout.unit-test.ts @@ -6,12 +6,12 @@ import { SmartContract, method } from '../zkapp.js'; // smart contract which creates an account update that has a child of its own class NestedCall extends SmartContract { - @method deposit() { + @method async deposit() { let payerUpdate = AccountUpdate.createSigned(this.sender); payerUpdate.send({ to: this.address, amount: UInt64.one }); } - @method depositUsingTree() { + @method async depositUsingTree() { let payerUpdate = AccountUpdate.createSigned(this.sender); let receiverUpdate = AccountUpdate.create(this.address); payerUpdate.send({ to: receiverUpdate, amount: UInt64.one }); @@ -37,7 +37,7 @@ let zkapp = new NestedCall(zkappAddress); // deploy zkapp -await (await Mina.transaction(sender, async () => zkapp.deploy())) +await (await Mina.transaction(sender, () => zkapp.deploy())) .sign([zkappKey, senderKey]) .send(); @@ -45,7 +45,7 @@ await (await Mina.transaction(sender, async () => zkapp.deploy())) let balanceBefore = Mina.getBalance(zkappAddress); -let depositTx = await Mina.transaction(sender, async () => zkapp.deposit()); +let depositTx = await Mina.transaction(sender, () => zkapp.deposit()); console.log(depositTx.toPretty()); await depositTx.prove(); await depositTx.sign([senderKey]).send(); @@ -56,9 +56,7 @@ Mina.getBalance(zkappAddress).assertEquals(balanceBefore.add(1)); balanceBefore = balanceBefore.add(1); -depositTx = await Mina.transaction(sender, async () => - zkapp.depositUsingTree() -); +depositTx = await Mina.transaction(sender, () => zkapp.depositUsingTree()); console.log(depositTx.toPretty()); await depositTx.prove(); await depositTx.sign([senderKey]).send(); diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 793afcbfb9..89cb7c4408 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -42,14 +42,14 @@ abstract class TokenContract extends SmartContract { * If the contract needs to be re-deployed, you can switch off this behaviour by overriding the `isNew` precondition: * ```ts * deploy() { - * super.deploy(); + * await super.deploy(); * // DON'T DO THIS ON THE INITIAL DEPLOYMENT! * this.account.isNew.requireNothing(); * } * ``` */ - deploy(args?: DeployArgs) { - super.deploy(args); + async deploy(args?: DeployArgs) { + await super.deploy(args); // set access permission, to prevent unauthorized token operations this.account.permissions.set({ @@ -80,7 +80,7 @@ abstract class TokenContract extends SmartContract { // APPROVABLE API has to be specified by subclasses, // but the hard part is `forEachUpdate()` - abstract approveBase(forest: AccountUpdateForest): void; + abstract approveBase(forest: AccountUpdateForest): Promise; /** * Iterate through the account updates in `updates` and apply `callback` to each. @@ -134,17 +134,19 @@ abstract class TokenContract extends SmartContract { /** * Approve a single account update (with arbitrarily many children). */ - approveAccountUpdate(accountUpdate: AccountUpdate | AccountUpdateTree) { + async approveAccountUpdate(accountUpdate: AccountUpdate | AccountUpdateTree) { let forest = toForest([accountUpdate]); - this.approveBase(forest); + await this.approveBase(forest); } /** * Approve a list of account updates (with arbitrarily many children). */ - approveAccountUpdates(accountUpdates: (AccountUpdate | AccountUpdateTree)[]) { + async approveAccountUpdates( + accountUpdates: (AccountUpdate | AccountUpdateTree)[] + ) { let forest = toForest(accountUpdates); - this.approveBase(forest); + await this.approveBase(forest); } // TRANSFERABLE API - simple wrapper around Approvable API @@ -152,12 +154,12 @@ abstract class TokenContract extends SmartContract { /** * Transfer `amount` of tokens from `from` to `to`. */ - transfer( + async transfer( from: PublicKey | AccountUpdate, to: PublicKey | AccountUpdate, amount: UInt64 | number | bigint ) { - // coerce the inputs to AccountUpdate and pass to `approveUpdates()` + // coerce the inputs to AccountUpdate and pass to `approveBase()` let tokenId = this.deriveTokenId(); if (from instanceof PublicKey) { from = AccountUpdate.defaultAccountUpdate(from, tokenId); @@ -173,7 +175,7 @@ abstract class TokenContract extends SmartContract { to.balanceChange = Int64.from(amount); let forest = toForest([from, to]); - this.approveBase(forest); + await this.approveBase(forest); } } diff --git a/src/lib/mina/token/token-contract.unit-test.ts b/src/lib/mina/token/token-contract.unit-test.ts index 88cd92e10d..def7ecd3c7 100644 --- a/src/lib/mina/token/token-contract.unit-test.ts +++ b/src/lib/mina/token/token-contract.unit-test.ts @@ -14,7 +14,7 @@ class ExampleTokenContract extends TokenContract { // APPROVABLE API @method - approveBase(updates: AccountUpdateForest) { + async approveBase(updates: AccountUpdateForest) { this.checkZeroBalanceChange(updates); } @@ -22,7 +22,7 @@ class ExampleTokenContract extends TokenContract { SUPPLY = UInt64.from(10n ** 18n); @method - init() { + async init() { super.init(); // mint the entire supply to the token account with the same address as this contract @@ -48,7 +48,7 @@ let tokenId = token.deriveTokenId(); // deploy token contract let deployTx = await Mina.transaction(sender, async () => { AccountUpdate.fundNewAccount(sender, 2); - token.deploy(); + await token.deploy(); }); await deployTx.prove(); await deployTx.sign([tokenKey, senderKey]).send(); @@ -61,7 +61,7 @@ assert( // can transfer tokens between two accounts let transferTx = await Mina.transaction(sender, async () => { AccountUpdate.fundNewAccount(sender); - token.transfer(tokenAddress, otherAddress, UInt64.one); + await token.transfer(tokenAddress, otherAddress, UInt64.one); }); await transferTx.prove(); await transferTx.sign([tokenKey, senderKey]).send(); @@ -84,7 +84,7 @@ update3.body.callDepth = 2; let forest = AccountUpdateForest.fromFlatArray([update1, update2, update3]); await assert.rejects( - () => Mina.transaction(sender, async () => token.approveBase(forest)), + () => Mina.transaction(sender, () => token.approveBase(forest)), /Field\.assertEquals\(\): 1 != 0/ ); @@ -101,8 +101,6 @@ forest = AccountUpdateForest.fromFlatArray([ update4, ]); -let approveTx = await Mina.transaction(sender, async () => - token.approveBase(forest) -); +let approveTx = await Mina.transaction(sender, () => token.approveBase(forest)); await approveTx.prove(); await approveTx.sign([senderKey, otherKey]).send(); diff --git a/src/lib/precondition.test.ts b/src/lib/precondition.test.ts index 365d9c0816..9e0c55429b 100644 --- a/src/lib/precondition.test.ts +++ b/src/lib/precondition.test.ts @@ -1,6 +1,4 @@ import { - shutdown, - isReady, UInt64, UInt32, SmartContract, @@ -14,7 +12,7 @@ import { } from 'o1js'; class MyContract extends SmartContract { - @method shouldMakeCompileThrow() { + @method async shouldMakeCompileThrow() { this.network.blockchainLength.get(); } } @@ -27,7 +25,6 @@ let feePayerKey: PrivateKey; beforeAll(async () => { // set up local blockchain, create zkapp keys, deploy the contract - await isReady; let Local = Mina.LocalBlockchain({ proofsEnabled: false }); Mina.setActiveInstance(Local); feePayerKey = Local.testAccounts[0].privateKey; @@ -39,11 +36,10 @@ beforeAll(async () => { let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); tx.sign([feePayerKey, zkappKey]).send(); }); -afterAll(() => setTimeout(shutdown, 0)); describe('preconditions', () => { it('get without constraint should throw', async () => { diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 1b4122a235..3361735747 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -28,7 +28,10 @@ class TokenContract extends SmartContract { * This deploy method lets a another token account deploy their zkApp and verification key as a child of this token contract. * This is important since we want the native token id of the deployed zkApp to be the token id of the token contract. */ - @method deployZkapp(address: PublicKey, verificationKey: VerificationKey) { + @method async deployZkapp( + address: PublicKey, + verificationKey: VerificationKey + ) { let tokenId = this.token.id; let zkapp = AccountUpdate.defaultAccountUpdate(address, tokenId); this.approve(zkapp); @@ -55,7 +58,7 @@ class TokenContract extends SmartContract { }); } - @method mint(receiverAddress: PublicKey, amount: UInt64) { + @method async mint(receiverAddress: PublicKey, amount: UInt64) { let totalAmountInCirculation = this.totalAmountInCirculation.get(); this.totalAmountInCirculation.assertEquals(totalAmountInCirculation); let newTotalAmountInCirculation = totalAmountInCirculation.add(amount); @@ -70,7 +73,7 @@ class TokenContract extends SmartContract { this.totalAmountInCirculation.set(newTotalAmountInCirculation); } - @method burn(receiverAddress: PublicKey, amount: UInt64) { + @method async burn(receiverAddress: PublicKey, amount: UInt64) { let totalAmountInCirculation = this.totalAmountInCirculation.get(); this.totalAmountInCirculation.assertEquals(totalAmountInCirculation); let newTotalAmountInCirculation = totalAmountInCirculation.sub(amount); @@ -85,7 +88,7 @@ class TokenContract extends SmartContract { this.totalAmountInCirculation.set(newTotalAmountInCirculation); } - @method approveTransfer( + @method async approveTransfer( senderAddress: PublicKey, receiverAddress: PublicKey, amount: UInt64, @@ -103,17 +106,17 @@ class TokenContract extends SmartContract { } class ZkAppB extends SmartContract { - @method approveSend(amount: UInt64) { + @method async approveSend(amount: UInt64) { this.balance.subInPlace(amount); } } class ZkAppC extends SmartContract { - @method approveSend(amount: UInt64) { + @method async approveSend(amount: UInt64) { this.balance.subInPlace(amount); } - @method approveIncorrectLayout(amount: UInt64) { + @method async approveIncorrectLayout(amount: UInt64) { this.balance.subInPlace(amount); let update = AccountUpdate.defaultAccountUpdate(this.address); this.self.approve(update); @@ -168,7 +171,7 @@ async function setupLocal() { to: tokenZkappAddress, amount: Mina.getNetworkConstants().accountCreationFee, }); - tokenZkapp.deploy(); + await tokenZkapp.deploy(); }); tx.sign([tokenZkappKey, feePayerKey]); await tx.send(); @@ -185,9 +188,9 @@ async function setupLocalProofs() { to: tokenZkappAddress, amount: Mina.getNetworkConstants().accountCreationFee, }); - tokenZkapp.deploy(); - tokenZkapp.deployZkapp(zkAppBAddress, ZkAppB._verificationKey!); - tokenZkapp.deployZkapp(zkAppCAddress, ZkAppC._verificationKey!); + await tokenZkapp.deploy(); + await tokenZkapp.deployZkapp(zkAppBAddress, ZkAppB._verificationKey!); + await tokenZkapp.deployZkapp(zkAppCAddress, ZkAppC._verificationKey!); }); await tx.prove(); tx.sign([tokenZkappKey, zkAppBKey, zkAppCKey, feePayerKey]); @@ -256,7 +259,7 @@ describe('Token', () => { await ( await Mina.transaction({ sender: feePayer }, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) @@ -270,7 +273,7 @@ describe('Token', () => { test('minting should fail if overflow occurs ', async () => { await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000_000_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000_000_000)); tokenZkapp.requireSignature(); }).catch((e) => { expect(e).toBeDefined(); @@ -293,7 +296,7 @@ describe('Token', () => { await ( await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) @@ -301,7 +304,7 @@ describe('Token', () => { .send(); await ( await Mina.transaction(feePayer, async () => { - tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); + await tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); tokenZkapp.requireSignature(); }) ) @@ -316,7 +319,7 @@ describe('Token', () => { await ( await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(1_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(1_000)); tokenZkapp.requireSignature(); }) ) @@ -324,7 +327,7 @@ describe('Token', () => { .send(); let tx = ( await Mina.transaction(feePayer, async () => { - tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); + await tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); tokenZkapp.requireSignature(); }) ).sign([zkAppBKey, feePayerKey, tokenZkappKey]); @@ -348,7 +351,7 @@ describe('Token', () => { test('change the balance of a token account after sending', async () => { let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }); await tx.sign([feePayerKey, tokenZkappKey]).send(); @@ -378,7 +381,7 @@ describe('Token', () => { await ( await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) @@ -403,7 +406,7 @@ describe('Token', () => { await ( await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) @@ -466,7 +469,7 @@ describe('Token', () => { test('token contract can successfully mint and updates the balances in the ledger (proof)', async () => { let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); }); await tx.prove(); tx.sign([tokenZkappKey, feePayerKey]); @@ -491,12 +494,12 @@ describe('Token', () => { test('token contract can successfully burn and updates the balances in the ledger (proof)', async () => { let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }); await tx.sign([feePayerKey, tokenZkappKey]).send(); tx = await Mina.transaction(feePayer, async () => { - tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); + await tokenZkapp.burn(zkAppBAddress, UInt64.from(10_000)); }); await tx.prove(); tx.sign([zkAppBKey, feePayerKey]); @@ -522,16 +525,16 @@ describe('Token', () => { test('should approve and the balance of a token account after sending', async () => { let tx = await Mina.transaction(feePayer, async () => { - tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppBAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }); await tx.prove(); await tx.sign([feePayerKey, tokenZkappKey]).send(); tx = await Mina.transaction(feePayer, async () => { - zkAppB.approveSend(UInt64.from(10_000)); + await zkAppB.approveSend(UInt64.from(10_000)); - tokenZkapp.approveTransfer( + await tokenZkapp.approveTransfer( zkAppBAddress, zkAppCAddress, UInt64.from(10_000), @@ -552,7 +555,7 @@ describe('Token', () => { test('should fail to approve with an incorrect layout', async () => { await ( await Mina.transaction(feePayer, async () => { - tokenZkapp.mint(zkAppCAddress, UInt64.from(100_000)); + await tokenZkapp.mint(zkAppCAddress, UInt64.from(100_000)); tokenZkapp.requireSignature(); }) ) @@ -561,8 +564,8 @@ describe('Token', () => { await expect(() => Mina.transaction(feePayer, async () => { - zkAppC.approveIncorrectLayout(UInt64.from(10_000)); - tokenZkapp.approveTransfer( + await zkAppC.approveIncorrectLayout(UInt64.from(10_000)); + await tokenZkapp.approveTransfer( zkAppBAddress, zkAppCAddress, UInt64.from(10_000), diff --git a/src/lib/util/assert.ts b/src/lib/util/assert.ts index 122880686c..f996b4bede 100644 --- a/src/lib/util/assert.ts +++ b/src/lib/util/assert.ts @@ -6,7 +6,7 @@ function assert(stmt: boolean, message?: string): asserts stmt { } } -function assertPromise(value: Promise): Promise { - assert(value instanceof Promise, 'Expected a promise'); +function assertPromise(value: Promise, message?: string): Promise { + assert(value instanceof Promise, message ?? 'Expected a promise'); return value; } diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 7a28a72e90..82fd4bfbe9 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -159,6 +159,7 @@ function wrapMethod( methodIntf: MethodInterface ) { let methodName = methodIntf.methodName; + let noPromiseError = `Expected \`${ZkappClass.name}.${methodName}()\` to return a promise.`; return async function wrappedMethod( this: SmartContract, ...actualArgs: any[] @@ -209,7 +210,10 @@ function wrapMethod( let result: unknown; try { let clonedArgs = actualArgs.map(cloneCircuitValue); - result = await assertPromise(method.apply(this, clonedArgs)); + result = await assertPromise( + method.apply(this, clonedArgs), + noPromiseError + ); } finally { memoizationContext.leave(id); } @@ -239,7 +243,10 @@ function wrapMethod( } } else if (!Mina.currentTransaction.has()) { // outside a transaction, just call the method, but check precondition invariants - let result = await assertPromise(method.apply(this, actualArgs)); + let result = await assertPromise( + method.apply(this, actualArgs), + noPromiseError + ); // check the self accountUpdate right after calling the method // TODO: this needs to be done in a unified way for all account updates that are created assertPreconditionInvariants(this.self); @@ -272,7 +279,8 @@ function wrapMethod( } return a; }) - ) + ), + noPromiseError ); } finally { memoizationContext.leave(memoId); @@ -373,7 +381,8 @@ function wrapMethod( let result: any; try { result = await assertPromise( - method.apply(this, constantArgs.map(cloneCircuitValue)) + method.apply(this, constantArgs.map(cloneCircuitValue)), + noPromiseError ); } finally { memoizationContext.leave(memoId); @@ -668,12 +677,12 @@ class SmartContract extends SmartContractBase { * ```ts * let tx = await Mina.transaction(sender, async () => { * AccountUpdate.fundNewAccount(sender); - * zkapp.deploy(); + * await zkapp.deploy(); * }); * tx.sign([senderKey, zkAppKey]); * ``` */ - deploy({ + async deploy({ verificationKey, zkappKey, }: { @@ -706,7 +715,7 @@ class SmartContract extends SmartContractBase { !Mina.hasAccount(this.address) || Mina.getAccount(this.address).zkapp?.verificationKey === undefined; if (!shouldInit) return; - else this.init(); + else await this.init(); let initUpdate = this.self; // switch back to the deploy account update so the user can make modifications to it this.#executionState = { @@ -1128,9 +1137,9 @@ super.init(); let { rows, digest, gates, summary } = await analyzeMethod( ZkappPublicInput, methodIntf, - (publicInput, publicKey, tokenId, ...args) => { + async (publicInput, publicKey, tokenId, ...args) => { let instance: SmartContract = new ZkappClass(publicKey, tokenId); - let result = (instance as any)[methodIntf.methodName]( + let result = await (instance as any)[methodIntf.methodName]( publicInput, ...args ); From d6534fe06b356e07d2268c90f9de5e9ac51b2a17 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 10:36:12 +0100 Subject: [PATCH 03/16] fix return types: require void return and introduce separate decorator method.returns --- src/examples/simple-zkapp.ts | 17 ++++++---- src/examples/zkapps/composability.ts | 25 +++++++------- src/lib/zkapp.ts | 49 +++++++++++++++++++++------- 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/src/examples/simple-zkapp.ts b/src/examples/simple-zkapp.ts index c93e0fd3b8..4589ab2a6c 100644 --- a/src/examples/simple-zkapp.ts +++ b/src/examples/simple-zkapp.ts @@ -22,12 +22,14 @@ class SimpleZkapp extends SmartContract { events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; - @method init() { + @method + async init() { super.init(); this.x.set(initialState); } - @method update(y: Field): Field { + @method.returns(Field) + async update(y: Field) { this.account.provedState.requireEquals(Bool(true)); this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); this.emitEvent('update', y); @@ -42,7 +44,8 @@ class SimpleZkapp extends SmartContract { * 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) { + @method + async payout(caller: PrivateKey) { this.account.provedState.requireEquals(Bool(true)); // check that caller is the privileged account @@ -112,7 +115,7 @@ console.log('account state is proved:', account.zkapp?.provedState.toBoolean()); console.log('update'); tx = await Mina.transaction(sender, async () => { - zkapp.update(Field(3)); + await zkapp.update(Field(3)); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -128,7 +131,7 @@ await tx.sign([senderKey]).send(); console.log('payout'); tx = await Mina.transaction(sender, async () => { AccountUpdate.fundNewAccount(sender); - zkapp.payout(privilegedKey); + await zkapp.payout(privilegedKey); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -139,7 +142,7 @@ console.log(`final balance: ${zkapp.account.balance.get().div(1e9)} MINA`); console.log('try to payout a second time..'); tx = await Mina.transaction(sender, async () => { - zkapp.payout(privilegedKey); + await zkapp.payout(privilegedKey); }); try { await tx.prove(); @@ -151,7 +154,7 @@ try { console.log('try to payout to a different account..'); try { tx = await Mina.transaction(sender, async () => { - zkapp.payout(Local.testAccounts[2].privateKey); + await zkapp.payout(Local.testAccounts[2].privateKey); }); await tx.prove(); await tx.sign([senderKey]).send(); diff --git a/src/examples/zkapps/composability.ts b/src/examples/zkapps/composability.ts index 6b79697eb2..81867b8e6d 100644 --- a/src/examples/zkapps/composability.ts +++ b/src/examples/zkapps/composability.ts @@ -3,7 +3,6 @@ */ import { Field, - isReady, method, Mina, AccountUpdate, @@ -16,11 +15,10 @@ import { getProfiler } from '../utils/profiler.js'; const doProofs = true; -await isReady; - // contract which can add 1 to a number class Incrementer extends SmartContract { - @method increment(x: Field): Field { + @method.returns(Field) + async increment(x: Field) { return x.add(1); } } @@ -28,12 +26,13 @@ class Incrementer extends SmartContract { // contract which can add two numbers, plus 1, and return the result // incrementing by one is outsourced to another contract (it's cleaner that way, we want to stick to the single responsibility principle) class Adder extends SmartContract { - @method addPlus1(x: Field, y: Field): Field { + @method.returns(Field) + async addPlus1(x: Field, y: Field) { // compute result let sum = x.add(y); // call the other contract to increment let incrementer = new Incrementer(incrementerAddress); - return incrementer.increment(sum); + return await incrementer.increment(sum); } } @@ -42,9 +41,10 @@ class Caller extends SmartContract { @state(Field) sum = State(); events = { sum: Field }; - @method callAddAndEmit(x: Field, y: Field) { + @method + async callAddAndEmit(x: Field, y: Field) { let adder = new Adder(adderAddress); - let sum = adder.addPlus1(x, y); + let sum = await adder.addPlus1(x, y); this.emitEvent('sum', sum); this.sum.set(sum); } @@ -86,18 +86,17 @@ if (doProofs) { console.log('deploy'); let tx = await Mina.transaction(feePayer, async () => { - // TODO: enable funding multiple accounts properly AccountUpdate.fundNewAccount(feePayer, 3); - zkapp.deploy(); - adderZkapp.deploy(); - incrementerZkapp.deploy(); + await zkapp.deploy(); + await adderZkapp.deploy(); + await incrementerZkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey, adderKey, incrementerKey]).send(); console.log('call interaction'); tx = await Mina.transaction(feePayer, async () => { // we just call one contract here, nothing special to do - zkapp.callAddAndEmit(Field(5), Field(6)); + await zkapp.callAddAndEmit(Field(5), Field(6)); }); console.log('proving (3 proofs.. can take a bit!)'); await tx.prove(); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 82fd4bfbe9..dff5165184 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -45,7 +45,6 @@ import { Empty, emptyValue, getPreviousProofsForProver, - isAsFields, methodArgumentsToConstant, methodArgumentTypesAndValues, MethodInterface, @@ -80,21 +79,30 @@ const reservedPropNames = new Set(['_methods', '_']); type AsyncFunction = (...args: any) => Promise; /** - * A decorator to use in a zkApp to mark a method as callable by anyone. + * A decorator to use in a zkApp to mark a method as provable. * You can use inside your zkApp class as: * * ``` - * \@method myMethod(someArg: Field) { + * \@method async myMethod(someArg: Field) { + * // your code here + * } + * ``` + * + * To return a value from the method, you have to explicitly declare the return type using the {@link method.returns} decorator: + * ``` + * \@method.returns(Field) + * async myMethod(someArg: Field): Promise { * // your code here * } * ``` */ function method( target: T & { - [k in K]: (...args: any) => Promise; + [k in K]: (...args: any) => Promise; }, methodName: K & string & keyof T, - descriptor: PropertyDescriptor + descriptor: PropertyDescriptor, + returnType?: Provable ) { const ZkappClass = target.constructor as typeof SmartContract; if (reservedPropNames.has(methodName)) { @@ -110,11 +118,6 @@ function method( target, methodName ); - let returnType: Provable = Reflect.getMetadata( - 'design:returntype', - target, - methodName - ); class SelfProof extends Proof { static publicInputType = ZkappPublicInput; @@ -135,7 +138,7 @@ function method( SelfProof ); - if (isAsFields(returnType)) { + if (returnType !== undefined) { internalMethodEntry.returnType = returnType; methodEntry.returnType = returnType; } @@ -152,6 +155,30 @@ function method( descriptor.value = wrapMethod(func, ZkappClass, internalMethodEntry); } +/** + * A decorator to mark a zkApp method as provable, and declare its return type. + * + * ``` + * \@method.returns(Field) + * async myMethod(someArg: Field): Promise { + * // your code here + * } + * ``` + */ +method.returns = function ( + returnType: Provable +) { + return function decorateMethod( + target: T & { + [k in K]: (...args: any) => Promise; + }, + methodName: K & string & keyof T, + descriptor: PropertyDescriptor + ) { + return method(target as any, methodName, descriptor, returnType); + }; +}; + // do different things when calling a method, depending on the circumstance function wrapMethod( method: AsyncFunction, From 406a702eb6b53657786f4ec472085750a89b96ab Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 10:56:51 +0100 Subject: [PATCH 04/16] remove now unnecessary hasReturn metadata and return type check --- src/lib/zkapp.ts | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index dff5165184..a7550512f2 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -373,23 +373,6 @@ function wrapMethod( selfAccountUpdate(this, methodName) ); try { - // if the call result is not undefined but there's no known returnType, the returnType was probably not annotated properly, - // so we have to explain to the user how to do that - let { returnType } = methodIntf; - let noReturnTypeError = - `To return a result from ${methodIntf.methodName}() inside another zkApp, you need to declare the return type.\n` + - `This can be done by annotating the type at the end of the function signature. For example:\n\n` + - `@method ${methodIntf.methodName}(): Field {\n` + - ` // ...\n` + - `}\n\n` + - `Note: Only types built out of \`Field\` are valid return types. This includes o1js primitive types and custom CircuitValues.`; - // if we're lucky, analyzeMethods was already run on the callee smart contract, and we can catch this error early - if ( - ZkappClass._methodMetadata?.[methodIntf.methodName]?.hasReturn && - returnType === undefined - ) { - throw Error(noReturnTypeError); - } // we just reuse the blinding value of the caller for the callee let blindingValue = getBlindingValue(); @@ -418,11 +401,12 @@ function wrapMethod( assertStatePrecondition(this); if (result !== undefined) { - if (returnType === undefined) { - throw Error(noReturnTypeError); - } else { - result = toConstant(returnType, result); - } + let { returnType } = methodIntf; + assert( + returnType !== undefined, + "Bug: returnType is undefined but the method result isn't." + ); + result = toConstant(returnType, result); } // store inputs + result in callData @@ -469,7 +453,7 @@ function wrapMethod( children: AccountUpdateForest; }>( provable({ - result: returnType ?? provable(null), + result: methodIntf.returnType ?? provable(null), children: AccountUpdateForest.provable, }), runCalledContract, @@ -600,7 +584,6 @@ class SmartContract extends SmartContractBase { actions: number; rows: number; digest: string; - hasReturn: boolean; gates: Gate[]; } >; // keyed by method name @@ -1142,7 +1125,6 @@ super.init(); * @returns an object, keyed by method name, each entry containing: * - `rows` the size of the constraint system created by this method * - `digest` a digest of the method circuit - * - `hasReturn` a boolean indicating whether the method returns a value * - `actions` the number of actions the method dispatches * - `gates` the constraint system, represented as an array of gates */ @@ -1160,7 +1142,6 @@ super.init(); try { for (let methodIntf of methodIntfs) { let accountUpdate: AccountUpdate; - let hasReturn = false; let { rows, digest, gates, summary } = await analyzeMethod( ZkappPublicInput, methodIntf, @@ -1170,7 +1151,6 @@ super.init(); publicInput, ...args ); - hasReturn = result !== undefined; accountUpdate = instance.#executionState!.accountUpdate; return result; } @@ -1179,7 +1159,6 @@ super.init(); actions: accountUpdate!.body.actions.data.length, rows, digest, - hasReturn, gates, }; if (printSummary) console.log(methodIntf.methodName, summary()); From 029ae6665c7a2a8b3a588e101fc5a31234bdd1a2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 12:47:42 +0100 Subject: [PATCH 05/16] fix about half of examples --- src/examples/circuit-string.ts | 20 ++--- src/examples/commonjs.cjs | 4 +- src/examples/nullifier.ts | 8 +- src/examples/simple-zkapp-berkeley.ts | 18 +---- src/examples/simple-zkapp.js | 6 +- src/examples/simple-zkapp.web.ts | 12 +-- src/examples/zkapps/dex/dex-with-actions.ts | 46 ++++++----- src/examples/zkapps/dex/dex.ts | 81 ++++++++++--------- src/examples/zkapps/dex/erc20.ts | 33 ++++---- .../zkapps/dex/happy-path-with-actions.ts | 34 ++++---- .../zkapps/dex/happy-path-with-proofs.ts | 24 +++--- src/examples/zkapps/dex/run-live.ts | 42 ++++++---- src/examples/zkapps/dex/run.ts | 56 ++++++------- src/examples/zkapps/dex/upgradability.ts | 57 ++++++------- src/examples/zkapps/local-events-zkapp.ts | 8 +- .../zkapps/set-local-preconditions-zkapp.ts | 16 ++-- src/examples/zkapps/simple-zkapp-payment.ts | 16 ++-- .../zkapps/simple-zkapp-with-proof.ts | 21 +++-- src/examples/zkapps/token-with-proofs.ts | 28 +++---- src/examples/zkapps/zkapp-self-update.ts | 13 +-- 20 files changed, 270 insertions(+), 273 deletions(-) diff --git a/src/examples/circuit-string.ts b/src/examples/circuit-string.ts index a69b515b53..a16f9d4ebf 100644 --- a/src/examples/circuit-string.ts +++ b/src/examples/circuit-string.ts @@ -10,7 +10,7 @@ import * as assert from 'assert/strict'; // circuit which tests a couple of string features class MyContract extends SmartContract { - @method checkString(s: CircuitString) { + @method async checkString(s: CircuitString) { let sWithExclamation = s.append(CircuitString.fromString('!')); sWithExclamation .equals(CircuitString.fromString('a string!')) @@ -26,21 +26,21 @@ console.log('compile...'); await MyContract.compile(); // should work console.log('prove...'); -let tx = await Mina.transaction(async () => { - new MyContract(address).checkString(CircuitString.fromString('a string')); -}); +let tx = await Mina.transaction(() => + new MyContract(address).checkString(CircuitString.fromString('a string')) +); await tx.prove(); console.log('test 1 - ok'); // should work -tx = await Mina.transaction(async () => { - new MyContract(address).checkString(CircuitString.fromString('some string')); -}); +tx = await Mina.transaction(() => + new MyContract(address).checkString(CircuitString.fromString('some string')) +); await tx.prove(); console.log('test 2 - ok'); // should fail -let fails = await Mina.transaction(async () => { - new MyContract(address).checkString(CircuitString.fromString('different')); -}) +let fails = await Mina.transaction(() => + new MyContract(address).checkString(CircuitString.fromString('different')) +) .then(() => false) .catch(() => true); if (!fails) Error('proof was supposed to fail'); diff --git a/src/examples/commonjs.cjs b/src/examples/commonjs.cjs index ddf5fa4e21..fcfcdf730e 100644 --- a/src/examples/commonjs.cjs +++ b/src/examples/commonjs.cjs @@ -58,14 +58,14 @@ async function main() { console.log('deploy'); let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); console.log('initial state: ' + zkapp.x.get()); console.log('update'); - tx = await Mina.transaction(feePayer, async () => zkapp.update(Field(3))); + tx = await Mina.transaction(feePayer, () => zkapp.update(Field(3))); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('final state: ' + zkapp.x.get()); diff --git a/src/examples/nullifier.ts b/src/examples/nullifier.ts index 1cee451581..5478bcd64f 100644 --- a/src/examples/nullifier.ts +++ b/src/examples/nullifier.ts @@ -17,7 +17,7 @@ class PayoutOnlyOnce extends SmartContract { @state(Field) nullifierRoot = State(); @state(Field) nullifierMessage = State(); - @method payout(nullifier: Nullifier) { + @method async payout(nullifier: Nullifier) { let nullifierRoot = this.nullifierRoot.getAndRequireEquals(); let nullifierMessage = this.nullifierMessage.getAndRequireEquals(); @@ -75,7 +75,7 @@ console.log('deploy'); let tx = await Mina.transaction(sender, async () => { let senderUpdate = AccountUpdate.fundNewAccount(sender); senderUpdate.send({ to: zkappAddress, amount: initialBalance }); - zkapp.deploy({ zkappKey }); + await zkapp.deploy({ zkappKey }); zkapp.nullifierRoot.set(NullifierTree.getRoot()); zkapp.nullifierMessage.set(nullifierMessage); @@ -96,7 +96,7 @@ console.log(jsonNullifier); console.log('pay out'); tx = await Mina.transaction(sender, async () => { AccountUpdate.fundNewAccount(sender); - zkapp.payout(Nullifier.fromJSON(jsonNullifier)); + await zkapp.payout(Nullifier.fromJSON(jsonNullifier)); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -110,7 +110,7 @@ console.log('trying second pay out'); try { tx = await Mina.transaction(sender, async () => { - zkapp.payout(Nullifier.fromJSON(jsonNullifier)); + await zkapp.payout(Nullifier.fromJSON(jsonNullifier)); }); await tx.prove(); diff --git a/src/examples/simple-zkapp-berkeley.ts b/src/examples/simple-zkapp-berkeley.ts index 077d4e3fb2..5bedd28f7f 100644 --- a/src/examples/simple-zkapp-berkeley.ts +++ b/src/examples/simple-zkapp-berkeley.ts @@ -16,14 +16,9 @@ import { SmartContract, Mina, AccountUpdate, - isReady, - shutdown, - DeployArgs, fetchAccount, } from 'o1js'; -await isReady; - // a very simple SmartContract class SimpleZkapp extends SmartContract { @state(Field) x = State(); @@ -33,9 +28,8 @@ class SimpleZkapp extends SmartContract { this.x.set(initialState); } - @method update(y: Field) { - let x = this.x.get(); - this.x.assertEquals(x); + @method async update(y: Field) { + let x = this.x.getAndRequireEquals(); y.assertGreaterThan(0); this.x.set(x.add(y)); } @@ -85,7 +79,7 @@ if (!isDeployed) { { sender: feePayerAddress, fee: transactionFee }, async () => { AccountUpdate.fundNewAccount(feePayerAddress); - zkapp.deploy({ verificationKey }); + await zkapp.deploy({ verificationKey }); } ); // if you want to inspect the transaction, you can print it out: @@ -102,9 +96,7 @@ if (isDeployed) { console.log(`Found deployed zkapp, updating state ${x} -> ${x.add(10)}.`); let transaction = await Mina.transaction( { sender: feePayerAddress, fee: transactionFee }, - async () => { - zkapp.update(Field(10)); - } + () => zkapp.update(Field(10)) ); // fill in the proof - this can take a while... console.log('Creating an execution proof...'); @@ -117,5 +109,3 @@ if (isDeployed) { console.log('Sending the transaction...'); await transaction.sign([feePayerKey]).send(); } - -shutdown(); diff --git a/src/examples/simple-zkapp.js b/src/examples/simple-zkapp.js index dc349b0a5e..a5c79990d9 100644 --- a/src/examples/simple-zkapp.js +++ b/src/examples/simple-zkapp.js @@ -31,7 +31,7 @@ class SimpleZkapp extends SmartContract { this.x.set(initialState); } - update(y) { + async update(y) { this.emitEvent('update', y); this.emitEvent('update', y); this.account.balance.assertEquals(this.account.balance.get()); @@ -61,14 +61,14 @@ await SimpleZkapp.compile(); console.log('deploy'); let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); console.log('initial state: ' + zkapp.x.get()); console.log('update'); -tx = await Mina.transaction(feePayer, async () => zkapp.update(Field(3))); +tx = await Mina.transaction(feePayer, () => zkapp.update(Field(3))); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('final state: ' + zkapp.x.get()); diff --git a/src/examples/simple-zkapp.web.ts b/src/examples/simple-zkapp.web.ts index e524024a03..9cd65b839d 100644 --- a/src/examples/simple-zkapp.web.ts +++ b/src/examples/simple-zkapp.web.ts @@ -21,12 +21,12 @@ class SimpleZkapp extends SmartContract { events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; - @method init() { + @method async init() { super.init(); this.x.set(initialState); } - @method update(y: Field): Field { + @method.returns(Field) async update(y: Field) { this.account.provedState.requireEquals(Bool(true)); this.network.timestamp.requireBetween(beforeGenesis, UInt64.MAXINT()); this.emitEvent('update', y); @@ -41,7 +41,7 @@ class SimpleZkapp extends SmartContract { * 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) { + @method async payout(caller: PrivateKey) { this.account.provedState.requireEquals(Bool(true)); // check that caller is the privileged account @@ -92,7 +92,7 @@ console.log('deploy'); let tx = await Mina.transaction(sender, async () => { let senderUpdate = AccountUpdate.fundNewAccount(sender); senderUpdate.send({ to: zkappAddress, amount: initialBalance }); - zkapp.deploy({ zkappKey }); + await zkapp.deploy({ zkappKey }); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -105,7 +105,7 @@ console.log('account state is proved:', account.zkapp?.provedState.toBoolean()); console.log('update'); tx = await Mina.transaction(sender, async () => { - zkapp.update(Field(3)); + await zkapp.update(Field(3)); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -121,7 +121,7 @@ await tx.sign([senderKey]).send(); console.log('payout'); tx = await Mina.transaction(sender, async () => { AccountUpdate.fundNewAccount(sender); - zkapp.payout(privilegedKey); + await zkapp.payout(privilegedKey); }); await tx.prove(); await tx.sign([senderKey]).send(); diff --git a/src/examples/zkapps/dex/dex-with-actions.ts b/src/examples/zkapps/dex/dex-with-actions.ts index 6b8a7b3014..d8523bf29e 100644 --- a/src/examples/zkapps/dex/dex-with-actions.ts +++ b/src/examples/zkapps/dex/dex-with-actions.ts @@ -38,7 +38,7 @@ class Dex extends TokenContract { // Approvable API - @method approveBase(forest: AccountUpdateForest) { + @method async approveBase(forest: AccountUpdateForest) { this.checkZeroBalanceChange(forest); } @@ -78,7 +78,8 @@ class Dex extends TokenContract { }); } - @method createAccount() { + // TODO this could just use `this.approveAccountUpdate()` instead of a separate @method + @method async createAccount() { this.internal.mint({ address: this.sender, amount: UInt64.from(0) }); } @@ -92,7 +93,8 @@ class Dex extends TokenContract { * This can also be used if the pool is empty. In that case, there is no check on X/Y; * instead, the input X and Y amounts determine the initial ratio. */ - @method supplyLiquidityBase(dx: UInt64, dy: UInt64): UInt64 { + @method.returns(UInt64) + async supplyLiquidityBase(dx: UInt64, dy: UInt64) { let user = this.sender; let tokenX = new TrivialCoin(this.tokenX); let tokenY = new TrivialCoin(this.tokenY); @@ -110,8 +112,8 @@ class Dex extends TokenContract { let isDyCorrect = dy.equals(dx.mul(y).div(xSafe)); isDyCorrect.or(isXZero).assertTrue(); - tokenX.transfer(user, dexX, dx); - tokenY.transfer(user, dexY, dy); + await tokenX.transfer(user, dexX, dx); + await tokenY.transfer(user, dexY, dy); // calculate liquidity token output simply as dl = dx + dx // => maintains ratio x/l, y/l @@ -137,7 +139,7 @@ class Dex extends TokenContract { * the input amount of Y tokens is calculated automatically from the X tokens. * Fails if the liquidity pool is empty, so can't be used for the first deposit. */ - supplyLiquidity(dx: UInt64): UInt64 { + async supplyLiquidity(dx: UInt64) { // calculate dy outside circuit let x = Account(this.address, TokenId.derive(this.tokenX)).balance.get(); let y = Account(this.address, TokenId.derive(this.tokenY)).balance.get(); @@ -147,7 +149,7 @@ class Dex extends TokenContract { ); } let dy = dx.mul(y).div(x); - return this.supplyLiquidityBase(dx, dy); + return await this.supplyLiquidityBase(dx, dy); } /** @@ -163,7 +165,7 @@ class Dex extends TokenContract { * @emits RedeemAction - action on the Dex account that will make the token holder * contracts pay you tokens when reducing the action. */ - @method redeemInitialize(dl: UInt64) { + @method async redeemInitialize(dl: UInt64) { this.reducer.dispatch(new RedeemAction({ address: this.sender, dl })); this.internal.burn({ address: this.sender, amount: dl }); // TODO: preconditioning on the state here ruins concurrent interactions, @@ -178,7 +180,10 @@ class Dex extends TokenContract { * Helper for `DexTokenHolder.redeemFinalize()` which adds preconditions on * the current action state and token supply */ - @method assertActionsAndSupply(actionState: Field, totalSupply: UInt64) { + @method async assertActionsAndSupply( + actionState: Field, + totalSupply: UInt64 + ) { this.account.actionState.requireEquals(actionState); this.totalSupply.requireEquals(totalSupply); } @@ -193,11 +198,11 @@ class Dex extends TokenContract { * Note: this is not a `@method`, since it doesn't do anything beyond * the called methods which requires proof authorization. */ - swapX(dx: UInt64): UInt64 { + async swapX(dx: UInt64) { let tokenY = new TrivialCoin(this.tokenY); let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); - let dy = dexY.swap(this.sender, dx, this.tokenX); - tokenY.transfer(dexY.self, this.sender, dy); + let dy = await dexY.swap(this.sender, dx, this.tokenX); + await tokenY.transfer(dexY.self, this.sender, dy); return dy; } @@ -211,11 +216,11 @@ class Dex extends TokenContract { * Note: this is not a `@method`, since it doesn't do anything beyond * the called methods which requires proof authorization. */ - swapY(dy: UInt64): UInt64 { + async swapY(dy: UInt64) { let tokenX = new TrivialCoin(this.tokenX); let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); - let dx = dexX.swap(this.sender, dy, this.tokenY); - tokenX.transfer(dexX.self, this.sender, dx); + let dx = await dexX.swap(this.sender, dy, this.tokenY); + await tokenX.transfer(dexX.self, this.sender, dx); return dx; } } @@ -237,7 +242,7 @@ class DexTokenHolder extends SmartContract { this.redeemActionState.set(Reducer.initialActionState); } - @method redeemLiquidityFinalize() { + @method async redeemLiquidityFinalize() { // get redeem actions let dex = new Dex(this.address); let fromActionState = this.redeemActionState.getAndRequireEquals(); @@ -285,15 +290,16 @@ class DexTokenHolder extends SmartContract { this.redeemActionState.set(redeemActionState); // precondition on the DEX contract, to prove we used the right actions & token supply - dex.assertActionsAndSupply(redeemActionState, l); + await dex.assertActionsAndSupply(redeemActionState, l); } // this works for both directions (in our case where both tokens use the same contract) - @method swap( + @method.returns(UInt64) + async swap( user: PublicKey, otherTokenAmount: UInt64, otherTokenAddress: PublicKey - ): UInt64 { + ) { // we're writing this as if our token === y and other token === x let dx = otherTokenAmount; let tokenX = new TrivialCoin(otherTokenAddress); @@ -304,7 +310,7 @@ class DexTokenHolder extends SmartContract { let y = this.account.balance.getAndRequireEquals(); // send x from user to us (i.e., to the same address as this but with the other token) - tokenX.transfer(user, dexX, dx); + await tokenX.transfer(user, dexX, dx); // compute and send dy let dy = y.mul(dx).div(x.add(dx)); diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index 84dec73c87..483ca8243b 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -33,7 +33,7 @@ function createDex({ // Approvable API @method - approveBase(forest: AccountUpdateForest) { + async approveBase(forest: AccountUpdateForest) { this.checkZeroBalanceChange(forest); } @@ -54,7 +54,8 @@ function createDex({ * This can also be used if the pool is empty. In that case, there is no check on X/Y; * instead, the input X and Y amounts determine the initial ratio. */ - @method supplyLiquidityBase(dx: UInt64, dy: UInt64): UInt64 { + @method.returns(UInt64) + async supplyLiquidityBase(dx: UInt64, dy: UInt64) { let user = this.sender; let tokenX = new TokenContract(this.tokenX); let tokenY = new TokenContract(this.tokenY); @@ -78,8 +79,8 @@ function createDex({ let isDyCorrect = dy.equals(dx.mul(dexYBalance).div(xSafe)); isDyCorrect.or(isXZero).assertTrue(); - tokenX.transfer(user, dexXUpdate, dx); - tokenY.transfer(user, dexYUpdate, dy); + await tokenX.transfer(user, dexXUpdate, dx); + await tokenY.transfer(user, dexYUpdate, dy); // calculate liquidity token output simply as dl = dx + dy // => maintains ratio x/l, y/l @@ -123,7 +124,7 @@ function createDex({ * the input amount of Y tokens is calculated automatically from the X tokens. * Fails if the liquidity pool is empty, so can't be used for the first deposit. */ - supplyLiquidity(dx: UInt64): UInt64 { + async supplyLiquidity(dx: UInt64) { // calculate dy outside circuit let x = Account(this.address, TokenId.derive(this.tokenX)).balance.get(); let y = Account(this.address, TokenId.derive(this.tokenY)).balance.get(); @@ -133,7 +134,7 @@ function createDex({ ); } let dy = dx.mul(y).div(x); - return this.supplyLiquidityBase(dx, dy); + return await this.supplyLiquidityBase(dx, dy); } /** @@ -146,13 +147,13 @@ function createDex({ * Note: this is not a `@method` because there's nothing to prove which isn't already proven * by the called methods */ - redeemLiquidity(dl: UInt64) { + async redeemLiquidity(dl: UInt64) { // call the token X holder inside a token X-approved callback let tokenX = new TokenContract(this.tokenX); let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); - let dxdy = dexX.redeemLiquidity(this.sender, dl, this.tokenY); + let dxdy = await dexX.redeemLiquidity(this.sender, dl, this.tokenY); let dx = dxdy[0]; - tokenX.transfer(dexX.self, this.sender, dx); + await tokenX.transfer(dexX.self, this.sender, dx); return dxdy; } @@ -163,11 +164,12 @@ function createDex({ * * The transaction needs to be signed by the user's private key. */ - @method swapX(dx: UInt64): UInt64 { + @method.returns(UInt64) + async swapX(dx: UInt64) { let tokenY = new TokenContract(this.tokenY); let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); - let dy = dexY.swap(this.sender, dx, this.tokenX); - tokenY.transfer(dexY.self, this.sender, dy); + let dy = await dexY.swap(this.sender, dx, this.tokenX); + await tokenY.transfer(dexY.self, this.sender, dy); return dy; } @@ -178,11 +180,12 @@ function createDex({ * * The transaction needs to be signed by the user's private key. */ - @method swapY(dy: UInt64): UInt64 { + @method.returns(UInt64) + async swapY(dy: UInt64) { let tokenX = new TokenContract(this.tokenX); let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); - let dx = dexX.swap(this.sender, dy, this.tokenY); - tokenX.transfer(dexX.self, this.sender, dx); + let dx = await dexX.swap(this.sender, dy, this.tokenY); + await tokenX.transfer(dexX.self, this.sender, dx); return dx; } @@ -197,7 +200,8 @@ function createDex({ * * The transaction needs to be signed by the user's private key. */ - @method burnLiquidity(user: PublicKey, dl: UInt64): UInt64 { + @method.returns(UInt64) + async burnLiquidity(user: PublicKey, dl: UInt64) { // this makes sure there is enough l to burn (user balance stays >= 0), so l stays >= 0, so l was >0 before this.internal.burn({ address: user, amount: dl }); let l = this.totalSupply.get(); @@ -208,20 +212,21 @@ function createDex({ } class ModifiedDex extends Dex { - deploy() { - super.deploy(); + async deploy() { + await super.deploy(); // override the isNew requirement for re-deploying this.account.isNew.requireNothing(); } - @method swapX(dx: UInt64): UInt64 { + @method.returns(UInt64) + async swapX(dx: UInt64) { let tokenY = new TokenContract(this.tokenY); let dexY = new ModifiedDexTokenHolder( this.address, tokenY.deriveTokenId() ); - let dy = dexY.swap(this.sender, dx, this.tokenX); - tokenY.transfer(dexY.self, this.sender, dy); + let dy = await dexY.swap(this.sender, dx, this.tokenX); + await tokenY.transfer(dexY.self, this.sender, dy); return dy; } } @@ -231,10 +236,11 @@ function createDex({ // it's incomplete, as it gives the user only the Y part for an lqXY token; but doesn't matter as there's no incentive to call it directly // see the more complicated method `redeemLiquidity` below which gives back both tokens, by calling this method, // for the other token, in a callback - @method redeemLiquidityPartial(user: PublicKey, dl: UInt64): UInt64x2 { + @method.returns(UInt64x2) + async redeemLiquidityPartial(user: PublicKey, dl: UInt64) { // user burns dl, approved by the Dex main contract let dex = new Dex(addresses.dex); - let l = dex.burnLiquidity(user, dl); + let l = await dex.burnLiquidity(user, dl); // in return, we give dy back let y = this.account.balance.get(); @@ -252,18 +258,19 @@ function createDex({ } // more complicated circuit, where we trigger the Y(other)-lqXY trade in our child account updates and then add the X(our) part - @method redeemLiquidity( + @method.returns(UInt64x2) + async redeemLiquidity( user: PublicKey, dl: UInt64, otherTokenAddress: PublicKey - ): UInt64x2 { + ) { // first call the Y token holder, approved by the Y token contract; this makes sure we get dl, the user's lqXY let tokenY = new TokenContract(otherTokenAddress); let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); - let result = dexY.redeemLiquidityPartial(user, dl); + let result = await dexY.redeemLiquidityPartial(user, dl); let l = result[0]; let dy = result[1]; - tokenY.transfer(dexY.self, user, dy); + await tokenY.transfer(dexY.self, user, dy); // in return for dl, we give back dx, the X token part let x = this.account.balance.get(); @@ -276,11 +283,12 @@ function createDex({ } // this works for both directions (in our case where both tokens use the same contract) - @method swap( + @method.returns(UInt64) + async swap( user: PublicKey, otherTokenAmount: UInt64, otherTokenAddress: PublicKey - ): UInt64 { + ) { // we're writing this as if our token === y and other token === x let dx = otherTokenAmount; let tokenX = new TokenContract(otherTokenAddress); @@ -289,7 +297,7 @@ function createDex({ let x = dexX.account.balance.getAndRequireEquals(); let y = this.account.balance.getAndRequireEquals(); // send x from user to us (i.e., to the same address as this but with the other token) - tokenX.transfer(user, dexX, dx); + await tokenX.transfer(user, dexX, dx); // compute and send dy let dy = y.mul(dx).div(x.add(dx)); // just subtract dy balance and let adding balance be handled one level higher @@ -302,11 +310,12 @@ function createDex({ /** * This swap method has a slightly changed formula */ - @method swap( + @method.returns(UInt64) + async swap( user: PublicKey, otherTokenAmount: UInt64, otherTokenAddress: PublicKey - ): UInt64 { + ) { let dx = otherTokenAmount; let tokenX = new TokenContract(otherTokenAddress); // get balances @@ -314,7 +323,7 @@ function createDex({ let x = dexX.account.balance.getAndRequireEquals(); let y = this.account.balance.get(); this.account.balance.requireEquals(y); - tokenX.transfer(user, dexX, dx); + await tokenX.transfer(user, dexX, dx); // this formula has been changed - we just give the user an additional 15 token let dy = y.mul(dx).div(x.add(dx)).add(15); @@ -387,7 +396,7 @@ function createDex({ * Simple token with API flexible enough to handle all our use cases */ class TokenContract extends BaseTokenContract { - @method init() { + @method async init() { super.init(); // mint the entire supply to the token account with the same address as this contract /** @@ -410,7 +419,7 @@ class TokenContract extends BaseTokenContract { * * mint additional tokens to some user, so we can overflow token balances */ - @method init2() { + @method async init2() { let receiver = this.internal.mint({ address: addresses.user, amount: UInt64.from(10n ** 6n), @@ -422,7 +431,7 @@ class TokenContract extends BaseTokenContract { } @method - approveBase(forest: AccountUpdateForest) { + async approveBase(forest: AccountUpdateForest) { this.checkZeroBalanceChange(forest); } } diff --git a/src/examples/zkapps/dex/erc20.ts b/src/examples/zkapps/dex/erc20.ts index bbc7585fc0..586b301faa 100644 --- a/src/examples/zkapps/dex/erc20.ts +++ b/src/examples/zkapps/dex/erc20.ts @@ -31,21 +31,22 @@ export { Erc20Like, TrivialCoin }; * (in order to get proof authorization), and can't be created by the token contract itself. * - `transfer()` doesn't return a boolean, because in the zkApp protocol, * a transaction succeeds or fails in its entirety, and there is no need to handle partial failures. + * - All method signatures are async to support async circuits / fetching data from the chain. */ type Erc20Like = { // pure view functions which don't need @method - name?: () => CircuitString; - symbol?: () => CircuitString; - decimals?: () => Field; - totalSupply(): UInt64; - balanceOf(owner: PublicKey | AccountUpdate): UInt64; + name?: () => Promise; + symbol?: () => Promise; + decimals?: () => Promise; + totalSupply(): Promise; + balanceOf(owner: PublicKey | AccountUpdate): Promise; // mutations which need @method transfer( from: PublicKey | AccountUpdate, to: PublicKey | AccountUpdate, value: UInt64 - ): void; // emits "Transfer" event + ): Promise; // emits "Transfer" event // events events: { @@ -71,8 +72,8 @@ class TrivialCoin extends TokenContract implements Erc20Like { // constant supply SUPPLY = UInt64.from(10n ** 18n); - deploy(args?: DeployArgs) { - super.deploy(args); + async deploy(args?: DeployArgs) { + await super.deploy(args); this.account.tokenSymbol.set('TRIV'); // make account non-upgradable forever @@ -87,7 +88,7 @@ class TrivialCoin extends TokenContract implements Erc20Like { }); } - @method init() { + @method async init() { super.init(); // mint the entire supply to the token account with the same address as this contract @@ -105,24 +106,24 @@ class TrivialCoin extends TokenContract implements Erc20Like { } // ERC20 API - name(): CircuitString { + async name() { return CircuitString.fromString('TrivialCoin'); } - symbol(): CircuitString { + async symbol() { return CircuitString.fromString('TRIV'); } - decimals(): Field { + async decimals() { return Field(9); } - totalSupply(): UInt64 { + async totalSupply() { return this.SUPPLY; } - balanceOf(owner: PublicKey | AccountUpdate): UInt64 { + async balanceOf(owner: PublicKey | AccountUpdate) { let update = owner instanceof PublicKey ? AccountUpdate.create(owner, this.deriveTokenId()) : owner; - this.approveAccountUpdate(update); + await this.approveAccountUpdate(update); return update.account.balance.getAndRequireEquals(); } @@ -136,7 +137,7 @@ class TrivialCoin extends TokenContract implements Erc20Like { // implement Approvable API - @method approveBase(forest: AccountUpdateForest) { + @method async approveBase(forest: AccountUpdateForest) { this.checkZeroBalanceChange(forest); } } diff --git a/src/examples/zkapps/dex/happy-path-with-actions.ts b/src/examples/zkapps/dex/happy-path-with-actions.ts index 8b03a01a2c..7b186e641d 100644 --- a/src/examples/zkapps/dex/happy-path-with-actions.ts +++ b/src/examples/zkapps/dex/happy-path-with-actions.ts @@ -43,8 +43,8 @@ let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); tx = await Mina.transaction(feePayerAddress, async () => { - tokenX.deploy(); - tokenY.deploy(); + await tokenX.deploy(); + await tokenY.deploy(); // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves const accountFee = Mina.getNetworkConstants().accountCreationFee; @@ -63,11 +63,11 @@ tx = await Mina.transaction(feePayerAddress, async () => { AccountUpdate.createSigned(feePayerAddress).balance.subInPlace( Mina.getNetworkConstants().accountCreationFee.mul(3) ); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveAccountUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveAccountUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); await tx.sign([feePayerKey, keys.dex]).send(); @@ -80,8 +80,8 @@ tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 user accounts let feePayer = AccountUpdate.fundNewAccount(feePayerAddress, 3); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees - tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); - tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); + await tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); + await tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([feePayerKey, keys.tokenX, keys.tokenY]).send(); @@ -92,7 +92,7 @@ console.log('account updates length', tx.transaction.accountUpdates.length); tic("create user's lq token account"); tx = await Mina.transaction(addresses.user, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.createAccount(); + await dex.createAccount(); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -105,7 +105,7 @@ console.log(balances); tic('supply liquidity'); tx = await Mina.transaction(addresses.user, async () => { - dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); + await dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -118,7 +118,7 @@ console.log(balances); tic('redeem liquidity, step 1'); let USER_DL = 100n; tx = await Mina.transaction(addresses.user, async () => { - dex.redeemInitialize(UInt64.from(USER_DL)); + await dex.redeemInitialize(UInt64.from(USER_DL)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -128,8 +128,8 @@ console.log(getTokenBalances()); tic('redeem liquidity, step 2a (get back token X)'); tx = await Mina.transaction(addresses.user, async () => { - dexTokenHolderX.redeemLiquidityFinalize(); - tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderX.redeemLiquidityFinalize(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -139,8 +139,8 @@ console.log(getTokenBalances()); tic('redeem liquidity, step 2b (get back token Y)'); tx = await Mina.transaction(addresses.user, async () => { - dexTokenHolderY.redeemLiquidityFinalize(); - tokenY.approveAccountUpdate(dexTokenHolderY.self); + await dexTokenHolderY.redeemLiquidityFinalize(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -154,7 +154,7 @@ expect(balances.user.X).toEqual(USER_DL / 2n); tic('swap 10 X for Y'); USER_DX = 10n; tx = await Mina.transaction(addresses.user, async () => { - dex.swapX(UInt64.from(USER_DX)); + await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); diff --git a/src/examples/zkapps/dex/happy-path-with-proofs.ts b/src/examples/zkapps/dex/happy-path-with-proofs.ts index 456a35b6e4..09395d9801 100644 --- a/src/examples/zkapps/dex/happy-path-with-proofs.ts +++ b/src/examples/zkapps/dex/happy-path-with-proofs.ts @@ -45,8 +45,8 @@ let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); tx = await Mina.transaction(feePayerAddress, async () => { - tokenX.deploy(); - tokenY.deploy(); + await tokenX.deploy(); + await tokenY.deploy(); // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves const accountFee = Mina.getNetworkConstants().accountCreationFee; @@ -65,11 +65,11 @@ tx = await Mina.transaction(feePayerAddress, async () => { AccountUpdate.createSigned(feePayerAddress).balance.subInPlace( Mina.getNetworkConstants().accountCreationFee.mul(3) ); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveAccountUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveAccountUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); await tx.sign([feePayerKey, keys.dex]).send(); @@ -82,8 +82,8 @@ tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 user accounts let feePayer = AccountUpdate.fundNewAccount(feePayerAddress, 3); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees - tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); - tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); + await tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); + await tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([feePayerKey, keys.tokenX, keys.tokenY]).send(); @@ -95,7 +95,7 @@ expect(balances.user.X).toEqual(USER_DX); tic('supply liquidity'); tx = await Mina.transaction(addresses.user, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); + await dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -107,7 +107,7 @@ expect(balances.user.X).toEqual(0n); tic('redeem liquidity'); let USER_DL = 100n; tx = await Mina.transaction(addresses.user, async () => { - dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); @@ -120,7 +120,7 @@ expect(balances.user.X).toEqual(USER_DL / 2n); tic('swap 10 X for Y'); USER_DX = 10n; tx = await Mina.transaction(addresses.user, async () => { - dex.swapX(UInt64.from(USER_DX)); + await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); diff --git a/src/examples/zkapps/dex/run-live.ts b/src/examples/zkapps/dex/run-live.ts index 4c48747fdd..64d37cd0c9 100644 --- a/src/examples/zkapps/dex/run-live.ts +++ b/src/examples/zkapps/dex/run-live.ts @@ -73,8 +73,8 @@ let userSpec = { sender: addresses.user, fee: 0.1e9 }; if (successfulTransactions <= 0) { tic('deploy & init token contracts'); tx = await Mina.transaction(senderSpec, async () => { - tokenX.deploy(); - tokenY.deploy(); + await tokenX.deploy(); + await tokenY.deploy(); // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves const accountFee = Mina.getNetworkConstants().accountCreationFee; @@ -100,11 +100,11 @@ if (successfulTransactions <= 1) { AccountUpdate.createSigned(sender).balance.subInPlace( Mina.getNetworkConstants().accountCreationFee.mul(3) ); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveAccountUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveAccountUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.dex]).send(); @@ -125,8 +125,16 @@ if (successfulTransactions <= 2) { // pay fees for creating 3 user accounts let feePayer = AccountUpdate.fundNewAccount(sender, 3); feePayer.send({ to: addresses.user, amount: 8e9 }); // give users MINA to pay fees - tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); - tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); + await tokenX.transfer( + addresses.tokenX, + addresses.user, + UInt64.from(USER_DX) + ); + await tokenY.transfer( + addresses.tokenY, + addresses.user, + UInt64.from(USER_DX) + ); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.tokenX, keys.tokenY]).send(); @@ -144,7 +152,7 @@ if (successfulTransactions <= 3) { tic("create user's lq token account"); tx = await Mina.transaction(userSpec, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.createAccount(); + await dex.createAccount(); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -164,7 +172,7 @@ if (successfulTransactions <= 3) { if (successfulTransactions <= 4) { tic('supply liquidity'); tx = await Mina.transaction(userSpec, async () => { - dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); + await dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -186,7 +194,7 @@ let USER_DL = 100n; if (successfulTransactions <= 5) { tic('redeem liquidity, step 1'); tx = await Mina.transaction(userSpec, async () => { - dex.redeemInitialize(UInt64.from(USER_DL)); + await dex.redeemInitialize(UInt64.from(USER_DL)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -204,8 +212,8 @@ if (successfulTransactions <= 5) { if (successfulTransactions <= 6) { tic('redeem liquidity, step 2a (get back token X)'); tx = await Mina.transaction(userSpec, async () => { - dexTokenHolderX.redeemLiquidityFinalize(); - tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderX.redeemLiquidityFinalize(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -223,8 +231,8 @@ if (successfulTransactions <= 6) { if (successfulTransactions <= 7) { tic('redeem liquidity, step 2b (get back token Y)'); tx = await Mina.transaction(userSpec, async () => { - dexTokenHolderY.redeemLiquidityFinalize(); - tokenY.approveAccountUpdate(dexTokenHolderY.self); + await dexTokenHolderY.redeemLiquidityFinalize(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); @@ -247,7 +255,7 @@ if (successfulTransactions <= 8) { tic('swap 10 X for Y'); USER_DX = 10n; tx = await Mina.transaction(userSpec, async () => { - dex.swapX(UInt64.from(USER_DX)); + await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); diff --git a/src/examples/zkapps/dex/run.ts b/src/examples/zkapps/dex/run.ts index bae3a75921..fe0d913a44 100644 --- a/src/examples/zkapps/dex/run.ts +++ b/src/examples/zkapps/dex/run.ts @@ -76,8 +76,8 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, async () => { - tokenX.deploy(); - tokenY.deploy(); + await tokenX.deploy(); + await tokenY.deploy(); // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves const accountFee = Mina.getNetworkConstants().accountCreationFee; @@ -99,11 +99,11 @@ async function main({ withVesting }: { withVesting: boolean }) { tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts AccountUpdate.fundNewAccount(feePayerAddress, 3); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveAccountUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveAccountUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); tx.sign([feePayerKey, keys.dex]); @@ -120,11 +120,11 @@ async function main({ withVesting }: { withVesting: boolean }) { feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees feePayer.send({ to: addresses.user2, amount: 20e9 }); // transfer to fee payer so they can provide initial liquidity - tokenX.transfer(addresses.tokenX, feePayerAddress, UInt64.from(10_000)); - tokenY.transfer(addresses.tokenY, feePayerAddress, UInt64.from(10_000)); + await tokenX.transfer(addresses.tokenX, feePayerAddress, 10_000); + await tokenY.transfer(addresses.tokenY, feePayerAddress, 10_000); // mint tokens to the user (this is additional to the tokens minted at the beginning, so we can overflow the balance - tokenX.init2(); - tokenY.init2(); + await tokenX.init2(); + await tokenY.init2(); } ); await tx.prove(); @@ -143,7 +143,7 @@ async function main({ withVesting }: { withVesting: boolean }) { }, async () => { AccountUpdate.fundNewAccount(feePayerAddress); - dex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); + await dex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); } ); await tx.prove(); @@ -181,7 +181,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('user supply liquidity (1)'); tx = await Mina.transaction(addresses.user, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.supplyLiquidity(UInt64.from(USER_DX)); + await dex.supplyLiquidity(UInt64.from(USER_DX)); }); await tx.prove(); tx.sign([keys.user]); @@ -218,7 +218,7 @@ async function main({ withVesting }: { withVesting: boolean }) { USER_DX = 1000n; console.log('user supply liquidity (2)'); tx = await Mina.transaction(addresses.user, async () => { - dex.supplyLiquidity(UInt64.from(USER_DX)); + await dex.supplyLiquidity(UInt64.from(USER_DX)); }); await tx.prove(); tx.sign([keys.user]); @@ -250,14 +250,14 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('supplying with no tokens (should fail)'); tx = await Mina.transaction(addresses.user2, async () => { AccountUpdate.fundNewAccount(addresses.user2); - dex.supplyLiquidityBase(UInt64.from(100), UInt64.from(100)); + await dex.supplyLiquidityBase(UInt64.from(100), UInt64.from(100)); }); await tx.prove(); tx.sign([keys.user2]); await expect(tx.sendOrThrowIfError()).rejects.toThrow(/Overflow/); console.log('supplying with insufficient tokens (should fail)'); tx = await Mina.transaction(addresses.user, async () => { - dex.supplyLiquidityBase(UInt64.from(1e9), UInt64.from(1e9)); + await dex.supplyLiquidityBase(UInt64.from(1e9), UInt64.from(1e9)); }); await tx.prove(); tx.sign([keys.user]); @@ -273,7 +273,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('prepare supplying overflowing liquidity'); tx = await Mina.transaction(feePayerAddress, async () => { AccountUpdate.fundNewAccount(feePayerAddress); - tokenY.transfer( + await tokenY.transfer( addresses.tokenY, addresses.tokenX, UInt64.MAXINT().sub(200_000) @@ -284,7 +284,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('supply overflowing liquidity'); await expect(async () => { tx = await Mina.transaction(addresses.tokenX, async () => { - dex.supplyLiquidityBase( + await dex.supplyLiquidityBase( UInt64.MAXINT().sub(200_000), UInt64.MAXINT().sub(200_000) ); @@ -315,7 +315,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('supply with forbidden withdrawal (should fail)'); tx = await Mina.transaction(addresses.tokenX, async () => { AccountUpdate.fundNewAccount(addresses.tokenX); - dex.supplyLiquidity(UInt64.from(10)); + await dex.supplyLiquidity(UInt64.from(10)); }); await tx.prove(); await expect(tx.sign([keys.tokenX]).sendOrThrowIfError()).rejects.toThrow( @@ -341,7 +341,7 @@ async function main({ withVesting }: { withVesting: boolean }) { let USER_DL = 100n; console.log('user redeem liquidity (before liquidity token unlocks)'); tx = await Mina.transaction(addresses.user, async () => { - dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); tx.sign([keys.user]); @@ -367,7 +367,7 @@ async function main({ withVesting }: { withVesting: boolean }) { let USER_DL = 100n; console.log('user redeem liquidity'); tx = await Mina.transaction(addresses.user, async () => { - dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); tx.sign([keys.user]); @@ -405,7 +405,7 @@ async function main({ withVesting }: { withVesting: boolean }) { USER_DX = 1000n; console.log('user supply liquidity -- again, after lock period ended'); tx = await Mina.transaction(addresses.user, async () => { - dex.supplyLiquidity(UInt64.from(USER_DX)); + await dex.supplyLiquidity(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -438,7 +438,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('transfer liquidity tokens to user2'); tx = await Mina.transaction(addresses.user, async () => { AccountUpdate.fundNewAccount(addresses.user); - dex.transfer(addresses.user, addresses.user2, UInt64.from(USER_DL)); + await dex.transfer(addresses.user, addresses.user2, UInt64.from(USER_DL)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -451,8 +451,8 @@ async function main({ withVesting }: { withVesting: boolean }) { AccountUpdate.createSigned(addresses.user2).balance.subInPlace( Mina.getNetworkConstants().accountCreationFee.mul(2) ); - dex.redeemLiquidity(UInt64.from(USER_DL)); - dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); tx.sign([keys.user, keys.user2]); @@ -465,7 +465,7 @@ async function main({ withVesting }: { withVesting: boolean }) { AccountUpdate.createSigned(addresses.user2).balance.subInPlace( Mina.getNetworkConstants().accountCreationFee.mul(2) ); - dex.redeemLiquidity(UInt64.from(USER_DL)); + await dex.redeemLiquidity(UInt64.from(USER_DL)); }); await tx.prove(); await tx.sign([keys.user2]).send(); @@ -488,7 +488,7 @@ async function main({ withVesting }: { withVesting: boolean }) { */ console.log('user2 redeem liquidity (fails because insufficient balance)'); tx = await Mina.transaction(addresses.user2, async () => { - dex.redeemLiquidity(UInt64.from(1n)); + await dex.redeemLiquidity(UInt64.from(1n)); }); await tx.prove(); await expect(tx.sign([keys.user2]).sendOrThrowIfError()).rejects.toThrow( @@ -512,7 +512,7 @@ async function main({ withVesting }: { withVesting: boolean }) { USER_DX = 10n; console.log('swap 10 X for Y'); tx = await Mina.transaction(addresses.user, async () => { - dex.swapX(UInt64.from(USER_DX)); + await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index 4178499316..0dfdc01f1b 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -57,8 +57,8 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, async () => { - tokenX.deploy(); - tokenY.deploy(); + await tokenX.deploy(); + await tokenY.deploy(); // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves const accountFee = Mina.getNetworkConstants().accountCreationFee; @@ -94,11 +94,11 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts AccountUpdate.fundNewAccount(feePayerAddress, 3); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveAccountUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveAccountUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); console.log('manipulating setDelegate field to impossible...'); // setting the setDelegate permission field to impossible let dexAccount = AccountUpdate.create(addresses.dex); @@ -268,8 +268,8 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, async () => { - tokenX.deploy(); - tokenY.deploy(); + await tokenX.deploy(); + await tokenY.deploy(); // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves const accountFee = Mina.getNetworkConstants().accountCreationFee; @@ -309,11 +309,11 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { tx = await Mina.transaction(feePayerAddress, async () => { // pay fees for creating 3 dex accounts AccountUpdate.fundNewAccount(feePayerAddress, 3); - dex.deploy(); - dexTokenHolderX.deploy(); - tokenX.approveAccountUpdate(dexTokenHolderX.self); - dexTokenHolderY.deploy(); - tokenY.approveAccountUpdate(dexTokenHolderY.self); + await dex.deploy(); + await dexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(dexTokenHolderX.self); + await dexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); tx.sign([feePayerKey, keys.dex]); @@ -333,11 +333,11 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees feePayer.send({ to: addresses.user2, amount: 20e9 }); // transfer to fee payer so they can provide initial liquidity - tokenX.transfer(addresses.tokenX, feePayerAddress, UInt64.from(10_000)); - tokenY.transfer(addresses.tokenY, feePayerAddress, UInt64.from(10_000)); + await tokenX.transfer(addresses.tokenX, feePayerAddress, 10_000); + await tokenY.transfer(addresses.tokenY, feePayerAddress, 10_000); // mint tokens to the user (this is additional to the tokens minted at the beginning, so we can overflow the balance - tokenX.init2(); - tokenY.init2(); + await tokenX.init2(); + await tokenY.init2(); } ); await tx.prove(); @@ -367,11 +367,11 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { ); tx = await Mina.transaction(feePayerAddress, async () => { - modifiedDex.deploy(); - modifiedDexTokenHolderX.deploy(); - tokenX.approveAccountUpdate(modifiedDexTokenHolderX.self); - modifiedDexTokenHolderY.deploy(); - tokenY.approveAccountUpdate(modifiedDexTokenHolderY.self); + await modifiedDex.deploy(); + await modifiedDexTokenHolderX.deploy(); + await tokenX.approveAccountUpdate(modifiedDexTokenHolderX.self); + await modifiedDexTokenHolderY.deploy(); + await tokenY.approveAccountUpdate(modifiedDexTokenHolderY.self); }); await tx.prove(); tx.sign([feePayerKey, keys.dex]); @@ -399,7 +399,10 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { }, async () => { AccountUpdate.fundNewAccount(feePayerAddress); - modifiedDex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); + await modifiedDex.supplyLiquidityBase( + UInt64.from(10_000), + UInt64.from(10_000) + ); } ); await tx.prove(); @@ -411,7 +414,7 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { let USER_DX = 10n; console.log('swap 10 X for Y'); tx = await Mina.transaction(addresses.user, async () => { - modifiedDex.swapX(UInt64.from(USER_DX)); + await modifiedDex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); @@ -458,7 +461,7 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('trying to upgrade contract - should fail'); tx = await Mina.transaction(feePayerAddress, async () => { - modifiedDex.deploy(); // cannot deploy new VK because its forbidden + await modifiedDex.deploy(); // cannot deploy new VK because its forbidden }); await tx.prove(); await expect( @@ -470,7 +473,7 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { USER_DX = 10n; console.log('swap 10 X for Y'); tx = await Mina.transaction(addresses.user, async () => { - modifiedDex.swapX(UInt64.from(USER_DX)); + await modifiedDex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); await tx.sign([keys.user]).send(); diff --git a/src/examples/zkapps/local-events-zkapp.ts b/src/examples/zkapps/local-events-zkapp.ts index f47bb6713b..38d1bf490b 100644 --- a/src/examples/zkapps/local-events-zkapp.ts +++ b/src/examples/zkapps/local-events-zkapp.ts @@ -33,7 +33,7 @@ class SimpleZkapp extends SmartContract { this.x.set(initialState); } - @method update(y: Field) { + @method async update(y: Field) { this.emitEvent('complexEvent', { pub: PrivateKey.random().toPublicKey(), value: y, @@ -67,20 +67,20 @@ if (doProofs) { console.log('deploy'); let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); console.log('call update'); tx = await Mina.transaction(feePayer, async () => { - zkapp.update(Field(1)); + await zkapp.update(Field(1)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('call update'); tx = await Mina.transaction(feePayer, async () => { - zkapp.update(Field(2)); + await zkapp.update(Field(2)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); diff --git a/src/examples/zkapps/set-local-preconditions-zkapp.ts b/src/examples/zkapps/set-local-preconditions-zkapp.ts index 8318e7f2ba..ba43b82cdc 100644 --- a/src/examples/zkapps/set-local-preconditions-zkapp.ts +++ b/src/examples/zkapps/set-local-preconditions-zkapp.ts @@ -8,23 +8,17 @@ For example, you only want your smart contract to initiate a pay out when the `b import { method, - UInt64, PrivateKey, SmartContract, Mina, AccountUpdate, - isReady, - Permissions, - DeployArgs, UInt32, } from 'o1js'; const doProofs = false; -await isReady; - class SimpleZkapp extends SmartContract { - @method blockheightEquals(y: UInt32) { + @method async blockheightEquals(y: UInt32) { let length = this.network.blockchainLength.get(); this.network.blockchainLength.requireEquals(length); @@ -53,7 +47,7 @@ if (doProofs) { console.log('deploy'); let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); @@ -62,7 +56,7 @@ let blockHeight: UInt32 = UInt32.zero; console.log('assert block height 0'); tx = await Mina.transaction(feePayer, async () => { // block height starts at 0 - zkapp.blockheightEquals(UInt32.from(blockHeight)); + await zkapp.blockheightEquals(UInt32.from(blockHeight)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -72,7 +66,7 @@ Local.setBlockchainLength(blockHeight); console.log('assert block height 500'); tx = await Mina.transaction(feePayer, async () => { - zkapp.blockheightEquals(UInt32.from(blockHeight)); + await zkapp.blockheightEquals(UInt32.from(blockHeight)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -82,7 +76,7 @@ Local.setBlockchainLength(UInt32.from(5)); console.log('invalid block height precondition'); try { tx = await Mina.transaction(feePayer, async () => { - zkapp.blockheightEquals(UInt32.from(blockHeight)); + await zkapp.blockheightEquals(UInt32.from(blockHeight)); }); await tx.prove(); await tx.sign([feePayerKey]).send(); diff --git a/src/examples/zkapps/simple-zkapp-payment.ts b/src/examples/zkapps/simple-zkapp-payment.ts index afd4bd96c2..7aa9983faf 100644 --- a/src/examples/zkapps/simple-zkapp-payment.ts +++ b/src/examples/zkapps/simple-zkapp-payment.ts @@ -1,17 +1,13 @@ import { - isReady, method, Mina, AccountUpdate, PrivateKey, SmartContract, UInt64, - shutdown, Permissions, } from 'o1js'; -await isReady; - class SendMINAExample extends SmartContract { init() { super.init(); @@ -21,11 +17,11 @@ class SendMINAExample extends SmartContract { }); } - @method withdraw(amount: UInt64) { + @method async withdraw(amount: UInt64) { this.send({ to: this.sender, amount }); } - @method deposit(amount: UInt64) { + @method async deposit(amount: UInt64) { let senderUpdate = AccountUpdate.createSigned(this.sender); senderUpdate.send({ to: this, amount }); } @@ -71,14 +67,14 @@ tx = await Mina.transaction(feePayer, async () => { let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer, 3); feePayerUpdate.send({ to: account1Address, amount: 2e9 }); feePayerUpdate.send({ to: account2Address, amount: 0 }); // just touch account #2, so it's created - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); printBalances(); console.log('---------- deposit MINA into zkApp (with proof) ----------'); tx = await Mina.transaction(account1Address, async () => { - zkapp.deposit(UInt64.from(1e9)); + await zkapp.deposit(UInt64.from(1e9)); }); await tx.prove(); await tx.sign([account1Key]).send(); @@ -86,7 +82,7 @@ printBalances(); console.log('---------- send MINA from zkApp (with proof) ----------'); tx = await Mina.transaction(account1Address, async () => { - zkapp.withdraw(UInt64.from(1e9)); + await zkapp.withdraw(UInt64.from(1e9)); }); await tx.prove(); await tx.sign([account1Key]).send(); @@ -101,5 +97,3 @@ tx = await Mina.transaction(account1Address, async () => { }); await tx.sign([account1Key]).send(); printBalances(); - -shutdown(); diff --git a/src/examples/zkapps/simple-zkapp-with-proof.ts b/src/examples/zkapps/simple-zkapp-with-proof.ts index 4d4ccb91ac..43c611f330 100644 --- a/src/examples/zkapps/simple-zkapp-with-proof.ts +++ b/src/examples/zkapps/simple-zkapp-with-proof.ts @@ -7,17 +7,14 @@ import { SmartContract, Mina, AccountUpdate, - isReady, ZkappPublicInput, SelfProof, verify, Empty, } from 'o1js'; -await isReady; - class TrivialZkapp extends SmartContract { - @method proveSomething(hasToBe1: Field) { + @method async proveSomething(hasToBe1: Field) { hasToBe1.assertEquals(1); } } @@ -26,12 +23,12 @@ class TrivialProof extends TrivialZkapp.Proof() {} class NotSoSimpleZkapp extends SmartContract { @state(Field) x = State(); - @method initialize(proof: TrivialProof) { + @method async initialize(proof: TrivialProof) { proof.verify(); this.x.set(Field(1)); } - @method update( + @method async update( y: Field, oldProof: SelfProof, trivialProof: TrivialProof @@ -68,7 +65,7 @@ let { verificationKey: trivialVerificationKey } = await TrivialZkapp.compile(); console.log('prove (trivial zkapp)'); let [trivialProof] = await ( await Mina.transaction(feePayer, async () => { - new TrivialZkapp(zkappAddress2).proveSomething(Field(1)); + await new TrivialZkapp(zkappAddress2).proveSomething(Field(1)); }) ).prove(); @@ -86,14 +83,14 @@ let zkapp = new NotSoSimpleZkapp(zkappAddress); console.log('deploy'); let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy({ zkappKey }); + await zkapp.deploy({ zkappKey }); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('initialize'); -tx = await Mina.transaction(feePayerKey, async () => { - zkapp.initialize(trivialProof!); +tx = await Mina.transaction(feePayer, async () => { + await zkapp.initialize(trivialProof!); }); let [proof] = await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -108,7 +105,7 @@ console.log('initial state: ' + zkapp.x.get()); console.log('update'); tx = await Mina.transaction(feePayer, async () => { - zkapp.update(Field(3), proof!, trivialProof!); + await zkapp.update(Field(3), proof!, trivialProof!); }); [proof] = await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -123,7 +120,7 @@ console.log('state 2: ' + zkapp.x.get()); console.log('update'); tx = await Mina.transaction(feePayer, async () => { - zkapp.update(Field(3), proof!, trivialProof!); + await zkapp.update(Field(3), proof!, trivialProof!); }); [proof] = await tx.prove(); await tx.sign([feePayerKey]).send(); diff --git a/src/examples/zkapps/token-with-proofs.ts b/src/examples/zkapps/token-with-proofs.ts index ba82f6b082..37e0a3437f 100644 --- a/src/examples/zkapps/token-with-proofs.ts +++ b/src/examples/zkapps/token-with-proofs.ts @@ -12,29 +12,29 @@ import { class Token extends TokenContract { @method - approveBase(forest: AccountUpdateForest) { + async approveBase(forest: AccountUpdateForest) { this.checkZeroBalanceChange(forest); } - @method mint(receiverAddress: PublicKey) { + @method async mint(receiverAddress: PublicKey) { let amount = 1_000_000; this.internal.mint({ address: receiverAddress, amount }); } - @method burn(receiverAddress: PublicKey) { + @method async burn(receiverAddress: PublicKey) { let amount = 1_000; this.internal.burn({ address: receiverAddress, amount }); } } class ZkAppB extends SmartContract { - @method approveSend() { + @method async approveSend() { this.balance.subInPlace(1_000); } } class ZkAppC extends SmartContract { - @method approveSend() { + @method async approveSend() { this.balance.subInPlace(1_000); } } @@ -80,7 +80,7 @@ await ZkAppC.compile(); console.log('deploy tokenZkApp'); tx = await Mina.transaction(sender, async () => { - tokenZkApp.deploy(); + await tokenZkApp.deploy(); AccountUpdate.fundNewAccount(sender).send({ to: tokenZkApp.self, amount: initialBalance, @@ -91,9 +91,9 @@ await tx.sign([senderKey, tokenZkAppKey]).send(); console.log('deploy zkAppB and zkAppC'); tx = await Mina.transaction(sender, async () => { AccountUpdate.fundNewAccount(sender, 2); - zkAppC.deploy(); - zkAppB.deploy(); - tokenZkApp.approveAccountUpdates([zkAppC.self, zkAppB.self]); + await zkAppC.deploy(); + await zkAppB.deploy(); + await tokenZkApp.approveAccountUpdates([zkAppC.self, zkAppB.self]); }); console.log('deploy zkAppB and zkAppC (proof)'); await tx.prove(); @@ -101,17 +101,17 @@ await tx.sign([senderKey, zkAppBKey, zkAppCKey]).send(); console.log('mint token to zkAppB'); tx = await Mina.transaction(sender, async () => { - tokenZkApp.mint(zkAppBAddress); + await tokenZkApp.mint(zkAppBAddress); }); await tx.prove(); await tx.sign([senderKey]).send(); console.log('approve send from zkAppB'); tx = await Mina.transaction(sender, async () => { - zkAppB.approveSend(); + await zkAppB.approveSend(); // we call the token contract with the self update - tokenZkApp.transfer(zkAppB.self, zkAppCAddress, 1_000); + await tokenZkApp.transfer(zkAppB.self, zkAppCAddress, 1_000); }); console.log('approve send (proof)'); await tx.prove(); @@ -126,10 +126,10 @@ console.log('approve send from zkAppC'); tx = await Mina.transaction(sender, async () => { // Pay for tokenAccount1's account creation AccountUpdate.fundNewAccount(sender); - zkAppC.approveSend(); + await zkAppC.approveSend(); // we call the token contract with the tree - tokenZkApp.transfer(zkAppC.self, tokenAccount1, 1_000); + await tokenZkApp.transfer(zkAppC.self, tokenAccount1, 1_000); }); console.log('approve send (proof)'); await tx.prove(); diff --git a/src/examples/zkapps/zkapp-self-update.ts b/src/examples/zkapps/zkapp-self-update.ts index 0ee45ce3f5..e7d03c347a 100644 --- a/src/examples/zkapps/zkapp-self-update.ts +++ b/src/examples/zkapps/zkapp-self-update.ts @@ -6,14 +6,11 @@ import { VerificationKey, method, Permissions, - isReady, PrivateKey, Mina, AccountUpdate, - Circuit, Provable, TransactionVersion, - UInt32, } from 'o1js'; class Foo extends SmartContract { @@ -28,21 +25,19 @@ class Foo extends SmartContract { }); } - @method replaceVerificationKey(verificationKey: VerificationKey) { + @method async replaceVerificationKey(verificationKey: VerificationKey) { this.account.verificationKey.set(verificationKey); } } class Bar extends SmartContract { - @method call() { + @method async call() { Provable.log('Bar'); } } // setup -await isReady; - const Local = Mina.LocalBlockchain({ proofsEnabled: true }); Mina.setActiveInstance(Local); @@ -59,7 +54,7 @@ await Foo.compile(); const tx = await Mina.transaction(deployerAccount, async () => { AccountUpdate.fundNewAccount(deployerAccount); - zkApp.deploy(); + await zkApp.deploy(); }); await tx.prove(); await tx.sign([deployerKey, zkAppPrivateKey]).send(); @@ -72,7 +67,7 @@ Provable.log('original verification key', fooVerificationKey); const { verificationKey: barVerificationKey } = await Bar.compile(); const tx2 = await Mina.transaction(deployerAccount, async () => { - zkApp.replaceVerificationKey(barVerificationKey); + await zkApp.replaceVerificationKey(barVerificationKey); }); await tx2.prove(); await tx2.sign([deployerKey]).send(); From e9f5d7747cbea8eec48775d7f2539cab2e157895 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 16:23:16 +0100 Subject: [PATCH 06/16] remove unused plain js integration tests --- run-mina-integration-tests.sh | 5 - tests/integration/inductive-proofs.js | 151 ----------- tests/integration/package-lock.json | 53 ---- tests/integration/package.json | 6 - tests/integration/simple-zkapp-mock-apply.js | 132 ---------- tests/integration/simple-zkapp.js | 259 ------------------- tests/integration/tictoc.js | 17 -- 7 files changed, 623 deletions(-) delete mode 100755 run-mina-integration-tests.sh delete mode 100644 tests/integration/inductive-proofs.js delete mode 100644 tests/integration/package-lock.json delete mode 100644 tests/integration/package.json delete mode 100644 tests/integration/simple-zkapp-mock-apply.js delete mode 100644 tests/integration/simple-zkapp.js delete mode 100644 tests/integration/tictoc.js diff --git a/run-mina-integration-tests.sh b/run-mina-integration-tests.sh deleted file mode 100755 index 2ed8854fd7..0000000000 --- a/run-mina-integration-tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -set -e - -node tests/integration/simple-zkapp-mock-apply.js -node tests/integration/inductive-proofs.js diff --git a/tests/integration/inductive-proofs.js b/tests/integration/inductive-proofs.js deleted file mode 100644 index d8ff85cfd0..0000000000 --- a/tests/integration/inductive-proofs.js +++ /dev/null @@ -1,151 +0,0 @@ -import { - SelfProof, - Field, - ZkProgram, - isReady, - shutdown, -} from '../../dist/node/index.js'; -import { tic, toc } from './tictoc.js'; - -await isReady; - -let MaxProofsVerifiedZero = ZkProgram({ - name: 'no-recursion', - publicInput: Field, - - methods: { - baseCase: { - privateInputs: [], - - method(publicInput) { - publicInput.assertEquals(Field(0)); - }, - }, - }, -}); - -let MaxProofsVerifiedOne = ZkProgram({ - name: 'recursive-1', - publicInput: Field, - - methods: { - baseCase: { - privateInputs: [], - - method(publicInput) { - publicInput.assertEquals(Field(0)); - }, - }, - - mergeOne: { - privateInputs: [SelfProof], - - method(publicInput, earlierProof) { - earlierProof.verify(); - earlierProof.publicInput.add(1).assertEquals(publicInput); - }, - }, - }, -}); - -let MaxProofsVerifiedTwo = ZkProgram({ - name: 'recursive-2', - publicInput: Field, - - methods: { - baseCase: { - privateInputs: [], - - method(publicInput) { - publicInput.assertEquals(Field(0)); - }, - }, - - mergeOne: { - privateInputs: [SelfProof], - - method(publicInput, earlierProof) { - earlierProof.verify(); - earlierProof.publicInput.add(1).assertEquals(publicInput); - }, - }, - - mergeTwo: { - privateInputs: [SelfProof, SelfProof], - - method(publicInput, p1, p2) { - p1.verify(); - p1.publicInput.add(1).assertEquals(p2.publicInput); - p2.verify(); - p2.publicInput.add(1).assertEquals(publicInput); - }, - }, - }, -}); -tic('compiling three programs..'); -await MaxProofsVerifiedZero.compile(); -await MaxProofsVerifiedOne.compile(); -await MaxProofsVerifiedTwo.compile(); -toc(); - -await testRecursion(MaxProofsVerifiedZero, 0); -await testRecursion(MaxProofsVerifiedOne, 1); -await testRecursion(MaxProofsVerifiedTwo, 2); - -async function testRecursion(Program, maxProofsVerified) { - console.log(`testing maxProofsVerified = ${maxProofsVerified}`); - - let ProofClass = ZkProgram.Proof(Program); - - tic('executing base case..'); - let initialProof = await Program.baseCase(Field(0)); - toc(); - initialProof = testJsonRoundtrip(ProofClass, initialProof); - initialProof.verify(); - initialProof.publicInput.assertEquals(Field(0)); - - if (initialProof.maxProofsVerified != maxProofsVerified) { - throw Error( - `Expected initialProof to have maxProofsVerified = ${maxProofsVerified} but has ${initialProof.maxProofsVerified}` - ); - } - - let p1, p2; - if (initialProof.maxProofsVerified == 0) return; - - tic('executing mergeOne..'); - p1 = await Program.mergeOne(Field(1), initialProof); - toc(); - p1 = testJsonRoundtrip(ProofClass, p1); - p1.verify(); - p1.publicInput.assertEquals(Field(1)); - if (p1.maxProofsVerified != maxProofsVerified) { - throw Error( - `Expected p1 to have maxProofsVerified = ${maxProofsVerified} but has ${p1.maxProofsVerified}` - ); - } - - if (initialProof.maxProofsVerified == 1) return; - tic('executing mergeTwo..'); - p2 = await Program.mergeTwo(Field(2), initialProof, p1); - toc(); - p2 = testJsonRoundtrip(ProofClass, p2); - p2.verify(); - p2.publicInput.assertEquals(Field(2)); - if (p2.maxProofsVerified != maxProofsVerified) { - throw Error( - `Expected p2 to have maxProofsVerified = ${maxProofsVerified} but has ${p2.maxProofsVerified}` - ); - } -} - -function testJsonRoundtrip(ProofClass, proof) { - let jsonProof = proof.toJSON(); - console.log( - 'json roundtrip', - JSON.stringify({ ...jsonProof, proof: jsonProof.proof.slice(0, 10) + '..' }) - ); - return ProofClass.fromJSON(jsonProof); -} - -shutdown(); diff --git a/tests/integration/package-lock.json b/tests/integration/package-lock.json deleted file mode 100644 index c5617cfc71..0000000000 --- a/tests/integration/package-lock.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "o1js-integration-tests", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "o1js-integration-tests", - "version": "0.0.0" - }, - "../..": { - "version": "0.9.5", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "blakejs": "1.2.1", - "detect-gpu": "^5.0.5", - "env": "^0.0.2", - "isomorphic-fetch": "^3.0.0", - "js-sha256": "^0.9.0", - "reflect-metadata": "^0.1.13", - "tslib": "^2.3.0" - }, - "bin": { - "snarky-run": "src/build/run.js" - }, - "devDependencies": { - "@playwright/test": "^1.25.2", - "@types/isomorphic-fetch": "^0.0.36", - "@types/jest": "^27.0.0", - "@types/node": "^18.14.2", - "@typescript-eslint/eslint-plugin": "^5.0.0", - "esbuild": "^0.16.16", - "eslint": "^8.0.0", - "expect": "^29.0.1", - "fs-extra": "^10.0.0", - "glob": "^8.0.3", - "howslow": "^0.1.0", - "jest": "^28.1.3", - "minimist": "^1.2.7", - "prettier": "^2.8.4", - "replace-in-file": "^6.3.5", - "rimraf": "^3.0.2", - "ts-jest": "^28.0.8", - "typedoc": "^0.23.26", - "typescript": "^4.9.5" - }, - "engines": { - "node": ">=16.4.0" - } - } - } -} diff --git a/tests/integration/package.json b/tests/integration/package.json deleted file mode 100644 index afcb4997e2..0000000000 --- a/tests/integration/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "o1js-integration-tests", - "version": "0.0.0", - "type": "module", - "dependencies": {} -} diff --git a/tests/integration/simple-zkapp-mock-apply.js b/tests/integration/simple-zkapp-mock-apply.js deleted file mode 100644 index fc45e436c6..0000000000 --- a/tests/integration/simple-zkapp-mock-apply.js +++ /dev/null @@ -1,132 +0,0 @@ -import { - Field, - declareState, - declareMethods, - State, - PrivateKey, - SmartContract, - isReady, - shutdown, - Mina, - Permissions, - verify, - AccountUpdate, -} from '../../dist/node/index.js'; -import { tic, toc } from './tictoc.js'; - -await isReady; - -// declare the zkapp -const initialState = Field(1); -class SimpleZkapp extends SmartContract { - constructor(address) { - super(address); - this.x = State(); - } - - deploy(args) { - super.deploy(args); - this.account.permissions.set({ - ...Permissions.default(), - editState: Permissions.proofOrSignature(), - }); - } - - init() { - super.init(); - this.x.set(initialState); - } - - update(y) { - let x = this.x.get(); - this.x.assertEquals(x); - y.assertGreaterThan(0); - this.x.set(x.add(y)); - } -} -// note: this is our non-typescript way of doing what our decorators do -declareState(SimpleZkapp, { x: Field }); -declareMethods(SimpleZkapp, { init: [], update: [Field] }); - -// setup mock mina -let Local = Mina.LocalBlockchain(); -Mina.setActiveInstance(Local); -let { publicKey: sender, privateKey: senderKey } = Local.testAccounts[0]; - -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); -let zkapp = new SimpleZkapp(zkappAddress); - -tic('compute circuit digest'); -SimpleZkapp.digest(); -toc(); - -tic('compile smart contract'); -let { verificationKey } = await SimpleZkapp.compile(); -toc(); - -tic('create deploy transaction (with proof)'); -let deployTx = await Mina.transaction(sender, async () => { - AccountUpdate.fundNewAccount(sender); - zkapp.deploy(); -}); -let [, , proof] = await deployTx.prove(); -deployTx.sign([zkappKey, senderKey]); -toc(); - -tic('verify transaction proof'); -let ok = await verify(proof, verificationKey.data); -toc(); -console.log('did proof verify?', ok); -if (!ok) throw Error("proof didn't verify"); - -tic('apply deploy transaction'); -await deployTx.send(); -toc(); - -// check that deploy and initialize txns were applied -let zkappState = zkapp.x.get(); -zkappState.assertEquals(1); -console.log('got initial state: ' + zkappState); - -tic('create update transaction (no proof)'); -let tx = await Mina.transaction(sender, async () => { - zkapp.update(Field(2)); - zkapp.requireSignature(); -}); -tx.sign([senderKey, zkappKey]); -toc(); - -tic('apply update transaction (no proof)'); -await tx.send(); -toc(); - -// check that first update txn was applied -zkappState = zkapp.x.get(); -zkappState.assertEquals(3); -console.log('got updated state: ' + zkappState); - -tic('create update transaction (with proof)'); -tx = await Mina.transaction(sender, async () => { - zkapp.update(Field(2)); -}); -[proof] = await tx.prove(); -tx.sign([senderKey]); -toc(); - -tic('verify transaction proof'); -ok = await verify(proof, verificationKey.data); -toc(); -console.log('did proof verify?', ok); -if (!ok) throw Error("proof didn't verify"); - -tic('apply update transaction (with proof)'); -await tx.send(); -toc(); - -// check that second update txn was applied -zkappState = zkapp.x.get(); -zkappState.assertEquals(5); -console.log('got updated state: ' + zkappState); - -shutdown(); diff --git a/tests/integration/simple-zkapp.js b/tests/integration/simple-zkapp.js deleted file mode 100644 index 75f46601d6..0000000000 --- a/tests/integration/simple-zkapp.js +++ /dev/null @@ -1,259 +0,0 @@ -import { - Field, - declareState, - declareMethods, - State, - PrivateKey, - SmartContract, - isReady, - Mina, - PublicKey, - UInt64, - AccountUpdate, - Bool, - shutdown, - Permissions, - fetchAccount, -} from 'o1js'; - -await isReady; - -class NotSoSimpleZkapp extends SmartContract { - events = { update: Field, payout: UInt64, payoutReceiver: PublicKey }; - - constructor(address) { - super(address); - this.x = State(); - } - - init() { - super.init(); - this.x.set(initialState); - this.account.permissions.set({ - ...Permissions.default(), - send: Permissions.proof(), - editState: Permissions.proof(), - }); - } - - update(y) { - let x = this.x.get(); - this.x.assertEquals(x); - y.assertGreaterThan(0); - this.x.set(x.add(y)); - } - - payout(caller) { - let callerAddress = caller.toPublicKey(); - callerAddress.assertEquals(privilegedAddress); - - let callerAccountUpdate = AccountUpdate.create(callerAddress); - callerAccountUpdate.account.isNew.assertEquals(Bool(true)); - - let balance = this.account.balance.get(); - this.account.balance.assertEquals(balance); - let halfBalance = balance.div(2); - this.send({ to: callerAccountUpdate, amount: halfBalance }); - - // emit some events - this.emitEvent('payoutReceiver', callerAddress); - this.emitEvent('payout', halfBalance); - } - - deposit(amount) { - let senderUpdate = AccountUpdate.createSigned(this.sender); - senderUpdate.send({ to: this, amount }); - } -} -// note: this is our non-typescript way of doing what our decorators do -declareState(NotSoSimpleZkapp, { x: Field }); -declareMethods(NotSoSimpleZkapp, { - update: [Field], - payout: [PrivateKey], - deposit: [UInt64], -}); - -// slightly adjusted polling parameters for tx.wait() -const waitParams = { - maxAttempts: 30, - interval: 45000, -}; - -// parse command line; for local testing, use random keys as fallback -let [feePayerKeyBase58, graphql_uri] = process.argv.slice(2); - -let isLocal = false; - -if (feePayerKeyBase58 === 'local') { - isLocal = true; - let LocalNetwork = Mina.LocalBlockchain(graphql_uri); - Mina.setActiveInstance(LocalNetwork); - let { privateKey } = LocalNetwork.testAccounts[0]; - feePayerKeyBase58 = privateKey.toBase58(); -} else { - if (!graphql_uri) throw Error('Graphql uri is undefined, aborting'); - if (!feePayerKeyBase58) throw Error('Fee payer key is undefined, aborting'); - let LocalNetwork = Mina.Network(graphql_uri); - Mina.setActiveInstance(LocalNetwork); -} - -let zkappKey = PrivateKey.random(); -let zkappAddress = zkappKey.toPublicKey(); - -let feePayerKey = PrivateKey.fromBase58(feePayerKeyBase58); -let feePayerAddress = feePayerKey.toPublicKey(); - -if (!isLocal) { - let res = await fetchAccount({ - publicKey: feePayerAddress, - }); - if (res.error) { - throw Error( - `The fee payer account needs to be funded in order for the script to succeed! Please provide the private key of an already funded account. ${feePayerAddress.toBase58()}, ${feePayerKeyBase58}\n\n${ - res.error.message - }` - ); - } -} - -// a special account that is allowed to pull out half of the zkapp balance, once -let privilegedKey = PrivateKey.random(); -let privilegedAddress = privilegedKey.toPublicKey(); - -let zkappTargetBalance = 10_000_000_000; -let initialBalance = zkappTargetBalance; -let initialState = Field(1); - -console.log( - `simple-zkapp.js: Running with zkapp address ${zkappKey - .toPublicKey() - .toBase58()}, fee payer address ${feePayerAddress.toBase58()} and graphql uri ${graphql_uri}\n\n` -); - -console.log(`simple-zkapp.js: Starting integration test\n`); - -let zkapp = new NotSoSimpleZkapp(zkappAddress); -await NotSoSimpleZkapp.compile(); - -console.log('deploying contract\n'); -let tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - async () => { - AccountUpdate.fundNewAccount(feePayerAddress); - - zkapp.deploy(); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey, zkappKey]).send()).wait(waitParams); - -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -let zkappAccount = Mina.getAccount(zkappAddress); - -// we deployed the contract with an initial state of 1 -expectAssertEquals(zkappAccount.zkapp.appState[0], Field(1)); - -// the fresh zkapp account shouldn't have any funds -expectAssertEquals(zkappAccount.balance, UInt64.from(0)); - -console.log('deposit funds\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - async () => { - zkapp.deposit(UInt64.from(initialBalance)); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(waitParams); - -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -zkappAccount = Mina.getAccount(zkappAddress); - -// we deposit 10_000_000_000 funds into the zkapp account -expectAssertEquals(zkappAccount.balance, UInt64.from(initialBalance)); - -console.log('update 1\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - async () => { - zkapp.update(Field(30)); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(waitParams); - -console.log('update 2\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - async () => { - zkapp.update(Field(100)); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(waitParams); - -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -zkappAccount = Mina.getAccount(zkappAddress); - -// no balance change expected -expectAssertEquals(zkappAccount.balance, UInt64.from(initialBalance)); - -// we updated the zkapp state to 131 -expectAssertEquals(zkappAccount.zkapp.appState[0], Field(131)); - -console.log('payout 1\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - async () => { - AccountUpdate.fundNewAccount(feePayerAddress); - zkapp.payout(privilegedKey); - } -); -await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(waitParams); - -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -zkappAccount = Mina.getAccount(zkappAddress); - -// we withdraw (payout) half of the initial balance -expectAssertEquals(zkappAccount.balance, UInt64.from(initialBalance / 2)); - -console.log('payout 2 (expected to fail)\n'); -tx = await Mina.transaction( - { sender: feePayerAddress, fee: 100_000_000 }, - async () => { - zkapp.payout(privilegedKey); - } -); - -await tx.prove(); - -// this tx should fail -try { - let txId = await tx.sign([feePayerKey]).send(); - await txId.wait(waitParams); -} catch (err) { - // throw if this is not the expected error - if (!err.message.includes('Account_is_new_precondition_unsatisfied')) { - throw err; - } -} - -// although we just checked above that the tx failed, I just would like to double-check that anyway (cross checking logic) -if (!isLocal) await fetchAccount({ publicKey: zkappAddress }); -zkappAccount = Mina.getAccount(zkappAddress); - -// checking that state hasn't changed - we expect the tx to fail so the state should equal previous state -expectAssertEquals(zkappAccount.balance, UInt64.from(initialBalance / 2)); - -function expectAssertEquals(actual, expected) { - try { - actual.assertEquals(expected); - } catch (error) { - throw Error( - `Expected value ${expected.toString()}, but got ${actual.toString()}` - ); - } -} - -shutdown(); diff --git a/tests/integration/tictoc.js b/tests/integration/tictoc.js deleted file mode 100644 index cee8f0e64b..0000000000 --- a/tests/integration/tictoc.js +++ /dev/null @@ -1,17 +0,0 @@ -// helper for printing timings - -export { tic, toc }; - -let timingStack = []; -let i = 0; - -function tic(label = `Run command ${i++}`) { - process.stdout.write(`${label}... `); - timingStack.push([label, Date.now()]); -} - -function toc() { - let [label, start] = timingStack.pop(); - let time = (Date.now() - start) / 1000; - process.stdout.write(`\r${label}... ${time.toFixed(3)} sec\n`); -} From a3ce01dc4cf19bb084f5ba6f0a1ea4e65ceb912b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 16:26:30 +0100 Subject: [PATCH 07/16] delete redundant example --- src/examples/zkapps/reducer/reducer.ts | 160 ------------------------- 1 file changed, 160 deletions(-) delete mode 100644 src/examples/zkapps/reducer/reducer.ts diff --git a/src/examples/zkapps/reducer/reducer.ts b/src/examples/zkapps/reducer/reducer.ts deleted file mode 100644 index 6d23123ada..0000000000 --- a/src/examples/zkapps/reducer/reducer.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { - Field, - state, - State, - method, - PrivateKey, - SmartContract, - Mina, - AccountUpdate, - isReady, - Permissions, - Reducer, -} from 'o1js'; - -await isReady; - -const INCREMENT = Field(1); - -class CounterZkapp extends SmartContract { - // the "reducer" field describes a type of action that we can dispatch, and reduce later - reducer = 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(); - // helper field to store the point in the action history that our on-chain state is at - @state(Field) actionState = State(); - - @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.requireEquals(counter); - let actionState = this.actionState.get(); - this.actionState.requireEquals(actionState); - - // compute the new counter and hash from pending actions - let pendingActions = this.reducer.getActions({ - fromActionState: actionState, - }); - - let { state: newCounter, actionState: newActionState } = - this.reducer.reduce( - pendingActions, - // state type - Field, - // function that says how to apply an action - (state: Field, _action: Field) => { - return state.add(1); - }, - { state: counter, actionState } - ); - - // update on-chain state - this.counter.set(newCounter); - this.actionState.set(newActionState); - } -} - -const doProofs = true; -const initialCounter = Field(0); - -let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); -Mina.setActiveInstance(Local); - -// a test account that pays all the fees, and puts additional funds into the zkapp -let feePayerKey = Local.testAccounts[0].privateKey; -let feePayer = Local.testAccounts[0].publicKey; - -// the zkapp account -let zkappKey = PrivateKey.fromBase58( - 'EKEQc95PPQZnMY9d9p1vq1MWLeDJKtvKj4V75UDG3rjnf32BerWD' -); -let zkappAddress = zkappKey.toPublicKey(); -let zkapp = new CounterZkapp(zkappAddress); -if (doProofs) { - console.log('compile'); - await CounterZkapp.compile(); -} else { - // TODO: if we don't do this, then `analyzeMethods()` will be called during `runUnchecked()` in the tx callback below, - // which currently fails due to `finalize_is_running` in snarky not resetting internal state, and instead setting is_running unconditionally to false, - // so we can't nest different snarky circuit runners - await CounterZkapp.analyzeMethods(); -} - -console.log('deploy'); -let tx = await Mina.transaction(feePayer, async () => { - AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); - zkapp.counter.set(initialCounter); - zkapp.actionState.set(Reducer.initialActionState); -}); -await tx.sign([feePayerKey, zkappKey]).send(); - -console.log('applying actions..'); - -console.log('action 1'); - -tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('action 2'); -tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('action 3'); -tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('rolling up pending actions..'); - -console.log('state before: ' + zkapp.counter.get()); - -tx = await Mina.transaction(feePayer, async () => { - zkapp.rollupIncrements(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('state after rollup: ' + zkapp.counter.get()); - -console.log('applying more actions'); - -console.log('action 4'); -tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('action 5'); -tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('rolling up pending actions..'); - -console.log('state before: ' + zkapp.counter.get()); - -tx = await Mina.transaction(feePayer, async () => { - zkapp.rollupIncrements(); -}); -await tx.prove(); -await tx.sign([feePayerKey]).send(); - -console.log('state after rollup: ' + zkapp.counter.get()); From b5881ef22ca829a4723acb1117a439cee0a96170 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 16:26:41 +0100 Subject: [PATCH 08/16] delete pointless test --- src/lib/caller.unit-test.ts | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 src/lib/caller.unit-test.ts diff --git a/src/lib/caller.unit-test.ts b/src/lib/caller.unit-test.ts deleted file mode 100644 index 173ebffc07..0000000000 --- a/src/lib/caller.unit-test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { AccountUpdate, TokenId } from './account-update.js'; -import * as Mina from './mina.js'; -import { expect } from 'expect'; - -let Local = Mina.LocalBlockchain(); -Mina.setActiveInstance(Local); - -let [{ privateKey, publicKey }] = Local.testAccounts; - -let parentId = TokenId.derive(publicKey); - -/** - * tests whether the following two account updates gives the child token permissions: - * - * InheritFromParent -> ParentsOwnToken - */ -let tx = await Mina.transaction(privateKey, async () => { - let parent = AccountUpdate.defaultAccountUpdate(publicKey); - parent.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; - parent.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); - - let child = AccountUpdate.defaultAccountUpdate(publicKey, parentId); - child.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken; - - AccountUpdate.attachToTransaction(parent); - parent.approve(child); -}); - -// according to this test, the child doesn't get token permissions -await expect(tx.sendOrThrowIfError()).rejects.toThrow( - 'can not use or pass on token permissions' -); From df6fca1781a09633aac05ebd68d7ea90ca78155f Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 16:27:24 +0100 Subject: [PATCH 09/16] fix examples build --- src/examples/crypto/foreign-field.ts | 2 +- .../internals/advanced-provable-types.ts | 12 ++--- src/examples/zkapps/escrow/escrow.ts | 12 ++--- src/examples/zkapps/hashing/hash.ts | 8 ++-- src/examples/zkapps/hashing/run.ts | 10 ++-- .../zkapps/hello-world/hello-world.ts | 2 +- src/examples/zkapps/hello-world/run-live.ts | 4 +- src/examples/zkapps/hello-world/run.ts | 16 +++---- .../zkapps/merkle-tree/merkle-zkapp.ts | 8 ++-- .../zkapps/reducer/actions-as-merkle-list.ts | 18 +++---- src/examples/zkapps/reducer/map.ts | 23 +++++---- .../zkapps/reducer/reducer-composite.ts | 22 ++++----- src/examples/zkapps/sudoku/index.ts | 13 ++--- src/examples/zkapps/sudoku/sudoku.ts | 14 +++--- src/examples/zkapps/voting/demo.ts | 10 +--- .../zkapps/voting/deploy-contracts.ts | 16 +++---- src/examples/zkapps/voting/dummy-contract.ts | 6 +-- src/examples/zkapps/voting/member.ts | 1 - src/examples/zkapps/voting/membership.ts | 12 +++-- src/examples/zkapps/voting/run-berkeley.ts | 31 +++++------- src/examples/zkapps/voting/test.ts | 48 +++++++++---------- src/examples/zkapps/voting/voting-lib.ts | 2 +- src/examples/zkapps/voting/voting.ts | 26 +++++----- src/lib/account-update.ts | 4 +- src/lib/mina/transaction.ts | 18 +++---- src/tests/fake-proof.ts | 6 +-- src/tests/transaction-flow.ts | 40 +++++++--------- .../on-chain-state-mgmt-zkapp-ui.js | 4 +- 28 files changed, 179 insertions(+), 209 deletions(-) diff --git a/src/examples/crypto/foreign-field.ts b/src/examples/crypto/foreign-field.ts index b71af14c6e..044d8ebda6 100644 --- a/src/examples/crypto/foreign-field.ts +++ b/src/examples/crypto/foreign-field.ts @@ -90,7 +90,7 @@ class AlmostSmallField extends SmallField.AlmostReduced {} class MyContract extends SmartContract { @state(AlmostSmallField.provable) x = State(); - @method myMethod(y: AlmostSmallField) { + @method async myMethod(y: AlmostSmallField) { let x = y.mul(2); Provable.log(x); this.x.set(x.assertAlmostReduced()); diff --git a/src/examples/internals/advanced-provable-types.ts b/src/examples/internals/advanced-provable-types.ts index 6f4334a207..bae47f1115 100644 --- a/src/examples/internals/advanced-provable-types.ts +++ b/src/examples/internals/advanced-provable-types.ts @@ -140,10 +140,10 @@ console.log( * * This is why we have this custom way of witnessing account updates, with the `skipCheck` option. */ -result = await Provable.constraintSystem(() => { - let { accountUpdate: accountUpdateWitness } = AccountUpdate.witness( +result = await Provable.constraintSystem(async () => { + let { accountUpdate: accountUpdateWitness } = await AccountUpdate.witness( Empty, - () => ({ accountUpdate, result: undefined }), + async () => ({ accountUpdate, result: undefined }), { skipCheck: true } ); Provable.assertEqual(AccountUpdate, accountUpdateWitness, accountUpdate); @@ -156,10 +156,10 @@ console.log( * To relate an account update to the hash which is the public input, we need to perform the hash in-circuit. * This is takes several 100 constraints, and is basically the minimal size of a zkApp method. */ -result = await Provable.constraintSystem(() => { - let { accountUpdate: accountUpdateWitness } = AccountUpdate.witness( +result = await Provable.constraintSystem(async () => { + let { accountUpdate: accountUpdateWitness } = await AccountUpdate.witness( Empty, - () => ({ accountUpdate, result: undefined }), + async () => ({ accountUpdate, result: undefined }), { skipCheck: true } ); accountUpdateWitness.hash(); diff --git a/src/examples/zkapps/escrow/escrow.ts b/src/examples/zkapps/escrow/escrow.ts index 1917dca854..d7e1ea378b 100644 --- a/src/examples/zkapps/escrow/escrow.ts +++ b/src/examples/zkapps/escrow/escrow.ts @@ -1,13 +1,7 @@ -import { - SmartContract, - method, - UInt64, - AccountUpdate, - PublicKey, -} from 'o1js'; +import { SmartContract, method, UInt64, AccountUpdate, PublicKey } from 'o1js'; export class Escrow extends SmartContract { - @method deposit(user: PublicKey) { + @method async deposit(user: PublicKey) { // add your deposit logic circuit here // that will adjust the amount @@ -15,7 +9,7 @@ export class Escrow extends SmartContract { payerUpdate.send({ to: this.address, amount: UInt64.from(1000000) }); } - @method withdraw(user: PublicKey) { + @method async withdraw(user: PublicKey) { // add your withdrawal logic circuit here // that will adjust the amount diff --git a/src/examples/zkapps/hashing/hash.ts b/src/examples/zkapps/hashing/hash.ts index e762863c85..3dbb62cef7 100644 --- a/src/examples/zkapps/hashing/hash.ts +++ b/src/examples/zkapps/hashing/hash.ts @@ -24,25 +24,25 @@ export class HashStorage extends SmartContract { this.commitment.set(initialCommitment); } - @method SHA256(xs: Bytes32) { + @method async SHA256(xs: Bytes32) { const shaHash = Hash.SHA3_256.hash(xs); const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } - @method SHA384(xs: Bytes32) { + @method async SHA384(xs: Bytes32) { const shaHash = Hash.SHA3_384.hash(xs); const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } - @method SHA512(xs: Bytes32) { + @method async SHA512(xs: Bytes32) { const shaHash = Hash.SHA3_512.hash(xs); const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); } - @method Keccak256(xs: Bytes32) { + @method async Keccak256(xs: Bytes32) { const shaHash = Hash.Keccak256.hash(xs); const commitment = Hash.hash(shaHash.toFields()); this.commitment.set(commitment); diff --git a/src/examples/zkapps/hashing/run.ts b/src/examples/zkapps/hashing/run.ts index 78efd2b629..ac25f6868b 100644 --- a/src/examples/zkapps/hashing/run.ts +++ b/src/examples/zkapps/hashing/run.ts @@ -26,7 +26,7 @@ const hashData = Bytes.from(Array.from({ length: 32 }, (_, i) => i)); console.log('Deploying Hash Example....'); txn = await Mina.transaction(feePayer.publicKey, async () => { AccountUpdate.fundNewAccount(feePayer.publicKey); - zkAppInstance.deploy(); + await zkAppInstance.deploy(); }); await txn.sign([feePayer.privateKey, zkAppPrivateKey]).send(); @@ -38,7 +38,7 @@ console.log('Initial State', initialState); console.log(`Updating commitment from ${initialState} using SHA256 ...`); txn = await Mina.transaction(feePayer.publicKey, async () => { - zkAppInstance.SHA256(hashData); + await zkAppInstance.SHA256(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); @@ -47,7 +47,7 @@ console.log(`Current state successfully updated to ${currentState}`); console.log(`Updating commitment from ${initialState} using SHA384 ...`); txn = await Mina.transaction(feePayer.publicKey, async () => { - zkAppInstance.SHA384(hashData); + await zkAppInstance.SHA384(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); @@ -56,7 +56,7 @@ console.log(`Current state successfully updated to ${currentState}`); console.log(`Updating commitment from ${initialState} using SHA512 ...`); txn = await Mina.transaction(feePayer.publicKey, async () => { - zkAppInstance.SHA512(hashData); + await zkAppInstance.SHA512(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); @@ -65,7 +65,7 @@ console.log(`Current state successfully updated to ${currentState}`); console.log(`Updating commitment from ${initialState} using Keccak256...`); txn = await Mina.transaction(feePayer.publicKey, async () => { - zkAppInstance.Keccak256(hashData); + await zkAppInstance.Keccak256(hashData); }); await txn.prove(); await txn.sign([feePayer.privateKey]).send(); diff --git a/src/examples/zkapps/hello-world/hello-world.ts b/src/examples/zkapps/hello-world/hello-world.ts index 4ccd77bdd9..057526d952 100644 --- a/src/examples/zkapps/hello-world/hello-world.ts +++ b/src/examples/zkapps/hello-world/hello-world.ts @@ -12,7 +12,7 @@ export class HelloWorld extends SmartContract { this.account.delegate.set(adminPublicKey); } - @method update(squared: Field, admin: PrivateKey) { + @method async update(squared: Field, admin: PrivateKey) { const x = this.x.get(); this.x.requireNothing(); x.square().assertEquals(squared); diff --git a/src/examples/zkapps/hello-world/run-live.ts b/src/examples/zkapps/hello-world/run-live.ts index 0bddcc8f48..d3a1b642ca 100644 --- a/src/examples/zkapps/hello-world/run-live.ts +++ b/src/examples/zkapps/hello-world/run-live.ts @@ -53,7 +53,7 @@ let transaction = await Mina.transaction( { sender, fee: transactionFee }, async () => { AccountUpdate.fundNewAccount(sender); - zkApp.deploy({ verificationKey }); + await zkApp.deploy({ verificationKey }); } ); transaction.sign([senderKey, zkAppKey]); @@ -74,7 +74,7 @@ console.log('Trying to update deployed zkApp state.'); transaction = await Mina.transaction( { sender, fee: transactionFee }, async () => { - zkApp.update(Field(4), adminPrivateKey); + await zkApp.update(Field(4), adminPrivateKey); } ); await transaction.sign([senderKey]).prove(); diff --git a/src/examples/zkapps/hello-world/run.ts b/src/examples/zkapps/hello-world/run.ts index 61903c5224..1a6aba45fe 100644 --- a/src/examples/zkapps/hello-world/run.ts +++ b/src/examples/zkapps/hello-world/run.ts @@ -25,7 +25,7 @@ console.log('Deploying Hello World ....'); txn = await Mina.transaction(feePayer1.publicKey, async () => { AccountUpdate.fundNewAccount(feePayer1.publicKey); - zkAppInstance.deploy(); + await zkAppInstance.deploy(); }); await txn.sign([feePayer1.privateKey, zkAppPrivateKey]).sendOrThrowIfError(); @@ -42,7 +42,7 @@ console.log( ); txn = await Mina.transaction(feePayer1.publicKey, async () => { - zkAppInstance.update(Field(4), adminPrivateKey); + await zkAppInstance.update(Field(4), adminPrivateKey); }); await txn.prove(); await txn.sign([feePayer1.privateKey]).sendOrThrowIfError(); @@ -67,7 +67,7 @@ let correctlyFails = false; try { txn = await Mina.transaction(feePayer1.publicKey, async () => { - zkAppInstance.update(Field(16), wrongAdminPrivateKey); + await zkAppInstance.update(Field(16), wrongAdminPrivateKey); }); await txn.prove(); await txn.sign([feePayer1.privateKey]).sendOrThrowIfError(); @@ -88,7 +88,7 @@ try { ); txn = await Mina.transaction(feePayer1.publicKey, async () => { - zkAppInstance.update(Field(30), adminPrivateKey); + await zkAppInstance.update(Field(30), adminPrivateKey); }); await txn.prove(); await txn.sign([feePayer1.privateKey]).sendOrThrowIfError(); @@ -114,7 +114,7 @@ try { txn = await Mina.transaction( { sender: feePayer1.publicKey, fee: '10' }, async () => { - zkAppInstance.update(Field(256), adminPrivateKey); + await zkAppInstance.update(Field(256), adminPrivateKey); } ); await txn.prove(); @@ -133,7 +133,7 @@ if (!correctlyFails) { txn2 = await Mina.transaction( { sender: feePayer2.publicKey, fee: '2' }, async () => { - zkAppInstance.update(Field(16), adminPrivateKey); + await zkAppInstance.update(Field(16), adminPrivateKey); } ); await txn2.prove(); @@ -153,7 +153,7 @@ console.log(`Update successful. Current state is ${currentState}.`); txn3 = await Mina.transaction( { sender: feePayer3.publicKey, fee: '1' }, async () => { - zkAppInstance.update(Field(256), adminPrivateKey); + await zkAppInstance.update(Field(256), adminPrivateKey); } ); await txn3.prove(); @@ -176,7 +176,7 @@ try { txn4 = await Mina.transaction( { sender: feePayer4.publicKey, fee: '1' }, async () => { - zkAppInstance.update(Field(16), adminPrivateKey); + await zkAppInstance.update(Field(16), adminPrivateKey); } ); await txn4.prove(); diff --git a/src/examples/zkapps/merkle-tree/merkle-zkapp.ts b/src/examples/zkapps/merkle-tree/merkle-zkapp.ts index 923fb3ac53..c1adc8e17a 100644 --- a/src/examples/zkapps/merkle-tree/merkle-zkapp.ts +++ b/src/examples/zkapps/merkle-tree/merkle-zkapp.ts @@ -59,13 +59,13 @@ class Leaderboard extends SmartContract { // a commitment is a cryptographic primitive that allows us to commit to data, with the ability to "reveal" it later @state(Field) commitment = State(); - @method init() { + @method async init() { super.init(); this.commitment.set(initialCommitment); } @method - guessPreimage(guess: Field, account: Account, path: MyMerkleWitness) { + async guessPreimage(guess: Field, account: Account, path: MyMerkleWitness) { // this is our hash! its the hash of the preimage "22", but keep it a secret! let target = Field( '17057234437185175411792943285768571642343179330449434169483610110583519635705' @@ -138,7 +138,7 @@ let tx = await Mina.transaction(feePayer, async () => { to: zkappAddress, amount: initialBalance, }); - leaderboardZkApp.deploy(); + await leaderboardZkApp.deploy(); }); await tx.prove(); await tx.sign([feePayerKey, zkappKey]).send(); @@ -156,7 +156,7 @@ async function makeGuess(name: Names, index: bigint, guess: number) { let witness = new MyMerkleWitness(w); let tx = await Mina.transaction(feePayer, async () => { - leaderboardZkApp.guessPreimage(Field(guess), account, witness); + await leaderboardZkApp.guessPreimage(Field(guess), account, witness); }); await tx.prove(); await tx.sign([feePayerKey, zkappKey]).send(); diff --git a/src/examples/zkapps/reducer/actions-as-merkle-list.ts b/src/examples/zkapps/reducer/actions-as-merkle-list.ts index 1cd52da348..16ce8ea29c 100644 --- a/src/examples/zkapps/reducer/actions-as-merkle-list.ts +++ b/src/examples/zkapps/reducer/actions-as-merkle-list.ts @@ -56,18 +56,18 @@ class ActionsContract extends SmartContract { reducer = Reducer({ actionType: Action }); @method - postAddress(address: PublicKey) { + async postAddress(address: PublicKey) { this.reducer.dispatch(address); } // to exhibit the generality of reducer: can dispatch more than 1 action per account update - @method postTwoAddresses(a1: PublicKey, a2: PublicKey) { + @method async postTwoAddresses(a1: PublicKey, a2: PublicKey) { this.reducer.dispatch(a1); this.reducer.dispatch(a2); } @method - assertContainsAddress(address: PublicKey) { + async assertContainsAddress(address: PublicKey) { // get actions and, in a witness block, wrap them in a Merkle list of lists // note: need to reverse here because `getActions()` returns the last pushed action last, @@ -127,11 +127,11 @@ await deployTx.sign([senderKey, zkappKey]).send(); // push some actions let dispatchTx = await Mina.transaction(sender, async () => { - zkapp.postAddress(otherAddress); - zkapp.postAddress(zkappAddress); - zkapp.postTwoAddresses(anotherAddress, sender); - zkapp.postAddress(anotherAddress); - zkapp.postTwoAddresses(zkappAddress, otherAddress); + await zkapp.postAddress(otherAddress); + await zkapp.postAddress(zkappAddress); + await zkapp.postTwoAddresses(anotherAddress, sender); + await zkapp.postAddress(anotherAddress); + await zkapp.postTwoAddresses(zkappAddress, otherAddress); }); await dispatchTx.prove(); await dispatchTx.sign([senderKey]).send(); @@ -141,7 +141,7 @@ assert(zkapp.reducer.getActions().length === 5); // check if the actions contain the `sender` address Local.setProofsEnabled(true); -let containsTx = await Mina.transaction(sender, async () => +let containsTx = await Mina.transaction(sender, () => zkapp.assertContainsAddress(sender) ); await containsTx.prove(); diff --git a/src/examples/zkapps/reducer/map.ts b/src/examples/zkapps/reducer/map.ts index bda597997b..1521b3a12e 100644 --- a/src/examples/zkapps/reducer/map.ts +++ b/src/examples/zkapps/reducer/map.ts @@ -12,6 +12,7 @@ import { Bool, Poseidon, Provable, + assert, } from 'o1js'; /* @@ -50,11 +51,12 @@ class StorageContract extends SmartContract { actionType: KeyValuePair, }); - @method set(key: PublicKey, value: Field) { + @method async set(key: PublicKey, value: Field) { this.reducer.dispatch({ key: Poseidon.hash(key.toFields()), value }); } - @method get(key: PublicKey): Option { + @method.returns(Option) + async get(key: PublicKey) { let pendingActions = this.reducer.getActions({ fromActionState: Reducer.initialActionState, }); @@ -105,7 +107,7 @@ await StorageContract.compile(); let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); }); await tx.sign([feePayerKey, zkappKey]).send(); @@ -131,7 +133,7 @@ let value = map[0].value; console.log(`setting key ${key.toBase58()} with value ${value}`); tx = await Mina.transaction(feePayer, async () => { - zkapp.set(key, value); + await zkapp.set(key, value); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -141,7 +143,7 @@ value = map[1].value; console.log(`setting key ${key.toBase58()} with value ${value}`); tx = await Mina.transaction(feePayer, async () => { - zkapp.set(key, value); + await zkapp.set(key, value); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -151,7 +153,7 @@ value = map[2].value; console.log(`setting key ${key.toBase58()} with value ${value}`); tx = await Mina.transaction(feePayer, async () => { - zkapp.set(key, value); + await zkapp.set(key, value); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -160,13 +162,14 @@ key = map[0].key; value = map[0].value; console.log(`getting key ${key.toBase58()} with value ${value}`); -let result: any; +let result: Option | undefined; tx = await Mina.transaction(feePayer, async () => { - result = zkapp.get(key); + result = await zkapp.get(key); }); await tx.prove(); await tx.sign([feePayerKey]).send(); +assert(result !== undefined); console.log('found correct match?', result.isSome.toBoolean()); console.log('matches expected value?', result.value.equals(value).toBoolean()); @@ -175,7 +178,7 @@ value = map[1].value; console.log(`getting key ${key.toBase58()} with value ${value}`); tx = await Mina.transaction(feePayer, async () => { - result = zkapp.get(key); + result = await zkapp.get(key); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -185,7 +188,7 @@ console.log('matches expected value?', result.value.equals(value).toBoolean()); console.log(`getting key invalid key`); tx = await Mina.transaction(feePayer, async () => { - result = zkapp.get(PrivateKey.random().toPublicKey()); + result = await zkapp.get(PrivateKey.random().toPublicKey()); }); await tx.prove(); await tx.sign([feePayerKey]).send(); diff --git a/src/examples/zkapps/reducer/reducer-composite.ts b/src/examples/zkapps/reducer/reducer-composite.ts index 3bdccd7e59..b299d20e6e 100644 --- a/src/examples/zkapps/reducer/reducer-composite.ts +++ b/src/examples/zkapps/reducer/reducer-composite.ts @@ -31,14 +31,14 @@ class CounterZkapp extends SmartContract { // helper field to store the point in the action history that our on-chain state is at @state(Field) actionState = State(); - @method incrementCounter() { + @method async incrementCounter() { this.reducer.dispatch(INCREMENT); } - @method dispatchData(data: Field) { + @method async dispatchData(data: Field) { this.reducer.dispatch({ isIncrement: Bool(false), otherData: data }); } - @method rollupIncrements() { + @method async rollupIncrements() { // get previous counter & actions hash, assert that they're the same as on-chain values let counter = this.counter.get(); this.counter.requireEquals(counter); @@ -94,7 +94,7 @@ if (doProofs) { console.log('deploy'); let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer); - zkapp.deploy(); + await zkapp.deploy(); zkapp.counter.set(initialCounter); zkapp.actionState.set(Reducer.initialActionState); }); @@ -105,21 +105,21 @@ console.log('applying actions..'); console.log('action 1'); tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); + await zkapp.incrementCounter(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('action 2'); tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); + await zkapp.incrementCounter(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('action 3'); tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); + await zkapp.incrementCounter(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -129,7 +129,7 @@ console.log('rolling up pending actions..'); console.log('state before: ' + zkapp.counter.get()); tx = await Mina.transaction(feePayer, async () => { - zkapp.rollupIncrements(); + await zkapp.rollupIncrements(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -141,14 +141,14 @@ console.log('applying more actions'); console.log('action 4 (no increment)'); tx = await Mina.transaction(feePayer, async () => { - zkapp.dispatchData(Field.random()); + await zkapp.dispatchData(Field.random()); }); await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('action 5'); tx = await Mina.transaction(feePayer, async () => { - zkapp.incrementCounter(); + await zkapp.incrementCounter(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); @@ -158,7 +158,7 @@ console.log('rolling up pending actions..'); console.log('state before: ' + zkapp.counter.get()); tx = await Mina.transaction(feePayer, async () => { - zkapp.rollupIncrements(); + await zkapp.rollupIncrements(); }); await tx.prove(); await tx.sign([feePayerKey]).send(); diff --git a/src/examples/zkapps/sudoku/index.ts b/src/examples/zkapps/sudoku/index.ts index 33b4bbfdd1..859762fa63 100644 --- a/src/examples/zkapps/sudoku/index.ts +++ b/src/examples/zkapps/sudoku/index.ts @@ -1,6 +1,6 @@ import { Sudoku, SudokuZkApp } from './sudoku.js'; import { cloneSudoku, generateSudoku, solveSudoku } from './sudoku-lib.js'; -import { AccountUpdate, Mina, PrivateKey, shutdown } from 'o1js'; +import { AccountUpdate, Mina, PrivateKey } from 'o1js'; // setup const Local = Mina.LocalBlockchain(); @@ -23,8 +23,8 @@ console.log('Deploying and initializing Sudoku...'); await SudokuZkApp.compile(); let tx = await Mina.transaction(account, async () => { AccountUpdate.fundNewAccount(account); - zkApp.deploy(); - zkApp.update(Sudoku.from(sudoku)); + await zkApp.deploy(); + await zkApp.update(Sudoku.from(sudoku)); }); await tx.prove(); /** @@ -48,7 +48,7 @@ noSolution[0][0] = (noSolution[0][0] % 9) + 1; console.log('Submitting wrong solution...'); try { let tx = await Mina.transaction(account, async () => { - zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(noSolution)); + await zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(noSolution)); }); await tx.prove(); await tx.sign([accountKey]).send(); @@ -60,11 +60,8 @@ console.log('Is the sudoku solved?', zkApp.isSolved.get().toBoolean()); // submit the actual solution console.log('Submitting solution...'); tx = await Mina.transaction(account, async () => { - zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(solution!)); + await zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(solution!)); }); await tx.prove(); await tx.sign([accountKey]).send(); console.log('Is the sudoku solved?', zkApp.isSolved.get().toBoolean()); - -// cleanup -await shutdown(); diff --git a/src/examples/zkapps/sudoku/sudoku.ts b/src/examples/zkapps/sudoku/sudoku.ts index 9af8952ecf..5f17d9ce3a 100644 --- a/src/examples/zkapps/sudoku/sudoku.ts +++ b/src/examples/zkapps/sudoku/sudoku.ts @@ -5,7 +5,6 @@ import { Bool, state, State, - isReady, Poseidon, Struct, Provable, @@ -13,8 +12,6 @@ import { export { Sudoku, SudokuZkApp }; -await isReady; - class Sudoku extends Struct({ value: Provable.Array(Provable.Array(Field, 9), 9), }) { @@ -23,7 +20,7 @@ class Sudoku extends Struct({ } hash() { - return Poseidon.hash(this.value.flat()); + return Poseidon.hash(Sudoku.toFields(this)); } } @@ -37,16 +34,19 @@ class SudokuZkApp extends SmartContract { * to ensure the entire state is overwritten. * however, it's good to have an example which tests the CLI's ability to handle init() decorated with `@method`. */ - @method init() { + @method async init() { super.init(); } - @method update(sudokuInstance: Sudoku) { + @method async update(sudokuInstance: Sudoku) { this.sudokuHash.set(sudokuInstance.hash()); this.isSolved.set(Bool(false)); } - @method submitSolution(sudokuInstance: Sudoku, solutionInstance: Sudoku) { + @method async submitSolution( + sudokuInstance: Sudoku, + solutionInstance: Sudoku + ) { let sudoku = sudokuInstance.value; let solution = solutionInstance.value; diff --git a/src/examples/zkapps/voting/demo.ts b/src/examples/zkapps/voting/demo.ts index 30f6b303e5..1356fbce84 100644 --- a/src/examples/zkapps/voting/demo.ts +++ b/src/examples/zkapps/voting/demo.ts @@ -1,15 +1,7 @@ // used to do a dry run, without tests // ./run ./src/examples/zkapps/voting/demo.ts -import { - Field, - Mina, - AccountUpdate, - PrivateKey, - UInt64, - Reducer, - Bool, -} from 'o1js'; +import { Mina, AccountUpdate, PrivateKey, UInt64, Reducer, Bool } from 'o1js'; import { VotingApp, VotingAppParams } from './factory.js'; import { Member, MyMerkleWitness } from './member.js'; import { OffchainStorage } from './off-chain-storage.js'; diff --git a/src/examples/zkapps/voting/deploy-contracts.ts b/src/examples/zkapps/voting/deploy-contracts.ts index 1a1a0ec9fb..5dc5221bbb 100644 --- a/src/examples/zkapps/voting/deploy-contracts.ts +++ b/src/examples/zkapps/voting/deploy-contracts.ts @@ -15,8 +15,8 @@ import { Membership_ } from './membership.js'; import { Voting_ } from './voting.js'; class InvalidContract extends SmartContract { - deploy(args: DeployArgs) { - super.deploy(args); + async deploy(args: DeployArgs) { + await super.deploy(args); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.none(), @@ -66,15 +66,15 @@ export async function deployContracts( let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer, 3); - voting.deploy({ zkappKey: params.votingKey }); + await voting.deploy({ zkappKey: params.votingKey }); voting.committedVotes.set(votesRoot); voting.accumulatedVotes.set(Reducer.initialActionState); - candidateContract.deploy({ zkappKey: params.candidateKey }); + await candidateContract.deploy({ zkappKey: params.candidateKey }); candidateContract.committedMembers.set(candidateRoot); candidateContract.accumulatedMembers.set(Reducer.initialActionState); - voterContract.deploy({ zkappKey: params.voterKey }); + await voterContract.deploy({ zkappKey: params.voterKey }); voterContract.committedMembers.set(voterRoot); voterContract.accumulatedMembers.set(Reducer.initialActionState); }); @@ -130,7 +130,7 @@ export async function deployInvalidContracts( let tx = await Mina.transaction(feePayer, async () => { AccountUpdate.fundNewAccount(feePayer, 3); - voting.deploy({ zkappKey: params.votingKey }); + await voting.deploy({ zkappKey: params.votingKey }); voting.committedVotes.set(votesRoot); voting.accumulatedVotes.set(Reducer.initialActionState); @@ -140,7 +140,7 @@ export async function deployInvalidContracts( params.candidateKey.toPublicKey() ); - invalidCandidateContract.deploy({ zkappKey: params.candidateKey }); + await invalidCandidateContract.deploy({ zkappKey: params.candidateKey }); candidateContract = invalidCandidateContract as Membership_; @@ -148,7 +148,7 @@ export async function deployInvalidContracts( params.voterKey.toPublicKey() ); - invalidVoterContract.deploy({ zkappKey: params.voterKey }); + await invalidVoterContract.deploy({ zkappKey: params.voterKey }); voterContract = invalidVoterContract as Membership_; }); diff --git a/src/examples/zkapps/voting/dummy-contract.ts b/src/examples/zkapps/voting/dummy-contract.ts index 8b1d4e03cd..006cacc15a 100644 --- a/src/examples/zkapps/voting/dummy-contract.ts +++ b/src/examples/zkapps/voting/dummy-contract.ts @@ -12,8 +12,8 @@ import { export class DummyContract extends SmartContract { @state(Field) sum = State(); - deploy(args: DeployArgs) { - super.deploy(args); + async deploy(args: DeployArgs) { + await super.deploy(args); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.proofOrSignature(), @@ -31,7 +31,7 @@ export class DummyContract extends SmartContract { /** * Method used to add two variables together. */ - @method add(x: Field, y: Field) { + @method async add(x: Field, y: Field) { this.sum.set(x.add(y)); } } diff --git a/src/examples/zkapps/voting/member.ts b/src/examples/zkapps/voting/member.ts index c4b0f6e6cd..ef0a8b6050 100644 --- a/src/examples/zkapps/voting/member.ts +++ b/src/examples/zkapps/voting/member.ts @@ -1,5 +1,4 @@ import { - Bool, CircuitValue, Field, prop, diff --git a/src/examples/zkapps/voting/membership.ts b/src/examples/zkapps/voting/membership.ts index ce22e056f4..841f7b8707 100644 --- a/src/examples/zkapps/voting/membership.ts +++ b/src/examples/zkapps/voting/membership.ts @@ -68,8 +68,8 @@ export class Membership_ extends SmartContract { }), }; - deploy(args: DeployArgs) { - super.deploy(args); + async deploy(args: DeployArgs) { + await super.deploy(args); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.proofOrSignature(), @@ -88,7 +88,8 @@ export class Membership_ extends SmartContract { * Dispatches a new member sequence event. * @param member */ - @method addEntry(member: Member): Bool { + @method.returns(Bool) + async addEntry(member: Member) { // Emit event that indicates adding this item // Preconditions: Restrict who can vote or who can be a candidate @@ -147,7 +148,8 @@ export class Membership_ extends SmartContract { * @param accountId * @returns true if member exists */ - @method isMember(member: Member): Bool { + @method.returns(Bool) + async isMember(member: Member) { // Verify membership (voter or candidate) with the accountId via merkle tree committed to by the sequence events and returns a boolean // Preconditions: Item exists in committed storage @@ -162,7 +164,7 @@ export class Membership_ extends SmartContract { /** * Method used to commit to the accumulated list of members. */ - @method publish() { + @method async publish() { // Commit to the items accumulated so far. This is a periodic update let accumulatedMembers = this.accumulatedMembers.get(); diff --git a/src/examples/zkapps/voting/run-berkeley.ts b/src/examples/zkapps/voting/run-berkeley.ts index caa62d02ed..722e29e98c 100644 --- a/src/examples/zkapps/voting/run-berkeley.ts +++ b/src/examples/zkapps/voting/run-berkeley.ts @@ -2,12 +2,10 @@ import { AccountUpdate, Bool, fetchAccount, - isReady, Mina, PrivateKey, PublicKey, Reducer, - shutdown, SmartContract, UInt32, UInt64, @@ -20,7 +18,6 @@ import { ElectionPreconditions, } from './preconditions.js'; import { getResults, vote } from './voting-lib.js'; -await isReady; const Berkeley = Mina.Network({ mina: 'https://proxy.berkeley.minaexplorer.com/graphql', @@ -98,7 +95,7 @@ let tx = await Mina.transaction( contracts.voting.committedVotes.set(storage.votesStore.getRoot()); contracts.voting.accumulatedVotes.set(Reducer.initialActionState); - contracts.candidateContract.deploy({ zkappKey: params.candidateKey }); + await contracts.candidateContract.deploy({ zkappKey: params.candidateKey }); contracts.candidateContract.committedMembers.set( storage.candidatesStore.getRoot() ); @@ -106,13 +103,13 @@ let tx = await Mina.transaction( Reducer.initialActionState ); - contracts.voterContract.deploy({ zkappKey: params.voterKey }); + await contracts.voterContract.deploy({ zkappKey: params.voterKey }); contracts.voterContract.committedMembers.set(storage.votersStore.getRoot()); contracts.voterContract.accumulatedMembers.set(Reducer.initialActionState); } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(); +await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); console.log('successfully deployed contracts'); @@ -131,11 +128,11 @@ tx = await Mina.transaction( Member.from(members[0], UInt64.from(150)), storage.votersStore ); - contracts.voting.voterRegistration(m); + await contracts.voting.voterRegistration(m); } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(); +await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); console.log('voter registered'); await fetchAllAccounts(); @@ -153,11 +150,11 @@ tx = await Mina.transaction( Member.from(members[1], UInt64.from(150)), storage.candidatesStore ); - contracts.voting.candidateRegistration(m); + await contracts.voting.candidateRegistration(m); } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(); +await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); console.log('candidate registered'); // we have to wait a few seconds before continuing, otherwise we might not get the actions from the archive, we if continue too fast await new Promise((resolve) => setTimeout(resolve, 20000)); @@ -172,11 +169,11 @@ tx = await Mina.transaction( memo: 'Approving registrations', }, async () => { - contracts.voting.approveRegistrations(); + await contracts.voting.approveRegistrations(); } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(); +await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); console.log('registrations approved'); await fetchAllAccounts(); @@ -198,11 +195,11 @@ tx = await Mina.transaction( v.witness = new MyMerkleWitness(storage.votersStore.getWitness(0n)); console.log(v.witness.calculateRoot(v.getHash()).toString()); console.log(contracts.voting.committedVotes.get().toString()); - contracts.voting.vote(currentCandidate, v); + await contracts.voting.vote(currentCandidate, v); } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(); +await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); vote(0n, storage.votesStore, storage.candidatesStore); console.log('voted for a candidate'); await new Promise((resolve) => setTimeout(resolve, 20000)); @@ -213,11 +210,11 @@ console.log('counting votes'); tx = await Mina.transaction( { sender: feePayerAddress, fee: 10_000_000, memo: 'Counting votes' }, async () => { - contracts.voting.countVotes(); + await contracts.voting.countVotes(); } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).wait(); +await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); console.log('votes counted'); await new Promise((resolve) => setTimeout(resolve, 20000)); @@ -272,5 +269,3 @@ function registerMember( m.witness = new MyMerkleWitness(store.getWitness(i)); return m; } - -shutdown(); diff --git a/src/examples/zkapps/voting/test.ts b/src/examples/zkapps/voting/test.ts index dc0081adaf..3849a1f599 100644 --- a/src/examples/zkapps/voting/test.ts +++ b/src/examples/zkapps/voting/test.ts @@ -85,7 +85,7 @@ export async function testSet( await assertValidTx( true, async () => { - verificationKeySet.voting.voterRegistration(m); + await verificationKeySet.voting.voterRegistration(m); }, verificationKeySet.feePayer ); @@ -111,7 +111,7 @@ export async function testSet( await assertValidTx( false, async () => { - verificationKeySet.voting.voterRegistration(m); + await verificationKeySet.voting.voterRegistration(m); }, verificationKeySet.feePayer, 'Invalid proof' @@ -157,7 +157,7 @@ export async function testSet( await assertValidTx( true, async () => { - permissionedSet.voting.voterRegistration(m); + await permissionedSet.voting.voterRegistration(m); }, permissionedSet.feePayer ); @@ -186,7 +186,7 @@ export async function testSet( await assertValidTx( false, async () => { - permissionedSet.voting.voterRegistration(m); + await permissionedSet.voting.voterRegistration(m); }, permissionedSet.feePayer, 'actions' @@ -231,7 +231,7 @@ export async function testSet( let tx = await Mina.transaction( invalidSet.feePayer.toPublicKey(), async () => { - invalidSet.voting.voterRegistration(m); + await invalidSet.voting.voterRegistration(m); } ); await tx.prove(); @@ -293,7 +293,7 @@ export async function testSet( m.balance.toString() ); - sequenceOverflowSet.voting.voterRegistration(m); + await sequenceOverflowSet.voting.voterRegistration(m); } ); await tx.prove(); @@ -315,7 +315,7 @@ export async function testSet( let tx = await Mina.transaction( sequenceOverflowSet.feePayer.toPublicKey(), async () => { - sequenceOverflowSet.voting.approveRegistrations(); + await sequenceOverflowSet.voting.approveRegistrations(); } ); await tx.prove(); @@ -385,7 +385,7 @@ export async function testSet( await assertValidTx( true, async () => { - voting.voterRegistration(newVoter1); + await voting.voterRegistration(newVoter1); }, feePayer ); @@ -441,7 +441,7 @@ export async function testSet( await assertValidTx( false, async () => { - voting.voterRegistration(newVoterLow); + await voting.voterRegistration(newVoterLow); }, feePayer, 'Balance not high enough!' @@ -457,7 +457,7 @@ export async function testSet( await assertValidTx( false, async () => { - voting.voterRegistration(newVoterHigh); + await voting.voterRegistration(newVoterHigh); }, feePayer, 'Balance too high!' @@ -468,7 +468,7 @@ export async function testSet( await assertValidTx( false, async () => { - voting.voterRegistration(newVoter1); + await voting.voterRegistration(newVoter1); }, feePayer, 'Member already exists!' @@ -516,7 +516,7 @@ export async function testSet( ); // register new candidate - voting.candidateRegistration(newCandidate); + await voting.candidateRegistration(newCandidate); }, feePayer ); @@ -538,7 +538,7 @@ export async function testSet( ); // register new candidate - voting.candidateRegistration(newCandidate); + await voting.candidateRegistration(newCandidate); }, feePayer ); @@ -584,7 +584,7 @@ export async function testSet( true, async () => { // register new candidate - voting.approveRegistrations(); + await voting.approveRegistrations(); }, feePayer ); @@ -652,7 +652,7 @@ export async function testSet( false, async () => { // register late candidate - voting.candidateRegistration(lateCandidate); + await voting.candidateRegistration(lateCandidate); }, feePayer, 'Outside of election period!' @@ -672,7 +672,7 @@ export async function testSet( false, async () => { // register late voter - voting.voterRegistration(lateVoter); + await voting.voterRegistration(lateVoter); }, feePayer, 'Outside of election period!' @@ -729,7 +729,7 @@ export async function testSet( await assertValidTx( true, async () => { - voting.countVotes(); + await voting.countVotes(); }, feePayer ); @@ -777,7 +777,7 @@ export async function testSet( let v = votersStore.get(0n)!; v.witness = new MyMerkleWitness(votersStore.getWitness(0n)); - voting.vote(currentCandidate, v); + await voting.vote(currentCandidate, v); }, feePayer ); @@ -822,7 +822,7 @@ export async function testSet( async () => { // attempting to vote for the registered candidate - voting.vote(fakeCandidate, votersStore.get(0n)!); + await voting.vote(fakeCandidate, votersStore.get(0n)!); }, feePayer, 'Member is not a candidate!' @@ -839,7 +839,7 @@ export async function testSet( await assertValidTx( false, async () => { - voting.vote(fakeVoter, votersStore.get(0n)!); + await voting.vote(fakeVoter, votersStore.get(0n)!); }, feePayer, 'Member is not a candidate!' @@ -851,7 +851,7 @@ export async function testSet( false, async () => { const voter = votersStore.get(0n)!; - voting.vote(voter, votersStore.get(0n)!); + await voting.vote(voter, votersStore.get(0n)!); }, feePayer, 'Member is not a candidate!' @@ -878,7 +878,7 @@ export async function testSet( await assertValidTx( true, async () => { - voting.countVotes(); + await voting.countVotes(); }, feePayer ); @@ -931,7 +931,7 @@ export async function testSet( await assertValidTx( false, async () => { - voting.voterRegistration(voter); + await voting.voterRegistration(voter); }, feePayer, 'Outside of election period!' @@ -948,7 +948,7 @@ export async function testSet( await assertValidTx( false, async () => { - voting.candidateRegistration(candidate); + await voting.candidateRegistration(candidate); }, feePayer, 'Outside of election period!' diff --git a/src/examples/zkapps/voting/voting-lib.ts b/src/examples/zkapps/voting/voting-lib.ts index a3e90c5192..818fdd15d7 100644 --- a/src/examples/zkapps/voting/voting-lib.ts +++ b/src/examples/zkapps/voting/voting-lib.ts @@ -2,7 +2,7 @@ import { Member, MyMerkleWitness } from './member.js'; import { OffchainStorage } from './off-chain-storage.js'; import { Voting_ } from './voting.js'; import { Mina, PrivateKey } from 'o1js'; -import { Printer } from 'prettier'; + /** * Updates off-chain storage when registering a member or candidate * @param {bigint} i index of memberStore or candidatesStore diff --git a/src/examples/zkapps/voting/voting.ts b/src/examples/zkapps/voting/voting.ts index 77142f00ac..7ae346b873 100644 --- a/src/examples/zkapps/voting/voting.ts +++ b/src/examples/zkapps/voting/voting.ts @@ -97,8 +97,8 @@ export class Voting_ extends SmartContract { }), }; - deploy(args: DeployArgs) { - super.deploy(args); + async deploy(args: DeployArgs) { + await super.deploy(args); this.account.permissions.set({ ...Permissions.default(), editState: Permissions.proofOrSignature(), @@ -118,7 +118,7 @@ export class Voting_ extends SmartContract { * @param member */ @method - voterRegistration(member: Member) { + async voterRegistration(member: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); this.network.globalSlotSinceGenesis.requireBetween( currentSlot, @@ -153,7 +153,7 @@ export class Voting_ extends SmartContract { ); let VoterContract: Membership_ = new Membership_(voterAddress); - let exists = VoterContract.addEntry(member); + let exists = await VoterContract.addEntry(member); // the check happens here because we want to see if the other contract returns a value // if exists is true, that means the member already exists within the accumulated state @@ -167,7 +167,7 @@ export class Voting_ extends SmartContract { * @param member */ @method - candidateRegistration(member: Member) { + async candidateRegistration(member: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); this.network.globalSlotSinceGenesis.requireBetween( currentSlot, @@ -202,7 +202,7 @@ export class Voting_ extends SmartContract { ); let CandidateContract: Membership_ = new Membership_(candidateAddress); - let exists = CandidateContract.addEntry(member); + let exists = await CandidateContract.addEntry(member); // the check happens here because we want to see if the other contract returns a value // if exists is true, that means the member already exists within the accumulated state @@ -215,13 +215,13 @@ export class Voting_ extends SmartContract { * Calls the `publish()` method of the Candidate-Membership and Voter-Membership contract. */ @method - approveRegistrations() { + async approveRegistrations() { // Invokes the publish method of both Voter and Candidate Membership contracts. let VoterContract: Membership_ = new Membership_(voterAddress); - VoterContract.publish(); + await VoterContract.publish(); let CandidateContract: Membership_ = new Membership_(candidateAddress); - CandidateContract.publish(); + await CandidateContract.publish(); } /** @@ -231,7 +231,7 @@ export class Voting_ extends SmartContract { * @param voter */ @method - vote(candidate: Member, voter: Member) { + async vote(candidate: Member, voter: Member) { let currentSlot = this.network.globalSlotSinceGenesis.get(); this.network.globalSlotSinceGenesis.requireBetween( currentSlot, @@ -250,10 +250,10 @@ export class Voting_ extends SmartContract { // verifying that both the voter and the candidate are actually part of our member set // ideally we would also verify a signature here, but ignoring that for now let VoterContract: Membership_ = new Membership_(voterAddress); - VoterContract.isMember(voter).assertTrue('Member is not a voter!'); + (await VoterContract.isMember(voter)).assertTrue('Member is not a voter!'); let CandidateContract: Membership_ = new Membership_(candidateAddress); - CandidateContract.isMember(candidate).assertTrue( + (await CandidateContract.isMember(candidate)).assertTrue( 'Member is not a candidate!' ); @@ -268,7 +268,7 @@ export class Voting_ extends SmartContract { * and applies state changes to the votes merkle tree. */ @method - countVotes() { + async countVotes() { let accumulatedVotes = this.accumulatedVotes.get(); this.accumulatedVotes.requireEquals(accumulatedVotes); diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index 624e20db8b..ebf6f6ebc6 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -819,7 +819,7 @@ class AccountUpdate implements Types.AccountUpdate { * then you should use the following code before sending your transaction: * * ```ts - * let tx = Mina.transaction(...); // create transaction as usual, using `requireSignature()` somewhere + * let tx = await Mina.transaction(...); // create transaction as usual, using `requireSignature()` somewhere * tx.sign([privateKey]); // pass the private key of this account to `sign()`! * ``` * @@ -1070,7 +1070,7 @@ class AccountUpdate implements Types.AccountUpdate { * then you should use the following code before sending your transaction: * * ```ts - * let tx = Mina.transaction(...); // create transaction as usual, using `createSigned()` somewhere + * let tx = await Mina.transaction(...); // create transaction as usual, using `createSigned()` somewhere * tx.sign([privateKey]); // pass the private key of this account to `sign()`! * ``` * diff --git a/src/lib/mina/transaction.ts b/src/lib/mina/transaction.ts index 863ee94a90..8901d15f4f 100644 --- a/src/lib/mina/transaction.ts +++ b/src/lib/mina/transaction.ts @@ -454,18 +454,14 @@ function transaction( ): Promise { let sender: DeprecatedFeePayerSpec; let f: () => Promise; - try { - if (fOrUndefined !== undefined) { - sender = senderOrF as DeprecatedFeePayerSpec; - f = fOrUndefined; - } else { - sender = undefined; - f = senderOrF as () => Promise; - } - return activeInstance.transaction(sender, f); - } catch (error) { - throw prettifyStacktrace(error); + if (fOrUndefined !== undefined) { + sender = senderOrF as DeprecatedFeePayerSpec; + f = fOrUndefined; + } else { + sender = undefined; + f = senderOrF as () => Promise; } + return activeInstance.transaction(sender, f); } async function sendTransaction(txn: Transaction) { diff --git a/src/tests/fake-proof.ts b/src/tests/fake-proof.ts index 0490d69f9e..dcc1767560 100644 --- a/src/tests/fake-proof.ts +++ b/src/tests/fake-proof.ts @@ -44,7 +44,7 @@ const RecursiveProgram = ZkProgram({ }); class RecursiveContract extends SmartContract { - @method verifyReal(proof: RealProof) { + @method async verifyReal(proof: RealProof) { proof.verify(); } } @@ -70,7 +70,7 @@ for (let proof of [fakeProof, dummyProof]) { // contract rejects proof await assert.rejects(async () => { - let tx = await Mina.transaction(async () => zkApp.verifyReal(proof)); + let tx = await Mina.transaction(() => zkApp.verifyReal(proof)); await tx.prove(); }, 'recursive contract rejects fake proof'); } @@ -86,7 +86,7 @@ assert( ); // contract accepts proof -let tx = await Mina.transaction(async () => zkApp.verifyReal(realProof)); +let tx = await Mina.transaction(() => zkApp.verifyReal(realProof)); let [contractProof] = await tx.prove(); assert( await verify(contractProof!, contractVk.data), diff --git a/src/tests/transaction-flow.ts b/src/tests/transaction-flow.ts index d0d1fd3987..462c051e98 100644 --- a/src/tests/transaction-flow.ts +++ b/src/tests/transaction-flow.ts @@ -38,11 +38,11 @@ class SimpleZkapp extends SmartContract { this.actionState.set(Reducer.initialActionState); } - @method incrementCounter() { + @method async incrementCounter() { this.reducer.dispatch(Field(1)); } - @method rollupIncrements() { + @method async rollupIncrements() { const counter = this.counter.get(); this.counter.requireEquals(counter); const actionState = this.actionState.get(); @@ -70,7 +70,7 @@ class SimpleZkapp extends SmartContract { this.actionState.set(newActionState); } - @method update(y: Field, publicKey: PublicKey) { + @method async update(y: Field, publicKey: PublicKey) { this.emitEvent('complexEvent', { pub: publicKey, value: y, @@ -177,9 +177,7 @@ await testLocalAndRemote(async () => { await assert.doesNotReject(async () => { const transaction = await Mina.transaction( { sender, fee: transactionFee }, - async () => { - zkApp.deploy({ verificationKey }); - } + () => zkApp.deploy({ verificationKey }) ); transaction.sign([senderKey, zkAppKey]); await sendAndVerifyTransaction(transaction); @@ -195,7 +193,7 @@ await testLocalAndRemote(async () => { const transaction = await Mina.transaction( { sender, fee: transactionFee }, async () => { - zkApp.update(Field(1), PrivateKey.random().toPublicKey()); + await zkApp.update(Field(1), PrivateKey.random().toPublicKey()); } ); transaction.sign([senderKey, zkAppKey]); @@ -214,7 +212,7 @@ await testLocalAndRemote(async () => { const transaction = await Mina.transaction( { sender, fee: transactionFee }, async () => { - zkApp.update(Field(1), PrivateKey.random().toPublicKey()); + await zkApp.update(Field(1), PrivateKey.random().toPublicKey()); } ); transaction.sign([senderKey, zkAppKey]); @@ -236,7 +234,7 @@ await testLocalAndRemote(async () => { { sender, fee: transactionFee }, async () => { AccountUpdate.fundNewAccount(zkAppAddress); - zkApp.update(Field(1), PrivateKey.random().toPublicKey()); + await zkApp.update(Field(1), PrivateKey.random().toPublicKey()); } ); transaction.sign([senderKey, zkAppKey]); @@ -254,7 +252,7 @@ await testLocalAndRemote(async () => { { sender, fee: transactionFee }, async () => { AccountUpdate.fundNewAccount(zkAppAddress); - zkApp.update(Field(1), PrivateKey.random().toPublicKey()); + await zkApp.update(Field(1), PrivateKey.random().toPublicKey()); } ); transaction.sign([senderKey, zkAppKey]); @@ -268,18 +266,14 @@ await testLocalAndRemote(async () => { try { let transaction = await Mina.transaction( { sender, fee: transactionFee }, - async () => { - zkApp.incrementCounter(); - } + () => zkApp.incrementCounter() ); transaction.sign([senderKey, zkAppKey]); await sendAndVerifyTransaction(transaction); transaction = await Mina.transaction( { sender, fee: transactionFee }, - async () => { - zkApp.rollupIncrements(); - } + async () => zkApp.rollupIncrements() ); transaction.sign([senderKey, zkAppKey]); await sendAndVerifyTransaction(transaction); @@ -287,11 +281,11 @@ await testLocalAndRemote(async () => { transaction = await Mina.transaction( { sender, fee: transactionFee }, async () => { - zkApp.incrementCounter(); - zkApp.incrementCounter(); - zkApp.incrementCounter(); - zkApp.incrementCounter(); - zkApp.incrementCounter(); + await zkApp.incrementCounter(); + await zkApp.incrementCounter(); + await zkApp.incrementCounter(); + await zkApp.incrementCounter(); + await zkApp.incrementCounter(); } ); transaction.sign([senderKey, zkAppKey]); @@ -299,9 +293,7 @@ await testLocalAndRemote(async () => { transaction = await Mina.transaction( { sender, fee: transactionFee }, - async () => { - zkApp.rollupIncrements(); - } + async () => zkApp.rollupIncrements() ); transaction.sign([senderKey, zkAppKey]); await sendAndVerifyTransaction(transaction); diff --git a/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js b/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js index 636efac328..583c3b2191 100644 --- a/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js +++ b/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js @@ -48,7 +48,7 @@ deployButton.addEventListener('click', async () => { if (!eventsContainer.innerHTML.includes('zkApp Deployed successfully')) { AccountUpdate.fundNewAccount(feePayer); } - zkAppInstance.deploy(); + await zkAppInstance.deploy(); }); await deploymentTransaction.sign([feePayerKey, zkAppPrivateKey]).send(); @@ -85,7 +85,7 @@ updateButton.addEventListener('click', async (event) => { eventsContainer ); const transaction = await Mina.transaction(feePayer, async () => { - zkAppInstance.update( + await zkAppInstance.update( Field(parseInt(zkAppStateValue.value)), adminPrivateKey ); From 52b7f09327ef3415e7b5c1dcf14b4e21e8360f0b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Mar 2024 16:30:23 +0100 Subject: [PATCH 10/16] fixup --- src/examples/commonjs.cjs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/examples/commonjs.cjs b/src/examples/commonjs.cjs index fcfcdf730e..4974390e33 100644 --- a/src/examples/commonjs.cjs +++ b/src/examples/commonjs.cjs @@ -25,12 +25,11 @@ class SimpleZkapp extends SmartContract { this.x.set(initialState); } - update(y) { + async update(y) { this.emitEvent('update', y); this.emitEvent('update', y); - this.account.balance.assertEquals(this.account.balance.get()); - let x = this.x.get(); - this.x.assertEquals(x); + this.account.balance.requireEquals(this.account.balance.get()); + let x = this.x.getAndRequireEquals(); this.x.set(x.add(y)); } } From 54a4273a7118e1612fe26806e1030a82df63cb12 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Mar 2024 18:17:23 +0100 Subject: [PATCH 11/16] make state.fetch() work in contracts --- .../zkapps/hello-world/hello-world.ts | 19 +++++++++++++++-- src/lib/state.ts | 21 +++++++++++-------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/examples/zkapps/hello-world/hello-world.ts b/src/examples/zkapps/hello-world/hello-world.ts index 057526d952..6da2800f01 100644 --- a/src/examples/zkapps/hello-world/hello-world.ts +++ b/src/examples/zkapps/hello-world/hello-world.ts @@ -1,4 +1,13 @@ -import { Field, PrivateKey, SmartContract, State, method, state } from 'o1js'; +import { + Field, + PrivateKey, + Provable, + SmartContract, + State, + assert, + method, + state, +} from 'o1js'; export const adminPrivateKey = PrivateKey.random(); export const adminPublicKey = adminPrivateKey.toPublicKey(); @@ -13,7 +22,13 @@ export class HelloWorld extends SmartContract { } @method async update(squared: Field, admin: PrivateKey) { - const x = this.x.get(); + // explicitly fetch state from the chain + const x = await Provable.witnessAsync(Field, async () => { + let x = await this.x.fetch(); + assert(x !== undefined, 'x can be fetched'); + return x; + }); + this.x.requireNothing(); x.square().assertEquals(squared); this.x.set(squared); diff --git a/src/lib/state.ts b/src/lib/state.ts index 78a97aa36b..7d265a3bf9 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -3,7 +3,7 @@ import { FlexibleProvablePure } from './circuit-value.js'; import { AccountUpdate, TokenId } from './account-update.js'; import { PublicKey } from './signature.js'; import * as Mina from './mina.js'; -import { fetchAccount } from './fetch.js'; +import { fetchAccount, networkConfig } from './fetch.js'; import { SmartContract } from './zkapp.js'; import { Account } from './mina/account.js'; import { Provable } from './provable.js'; @@ -329,17 +329,20 @@ function createState(): InternalStateType { throw Error( 'fetch can only be called when the State is assigned to a SmartContract @state.' ); - if (Mina.currentTransaction.has()) - throw Error( - 'fetch is not intended to be called inside a transaction block.' - ); + let layout = getLayoutPosition(this._contract); let address: PublicKey = this._contract.instance.address; - let { account } = await fetchAccount({ - publicKey: address, - tokenId: TokenId.toBase58(TokenId.default), - }); + let account: Account | undefined; + if (networkConfig.minaEndpoint === '') { + account = Mina.getAccount(address, TokenId.default); + } else { + ({ account } = await fetchAccount({ + publicKey: address, + tokenId: TokenId.toBase58(TokenId.default), + })); + } if (account === undefined) return undefined; + let stateAsFields: Field[]; if (account.zkapp?.appState === undefined) { stateAsFields = Array(layout.length).fill(Field(0)); From 1648ea48e88745c5fdc1fedb3f36e13724627462 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Mar 2024 21:53:45 +0100 Subject: [PATCH 12/16] make zkprogram methods async (yes 2-line change) --- src/lib/proof-system.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/proof-system.ts b/src/lib/proof-system.ts index e7af194a0a..60c992e277 100644 --- a/src/lib/proof-system.ts +++ b/src/lib/proof-system.ts @@ -1058,14 +1058,14 @@ type Method< > = PublicInput extends undefined ? { privateInputs: Args; - method(...args: TupleToInstances): PublicOutput; + method(...args: TupleToInstances): Promise; } : { privateInputs: Args; method( publicInput: PublicInput, ...args: TupleToInstances - ): PublicOutput; + ): Promise; }; type Prover< From b8ea5ba14d75bcb3981a366043ada18fefb69636 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Mar 2024 21:54:04 +0100 Subject: [PATCH 13/16] adapt unit tests --- src/lib/gadgets/arithmetic.unit-test.ts | 2 +- src/lib/gadgets/bitwise.unit-test.ts | 18 +++++++++--------- src/lib/gadgets/ecdsa.unit-test.ts | 2 +- src/lib/gadgets/foreign-field.unit-test.ts | 10 +++++----- src/lib/gadgets/range-check.unit-test.ts | 8 ++++---- src/lib/gadgets/sha256.unit-test.ts | 2 +- src/lib/keccak.unit-test.ts | 4 ++-- src/lib/proof-system.unit-test.ts | 8 ++++---- .../tests/verify-in-snark.unit-test.ts | 2 +- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/lib/gadgets/arithmetic.unit-test.ts b/src/lib/gadgets/arithmetic.unit-test.ts index 9030eec117..d03f4e0934 100644 --- a/src/lib/gadgets/arithmetic.unit-test.ts +++ b/src/lib/gadgets/arithmetic.unit-test.ts @@ -19,7 +19,7 @@ let Arithmetic = ZkProgram({ methods: { divMod32: { privateInputs: [Field], - method(a: Field) { + async method(a: Field) { return Gadgets.divMod32(a); }, }, diff --git a/src/lib/gadgets/bitwise.unit-test.ts b/src/lib/gadgets/bitwise.unit-test.ts index 9e2f93f919..7b0ef73b42 100644 --- a/src/lib/gadgets/bitwise.unit-test.ts +++ b/src/lib/gadgets/bitwise.unit-test.ts @@ -35,56 +35,56 @@ let Bitwise = ZkProgram({ methods: { xor: { privateInputs: [Field, Field], - method(a: Field, b: Field) { + async method(a: Field, b: Field) { return Gadgets.xor(a, b, 254); }, }, notUnchecked: { privateInputs: [Field], - method(a: Field) { + async method(a: Field) { return Gadgets.not(a, 254, false); }, }, notChecked: { privateInputs: [Field], - method(a: Field) { + async method(a: Field) { return Gadgets.not(a, 254, true); }, }, and: { privateInputs: [Field, Field], - method(a: Field, b: Field) { + async method(a: Field, b: Field) { return Gadgets.and(a, b, 64); }, }, rot32: { privateInputs: [Field], - method(a: Field) { + async method(a: Field) { return Gadgets.rotate32(a, 12, 'left'); }, }, rot64: { privateInputs: [Field], - method(a: Field) { + async method(a: Field) { return Gadgets.rotate64(a, 12, 'left'); }, }, leftShift64: { privateInputs: [Field], - method(a: Field) { + async method(a: Field) { return Gadgets.leftShift64(a, 12); }, }, leftShift32: { privateInputs: [Field], - method(a: Field) { + async method(a: Field) { Gadgets.rangeCheck32(a); return Gadgets.leftShift32(a, 12); }, }, rightShift64: { privateInputs: [Field], - method(a: Field) { + async method(a: Field) { return Gadgets.rightShift64(a, 12); }, }, diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index c9fac69e9b..138d6c8d3d 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -146,7 +146,7 @@ let program = ZkProgram({ methods: { ecdsa: { privateInputs: [], - method() { + async method() { let signature_ = Provable.witness( Ecdsa.Signature.provable, () => signature diff --git a/src/lib/gadgets/foreign-field.unit-test.ts b/src/lib/gadgets/foreign-field.unit-test.ts index e5af3e9a50..7a7831b565 100644 --- a/src/lib/gadgets/foreign-field.unit-test.ts +++ b/src/lib/gadgets/foreign-field.unit-test.ts @@ -166,32 +166,32 @@ let ffProgram = ZkProgram({ methods: { sumchain: { privateInputs: [Provable.Array(Field3.provable, chainLength)], - method(xs) { + async method(xs) { return ForeignField.sum(xs, signs, F.modulus); }, }, mulWithBoundsCheck: { privateInputs: [Field3.provable, Field3.provable], - method(x, y) { + async method(x, y) { ForeignField.assertAlmostReduced([x, y], F.modulus); return ForeignField.mul(x, y, F.modulus); }, }, mul: { privateInputs: [Field3.provable, Field3.provable], - method(x, y) { + async method(x, y) { return ForeignField.mul(x, y, F.modulus); }, }, inv: { privateInputs: [Field3.provable], - method(x) { + async method(x) { return ForeignField.inv(x, F.modulus); }, }, div: { privateInputs: [Field3.provable, Field3.provable], - method(x, y) { + async method(x, y) { return ForeignField.div(x, y, F.modulus); }, }, diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 584e52cb57..b9665caed7 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -75,25 +75,25 @@ let RangeCheck = ZkProgram({ methods: { check64: { privateInputs: [Field], - method(x) { + async method(x) { Gadgets.rangeCheck64(x); }, }, check8: { privateInputs: [Field], - method(x) { + async method(x) { Gadgets.rangeCheck8(x); }, }, checkMulti: { privateInputs: [Field, Field, Field], - method(x, y, z) { + async method(x, y, z) { Gadgets.multiRangeCheck([x, y, z]); }, }, checkCompact: { privateInputs: [Field, Field], - method(xy, z) { + async method(xy, z) { let [x, y] = Gadgets.compactMultiRangeCheck(xy, z); x.add(y.mul(1n << l)).assertEquals(xy); }, diff --git a/src/lib/gadgets/sha256.unit-test.ts b/src/lib/gadgets/sha256.unit-test.ts index f795ec6cb9..dbb5954a45 100644 --- a/src/lib/gadgets/sha256.unit-test.ts +++ b/src/lib/gadgets/sha256.unit-test.ts @@ -24,7 +24,7 @@ const Sha256Program = ZkProgram({ methods: { sha256: { privateInputs: [Bytes(192).provable], - method(preImage: Bytes) { + async method(preImage: Bytes) { return Gadgets.SHA256.hash(preImage); }, }, diff --git a/src/lib/keccak.unit-test.ts b/src/lib/keccak.unit-test.ts index 090a53e915..60c2aa45e8 100644 --- a/src/lib/keccak.unit-test.ts +++ b/src/lib/keccak.unit-test.ts @@ -126,13 +126,13 @@ const KeccakProgram = ZkProgram({ methods: { nistSha3: { privateInputs: [], - method(preImage: Bytes) { + async method(preImage: Bytes) { return Keccak.nistSha3(digestLength, preImage); }, }, preNist: { privateInputs: [], - method(preImage: Bytes) { + async method(preImage: Bytes) { return Keccak.preNist(digestLength, preImage); }, }, diff --git a/src/lib/proof-system.unit-test.ts b/src/lib/proof-system.unit-test.ts index fef3c3735e..5f85d65048 100644 --- a/src/lib/proof-system.unit-test.ts +++ b/src/lib/proof-system.unit-test.ts @@ -21,7 +21,7 @@ import { FieldConst, FieldVar } from './field.js'; const EmptyProgram = ZkProgram({ name: 'empty', publicInput: Field, - methods: { run: { privateInputs: [], method: (_) => {} } }, + methods: { run: { privateInputs: [], async method(_) {} } }, }); class EmptyProof extends ZkProgram.Proof(EmptyProgram) {} @@ -108,7 +108,7 @@ const program = ZkProgram({ methods: { baseCase: { privateInputs: [Provable.Array(Field, N)], - method(_: Field[]) {}, + async method(_: Field[]) {}, }, }, }); @@ -138,10 +138,10 @@ const CounterProgram = ZkProgram({ methods: { increment: { privateInputs: [UInt64], - method: ( + async method( { current, updated }: CounterPublicInput, incrementBy: UInt64 - ) => { + ) { const newCount = current.add(incrementBy); newCount.assertEquals(updated); }, diff --git a/src/mina-signer/tests/verify-in-snark.unit-test.ts b/src/mina-signer/tests/verify-in-snark.unit-test.ts index af9b2a3656..02e692fc88 100644 --- a/src/mina-signer/tests/verify-in-snark.unit-test.ts +++ b/src/mina-signer/tests/verify-in-snark.unit-test.ts @@ -36,7 +36,7 @@ const MyProgram = ZkProgram({ methods: { verifySignature: { privateInputs: [Signature, Message], - method(signature: Signature, message: Field[]) { + async method(signature: Signature, message: Field[]) { signature.verify(publicKey, message).assertTrue(); }, }, From e818a05d96d1620528b960a0579f8e356eca052b Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 5 Mar 2024 21:58:59 +0100 Subject: [PATCH 14/16] adapt examples to async zkprograms --- src/examples/benchmarks/mul-web.ts | 2 +- src/examples/benchmarks/mul.ts | 2 +- src/examples/crypto/ecdsa/ecdsa.ts | 4 ++-- src/examples/crypto/sha256/sha256.ts | 2 +- src/examples/zkprogram/gadgets.ts | 6 +++--- src/examples/zkprogram/program-with-input.ts | 7 ++----- src/examples/zkprogram/program.ts | 7 ++----- 7 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/examples/benchmarks/mul-web.ts b/src/examples/benchmarks/mul-web.ts index 8f964a47d0..1ad1d31912 100644 --- a/src/examples/benchmarks/mul-web.ts +++ b/src/examples/benchmarks/mul-web.ts @@ -42,7 +42,7 @@ function picklesCircuit(nMuls: number) { methods: { run: { privateInputs: [], - method() { + async method() { main(nMuls); }, }, diff --git a/src/examples/benchmarks/mul.ts b/src/examples/benchmarks/mul.ts index ebe407a3e6..97211864d7 100644 --- a/src/examples/benchmarks/mul.ts +++ b/src/examples/benchmarks/mul.ts @@ -40,7 +40,7 @@ function picklesCircuit(nMuls: number) { methods: { run: { privateInputs: [], - method() { + async method() { main(nMuls); }, }, diff --git a/src/examples/crypto/ecdsa/ecdsa.ts b/src/examples/crypto/ecdsa/ecdsa.ts index d681a8037e..8e9e951ee6 100644 --- a/src/examples/crypto/ecdsa/ecdsa.ts +++ b/src/examples/crypto/ecdsa/ecdsa.ts @@ -22,7 +22,7 @@ const keccakAndEcdsa = ZkProgram({ methods: { verifyEcdsa: { privateInputs: [Ecdsa.provable, Secp256k1.provable], - method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) { + async method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) { return signature.verify(message, publicKey); }, }, @@ -37,7 +37,7 @@ const ecdsa = ZkProgram({ methods: { verifySignedHash: { privateInputs: [Ecdsa.provable, Secp256k1.provable], - method(message: Scalar, signature: Ecdsa, publicKey: Secp256k1) { + async method(message: Scalar, signature: Ecdsa, publicKey: Secp256k1) { return signature.verifySignedHash(message, publicKey); }, }, diff --git a/src/examples/crypto/sha256/sha256.ts b/src/examples/crypto/sha256/sha256.ts index a85357bda6..a6e335fd7f 100644 --- a/src/examples/crypto/sha256/sha256.ts +++ b/src/examples/crypto/sha256/sha256.ts @@ -10,7 +10,7 @@ let SHA256Program = ZkProgram({ methods: { sha256: { privateInputs: [Bytes12.provable], - method(xs: Bytes12) { + async method(xs: Bytes12) { return Gadgets.SHA256.hash(xs); }, }, diff --git a/src/examples/zkprogram/gadgets.ts b/src/examples/zkprogram/gadgets.ts index d1bc8e5d50..e3cdb0aabe 100644 --- a/src/examples/zkprogram/gadgets.ts +++ b/src/examples/zkprogram/gadgets.ts @@ -19,7 +19,7 @@ const BitwiseProver = ZkProgram({ methods: { rot: { privateInputs: [], - method: () => { + async method() { let a = Provable.witness(Field, () => Field(48)); let actualLeft = Gadgets.rotate64(a, 2, 'left'); let actualRight = Gadgets.rotate64(a, 2, 'right'); @@ -33,7 +33,7 @@ const BitwiseProver = ZkProgram({ }, xor: { privateInputs: [], - method: () => { + async method() { let a = Provable.witness(Field, () => Field(5)); let b = Provable.witness(Field, () => Field(2)); let actual = Gadgets.xor(a, b, 4); @@ -43,7 +43,7 @@ const BitwiseProver = ZkProgram({ }, and: { privateInputs: [], - method: () => { + async method() { let a = Provable.witness(Field, () => Field(3)); let b = Provable.witness(Field, () => Field(5)); let actual = Gadgets.and(a, b, 4); diff --git a/src/examples/zkprogram/program-with-input.ts b/src/examples/zkprogram/program-with-input.ts index 16aab5ab1c..b50cf4c65f 100644 --- a/src/examples/zkprogram/program-with-input.ts +++ b/src/examples/zkprogram/program-with-input.ts @@ -3,14 +3,11 @@ import { Field, ZkProgram, verify, - isReady, Proof, JsonProof, Provable, } from 'o1js'; -await isReady; - let MyProgram = ZkProgram({ name: 'example-with-input', publicInput: Field, @@ -18,14 +15,14 @@ let MyProgram = ZkProgram({ methods: { baseCase: { privateInputs: [], - method(input: Field) { + async method(input: Field) { input.assertEquals(Field(0)); }, }, inductiveCase: { privateInputs: [SelfProof], - method(input: Field, earlierProof: SelfProof) { + async method(input: Field, earlierProof: SelfProof) { earlierProof.verify(); earlierProof.publicInput.add(1).assertEquals(input); }, diff --git a/src/examples/zkprogram/program.ts b/src/examples/zkprogram/program.ts index 7cc09602b1..b5d6036df8 100644 --- a/src/examples/zkprogram/program.ts +++ b/src/examples/zkprogram/program.ts @@ -3,15 +3,12 @@ import { Field, ZkProgram, verify, - isReady, Proof, JsonProof, Provable, Empty, } from 'o1js'; -await isReady; - let MyProgram = ZkProgram({ name: 'example-with-output', publicOutput: Field, @@ -19,14 +16,14 @@ let MyProgram = ZkProgram({ methods: { baseCase: { privateInputs: [], - method() { + async method() { return Field(0); }, }, inductiveCase: { privateInputs: [SelfProof], - method(earlierProof: SelfProof) { + async method(earlierProof: SelfProof) { earlierProof.verify(); return earlierProof.publicOutput.add(1); }, From 294a662bfaaa0db00718c95834df0775cf00067f Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 6 Mar 2024 09:24:48 +0100 Subject: [PATCH 15/16] changelog --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d985e9f33d..72bd28179e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,15 +19,21 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Breaking changes +- **Async circuits**. Require all smart contract and zkprogram methods to be async https://github.com/o1-labs/o1js/pull/1477 + - This change allows you to use `await` inside your methods. Change the method signature by adding the `async` keyword. + - Don't forget to add `await` to all contract calls! `await MyContract.myMethod();` + - To declare a return value from a method, use the new `@method.returns()` decorator - Require the callback to `Mina.transaction()` to be async https://github.com/o1-labs/o1js/pull/1468 - - This change was done in support to support async contract methods - Change `{SmartContract,ZkProgram}.analyzeMethods()` to be async https://github.com/o1-labs/o1js/pull/1450 - `Provable.runAndCheck()`, `Provable.constraintSystem()` and `{SmartContract,ZkProgram}.digest()` are also async now - - These changes were made to add internal support for async circuits - `Provable.runAndCheckSync()` added and immediately deprecated for a smoother upgrade path for tests - `Reducer.reduce()` requires the maximum number of actions per method as an explicit (optional) argument https://github.com/o1-labs/o1js/pull/1450 - The default value is 1 and should work for most existing contracts +### Added + +- `Provable.witnessAsync()` to introduce provable values from an async callback https://github.com/o1-labs/o1js/pull/1468 + ## [0.17.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...74948acac) - 2024-03-06 ### Breaking changes @@ -45,7 +51,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- `Provable.witnessAsync()` to introduce provable values from an async callback https://github.com/o1-labs/o1js/pull/1468 - Support for custom network identifiers other than `mainnet` or `testnet` https://github.com/o1-labs/o1js/pull/1444 - `PrivateKey.randomKeypair()` to generate private and public key in one command https://github.com/o1-labs/o1js/pull/1446 - `setNumberOfWorkers()` to allow developer to override the number of workers used during compilation and proof generation/verification https://github.com/o1-labs/o1js/pull/1456 From 402b7c897ab03a957c1afa6c8488f36234aad2dd Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 7 Mar 2024 13:00:47 +0100 Subject: [PATCH 16/16] fixup example after merge --- src/examples/zkapps/voting/run-berkeley.ts | 12 ++++++------ src/lib/mina/transaction.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/examples/zkapps/voting/run-berkeley.ts b/src/examples/zkapps/voting/run-berkeley.ts index 722e29e98c..a14808a029 100644 --- a/src/examples/zkapps/voting/run-berkeley.ts +++ b/src/examples/zkapps/voting/run-berkeley.ts @@ -109,7 +109,7 @@ let tx = await Mina.transaction( } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); +await (await tx.sign([feePayerKey]).send()).wait(); console.log('successfully deployed contracts'); @@ -132,7 +132,7 @@ tx = await Mina.transaction( } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); +await (await tx.sign([feePayerKey]).send()).wait(); console.log('voter registered'); await fetchAllAccounts(); @@ -154,7 +154,7 @@ tx = await Mina.transaction( } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); +await (await tx.sign([feePayerKey]).send()).wait(); console.log('candidate registered'); // we have to wait a few seconds before continuing, otherwise we might not get the actions from the archive, we if continue too fast await new Promise((resolve) => setTimeout(resolve, 20000)); @@ -173,7 +173,7 @@ tx = await Mina.transaction( } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); +await (await tx.sign([feePayerKey]).send()).wait(); console.log('registrations approved'); await fetchAllAccounts(); @@ -199,7 +199,7 @@ tx = await Mina.transaction( } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); +await (await tx.sign([feePayerKey]).send()).wait(); vote(0n, storage.votesStore, storage.candidatesStore); console.log('voted for a candidate'); await new Promise((resolve) => setTimeout(resolve, 20000)); @@ -214,7 +214,7 @@ tx = await Mina.transaction( } ); await tx.prove(); -await (await tx.sign([feePayerKey]).send()).waitOrThrowIfError(); +await (await tx.sign([feePayerKey]).send()).wait(); console.log('votes counted'); await new Promise((resolve) => setTimeout(resolve, 20000)); diff --git a/src/lib/mina/transaction.ts b/src/lib/mina/transaction.ts index c9022c3038..a4b719cc34 100644 --- a/src/lib/mina/transaction.ts +++ b/src/lib/mina/transaction.ts @@ -165,7 +165,7 @@ type PendingTransaction = Pick< * @example * ```ts * try { - * const transaction = await pendingTransaction.waitOrThrowIfError({ maxAttempts: 10, interval: 2000 }); + * const transaction = await pendingTransaction.wait({ maxAttempts: 10, interval: 2000 }); * console.log('Transaction included in a block.'); * } catch (error) { * console.error('Transaction rejected or failed to finalize:', error);