From f9ae5353b1c4c2065cf98ff28db28a773257f605 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sat, 25 Jun 2022 13:05:16 -0700 Subject: [PATCH 01/49] feat: Add transfer of custom tokens naively --- src/lib/party.ts | 20 ++++++++++++++++++++ src/lib/zkapp.ts | 10 +++++++++- src/snarky.d.ts | 4 ++++ src/snarky/snarky-class-spec.json | 8 ++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index a502322392..07ebaa8949 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -516,6 +516,26 @@ class Party { return new (Party as any)(body, authorization, party.isSelf); } + get tokenId() { + return this.body.tokenId; + } + + public transfer( + amount: Int64 | UInt32 | UInt64 | string | number | bigint, + receiver: Party, + tokenId?: string + ) { + let party = this; + party.body.balanceChange = party.body.balanceChange.sub(amount); + receiver.body.balanceChange = receiver.body.balanceChange.add(amount); + + if (tokenId) { + let token = Ledger.fieldOfBase58(tokenId); + party.body.tokenId = token; + receiver.body.tokenId = token; + } + } + get balance() { let party = this; return { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 75f818d66e..285c5b1cad 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -22,7 +22,7 @@ import { } from './party'; import { PrivateKey, PublicKey } from './signature'; import * as Mina from './mina'; -import { UInt32, UInt64 } from './int'; +import { Int64, UInt32, UInt64 } from './int'; import { mainContext, inCheckedComputation, @@ -333,6 +333,14 @@ class SmartContract { return this.self.network; } + transfer( + amount: Int64 | UInt32 | UInt64 | string | number | bigint, + receiver: Party, + tokenId?: string + ) { + return this.self.transfer(amount, receiver, tokenId); + } + get balance() { return this.self.balance; } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 2bd6b25e00..d596591f4b 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,3 +1,5 @@ +import { TokenId } from 'snarky/parties-leaves-json'; + export { Field, Bool, @@ -755,6 +757,8 @@ declare class Ledger { i: number ): string; + static customTokenID(publicKey: { g: Group }): string; + static publicKeyToString(publicKey: { g: Group }): string; static publicKeyOfString(publicKeyBase58: string): Group; static privateKeyToString(privateKey: { s: Scalar }): string; diff --git a/src/snarky/snarky-class-spec.json b/src/snarky/snarky-class-spec.json index d3f143346c..5f32b17ca8 100644 --- a/src/snarky/snarky-class-spec.json +++ b/src/snarky/snarky-class-spec.json @@ -384,6 +384,14 @@ "name": "create", "type": "function" }, + { + "name": "customTokenID", + "type": "function" + }, + { + "name": "createTokenAccount", + "type": "function" + }, { "name": "hashTransaction", "type": "function" From 980c1d8d180c64e197d1e5df1f90cc380ef74f11 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 5 Jul 2022 12:46:36 -0700 Subject: [PATCH 02/49] Mess around with Parties --- src/lib/party.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 07ebaa8949..f8a2f72243 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -527,17 +527,24 @@ class Party { ) { let party = this; party.body.balanceChange = party.body.balanceChange.sub(amount); + party.body.useFullCommitment = Bool(true); + receiver.body.balanceChange = receiver.body.balanceChange.add(amount); if (tokenId) { - let token = Ledger.fieldOfBase58(tokenId); - party.body.tokenId = token; + const token = Ledger.fieldOfBase58(tokenId); + console.log('LOOK HERE TOKEN', token); + console.log('LOOK HERE TOKEN1', receiver.body.tokenId); receiver.body.tokenId = token; + receiver.body.caller = token; + receiver.body.callDepth = 1; + // receiver.body.useFullCommitment = Bool(true); } } get balance() { let party = this; + return { addInPlace(x: Int64 | UInt32 | UInt64 | string | number | bigint) { party.body.balanceChange = party.body.balanceChange.add(x); From 7ddbaa2e032cff3b23341c55ffba2ce11bc5a5cc Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 5 Jul 2022 15:34:14 -0700 Subject: [PATCH 03/49] feat: Add token class --- src/lib/hash.ts | 2 +- src/lib/party.ts | 33 +++++++++++++++++++++++++++++++-- src/lib/token.ts | 21 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/lib/token.ts diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 493eb09c02..5ac7dd1384 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -5,7 +5,7 @@ import { inCheckedComputation } from './global-context'; export { Poseidon }; // internal API -export { prefixes, emptyHashWithPrefix, hashWithPrefix }; +export { prefixes, emptyHashWithPrefix, hashWithPrefix, salt }; class Sponge { private sponge: unknown; diff --git a/src/lib/party.ts b/src/lib/party.ts index f8a2f72243..33dc26f954 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -8,6 +8,8 @@ import { withContextAsync } from './global-context'; import * as Precondition from './precondition'; import { Proof } from './proof_system'; import { emptyHashWithPrefix, hashWithPrefix, prefixes } from './hash'; +import { salt } from './hash'; +import { Token } from './token'; export { SetOrKeep, @@ -492,6 +494,12 @@ type UnfinishedSignature = undefined | LazySignature | string; type LazyControl = Control | LazySignature | LazyProof; +type SendParams = { + from: PublicKey; + to: PublicKey; + amount: Int64 | UInt32 | UInt64 | string | number | bigint; +}; + class Party { body: Body; authorization: LazyControl; @@ -516,6 +524,29 @@ class Party { return new (Party as any)(body, authorization, party.isSelf); } + get token() { + let thisParty = this; + let customToken = new Token(thisParty.body.publicKey); + + return { + transfer({ from, to, amount }: SendParams) { + const party = from ? Party.createUnsigned(from) : thisParty; + const receiver = Party.createUnsigned(to); + + party.body.balanceChange = party.body.balanceChange.sub(amount); + receiver.body.balanceChange = receiver.body.balanceChange.add(amount); + + const token = Ledger.fieldOfBase58(customToken.id); + console.log('LOOK HERE TOKEN', token); + console.log('LOOK HERE TOKEN1', receiver.body.tokenId); + receiver.body.tokenId = token; + receiver.body.caller = token; + receiver.body.callDepth = 1; + // receiver.body.useFullCommitment = Bool(true); + }, + }; + } + get tokenId() { return this.body.tokenId; } @@ -527,8 +558,6 @@ class Party { ) { let party = this; party.body.balanceChange = party.body.balanceChange.sub(amount); - party.body.useFullCommitment = Bool(true); - receiver.body.balanceChange = receiver.body.balanceChange.add(amount); if (tokenId) { diff --git a/src/lib/token.ts b/src/lib/token.ts new file mode 100644 index 0000000000..25c15fe362 --- /dev/null +++ b/src/lib/token.ts @@ -0,0 +1,21 @@ +import { TokenId } from 'snarky/parties-leaves'; +import { PublicKey } from './signature'; +import { Field } from '../snarky'; +import { Ledger } from '../snarky'; + +const getDefaultTokenId = () => Field.one.toString(); + +export class Token { + readonly id: string; + readonly parentTokenId: string; + readonly tokenOwner: PublicKey; + + constructor( + tokenOwner: PublicKey, + parentTokenId: string = getDefaultTokenId() + ) { + this.parentTokenId = parentTokenId; + this.tokenOwner = tokenOwner; + this.id = Ledger.customTokenID(tokenOwner); + } +} From c9ebc35dd2286aed9fcc638b88c740622e3cadaa Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 6 Jul 2022 12:09:19 -0700 Subject: [PATCH 04/49] this.token.transfer successfully changes balances with public key input --- src/lib/party.ts | 41 +++++++++++++++++++++++++++-------------- src/lib/zkapp.ts | 4 ++++ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 33dc26f954..b89d1cee81 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -500,6 +500,11 @@ type SendParams = { amount: Int64 | UInt32 | UInt64 | string | number | bigint; }; +type MintOrBurnParams = { + address: PublicKey; + amount: Int64 | UInt32 | UInt64 | string | number | bigint; +}; + class Party { body: Body; authorization: LazyControl; @@ -526,23 +531,31 @@ class Party { get token() { let thisParty = this; - let customToken = new Token(thisParty.body.publicKey); + let customToken = new Token(this.body.publicKey); return { + id: customToken.id, + + parentTokenId: customToken.parentTokenId, + + tokenOwner: customToken.tokenOwner, + + mint({ address, amount }: MintOrBurnParams) {}, + + burn({ address, amount }: MintOrBurnParams) {}, + transfer({ from, to, amount }: SendParams) { - const party = from ? Party.createUnsigned(from) : thisParty; - const receiver = Party.createUnsigned(to); - - party.body.balanceChange = party.body.balanceChange.sub(amount); - receiver.body.balanceChange = receiver.body.balanceChange.add(amount); - - const token = Ledger.fieldOfBase58(customToken.id); - console.log('LOOK HERE TOKEN', token); - console.log('LOOK HERE TOKEN1', receiver.body.tokenId); - receiver.body.tokenId = token; - receiver.body.caller = token; - receiver.body.callDepth = 1; - // receiver.body.useFullCommitment = Bool(true); + if (from === thisParty.publicKey) { + thisParty.body.balanceChange = + thisParty.body.balanceChange.sub(amount); + } else { + let senderParty = Party.createUnsigned(from); + senderParty.body.balanceChange = + senderParty.body.balanceChange.sub(amount); + } + let receiverParty = Party.createUnsigned(to); + receiverParty.body.balanceChange = + receiverParty.body.balanceChange.add(amount); }, }; } diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 285c5b1cad..1f8c954d7e 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -333,6 +333,10 @@ class SmartContract { return this.self.network; } + get token() { + return this.self.token; + } + transfer( amount: Int64 | UInt32 | UInt64 | string | number | bigint, receiver: Party, From c5a34d522f0c32cdf93ca0a904ff4a43311a8e9e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 6 Jul 2022 14:28:35 -0700 Subject: [PATCH 05/49] feat: Brute force custom token transfer --- src/lib/party.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index b89d1cee81..900cef1f32 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -546,16 +546,25 @@ class Party { transfer({ from, to, amount }: SendParams) { if (from === thisParty.publicKey) { - thisParty.body.balanceChange = - thisParty.body.balanceChange.sub(amount); + thisParty.body.balanceChange = thisParty.body.balanceChange.sub( + Mina.accountCreationFee() + ); } else { let senderParty = Party.createUnsigned(from); - senderParty.body.balanceChange = - senderParty.body.balanceChange.sub(amount); + senderParty.body.balanceChange = senderParty.body.balanceChange.sub( + Mina.accountCreationFee() + ); } + let receiverParty = Party.createUnsigned(to); receiverParty.body.balanceChange = receiverParty.body.balanceChange.add(amount); + + const token = Ledger.fieldOfBase58(customToken.id); + + receiverParty.body.tokenId = token; + receiverParty.body.caller = token; + receiverParty.body.callDepth = 1; }, }; } From c87de4cc89b8ac2c4b464f1fbb331c93d8715581 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 6 Jul 2022 15:03:08 -0700 Subject: [PATCH 06/49] Change createUnsigned to use optional object parameter for tokens --- src/lib/party.ts | 56 +++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 900cef1f32..0260cc603e 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -498,6 +498,7 @@ type SendParams = { from: PublicKey; to: PublicKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; + accountCreation?: boolean; }; type MintOrBurnParams = { @@ -531,7 +532,7 @@ class Party { get token() { let thisParty = this; - let customToken = new Token(this.body.publicKey); + let customToken = new Token(thisParty.body.publicKey); return { id: customToken.id, @@ -544,27 +545,31 @@ class Party { burn({ address, amount }: MintOrBurnParams) {}, - transfer({ from, to, amount }: SendParams) { + transfer({ from, to, amount, accountCreation }: SendParams) { if (from === thisParty.publicKey) { - thisParty.body.balanceChange = thisParty.body.balanceChange.sub( - Mina.accountCreationFee() - ); + if (accountCreation) { + thisParty.body.balanceChange = thisParty.body.balanceChange.sub( + Mina.accountCreationFee() + ); + } } else { - let senderParty = Party.createUnsigned(from); - senderParty.body.balanceChange = senderParty.body.balanceChange.sub( - Mina.accountCreationFee() - ); + if (accountCreation) { + let senderParty = Party.createUnsigned(from); + senderParty.body.balanceChange = senderParty.body.balanceChange.sub( + Mina.accountCreationFee() + ); + } } - let receiverParty = Party.createUnsigned(to); - receiverParty.body.balanceChange = - receiverParty.body.balanceChange.add(amount); - const token = Ledger.fieldOfBase58(customToken.id); + let receiverParty = Party.createUnsigned(to, { + caller: token, + tokenId: token, + callDepth: 1, // TODO: Make this smarter + }); - receiverParty.body.tokenId = token; - receiverParty.body.caller = token; - receiverParty.body.callDepth = 1; + receiverParty.body.balanceChange = + receiverParty.body.balanceChange.add(amount); }, }; } @@ -747,18 +752,29 @@ class Party { return { body, authorization: undefined }; } - static createUnsigned(publicKey: PublicKey) { + static createUnsigned( + publicKey: PublicKey, + bodyInput?: { + caller?: Field; + tokenId?: Field; + callDepth?: number; + } + ) { // TODO: This should be a witness block that uses the setVariable // API to set the value of a variable after it's allocated - - const pk = publicKey; - const body: Body = Body.keepAll(pk); if (Mina.currentTransaction === undefined) { throw new Error( 'Party.createUnsigned: Cannot run outside of a transaction' ); } + const pk = publicKey; + const body: Body = Body.keepAll(pk); + const { caller, tokenId, callDepth } = bodyInput || {}; + body.caller = caller || body.caller; + body.tokenId = tokenId || body.tokenId; + body.callDepth = callDepth || body.callDepth; + const party = new Party(body); Mina.currentTransaction.nextPartyIndex++; Mina.currentTransaction.parties.push(party); From 4d44977863f60ea840b5edfbdc8481b8866a9d0b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 6 Jul 2022 15:18:51 -0700 Subject: [PATCH 07/49] Remove old transfer --- src/lib/party.ts | 20 -------------------- src/lib/zkapp.ts | 8 -------- 2 files changed, 28 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 0260cc603e..a2a078b6a8 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -578,26 +578,6 @@ class Party { return this.body.tokenId; } - public transfer( - amount: Int64 | UInt32 | UInt64 | string | number | bigint, - receiver: Party, - tokenId?: string - ) { - let party = this; - party.body.balanceChange = party.body.balanceChange.sub(amount); - receiver.body.balanceChange = receiver.body.balanceChange.add(amount); - - if (tokenId) { - const token = Ledger.fieldOfBase58(tokenId); - console.log('LOOK HERE TOKEN', token); - console.log('LOOK HERE TOKEN1', receiver.body.tokenId); - receiver.body.tokenId = token; - receiver.body.caller = token; - receiver.body.callDepth = 1; - // receiver.body.useFullCommitment = Bool(true); - } - } - get balance() { let party = this; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 1f8c954d7e..2c9416b49e 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -337,14 +337,6 @@ class SmartContract { return this.self.token; } - transfer( - amount: Int64 | UInt32 | UInt64 | string | number | bigint, - receiver: Party, - tokenId?: string - ) { - return this.self.transfer(amount, receiver, tokenId); - } - get balance() { return this.self.balance; } From 25b06b7847a37d92538cc8cc1a5c4199471cb115 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 7 Jul 2022 13:40:48 -0700 Subject: [PATCH 08/49] Use Bool() for account creation fee for token accounts --- src/lib/party.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index a2a078b6a8..6321ebaf9b 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -498,7 +498,7 @@ type SendParams = { from: PublicKey; to: PublicKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; - accountCreation?: boolean; + newTokenAccount: Bool | boolean; }; type MintOrBurnParams = { @@ -545,20 +545,22 @@ class Party { burn({ address, amount }: MintOrBurnParams) {}, - transfer({ from, to, amount, accountCreation }: SendParams) { + transfer({ from, to, amount, newTokenAccount }: SendParams) { + let tokenCreationFee = Circuit.if( + newTokenAccount, + Mina.accountCreationFee(), + UInt64.zero + ); + + console.log('tokenCreationFee', tokenCreationFee); + if (from === thisParty.publicKey) { - if (accountCreation) { - thisParty.body.balanceChange = thisParty.body.balanceChange.sub( - Mina.accountCreationFee() - ); - } + thisParty.body.balanceChange = + thisParty.body.balanceChange.sub(tokenCreationFee); } else { - if (accountCreation) { - let senderParty = Party.createUnsigned(from); - senderParty.body.balanceChange = senderParty.body.balanceChange.sub( - Mina.accountCreationFee() - ); - } + let senderParty = Party.createUnsigned(from); + senderParty.body.balanceChange = + senderParty.body.balanceChange.sub(tokenCreationFee); } const token = Ledger.fieldOfBase58(customToken.id); From 74bbd4fc56653c9e0577a62e9fe98491516bd8cb Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 8 Jul 2022 10:53:51 -0700 Subject: [PATCH 09/49] Add basic mint/burn methods --- src/lib/party.ts | 59 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 6321ebaf9b..1e5c83773c 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -504,6 +504,7 @@ type SendParams = { type MintOrBurnParams = { address: PublicKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; + newTokenAccount: Bool | boolean; }; class Party { @@ -533,6 +534,7 @@ class Party { get token() { let thisParty = this; let customToken = new Token(thisParty.body.publicKey); + const token = Ledger.fieldOfBase58(customToken.id); return { id: customToken.id, @@ -541,29 +543,76 @@ class Party { tokenOwner: customToken.tokenOwner, - mint({ address, amount }: MintOrBurnParams) {}, + mint({ address, amount, newTokenAccount }: MintOrBurnParams) { + let tokenCreationFee = Circuit.if( + newTokenAccount, + Mina.accountCreationFee(), + UInt64.zero + ); - burn({ address, amount }: MintOrBurnParams) {}, + thisParty.body.balanceChange = + thisParty.body.balanceChange.sub(tokenCreationFee); - transfer({ from, to, amount, newTokenAccount }: SendParams) { + let receiverParty = Party.createUnsigned(address, { + caller: token, + tokenId: token, + callDepth: 1, // TODO: Make this smarter + }); + receiverParty.body.balanceChange = + receiverParty.body.balanceChange.add(amount); + }, + + burn({ address, amount, newTokenAccount }: MintOrBurnParams) { let tokenCreationFee = Circuit.if( newTokenAccount, Mina.accountCreationFee(), UInt64.zero ); - console.log('tokenCreationFee', tokenCreationFee); + thisParty.body.balanceChange = + thisParty.body.balanceChange.sub(tokenCreationFee); + + let receiverParty = Party.createUnsigned(address, { + caller: token, + tokenId: token, + callDepth: 1, // TODO: Make this smarter + }); + receiverParty.body.balanceChange = + receiverParty.body.balanceChange.sub(amount); + }, + + transfer({ from, to, amount, newTokenAccount }: SendParams) { + let tokenCreationFee = Circuit.if( + newTokenAccount, + Mina.accountCreationFee(), + UInt64.zero + ); if (from === thisParty.publicKey) { thisParty.body.balanceChange = thisParty.body.balanceChange.sub(tokenCreationFee); + + let transferParty = Party.createUnsigned(thisParty.publicKey, { + caller: token, + tokenId: token, + }); + + transferParty.body.balanceChange = + transferParty.body.balanceChange.sub(amount); } else { let senderParty = Party.createUnsigned(from); senderParty.body.balanceChange = senderParty.body.balanceChange.sub(tokenCreationFee); + + let transferParty = Party.createUnsigned(from, { + caller: token, + tokenId: token, + callDepth: 1, + }); + transferParty.body.balanceChange = + transferParty.body.balanceChange.sub(amount); } - const token = Ledger.fieldOfBase58(customToken.id); let receiverParty = Party.createUnsigned(to, { caller: token, tokenId: token, From 432abc1879d3395f99d26d9fa3af1e5a24f4869b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 11 Jul 2022 10:21:42 -0700 Subject: [PATCH 10/49] wip: burn and transfer pass transaction logic --- src/lib/party.ts | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 1e5c83773c..494c62671c 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -495,14 +495,15 @@ type UnfinishedSignature = undefined | LazySignature | string; type LazyControl = Control | LazySignature | LazyProof; type SendParams = { - from: PublicKey; - to: PublicKey; + from: PrivateKey; + to: PrivateKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; newTokenAccount: Bool | boolean; }; type MintOrBurnParams = { address: PublicKey; + privateKey: PrivateKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; newTokenAccount: Bool | boolean; }; @@ -562,7 +563,7 @@ class Party { receiverParty.body.balanceChange.add(amount); }, - burn({ address, amount, newTokenAccount }: MintOrBurnParams) { + burn({ address, amount, newTokenAccount, privateKey }: MintOrBurnParams) { let tokenCreationFee = Circuit.if( newTokenAccount, Mina.accountCreationFee(), @@ -579,6 +580,8 @@ class Party { }); receiverParty.body.balanceChange = receiverParty.body.balanceChange.sub(amount); + + receiverParty.signInPlace(privateKey); }, transfer({ from, to, amount, newTokenAccount }: SendParams) { @@ -588,7 +591,13 @@ class Party { UInt64.zero ); - if (from === thisParty.publicKey) { + let fromAddress = from.toPublicKey(); + let toAddress = to.toPublicKey(); + + console.log('---TOKEN ACCOUNT1', fromAddress.toBase58()); + console.log('---TOKEN ACCOUNT2', toAddress.toBase58()); + + if (fromAddress === thisParty.publicKey) { thisParty.body.balanceChange = thisParty.body.balanceChange.sub(tokenCreationFee); @@ -600,20 +609,20 @@ class Party { transferParty.body.balanceChange = transferParty.body.balanceChange.sub(amount); } else { - let senderParty = Party.createUnsigned(from); - senderParty.body.balanceChange = - senderParty.body.balanceChange.sub(tokenCreationFee); + thisParty.body.balanceChange = + thisParty.body.balanceChange.sub(tokenCreationFee); - let transferParty = Party.createUnsigned(from, { + let transferParty = Party.createUnsigned(fromAddress, { caller: token, tokenId: token, callDepth: 1, }); transferParty.body.balanceChange = transferParty.body.balanceChange.sub(amount); + transferParty.signInPlace(from); } - let receiverParty = Party.createUnsigned(to, { + let receiverParty = Party.createUnsigned(toAddress, { caller: token, tokenId: token, callDepth: 1, // TODO: Make this smarter @@ -621,6 +630,7 @@ class Party { receiverParty.body.balanceChange = receiverParty.body.balanceChange.add(amount); + receiverParty.signInPlace(to); }, }; } @@ -785,7 +795,7 @@ class Party { static createUnsigned( publicKey: PublicKey, - bodyInput?: { + options?: { caller?: Field; tokenId?: Field; callDepth?: number; @@ -801,10 +811,11 @@ class Party { const pk = publicKey; const body: Body = Body.keepAll(pk); - const { caller, tokenId, callDepth } = bodyInput || {}; + const { caller, tokenId, callDepth } = options ?? {}; body.caller = caller || body.caller; body.tokenId = tokenId || body.tokenId; body.callDepth = callDepth || body.callDepth; + // body.useFullCommitment = Bool(true); const party = new Party(body); Mina.currentTransaction.nextPartyIndex++; @@ -814,7 +825,13 @@ class Party { static createSigned( signer: PrivateKey, - options?: { isSameAsFeePayer?: Bool | boolean; nonce?: UInt32 } + options?: { + isSameAsFeePayer?: Bool | boolean; + nonce?: UInt32; + // caller?: Field; + // tokenId?: Field; + // callDepth?: number; + } ) { let { nonce, isSameAsFeePayer } = options ?? {}; // if not specified, optimistically determine isSameAsFeePayer from the current transaction @@ -859,6 +876,11 @@ class Party { nonce = nonce.add(nonceIncrement); Party.assertEquals(body.preconditions.account.nonce, nonce); body.incrementNonce = Bool(true); + // const { caller, tokenId, callDepth } = options ?? {}; + // body.caller = caller || body.caller; + // body.tokenId = tokenId || body.tokenId; + // body.callDepth = callDepth || body.callDepth; + // body.useFullCommitment = Bool(true); let party = new Party(body); party.authorization = { kind: 'lazy-signature', privateKey: signer }; From a94b68b5b5e7a37edb8ebd6e8512b3660c2d341f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 11 Jul 2022 16:09:08 -0700 Subject: [PATCH 11/49] Set lazy-signature for required parties in token transfers --- src/lib/party.ts | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 494c62671c..efaa9d6985 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -495,15 +495,14 @@ type UnfinishedSignature = undefined | LazySignature | string; type LazyControl = Control | LazySignature | LazyProof; type SendParams = { - from: PrivateKey; - to: PrivateKey; + from: PublicKey; + to: PublicKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; newTokenAccount: Bool | boolean; }; type MintOrBurnParams = { address: PublicKey; - privateKey: PrivateKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; newTokenAccount: Bool | boolean; }; @@ -558,12 +557,14 @@ class Party { caller: token, tokenId: token, callDepth: 1, // TODO: Make this smarter + useFullCommitment: Bool(true), }); + receiverParty.body.balanceChange = receiverParty.body.balanceChange.add(amount); }, - burn({ address, amount, newTokenAccount, privateKey }: MintOrBurnParams) { + burn({ address, amount, newTokenAccount }: MintOrBurnParams) { let tokenCreationFee = Circuit.if( newTokenAccount, Mina.accountCreationFee(), @@ -577,11 +578,13 @@ class Party { caller: token, tokenId: token, callDepth: 1, // TODO: Make this smarter + useFullCommitment: Bool(true), }); receiverParty.body.balanceChange = receiverParty.body.balanceChange.sub(amount); - - receiverParty.signInPlace(privateKey); + receiverParty.authorization = { + kind: 'lazy-signature', + }; }, transfer({ from, to, amount, newTokenAccount }: SendParams) { @@ -591,13 +594,7 @@ class Party { UInt64.zero ); - let fromAddress = from.toPublicKey(); - let toAddress = to.toPublicKey(); - - console.log('---TOKEN ACCOUNT1', fromAddress.toBase58()); - console.log('---TOKEN ACCOUNT2', toAddress.toBase58()); - - if (fromAddress === thisParty.publicKey) { + if (from === thisParty.publicKey) { thisParty.body.balanceChange = thisParty.body.balanceChange.sub(tokenCreationFee); @@ -612,25 +609,29 @@ class Party { thisParty.body.balanceChange = thisParty.body.balanceChange.sub(tokenCreationFee); - let transferParty = Party.createUnsigned(fromAddress, { + let transferParty = Party.createUnsigned(from, { caller: token, tokenId: token, callDepth: 1, + useFullCommitment: Bool(true), }); + transferParty.body.balanceChange = transferParty.body.balanceChange.sub(amount); - transferParty.signInPlace(from); + transferParty.authorization = { + kind: 'lazy-signature', + }; } - let receiverParty = Party.createUnsigned(toAddress, { + let receiverParty = Party.createUnsigned(to, { caller: token, tokenId: token, callDepth: 1, // TODO: Make this smarter + useFullCommitment: Bool(true), }); receiverParty.body.balanceChange = receiverParty.body.balanceChange.add(amount); - receiverParty.signInPlace(to); }, }; } @@ -799,6 +800,7 @@ class Party { caller?: Field; tokenId?: Field; callDepth?: number; + useFullCommitment?: Bool; } ) { // TODO: This should be a witness block that uses the setVariable @@ -811,11 +813,11 @@ class Party { const pk = publicKey; const body: Body = Body.keepAll(pk); - const { caller, tokenId, callDepth } = options ?? {}; + const { caller, tokenId, callDepth, useFullCommitment } = options ?? {}; body.caller = caller || body.caller; body.tokenId = tokenId || body.tokenId; body.callDepth = callDepth || body.callDepth; - // body.useFullCommitment = Bool(true); + body.useFullCommitment = useFullCommitment || body.useFullCommitment; const party = new Party(body); Mina.currentTransaction.nextPartyIndex++; From f5cb144648056d6b2365672f04f7f6b0a2bf94a6 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 11 Jul 2022 16:09:31 -0700 Subject: [PATCH 12/49] Fix spelling in sign function --- src/lib/mina.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index ca826a8d52..55947b7ace 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -44,7 +44,7 @@ interface Transaction { transaction: Parties; toJSON(): string; toGraphqlQuery(): string; - sign(additionialKeys?: PrivateKey[]): Transaction; + sign(additionalKeys?: PrivateKey[]): Transaction; prove(): Promise<(Proof | undefined)[]>; send(): TransactionId; } From 1b4b990f392fb5b6e939ef57e110871d9ce84a13 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 12 Jul 2022 13:17:17 -0700 Subject: [PATCH 13/49] Add token id in Ledger.getAccount --- src/lib/mina.ts | 9 ++++++--- src/lib/party.ts | 39 ++++++++++++++++++++++++++------------- src/lib/token.ts | 21 --------------------- src/snarky.d.ts | 2 +- 4 files changed, 33 insertions(+), 38 deletions(-) delete mode 100644 src/lib/token.ts diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 55947b7ace..5935b0186e 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -225,11 +225,14 @@ function LocalBlockchain({ Math.ceil((new Date().valueOf() - startTime) / msPerSlot) ); }, - getAccount(publicKey: PublicKey): Account { - let ledgerAccount = ledger.getAccount(publicKey); + getAccount(publicKey: PublicKey, tokenId?: string): Account { + const tokenIdAsField = tokenId + ? Ledger.fieldOfBase58(tokenId) + : Field.one; + let ledgerAccount = ledger.getAccount(publicKey, tokenIdAsField); if (ledgerAccount == undefined) { throw new Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()}` + `getAccount: Could not find account for public key ${publicKey.toBase58()} with the token id ${tokenId?.toString()}` ); } else { return { diff --git a/src/lib/party.ts b/src/lib/party.ts index efaa9d6985..5166b02242 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -9,7 +9,6 @@ import * as Precondition from './precondition'; import { Proof } from './proof_system'; import { emptyHashWithPrefix, hashWithPrefix, prefixes } from './hash'; import { salt } from './hash'; -import { Token } from './token'; export { SetOrKeep, @@ -494,6 +493,19 @@ type UnfinishedSignature = undefined | LazySignature | string; type LazyControl = Control | LazySignature | LazyProof; +class Token { + readonly id: string; + readonly parentTokenId: string; + readonly tokenOwner: PublicKey; + + constructor(options: { tokenOwner: PublicKey; parentTokenId?: string }) { + const { tokenOwner, parentTokenId } = options ?? {}; + this.parentTokenId = parentTokenId ?? getDefaultTokenId().toString(); + this.tokenOwner = tokenOwner; + this.id = Ledger.customTokenID(tokenOwner); + } +} + type SendParams = { from: PublicKey; to: PublicKey; @@ -533,8 +545,8 @@ class Party { get token() { let thisParty = this; - let customToken = new Token(thisParty.body.publicKey); - const token = Ledger.fieldOfBase58(customToken.id); + const customToken = new Token({ tokenOwner: thisParty.body.publicKey }); + const tokenIdAsField = Ledger.fieldOfBase58(customToken.id); return { id: customToken.id, @@ -554,8 +566,8 @@ class Party { thisParty.body.balanceChange.sub(tokenCreationFee); let receiverParty = Party.createUnsigned(address, { - caller: token, - tokenId: token, + caller: tokenIdAsField, + tokenId: tokenIdAsField, callDepth: 1, // TODO: Make this smarter useFullCommitment: Bool(true), }); @@ -575,11 +587,12 @@ class Party { thisParty.body.balanceChange.sub(tokenCreationFee); let receiverParty = Party.createUnsigned(address, { - caller: token, - tokenId: token, + caller: tokenIdAsField, + tokenId: tokenIdAsField, callDepth: 1, // TODO: Make this smarter useFullCommitment: Bool(true), }); + receiverParty.body.balanceChange = receiverParty.body.balanceChange.sub(amount); receiverParty.authorization = { @@ -599,8 +612,8 @@ class Party { thisParty.body.balanceChange.sub(tokenCreationFee); let transferParty = Party.createUnsigned(thisParty.publicKey, { - caller: token, - tokenId: token, + caller: tokenIdAsField, + tokenId: tokenIdAsField, }); transferParty.body.balanceChange = @@ -610,8 +623,8 @@ class Party { thisParty.body.balanceChange.sub(tokenCreationFee); let transferParty = Party.createUnsigned(from, { - caller: token, - tokenId: token, + caller: tokenIdAsField, + tokenId: tokenIdAsField, callDepth: 1, useFullCommitment: Bool(true), }); @@ -624,8 +637,8 @@ class Party { } let receiverParty = Party.createUnsigned(to, { - caller: token, - tokenId: token, + caller: tokenIdAsField, + tokenId: tokenIdAsField, callDepth: 1, // TODO: Make this smarter useFullCommitment: Bool(true), }); diff --git a/src/lib/token.ts b/src/lib/token.ts deleted file mode 100644 index 25c15fe362..0000000000 --- a/src/lib/token.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { TokenId } from 'snarky/parties-leaves'; -import { PublicKey } from './signature'; -import { Field } from '../snarky'; -import { Ledger } from '../snarky'; - -const getDefaultTokenId = () => Field.one.toString(); - -export class Token { - readonly id: string; - readonly parentTokenId: string; - readonly tokenOwner: PublicKey; - - constructor( - tokenOwner: PublicKey, - parentTokenId: string = getDefaultTokenId() - ) { - this.parentTokenId = parentTokenId; - this.tokenOwner = tokenOwner; - this.id = Ledger.customTokenID(tokenOwner); - } -} diff --git a/src/snarky.d.ts b/src/snarky.d.ts index d596591f4b..688853ab42 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -732,7 +732,7 @@ declare class Ledger { applyJsonTransaction(txJson: string, accountCreationFee: string): Account[]; - getAccount(publicKey: { g: Group }): Account | undefined; + getAccount(publicKey: { g: Group }, tokenId: Field): Account | undefined; static hashTransaction(partyHash: Field): Field; static hashTransactionChecked(partyHash: Field): Field; From 0bd67fdf69497860a5cdf13b43972cfa5d321f0d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 12 Jul 2022 13:31:48 -0700 Subject: [PATCH 14/49] Remove newTokenAccount in favor of using Party.fundNewAccount() --- src/lib/party.ts | 39 +++------------------------------------ 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 5166b02242..1212c1e014 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -510,13 +510,11 @@ type SendParams = { from: PublicKey; to: PublicKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; - newTokenAccount: Bool | boolean; }; type MintOrBurnParams = { address: PublicKey; amount: Int64 | UInt32 | UInt64 | string | number | bigint; - newTokenAccount: Bool | boolean; }; class Party { @@ -555,16 +553,7 @@ class Party { tokenOwner: customToken.tokenOwner, - mint({ address, amount, newTokenAccount }: MintOrBurnParams) { - let tokenCreationFee = Circuit.if( - newTokenAccount, - Mina.accountCreationFee(), - UInt64.zero - ); - - thisParty.body.balanceChange = - thisParty.body.balanceChange.sub(tokenCreationFee); - + mint({ address, amount }: MintOrBurnParams) { let receiverParty = Party.createUnsigned(address, { caller: tokenIdAsField, tokenId: tokenIdAsField, @@ -576,16 +565,7 @@ class Party { receiverParty.body.balanceChange.add(amount); }, - burn({ address, amount, newTokenAccount }: MintOrBurnParams) { - let tokenCreationFee = Circuit.if( - newTokenAccount, - Mina.accountCreationFee(), - UInt64.zero - ); - - thisParty.body.balanceChange = - thisParty.body.balanceChange.sub(tokenCreationFee); - + burn({ address, amount }: MintOrBurnParams) { let receiverParty = Party.createUnsigned(address, { caller: tokenIdAsField, tokenId: tokenIdAsField, @@ -600,28 +580,15 @@ class Party { }; }, - transfer({ from, to, amount, newTokenAccount }: SendParams) { - let tokenCreationFee = Circuit.if( - newTokenAccount, - Mina.accountCreationFee(), - UInt64.zero - ); - + transfer({ from, to, amount }: SendParams) { if (from === thisParty.publicKey) { - thisParty.body.balanceChange = - thisParty.body.balanceChange.sub(tokenCreationFee); - let transferParty = Party.createUnsigned(thisParty.publicKey, { caller: tokenIdAsField, tokenId: tokenIdAsField, }); - transferParty.body.balanceChange = transferParty.body.balanceChange.sub(amount); } else { - thisParty.body.balanceChange = - thisParty.body.balanceChange.sub(tokenCreationFee); - let transferParty = Party.createUnsigned(from, { caller: tokenIdAsField, tokenId: tokenIdAsField, From 9717a510ab11e78806c16cd541920e4543ca5fd5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 12 Jul 2022 14:07:06 -0700 Subject: [PATCH 15/49] Change this.token to a function to take optional token id --- src/lib/party.ts | 7 +++++-- src/lib/zkapp.ts | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 1212c1e014..dc922f4596 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -541,9 +541,12 @@ class Party { return new (Party as any)(body, authorization, party.isSelf); } - get token() { + token(tokenId?: string) { let thisParty = this; - const customToken = new Token({ tokenOwner: thisParty.body.publicKey }); + const customToken = new Token({ + tokenOwner: thisParty.body.publicKey, + parentTokenId: tokenId, + }); const tokenIdAsField = Ledger.fieldOfBase58(customToken.id); return { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 2c9416b49e..0146abeafa 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -333,8 +333,8 @@ class SmartContract { return this.self.network; } - get token() { - return this.self.token; + token(tokenId?: string) { + return this.self.token(tokenId); } get balance() { From 4dde4ff02119f84bc6f3496e62738276e12e672b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 12 Jul 2022 14:49:26 -0700 Subject: [PATCH 16/49] Add token in getAccount --- src/lib/mina.ts | 11 ++++++----- src/lib/party.ts | 10 ++++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 5935b0186e..6d0ed686c0 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -12,6 +12,7 @@ import { Party, ZkappStateLength, ZkappPublicInput, + getDefaultTokenId, } from './party'; import * as Fetch from './fetch'; import { assertPreconditionInvariants, NetworkValue } from './precondition'; @@ -177,7 +178,7 @@ function createTransaction( interface Mina { transaction(sender: SenderSpec, f: () => void): Promise; currentSlot(): UInt32; - getAccount(publicKey: Types.PublicKey): Account; + getAccount(publicKey: Types.PublicKey, tokenId?: string): Account; getNetworkState(): NetworkValue; accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): TransactionId; @@ -228,11 +229,11 @@ function LocalBlockchain({ getAccount(publicKey: PublicKey, tokenId?: string): Account { const tokenIdAsField = tokenId ? Ledger.fieldOfBase58(tokenId) - : Field.one; + : Ledger.fieldOfBase58(getDefaultTokenId()); let ledgerAccount = ledger.getAccount(publicKey, tokenIdAsField); if (ledgerAccount == undefined) { throw new Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()} with the token id ${tokenId?.toString()}` + `getAccount: Could not find account for public key ${publicKey.toBase58()} with the token id ${tokenIdAsField?.toString()}` ); } else { return { @@ -459,8 +460,8 @@ function currentSlot(): UInt32 { /** * @return The account data associated to the given public key. */ -function getAccount(pubkey: Types.PublicKey) { - return activeInstance.getAccount(pubkey); +function getAccount(pubkey: Types.PublicKey, tokenId?: string) { + return activeInstance.getAccount(pubkey, tokenId); } /** diff --git a/src/lib/party.ts b/src/lib/party.ts index dc922f4596..614c45e292 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -32,6 +32,8 @@ export { ZkappPublicInput, Events, partyToPublicInput, + Token, + getDefaultTokenId, }; const ZkappStateLength = 8; @@ -232,7 +234,7 @@ let Permissions = { }), }; -const getDefaultTokenId = () => Field.one; +const getDefaultTokenId = () => Ledger.fieldToBase58(Field.one); type Event = Field[]; @@ -346,11 +348,11 @@ const Body = { return { publicKey, update: Body.noUpdate(), - tokenId: getDefaultTokenId(), + tokenId: Ledger.fieldOfBase58(getDefaultTokenId()), balanceChange: Int64.zero, events: Events.empty(), sequenceEvents: Events.empty(), - caller: getDefaultTokenId(), + caller: Ledger.fieldOfBase58(getDefaultTokenId()), callData: Field.zero, // TODO new MerkleList(), callDepth: 0, preconditions: Preconditions.ignoreAll(), @@ -500,7 +502,7 @@ class Token { constructor(options: { tokenOwner: PublicKey; parentTokenId?: string }) { const { tokenOwner, parentTokenId } = options ?? {}; - this.parentTokenId = parentTokenId ?? getDefaultTokenId().toString(); + this.parentTokenId = parentTokenId ?? getDefaultTokenId(); this.tokenOwner = tokenOwner; this.id = Ledger.customTokenID(tokenOwner); } From b1f5867e5996e0fccbdb7616faac19b37b20be2f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 12 Jul 2022 14:59:12 -0700 Subject: [PATCH 17/49] Refactor this.token().transfer to this.token().send() --- src/lib/party.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 614c45e292..fc75188d1d 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -578,6 +578,7 @@ class Party { useFullCommitment: Bool(true), }); + // Require signature from the token account being deducted receiverParty.body.balanceChange = receiverParty.body.balanceChange.sub(amount); receiverParty.authorization = { @@ -585,25 +586,26 @@ class Party { }; }, - transfer({ from, to, amount }: SendParams) { + send({ from, to, amount }: SendParams) { if (from === thisParty.publicKey) { - let transferParty = Party.createUnsigned(thisParty.publicKey, { + let senderParty = Party.createUnsigned(thisParty.publicKey, { caller: tokenIdAsField, tokenId: tokenIdAsField, }); - transferParty.body.balanceChange = - transferParty.body.balanceChange.sub(amount); + senderParty.body.balanceChange = + senderParty.body.balanceChange.sub(amount); } else { - let transferParty = Party.createUnsigned(from, { + let senderParty = Party.createUnsigned(from, { caller: tokenIdAsField, tokenId: tokenIdAsField, callDepth: 1, useFullCommitment: Bool(true), }); - transferParty.body.balanceChange = - transferParty.body.balanceChange.sub(amount); - transferParty.authorization = { + // Require signature if the sender party is not the zkApp + senderParty.body.balanceChange = + senderParty.body.balanceChange.sub(amount); + senderParty.authorization = { kind: 'lazy-signature', }; } From 753ce86d08915f2b9e12d084939719b05f86f0e7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 12 Jul 2022 16:20:08 -0700 Subject: [PATCH 18/49] Use getBalance for fetching balance from local ledger --- src/lib/fetch.ts | 1 + src/lib/mina.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index ff1543d4db..9ac9d01de5 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -171,6 +171,7 @@ const accountQuery = (publicKey: string) => `{ nonce zkappUri zkappState + tokenId permissions { editState send diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 6d0ed686c0..24d0aae881 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -460,8 +460,8 @@ function currentSlot(): UInt32 { /** * @return The account data associated to the given public key. */ -function getAccount(pubkey: Types.PublicKey, tokenId?: string) { - return activeInstance.getAccount(pubkey, tokenId); +function getAccount(pubkey: Types.PublicKey) { + return activeInstance.getAccount(pubkey); } /** @@ -474,8 +474,8 @@ function getNetworkState() { /** * @return The balance associated to the given public key. */ -function getBalance(pubkey: Types.PublicKey) { - return activeInstance.getAccount(pubkey).balance; +function getBalance(pubkey: Types.PublicKey, tokenId?: string) { + return activeInstance.getAccount(pubkey, tokenId).balance; } function accountCreationFee() { From b353132a72735202d74b6d31b15f3d0db8272484 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Jul 2022 09:04:42 -0700 Subject: [PATCH 19/49] Remove comment code --- src/lib/mina.ts | 6 +++++- src/lib/party.ts | 28 ++++++++-------------------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 24d0aae881..ac8c9413e1 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -294,7 +294,11 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { 'currentSlot() is not implemented yet for remote blockchains.' ); }, - getAccount(publicKey: PublicKey) { + getAccount(publicKey: PublicKey, tokenId?: string) { + const tokenIdAsField = tokenId + ? Ledger.fieldOfBase58(tokenId) + : Ledger.fieldOfBase58(getDefaultTokenId()); + if (currentTransaction?.fetchMode === 'test') { Fetch.markAccountToBeFetched(publicKey, graphqlEndpoint); let account = Fetch.getCachedAccount(publicKey, graphqlEndpoint); diff --git a/src/lib/party.ts b/src/lib/party.ts index fc75188d1d..e09c954b4b 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -545,17 +545,14 @@ class Party { token(tokenId?: string) { let thisParty = this; - const customToken = new Token({ + let customToken = new Token({ tokenOwner: thisParty.body.publicKey, parentTokenId: tokenId, }); - const tokenIdAsField = Ledger.fieldOfBase58(customToken.id); - + let tokenIdAsField = Ledger.fieldOfBase58(customToken.id); return { id: customToken.id, - parentTokenId: customToken.parentTokenId, - tokenOwner: customToken.tokenOwner, mint({ address, amount }: MintOrBurnParams) { @@ -578,9 +575,9 @@ class Party { useFullCommitment: Bool(true), }); - // Require signature from the token account being deducted receiverParty.body.balanceChange = receiverParty.body.balanceChange.sub(amount); + // Require signature from the token account being deducted receiverParty.authorization = { kind: 'lazy-signature', }; @@ -602,9 +599,9 @@ class Party { useFullCommitment: Bool(true), }); - // Require signature if the sender party is not the zkApp senderParty.body.balanceChange = senderParty.body.balanceChange.sub(amount); + // Require signature if the sender party is not the zkApp senderParty.authorization = { kind: 'lazy-signature', }; @@ -616,7 +613,6 @@ class Party { callDepth: 1, // TODO: Make this smarter useFullCommitment: Bool(true), }); - receiverParty.body.balanceChange = receiverParty.body.balanceChange.add(amount); }, @@ -801,10 +797,10 @@ class Party { const pk = publicKey; const body: Body = Body.keepAll(pk); const { caller, tokenId, callDepth, useFullCommitment } = options ?? {}; - body.caller = caller || body.caller; - body.tokenId = tokenId || body.tokenId; - body.callDepth = callDepth || body.callDepth; - body.useFullCommitment = useFullCommitment || body.useFullCommitment; + body.caller = caller ?? body.caller; + body.tokenId = tokenId ?? body.tokenId; + body.callDepth = callDepth ?? body.callDepth; + body.useFullCommitment = useFullCommitment ?? body.useFullCommitment; const party = new Party(body); Mina.currentTransaction.nextPartyIndex++; @@ -817,9 +813,6 @@ class Party { options?: { isSameAsFeePayer?: Bool | boolean; nonce?: UInt32; - // caller?: Field; - // tokenId?: Field; - // callDepth?: number; } ) { let { nonce, isSameAsFeePayer } = options ?? {}; @@ -865,11 +858,6 @@ class Party { nonce = nonce.add(nonceIncrement); Party.assertEquals(body.preconditions.account.nonce, nonce); body.incrementNonce = Bool(true); - // const { caller, tokenId, callDepth } = options ?? {}; - // body.caller = caller || body.caller; - // body.tokenId = tokenId || body.tokenId; - // body.callDepth = callDepth || body.callDepth; - // body.useFullCommitment = Bool(true); let party = new Party(body); party.authorization = { kind: 'lazy-signature', privateKey: signer }; From c8aa6cc8f62ea1689d65c06cec6d98631c2045fc Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 13 Jul 2022 12:53:21 -0700 Subject: [PATCH 20/49] Add tokenId to getAccount --- src/lib/fetch.ts | 58 +++++++++++++++++++++++++++-------------- src/lib/mina.ts | 53 ++++++++++++++++++++++++------------- src/lib/party.ts | 14 +++++----- src/lib/precondition.ts | 7 +++-- src/lib/state.ts | 12 ++++++--- src/lib/zkapp.ts | 11 ++++++-- 6 files changed, 105 insertions(+), 50 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 9ac9d01de5..2e69ff869f 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,9 +1,15 @@ import 'isomorphic-fetch'; import { Bool, Field, Types } from '../snarky'; import { UInt32, UInt64 } from './int'; -import { Permission, Permissions, ZkappStateLength } from './party'; +import { + getDefaultTokenId, + Permission, + Permissions, + ZkappStateLength, +} from './party'; import { PublicKey } from './signature'; import { NetworkValue } from './precondition'; +import { AccountParams } from './mina'; export { fetchAccount, @@ -41,16 +47,14 @@ function setGraphqlEndpoint(graphqlEndpoint: string) { * @returns zkapp information on the specified account or an error is thrown */ async function fetchAccount( - publicKey: string | PublicKey, + accountInfo: AccountParams, graphqlEndpoint = defaultGraphqlEndpoint, { timeout = defaultTimeout } = {} ): Promise< | { account: Account; error: undefined } | { account: undefined; error: FetchError } > { - let publicKeyBase58 = - publicKey instanceof PublicKey ? publicKey.toBase58() : publicKey; - let response = await fetchAccountInternal(publicKeyBase58, graphqlEndpoint, { + let response = await fetchAccountInternal(accountInfo, graphqlEndpoint, { timeout, }); return response.error === undefined @@ -64,12 +68,13 @@ async function fetchAccount( // internal version of fetchAccount which does the same, but returns the original JSON version // of the account, to save some back-and-forth conversions when caching accounts async function fetchAccountInternal( - publicKey: string, + accountInfo: AccountParams, graphqlEndpoint = defaultGraphqlEndpoint, config?: FetchConfig ) { + const { publicKey, tokenId } = accountInfo; let [response, error] = await makeGraphqlRequest( - accountQuery(publicKey), + accountQuery(publicKey.toBase58(), tokenId ?? getDefaultTokenId()), graphqlEndpoint, config ); @@ -81,7 +86,7 @@ async function fetchAccountInternal( account: undefined, error: { statusCode: 404, - statusText: `fetchAccount: Account with public key ${publicKey} does not exist.`, + statusText: `fetchAccount: Account with public key ${publicKey.toBase58()} does not exist.`, }, }; } @@ -123,6 +128,7 @@ function toPermission(p: AuthRequired): Permission { type FetchedAccount = { publicKey: string; nonce: string; + tokenId: string; zkappUri?: string; zkappState: string[] | null; receiptChainHash?: string; @@ -149,6 +155,7 @@ type Account = { publicKey: PublicKey; nonce: UInt32; balance: UInt64; + tokenId: string; zkapp?: { appState: Field[] }; permissions?: Permissions; receiptChainHash?: Field; @@ -160,18 +167,18 @@ type Account = { type FlexibleAccount = { publicKey: PublicKey | string; nonce: UInt32 | string | number; + tokenId: string; balance?: UInt64 | string | number; zkapp?: { appState: (Field | string | number)[] }; }; // TODO provedState -const accountQuery = (publicKey: string) => `{ - account(publicKey: "${publicKey}") { +const accountQuery = (publicKey: string, tokenId: string) => `{ + account(publicKey: "${publicKey}" tokenId: "${tokenId}") { publicKey nonce zkappUri zkappState - tokenId permissions { editState send @@ -189,6 +196,7 @@ const accountQuery = (publicKey: string) => `{ balance { total } delegateAccount { publicKey } sequenceEvents + tokenId } } `; @@ -233,7 +241,7 @@ function parseFetchedAccount({ } function stringifyAccount(account: FlexibleAccount): FetchedAccount { - let { publicKey, nonce, balance, zkapp } = account; + let { publicKey, nonce, balance, zkapp, tokenId } = account; return { publicKey: publicKey instanceof PublicKey ? publicKey.toBase58() : publicKey, @@ -242,6 +250,7 @@ function stringifyAccount(account: FlexibleAccount): FetchedAccount { zkapp?.appState.map((s) => s.toString()) ?? Array(ZkappStateLength).fill('0'), balance: { total: balance?.toString() ?? '0' }, + tokenId, }; } @@ -263,15 +272,20 @@ let networkCache = {} as Record< >; let accountsToFetch = {} as Record< string, - { publicKey: string; graphqlEndpoint: string } + { publicKey: string; tokenId: string; graphqlEndpoint: string } >; let networksToFetch = {} as Record; let cacheExpiry = 10 * 60 * 1000; // 10 minutes -function markAccountToBeFetched(publicKey: PublicKey, graphqlEndpoint: string) { +function markAccountToBeFetched( + publicKey: PublicKey, + tokenId: string, + graphqlEndpoint: string +) { let publicKeyBase58 = publicKey.toBase58(); - accountsToFetch[`${publicKeyBase58};${graphqlEndpoint}`] = { + accountsToFetch[`${publicKeyBase58};${tokenId};${graphqlEndpoint}`] = { publicKey: publicKeyBase58, + tokenId, graphqlEndpoint, }; } @@ -286,8 +300,11 @@ async function fetchMissingData(graphqlEndpoint: string) { let cachedAccount = accountCache[key]; return cachedAccount === undefined || cachedAccount.timestamp < expired; }); - let promises = accounts.map(async ([key, { publicKey }]) => { - let response = await fetchAccountInternal(publicKey, graphqlEndpoint); + let promises = accounts.map(async ([key, { publicKey, tokenId }]) => { + let response = await fetchAccountInternal( + { publicKey: PublicKey.fromBase58(publicKey), tokenId }, + graphqlEndpoint + ); if (response.error === undefined) delete accountsToFetch[key]; }); @@ -312,10 +329,12 @@ async function fetchMissingData(graphqlEndpoint: string) { function getCachedAccount( publicKey: PublicKey, + tokenId: string, graphqlEndpoint = defaultGraphqlEndpoint ) { let account = - accountCache[`${publicKey.toBase58()};${graphqlEndpoint}`]?.account; + accountCache[`${publicKey.toBase58()};${tokenId}${graphqlEndpoint}`] + ?.account; if (account !== undefined) return parseFetchedAccount(account); } function getCachedNetwork(graphqlEndpoint = defaultGraphqlEndpoint) { @@ -330,6 +349,7 @@ function addCachedAccount( zkapp?: { appState: (string | number | Field)[]; }; + tokenId: string; }, graphqlEndpoint = defaultGraphqlEndpoint ) { @@ -340,7 +360,7 @@ function addCachedAccountInternal( account: FetchedAccount, graphqlEndpoint: string ) { - accountCache[`${account.publicKey};${graphqlEndpoint}`] = { + accountCache[`${account.publicKey};${account.tokenId};${graphqlEndpoint}`] = { account, graphqlEndpoint, timestamp: Date.now(), diff --git a/src/lib/mina.ts b/src/lib/mina.ts index ac8c9413e1..b5fce76e3e 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -35,6 +35,7 @@ export { getNetworkState, accountCreationFee, sendTransaction, + AccountParams, }; interface TransactionId { @@ -75,6 +76,11 @@ type SenderSpec = | { feePayerKey: PrivateKey; fee?: number | string | UInt64; memo?: string } | undefined; +type AccountParams = { + publicKey: PublicKey; + tokenId?: string; +}; + function createUnsignedTransaction( f: () => unknown, { fetchMode = 'cached' as FetchMode } = {} @@ -122,7 +128,10 @@ function createTransaction( if (feePayerKey !== undefined) { // if senderKey is provided, fetch account to get nonce and mark to be signed let senderAddress = feePayerKey.toPublicKey(); - let senderAccount = getAccount(senderAddress); + let senderAccount = getAccount({ + publicKey: senderAddress, + tokenId: getDefaultTokenId(), + }); feePayerParty = Party.defaultFeePayer( senderAddress, feePayerKey, @@ -178,7 +187,7 @@ function createTransaction( interface Mina { transaction(sender: SenderSpec, f: () => void): Promise; currentSlot(): UInt32; - getAccount(publicKey: Types.PublicKey, tokenId?: string): Account; + getAccount(accountParams: AccountParams): Account; getNetworkState(): NetworkValue; accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): TransactionId; @@ -226,18 +235,21 @@ function LocalBlockchain({ Math.ceil((new Date().valueOf() - startTime) / msPerSlot) ); }, - getAccount(publicKey: PublicKey, tokenId?: string): Account { + getAccount(account: AccountParams): Account { + let { publicKey, tokenId } = account; + tokenId = tokenId ?? getDefaultTokenId(); const tokenIdAsField = tokenId ? Ledger.fieldOfBase58(tokenId) : Ledger.fieldOfBase58(getDefaultTokenId()); let ledgerAccount = ledger.getAccount(publicKey, tokenIdAsField); if (ledgerAccount == undefined) { throw new Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()} with the token id ${tokenIdAsField?.toString()}` + `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${tokenId}` ); } else { return { publicKey: publicKey, + tokenId, balance: new UInt64(ledgerAccount.balance.value), nonce: new UInt32(ledgerAccount.nonce.value), zkapp: ledgerAccount.zkapp, @@ -294,13 +306,11 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { 'currentSlot() is not implemented yet for remote blockchains.' ); }, - getAccount(publicKey: PublicKey, tokenId?: string) { - const tokenIdAsField = tokenId - ? Ledger.fieldOfBase58(tokenId) - : Ledger.fieldOfBase58(getDefaultTokenId()); - + getAccount(account: AccountParams): Account { + let { publicKey, tokenId } = account; + tokenId = tokenId ?? getDefaultTokenId(); if (currentTransaction?.fetchMode === 'test') { - Fetch.markAccountToBeFetched(publicKey, graphqlEndpoint); + Fetch.markAccountToBeFetched(publicKey, tokenId, graphqlEndpoint); let account = Fetch.getCachedAccount(publicKey, graphqlEndpoint); return account ?? dummyAccount(publicKey); } @@ -312,7 +322,7 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { if (account !== undefined) return account; } throw Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()}.\nGraphql endpoint: ${graphqlEndpoint}` + `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${tokenId}.\nGraphql endpoint: ${graphqlEndpoint}` ); }, getNetworkState() { @@ -384,9 +394,15 @@ let activeInstance: Mina = { currentSlot: () => { throw new Error('must call Mina.setActiveInstance first'); }, - getAccount: (publicKey: PublicKey) => { + getAccount: (account: AccountParams) => { + let { publicKey, tokenId } = account; + tokenId = tokenId ?? getDefaultTokenId(); if (currentTransaction?.fetchMode === 'test') { - Fetch.markAccountToBeFetched(publicKey, Fetch.defaultGraphqlEndpoint); + Fetch.markAccountToBeFetched( + publicKey, + tokenId, + Fetch.defaultGraphqlEndpoint + ); return dummyAccount(publicKey); } if ( @@ -399,7 +415,7 @@ let activeInstance: Mina = { ); if (account === undefined) throw Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()}. Either call Mina.setActiveInstance first or explicitly add the account with addCachedAccount` + `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${tokenId}.\nEither call Mina.setActiveInstance first or explicitly add the account with addCachedAccount` ); return account; } @@ -464,8 +480,8 @@ function currentSlot(): UInt32 { /** * @return The account data associated to the given public key. */ -function getAccount(pubkey: Types.PublicKey) { - return activeInstance.getAccount(pubkey); +function getAccount(account: AccountParams) { + return activeInstance.getAccount(account); } /** @@ -478,8 +494,8 @@ function getNetworkState() { /** * @return The balance associated to the given public key. */ -function getBalance(pubkey: Types.PublicKey, tokenId?: string) { - return activeInstance.getAccount(pubkey, tokenId).balance; +function getBalance(account: AccountParams) { + return activeInstance.getAccount(account).balance; } function accountCreationFee() { @@ -495,6 +511,7 @@ function dummyAccount(pubkey?: PublicKey): Account { balance: UInt64.zero, nonce: UInt32.zero, publicKey: pubkey ?? PublicKey.empty(), + tokenId: getDefaultTokenId(), zkapp: { appState: Array(ZkappStateLength).fill(Field.zero) }, }; } diff --git a/src/lib/party.ts b/src/lib/party.ts index e09c954b4b..2f564c950b 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -619,10 +619,6 @@ class Party { }; } - get tokenId() { - return this.body.tokenId; - } - get balance() { let party = this; @@ -726,7 +722,10 @@ class Party { try { let inProver = Circuit.inProver(); if (inProver || !Circuit.inCheckedComputation()) { - let account = Mina.getAccount(party.body.publicKey); + let account = Mina.getAccount({ + publicKey: party.body.publicKey as PublicKey, + tokenId: getDefaultTokenId(), + }); nonce = inProver ? Circuit.witness(UInt32, () => account.nonce) : account.nonce; @@ -832,7 +831,10 @@ class Party { // TODO: getAccount could always be used if we had a generic way to add account info prior to creating transactions if (nonce === undefined) { - let account = Mina.getAccount(publicKey); + let account = Mina.getAccount({ + publicKey, + tokenId: getDefaultTokenId(), + }); nonce = account.nonce; } diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index c51ebc3773..8e76dc8f17 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -9,7 +9,7 @@ import { import { circuitValueEquals } from './circuit_value'; import { PublicKey } from './signature'; import * as Mina from './mina'; -import { Party, Preconditions } from './party'; +import { getDefaultTokenId, Party, Preconditions } from './party'; import * as GlobalContext from './global-context'; import { UInt32, UInt64 } from './int'; @@ -178,7 +178,10 @@ To write a correct circuit, you must avoid any dependency on the concrete value let value: U; if (accountOrNetwork === 'account') { let address = party.body.publicKey; - let account: any = Mina.getAccount(address); + let account: any = Mina.getAccount({ + publicKey: address, + tokenId: getDefaultTokenId(), + }); value = account[key]; if (value === undefined) throw Error( diff --git a/src/lib/state.ts b/src/lib/state.ts index ccb8bb3244..0b5c38e083 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -1,6 +1,6 @@ import { Circuit, Field, AsFieldElements } from '../snarky'; import { circuitArray } from './circuit_value'; -import { Party } from './party'; +import { getDefaultTokenId, Party } from './party'; import { PublicKey } from './signature'; import * as Mina from './mina'; import { Account, fetchAccount } from './fetch'; @@ -202,7 +202,10 @@ function createState(): InternalStateType { if (!GlobalContext.inCompile() && !GlobalContext.inAnalyze()) { let account: Account; try { - account = Mina.getAccount(address); + account = Mina.getAccount({ + publicKey: address, + tokenId: getDefaultTokenId(), + }); } catch (err) { // TODO: there should also be a reasonable error here if (inProver) { @@ -255,7 +258,10 @@ To write a correct circuit, you must avoid any dependency on the concrete value ); let layout = getLayoutPosition(this._contract); let address: PublicKey = this._contract.instance.address; - let { account } = await fetchAccount(address); + let { account } = await fetchAccount({ + publicKey: address, + tokenId: getDefaultTokenId(), + }); if (account === undefined) return undefined; let stateAsFields: Field[]; if (account.zkapp === undefined) { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 0146abeafa..23b4b36d85 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -19,6 +19,7 @@ import { ZkappPublicInput, Events, partyToPublicInput, + getDefaultTokenId, } from './party'; import { PrivateKey, PublicKey } from './signature'; import * as Mina from './mina'; @@ -643,7 +644,10 @@ function addFeePayer( feePayerKey = PrivateKey.fromBase58(feePayerKey); let senderAddress = feePayerKey.toPublicKey(); if (feePayerNonce === undefined) { - let senderAccount = Mina.getAccount(senderAddress); + let senderAccount = Mina.getAccount({ + publicKey: senderAddress, + tokenId: getDefaultTokenId(), + }); feePayerNonce = senderAccount.nonce.toString(); } let newMemo = memo; @@ -669,7 +673,10 @@ function signFeePayer( feePayerKey = PrivateKey.fromBase58(feePayerKey); let senderAddress = feePayerKey.toPublicKey(); if (feePayerNonce === undefined) { - let senderAccount = Mina.getAccount(senderAddress); + let senderAccount = Mina.getAccount({ + publicKey: senderAddress, + tokenId: getDefaultTokenId(), + }); feePayerNonce = senderAccount.nonce.toString(); } if (feePayerMemo) parties.memo = Ledger.memoToBase58(feePayerMemo); From 728a132f4f39b9ce1fe995d3482ff48729593d23 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 14 Jul 2022 10:23:44 -0700 Subject: [PATCH 21/49] Refactor createUnsigned to take string instead of Field for caller and tokenId --- src/lib/party.ts | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 2f564c950b..2313801314 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -549,7 +549,6 @@ class Party { tokenOwner: thisParty.body.publicKey, parentTokenId: tokenId, }); - let tokenIdAsField = Ledger.fieldOfBase58(customToken.id); return { id: customToken.id, parentTokenId: customToken.parentTokenId, @@ -557,50 +556,56 @@ class Party { mint({ address, amount }: MintOrBurnParams) { let receiverParty = Party.createUnsigned(address, { - caller: tokenIdAsField, - tokenId: tokenIdAsField, - callDepth: 1, // TODO: Make this smarter + caller: this.id, + tokenId: this.id, + callDepth: 1, useFullCommitment: Bool(true), }); + // Add the amount to mint to the receiver's account receiverParty.body.balanceChange = receiverParty.body.balanceChange.add(amount); }, burn({ address, amount }: MintOrBurnParams) { let receiverParty = Party.createUnsigned(address, { - caller: tokenIdAsField, - tokenId: tokenIdAsField, - callDepth: 1, // TODO: Make this smarter + caller: this.id, + tokenId: this.id, + callDepth: 1, useFullCommitment: Bool(true), }); + // Sub the amount to burn from the receiver's account receiverParty.body.balanceChange = receiverParty.body.balanceChange.sub(amount); - // Require signature from the token account being deducted + + // Require signature from the receiver account being deducted receiverParty.authorization = { kind: 'lazy-signature', }; }, send({ from, to, amount }: SendParams) { + // Check if the sender party is the current zkApp address if (from === thisParty.publicKey) { + // If so, create a new party for the zkApp to send the amount to the receiver let senderParty = Party.createUnsigned(thisParty.publicKey, { - caller: tokenIdAsField, - tokenId: tokenIdAsField, + caller: this.id, + tokenId: this.id, }); senderParty.body.balanceChange = senderParty.body.balanceChange.sub(amount); } else { + // If not, create a new party for the sender to send the amount to the receiver let senderParty = Party.createUnsigned(from, { - caller: tokenIdAsField, - tokenId: tokenIdAsField, + caller: this.id, + tokenId: this.id, callDepth: 1, useFullCommitment: Bool(true), }); - senderParty.body.balanceChange = senderParty.body.balanceChange.sub(amount); + // Require signature if the sender party is not the zkApp senderParty.authorization = { kind: 'lazy-signature', @@ -608,11 +613,13 @@ class Party { } let receiverParty = Party.createUnsigned(to, { - caller: tokenIdAsField, - tokenId: tokenIdAsField, - callDepth: 1, // TODO: Make this smarter + caller: this.id, + tokenId: this.id, + callDepth: 1, useFullCommitment: Bool(true), }); + + // Add the amount to send to the receiver's account receiverParty.body.balanceChange = receiverParty.body.balanceChange.add(amount); }, @@ -779,8 +786,8 @@ class Party { static createUnsigned( publicKey: PublicKey, options?: { - caller?: Field; - tokenId?: Field; + caller?: string; + tokenId?: string; callDepth?: number; useFullCommitment?: Bool; } @@ -796,8 +803,8 @@ class Party { const pk = publicKey; const body: Body = Body.keepAll(pk); const { caller, tokenId, callDepth, useFullCommitment } = options ?? {}; - body.caller = caller ?? body.caller; - body.tokenId = tokenId ?? body.tokenId; + body.caller = caller ? Ledger.fieldOfBase58(caller) : body.caller; + body.tokenId = tokenId ? Ledger.fieldOfBase58(tokenId) : body.tokenId; body.callDepth = callDepth ?? body.callDepth; body.useFullCommitment = useFullCommitment ?? body.useFullCommitment; From 43830603e1b596ea2904d190cb4394deaa852fef Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 14 Jul 2022 11:19:05 -0700 Subject: [PATCH 22/49] Add proper typing for snarkyjs binding token functions --- src/lib/party.ts | 5 ++++- src/snarky.d.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 2313801314..7905d3d60b 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -504,7 +504,10 @@ class Token { const { tokenOwner, parentTokenId } = options ?? {}; this.parentTokenId = parentTokenId ?? getDefaultTokenId(); this.tokenOwner = tokenOwner; - this.id = Ledger.customTokenID(tokenOwner); + this.id = Ledger.customTokenID( + tokenOwner, + Ledger.fieldOfBase58(this.parentTokenId) + ); } } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 688853ab42..d06628fae4 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -757,7 +757,8 @@ declare class Ledger { i: number ): string; - static customTokenID(publicKey: { g: Group }): string; + static customTokenID(publicKey: { g: Group }, tokenId: Field): string; + static createTokenAccount(publicKey: { g: Group }, tokenId: Field): string; static publicKeyToString(publicKey: { g: Group }): string; static publicKeyOfString(publicKeyBase58: string): Group; From 5a1dbe6dbbcad747bcd4be936d4e8a81de523680 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 14 Jul 2022 16:03:09 -0700 Subject: [PATCH 23/49] Add tokenSymbol to Party API --- src/lib/fetch.ts | 7 ++++++- src/lib/mina.ts | 2 ++ src/lib/party.ts | 22 +++++++++++++++++++++- src/lib/zkapp.ts | 4 ++++ src/snarky.d.ts | 2 ++ 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 2e69ff869f..12a5da0482 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -129,6 +129,7 @@ type FetchedAccount = { publicKey: string; nonce: string; tokenId: string; + tokenSymbol: string; zkappUri?: string; zkappState: string[] | null; receiptChainHash?: string; @@ -156,6 +157,7 @@ type Account = { nonce: UInt32; balance: UInt64; tokenId: string; + tokenSymbol: string; zkapp?: { appState: Field[] }; permissions?: Permissions; receiptChainHash?: Field; @@ -168,6 +170,7 @@ type FlexibleAccount = { publicKey: PublicKey | string; nonce: UInt32 | string | number; tokenId: string; + tokenSymbol?: string; balance?: UInt64 | string | number; zkapp?: { appState: (Field | string | number)[] }; }; @@ -197,6 +200,7 @@ const accountQuery = (publicKey: string, tokenId: string) => `{ delegateAccount { publicKey } sequenceEvents tokenId + tokenSymbol } } `; @@ -241,7 +245,7 @@ function parseFetchedAccount({ } function stringifyAccount(account: FlexibleAccount): FetchedAccount { - let { publicKey, nonce, balance, zkapp, tokenId } = account; + let { publicKey, nonce, balance, zkapp, tokenId, tokenSymbol } = account; return { publicKey: publicKey instanceof PublicKey ? publicKey.toBase58() : publicKey, @@ -251,6 +255,7 @@ function stringifyAccount(account: FlexibleAccount): FetchedAccount { Array(ZkappStateLength).fill('0'), balance: { total: balance?.toString() ?? '0' }, tokenId, + tokenSymbol: tokenSymbol ?? '', }; } diff --git a/src/lib/mina.ts b/src/lib/mina.ts index b5fce76e3e..d7174b58fd 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -253,6 +253,7 @@ function LocalBlockchain({ balance: new UInt64(ledgerAccount.balance.value), nonce: new UInt32(ledgerAccount.nonce.value), zkapp: ledgerAccount.zkapp, + tokenSymbol: ledgerAccount.tokenSymbol, }; } }, @@ -513,6 +514,7 @@ function dummyAccount(pubkey?: PublicKey): Account { publicKey: pubkey ?? PublicKey.empty(), tokenId: getDefaultTokenId(), zkapp: { appState: Array(ZkappStateLength).fill(Field.zero) }, + tokenSymbol: '', }; } diff --git a/src/lib/party.ts b/src/lib/party.ts index 7905d3d60b..5fc0bcea8c 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -1,5 +1,13 @@ import { circuitValue, cloneCircuitValue } from './circuit_value'; -import { Field, Bool, Ledger, Circuit, Pickles, Types } from '../snarky'; +import { + Field, + Bool, + Ledger, + Circuit, + Pickles, + Types, + Poseidon, +} from '../snarky'; import { PrivateKey, PublicKey } from './signature'; import { UInt64, UInt32, Int64 } from './int'; import * as Mina from './mina'; @@ -629,6 +637,18 @@ class Party { }; } + tokenSymbol() { + let party = this; + return { + set(tokenSymbol: string) { + Party.setValue(party.update.tokenSymbol, { + data: tokenSymbol, + hash: salt(tokenSymbol)[0], + }); + }, + }; + } + get balance() { let party = this; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 23b4b36d85..f94f429651 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -338,6 +338,10 @@ class SmartContract { return this.self.token(tokenId); } + get tokenSymbol() { + return this.self.tokenSymbol; + } + get balance() { return this.self.balance; } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index d06628fae4..029401b258 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -719,6 +719,8 @@ interface Account { publicKey: { g: Group }; balance: UInt64_; nonce: UInt32_; + tokenId: Field; + tokenSymbol: string; zkapp: { appState: Field[] }; } From d78353ccf91f1f00ba1f3cad66ccded6fe1f4307 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 14 Jul 2022 16:05:12 -0700 Subject: [PATCH 24/49] Fix getCachedAccount by passing in tokenId --- src/lib/mina.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index d7174b58fd..4e8f1a2858 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -312,14 +312,22 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { tokenId = tokenId ?? getDefaultTokenId(); if (currentTransaction?.fetchMode === 'test') { Fetch.markAccountToBeFetched(publicKey, tokenId, graphqlEndpoint); - let account = Fetch.getCachedAccount(publicKey, graphqlEndpoint); + let account = Fetch.getCachedAccount( + publicKey, + tokenId, + graphqlEndpoint + ); return account ?? dummyAccount(publicKey); } if ( currentTransaction == undefined || currentTransaction.fetchMode === 'cached' ) { - let account = Fetch.getCachedAccount(publicKey, graphqlEndpoint); + let account = Fetch.getCachedAccount( + publicKey, + tokenId, + graphqlEndpoint + ); if (account !== undefined) return account; } throw Error( @@ -412,6 +420,7 @@ let activeInstance: Mina = { ) { let account = Fetch.getCachedAccount( publicKey, + tokenId, Fetch.defaultGraphqlEndpoint ); if (account === undefined) From 66432c4fbd18f433bf610646067e8f6971c4e375 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 14 Jul 2022 16:07:19 -0700 Subject: [PATCH 25/49] Fix usage of fetchAccount --- src/examples/fetch.ts | 9 +++++++-- src/examples/simple_zkapp_berkeley.ts | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/examples/fetch.ts b/src/examples/fetch.ts index 02acd08023..7f8bbb7f31 100644 --- a/src/examples/fetch.ts +++ b/src/examples/fetch.ts @@ -4,13 +4,18 @@ import { setGraphqlEndpoint, shutdown, fetchLastBlock, + PublicKey, } from 'snarkyjs'; await isReady; setGraphqlEndpoint('https://proxy.berkeley.minaexplorer.com/graphql'); -let zkappAddress = 'B62qpRzFVjd56FiHnNfxokVbcHMQLT119My1FEdSq8ss7KomLiSZcan'; -let { account, error } = await fetchAccount(zkappAddress); +let zkappAddress = PublicKey.fromBase58( + 'B62qpRzFVjd56FiHnNfxokVbcHMQLT119My1FEdSq8ss7KomLiSZcan' +); +let { account, error } = await fetchAccount({ + publicKey: zkappAddress, +}); console.log('account', JSON.stringify(account, null, 2)); console.log('error', error); diff --git a/src/examples/simple_zkapp_berkeley.ts b/src/examples/simple_zkapp_berkeley.ts index 7039784ba5..61b0d3cbfa 100644 --- a/src/examples/simple_zkapp_berkeley.ts +++ b/src/examples/simple_zkapp_berkeley.ts @@ -51,7 +51,7 @@ Mina.setActiveInstance(Berkeley); let feePayerKey = PrivateKey.fromBase58( 'EKEQc95PPQZnMY9d9p1vq1MWLeDJKtvKj4V75UDG3rjnf32BerWD' ); -let response = await fetchAccount(feePayerKey.toPublicKey()); +let response = await fetchAccount({ publicKey: feePayerKey.toPublicKey() }); if (response.error) throw Error(response.error.statusText); let { nonce, balance } = response.account; console.log(`Using fee payer account with nonce ${nonce}, balance ${balance}`); From ef8267cfcc2e3f46f268af3635c326e808b94f11 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 15 Jul 2022 10:15:23 -0700 Subject: [PATCH 26/49] Fix tokenSymbol setter --- src/lib/party.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 5fc0bcea8c..6cef232698 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -637,8 +637,9 @@ class Party { }; } - tokenSymbol() { + get tokenSymbol() { let party = this; + return { set(tokenSymbol: string) { Party.setValue(party.update.tokenSymbol, { From 335b55124af51e9dd984c916e181e7072fd1797e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 15 Jul 2022 10:40:52 -0700 Subject: [PATCH 27/49] Scaffold test file for tokens --- src/lib/token.test.ts | 111 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/lib/token.test.ts diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts new file mode 100644 index 0000000000..947da37bd0 --- /dev/null +++ b/src/lib/token.test.ts @@ -0,0 +1,111 @@ +import { + shutdown, + isReady, + UInt64, + UInt32, + SmartContract, + Mina, + PrivateKey, + Party, + method, + PublicKey, + DeployArgs, + Permissions, +} from '../../dist/server'; +import { deploy } from './zkapp'; + +class TokenContract extends SmartContract { + deploy(args: DeployArgs) { + super.deploy(args); + this.setPermissions({ + ...Permissions.default(), + editState: Permissions.proofOrSignature(), + }); + this.tokenSymbol.set('MY_TOKEN'); + } + + @method mint(receiverAddress: PublicKey, amount: number) { + this.token().mint({ + address: receiverAddress, + amount, + }); + } + + @method burn(receiverAddress: PublicKey, amount: number) { + this.token().burn({ + address: receiverAddress, + amount, + }); + } + + @method send( + senderAddress: PublicKey, + receiverAddress: PublicKey, + amount: number + ) { + this.token().send({ + to: receiverAddress, + from: senderAddress, + amount, + }); + } +} + +let zkappKey: PrivateKey; +let zkappAddress: PublicKey; +let zkapp: TokenContract; +let feePayer: PrivateKey; + +beforeAll(async () => { + // set up local blockchain, create zkapp keys, deploy the contract + await isReady; + let Local = Mina.LocalBlockchain(); + Mina.setActiveInstance(Local); + feePayer = Local.testAccounts[0].privateKey; + + zkappKey = PrivateKey.random(); + zkappAddress = zkappKey.toPublicKey(); + zkapp = new TokenContract(zkappAddress); + + let tokenAccount1Key = Local.testAccounts[1].privateKey; + let tokenAccount1 = tokenAccount1Key.toPublicKey(); + + let tokenAccount2Key = Local.testAccounts[2].privateKey; + let tokenAccount2 = tokenAccount2Key.toPublicKey(); + + let tx = await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.deploy({ zkappKey }); + }); + tx.send(); +}); + +afterAll(() => setTimeout(shutdown, 0)); + +describe('Token', () => { + describe('Create existing token', () => { + it('should have a valid custom token id', async () => {}); + it('should have a valid token symbol', async () => {}); + it('should create a valid token with a parentTokenId', async () => {}); + it('should error if passing in an invalid parentTokenId', async () => {}); + it('should error if passing in an invalid tokenSymbol', async () => {}); + }); + + describe('Mint token', () => { + it('should change the balance of a token account after token owner mints', async () => {}); + it('should error if token owner mints more tokens than allowed', async () => {}); + it('should error if token owner mints negative amount tokens', async () => {}); + }); + + describe('Burn token', () => { + it('should change the balance of a token account after token owner burns', async () => {}); + it('should error if token owner burns more tokens than token account has', async () => {}); + it('should error if token owner burns negative amount tokens', async () => {}); + }); + describe('Send token', () => { + it('should change the balance of a token account after sending', async () => {}); + it('should error creating a token account if no account creation fee is specified', async () => {}); + it('should error if sender sends more tokens than they have', async () => {}); + it('should error if sender sends negative amount of tokens', async () => {}); + }); +}); From ad3e9671d9db7bdf51c15ed6225755add1b12215 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 15 Jul 2022 13:46:48 -0700 Subject: [PATCH 28/49] Add first section of token tests, refactor existing Party token code --- src/lib/party.ts | 27 ++++++++++++--- src/lib/token.test.ts | 79 +++++++++++++++++++++++++++++++++++-------- src/lib/zkapp.ts | 2 +- 3 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 6cef232698..afcaf2a8d4 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -509,8 +509,27 @@ class Token { readonly tokenOwner: PublicKey; constructor(options: { tokenOwner: PublicKey; parentTokenId?: string }) { - const { tokenOwner, parentTokenId } = options ?? {}; - this.parentTokenId = parentTokenId ?? getDefaultTokenId(); + let { tokenOwner, parentTokenId } = options ?? {}; + + // Reassign to default tokenId if undefined + parentTokenId ??= getDefaultTokenId(); + + // Check if we can deserialize the parentTokenId + try { + Ledger.fieldOfBase58(parentTokenId); + } catch (e) { + throw new Error(`Invalid parentTokenId\n(error: ${(e).message})`); + } + + // Check if we can create a custom tokenId + try { + Ledger.customTokenID(tokenOwner, Ledger.fieldOfBase58(parentTokenId)); + } catch (e) { + throw new Error( + `Could not create a custom token id:\n(error: ${(e).message})` + ); + } + this.parentTokenId = parentTokenId; this.tokenOwner = tokenOwner; this.id = Ledger.customTokenID( tokenOwner, @@ -522,12 +541,12 @@ class Token { type SendParams = { from: PublicKey; to: PublicKey; - amount: Int64 | UInt32 | UInt64 | string | number | bigint; + amount: UInt32 | UInt64; }; type MintOrBurnParams = { address: PublicKey; - amount: Int64 | UInt32 | UInt64 | string | number | bigint; + amount: UInt32 | UInt64; }; class Party { diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 947da37bd0..15d1734cfb 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -11,8 +11,12 @@ import { PublicKey, DeployArgs, Permissions, + Token, + Ledger, + getDefaultTokenId, } from '../../dist/server'; -import { deploy } from './zkapp'; + +const tokenSymbol = 'MY_TOKEN'; class TokenContract extends SmartContract { deploy(args: DeployArgs) { @@ -21,17 +25,17 @@ class TokenContract extends SmartContract { ...Permissions.default(), editState: Permissions.proofOrSignature(), }); - this.tokenSymbol.set('MY_TOKEN'); + this.tokenSymbol.set(tokenSymbol); } - @method mint(receiverAddress: PublicKey, amount: number) { + @method mint(receiverAddress: PublicKey, amount: UInt64) { this.token().mint({ address: receiverAddress, amount, }); } - @method burn(receiverAddress: PublicKey, amount: number) { + @method burn(receiverAddress: PublicKey, amount: UInt64) { this.token().burn({ address: receiverAddress, amount, @@ -41,7 +45,7 @@ class TokenContract extends SmartContract { @method send( senderAddress: PublicKey, receiverAddress: PublicKey, - amount: number + amount: UInt64 ) { this.token().send({ to: receiverAddress, @@ -49,6 +53,12 @@ class TokenContract extends SmartContract { amount, }); } + + @method setInvalidTokenSymbol() { + this.tokenSymbol.set( + 'this-token-symbol-is-too-long-and-will-cause-an-error' + ); + } } let zkappKey: PrivateKey; @@ -56,6 +66,12 @@ let zkappAddress: PublicKey; let zkapp: TokenContract; let feePayer: PrivateKey; +let tokenAccount1Key: PrivateKey; +let tokenAccount1: PublicKey; + +let tokenAccount2Key: PrivateKey; +let tokenAccount2: PublicKey; + beforeAll(async () => { // set up local blockchain, create zkapp keys, deploy the contract await isReady; @@ -67,11 +83,11 @@ beforeAll(async () => { zkappAddress = zkappKey.toPublicKey(); zkapp = new TokenContract(zkappAddress); - let tokenAccount1Key = Local.testAccounts[1].privateKey; - let tokenAccount1 = tokenAccount1Key.toPublicKey(); + tokenAccount1Key = Local.testAccounts[1].privateKey; + tokenAccount1 = tokenAccount1Key.toPublicKey(); - let tokenAccount2Key = Local.testAccounts[2].privateKey; - let tokenAccount2 = tokenAccount2Key.toPublicKey(); + tokenAccount2Key = Local.testAccounts[2].privateKey; + tokenAccount2 = tokenAccount2Key.toPublicKey(); let tx = await Mina.transaction(feePayer, () => { Party.fundNewAccount(feePayer); @@ -84,11 +100,46 @@ afterAll(() => setTimeout(shutdown, 0)); describe('Token', () => { describe('Create existing token', () => { - it('should have a valid custom token id', async () => {}); - it('should have a valid token symbol', async () => {}); - it('should create a valid token with a parentTokenId', async () => {}); - it('should error if passing in an invalid parentTokenId', async () => {}); - it('should error if passing in an invalid tokenSymbol', async () => {}); + it('should have a valid custom token id', async () => { + const tokenId = zkapp.token().id; + const expectedTokenId = new Token({ tokenOwner: zkappAddress }).id; + expect(tokenId).toBeDefined(); + expect(tokenId).toEqual(expectedTokenId); + }); + + it('should have a valid token symbol', async () => { + const symbol = Mina.getAccount({ + publicKey: zkappAddress, + }).tokenSymbol; + expect(tokenSymbol).toBeDefined(); + expect(symbol).toEqual(tokenSymbol); + }); + + it('should create a valid token with a different parentTokenId', async () => { + const newTokenId = Ledger.customTokenID( + tokenAccount1, + Ledger.fieldOfBase58(zkapp.token().id) + ); + expect(newTokenId).toBeDefined(); + }); + + it('should error if passing in an invalid parentTokenId', async () => { + expect(() => { + new Token({ + tokenOwner: zkappAddress, + parentTokenId: 'invalid', + }); + }).toThrow(); + }); + + it('should error if passing in an invalid tokenSymbol', async () => { + await Mina.transaction(feePayer, () => { + zkapp.setInvalidTokenSymbol(); + zkapp.sign(zkappKey); + }).catch((e) => { + expect(e).toBeDefined(); + }); + }); }); describe('Mint token', () => { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index f94f429651..31d68668c3 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -23,7 +23,7 @@ import { } from './party'; import { PrivateKey, PublicKey } from './signature'; import * as Mina from './mina'; -import { Int64, UInt32, UInt64 } from './int'; +import { UInt32, UInt64 } from './int'; import { mainContext, inCheckedComputation, From 6f7191e34ea7761ab2e9dc4100660f93e69dcfd7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 15 Jul 2022 15:04:14 -0700 Subject: [PATCH 29/49] Add minting with precondition tests --- src/lib/token.test.ts | 58 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 15d1734cfb..0ccb3b612c 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -1,6 +1,9 @@ import { shutdown, isReady, + State, + state, + Field, UInt64, UInt32, SmartContract, @@ -19,6 +22,9 @@ import { const tokenSymbol = 'MY_TOKEN'; class TokenContract extends SmartContract { + @state(UInt64) totalAmountInCirculation = State(); + @state(UInt64) maxAmountInCirculation = State(); + deploy(args: DeployArgs) { super.deploy(args); this.setPermissions({ @@ -26,13 +32,29 @@ class TokenContract extends SmartContract { editState: Permissions.proofOrSignature(), }); this.tokenSymbol.set(tokenSymbol); + this.totalAmountInCirculation.set(UInt64.zero); + this.maxAmountInCirculation.set(UInt64.from(100_000_000)); } @method mint(receiverAddress: PublicKey, amount: UInt64) { + let totalAmountInCirculation = this.totalAmountInCirculation.get(); + this.totalAmountInCirculation.assertEquals(totalAmountInCirculation); + + let maxAmountInCirculation = this.maxAmountInCirculation.get(); + this.maxAmountInCirculation.assertEquals(maxAmountInCirculation); + + let newTotalAmountInCirculation = totalAmountInCirculation.add(amount); + + newTotalAmountInCirculation.value + .lte(maxAmountInCirculation.value) + .assertTrue(); + this.token().mint({ address: receiverAddress, amount, }); + + this.totalAmountInCirculation.set(newTotalAmountInCirculation); } @method burn(receiverAddress: PublicKey, amount: UInt64) { @@ -143,9 +165,39 @@ describe('Token', () => { }); describe('Mint token', () => { - it('should change the balance of a token account after token owner mints', async () => {}); - it('should error if token owner mints more tokens than allowed', async () => {}); - it('should error if token owner mints negative amount tokens', async () => {}); + it('should change the balance of a token account after token owner mints', async () => { + let tx = await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(100_000)); + zkapp.sign(zkappKey); + }); + tx.send(); + const balance = Mina.getBalance({ + publicKey: tokenAccount1, + tokenId: zkapp.token().id, + }).value.toBigInt(); + expect(balance).toEqual(BigInt(100_000)); + }); + + it('should error if token owner mints more tokens than allowed', async () => { + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(100_000_000_000)); + zkapp.sign(zkappKey); + }).catch((e) => { + expect(e).toBeDefined(); + }); + }); + + it('should error if token owner mints negative amount tokens', async () => { + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(-100_000)); + zkapp.sign(zkappKey); + }).catch((e) => { + expect(e).toBeDefined(); + }); + }); }); describe('Burn token', () => { From f0c2d72386dd5f3b3b537a60ca5b4beb62b0c833 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Sun, 17 Jul 2022 23:21:26 -0700 Subject: [PATCH 30/49] Refactor getAccount and getBalance parameters --- src/lib/fetch.ts | 5 ++--- src/lib/mina.ts | 50 ++++++++++++++++++----------------------- src/lib/party.ts | 13 +++++------ src/lib/precondition.ts | 5 +---- src/lib/state.ts | 5 +---- src/lib/token.test.ts | 12 +++++----- src/lib/zkapp.ts | 10 ++------- 7 files changed, 38 insertions(+), 62 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 12a5da0482..50859ace4a 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -9,7 +9,6 @@ import { } from './party'; import { PublicKey } from './signature'; import { NetworkValue } from './precondition'; -import { AccountParams } from './mina'; export { fetchAccount, @@ -47,7 +46,7 @@ function setGraphqlEndpoint(graphqlEndpoint: string) { * @returns zkapp information on the specified account or an error is thrown */ async function fetchAccount( - accountInfo: AccountParams, + accountInfo: { publicKey: PublicKey; tokenId?: string }, graphqlEndpoint = defaultGraphqlEndpoint, { timeout = defaultTimeout } = {} ): Promise< @@ -68,7 +67,7 @@ async function fetchAccount( // internal version of fetchAccount which does the same, but returns the original JSON version // of the account, to save some back-and-forth conversions when caching accounts async function fetchAccountInternal( - accountInfo: AccountParams, + accountInfo: { publicKey: PublicKey; tokenId?: string }, graphqlEndpoint = defaultGraphqlEndpoint, config?: FetchConfig ) { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 4e8f1a2858..16c568cdf1 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -35,7 +35,6 @@ export { getNetworkState, accountCreationFee, sendTransaction, - AccountParams, }; interface TransactionId { @@ -76,11 +75,6 @@ type SenderSpec = | { feePayerKey: PrivateKey; fee?: number | string | UInt64; memo?: string } | undefined; -type AccountParams = { - publicKey: PublicKey; - tokenId?: string; -}; - function createUnsignedTransaction( f: () => unknown, { fetchMode = 'cached' as FetchMode } = {} @@ -128,10 +122,7 @@ function createTransaction( if (feePayerKey !== undefined) { // if senderKey is provided, fetch account to get nonce and mark to be signed let senderAddress = feePayerKey.toPublicKey(); - let senderAccount = getAccount({ - publicKey: senderAddress, - tokenId: getDefaultTokenId(), - }); + let senderAccount = getAccount(senderAddress, getDefaultTokenId()); feePayerParty = Party.defaultFeePayer( senderAddress, feePayerKey, @@ -187,7 +178,7 @@ function createTransaction( interface Mina { transaction(sender: SenderSpec, f: () => void): Promise; currentSlot(): UInt32; - getAccount(accountParams: AccountParams): Account; + getAccount(publicKey: PublicKey, tokenId?: string): Account; getNetworkState(): NetworkValue; accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): TransactionId; @@ -235,13 +226,14 @@ function LocalBlockchain({ Math.ceil((new Date().valueOf() - startTime) / msPerSlot) ); }, - getAccount(account: AccountParams): Account { - let { publicKey, tokenId } = account; - tokenId = tokenId ?? getDefaultTokenId(); - const tokenIdAsField = tokenId - ? Ledger.fieldOfBase58(tokenId) - : Ledger.fieldOfBase58(getDefaultTokenId()); - let ledgerAccount = ledger.getAccount(publicKey, tokenIdAsField); + getAccount( + publicKey: PublicKey, + tokenId: string = getDefaultTokenId() + ): Account { + let ledgerAccount = ledger.getAccount( + publicKey, + Ledger.fieldOfBase58(tokenId) + ); if (ledgerAccount == undefined) { throw new Error( `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${tokenId}` @@ -307,9 +299,10 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { 'currentSlot() is not implemented yet for remote blockchains.' ); }, - getAccount(account: AccountParams): Account { - let { publicKey, tokenId } = account; - tokenId = tokenId ?? getDefaultTokenId(); + getAccount( + publicKey: PublicKey, + tokenId: string = getDefaultTokenId() + ): Account { if (currentTransaction?.fetchMode === 'test') { Fetch.markAccountToBeFetched(publicKey, tokenId, graphqlEndpoint); let account = Fetch.getCachedAccount( @@ -403,9 +396,10 @@ let activeInstance: Mina = { currentSlot: () => { throw new Error('must call Mina.setActiveInstance first'); }, - getAccount: (account: AccountParams) => { - let { publicKey, tokenId } = account; - tokenId = tokenId ?? getDefaultTokenId(); + getAccount( + publicKey: PublicKey, + tokenId: string = getDefaultTokenId() + ): Account { if (currentTransaction?.fetchMode === 'test') { Fetch.markAccountToBeFetched( publicKey, @@ -490,8 +484,8 @@ function currentSlot(): UInt32 { /** * @return The account data associated to the given public key. */ -function getAccount(account: AccountParams) { - return activeInstance.getAccount(account); +function getAccount(publicKey: PublicKey, tokenId?: string): Account { + return activeInstance.getAccount(publicKey, tokenId); } /** @@ -504,8 +498,8 @@ function getNetworkState() { /** * @return The balance associated to the given public key. */ -function getBalance(account: AccountParams) { - return activeInstance.getAccount(account).balance; +function getBalance(publicKey: PublicKey, tokenId?: string) { + return activeInstance.getAccount(publicKey, tokenId).balance; } function accountCreationFee() { diff --git a/src/lib/party.ts b/src/lib/party.ts index afcaf2a8d4..277d12d0f1 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -772,10 +772,10 @@ class Party { try { let inProver = Circuit.inProver(); if (inProver || !Circuit.inCheckedComputation()) { - let account = Mina.getAccount({ - publicKey: party.body.publicKey as PublicKey, - tokenId: getDefaultTokenId(), - }); + let account = Mina.getAccount( + party.body.publicKey as PublicKey, + getDefaultTokenId() + ); nonce = inProver ? Circuit.witness(UInt32, () => account.nonce) : account.nonce; @@ -881,10 +881,7 @@ class Party { // TODO: getAccount could always be used if we had a generic way to add account info prior to creating transactions if (nonce === undefined) { - let account = Mina.getAccount({ - publicKey, - tokenId: getDefaultTokenId(), - }); + let account = Mina.getAccount(publicKey, getDefaultTokenId()); nonce = account.nonce; } diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index 8e76dc8f17..674ce7103d 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -178,10 +178,7 @@ To write a correct circuit, you must avoid any dependency on the concrete value let value: U; if (accountOrNetwork === 'account') { let address = party.body.publicKey; - let account: any = Mina.getAccount({ - publicKey: address, - tokenId: getDefaultTokenId(), - }); + let account: any = Mina.getAccount(address, getDefaultTokenId()); value = account[key]; if (value === undefined) throw Error( diff --git a/src/lib/state.ts b/src/lib/state.ts index 0b5c38e083..ac7aab1222 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -202,10 +202,7 @@ function createState(): InternalStateType { if (!GlobalContext.inCompile() && !GlobalContext.inAnalyze()) { let account: Account; try { - account = Mina.getAccount({ - publicKey: address, - tokenId: getDefaultTokenId(), - }); + account = Mina.getAccount(address, getDefaultTokenId()); } catch (err) { // TODO: there should also be a reasonable error here if (inProver) { diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 0ccb3b612c..6110b6624b 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -130,9 +130,7 @@ describe('Token', () => { }); it('should have a valid token symbol', async () => { - const symbol = Mina.getAccount({ - publicKey: zkappAddress, - }).tokenSymbol; + const symbol = Mina.getAccount(zkappAddress).tokenSymbol; expect(tokenSymbol).toBeDefined(); expect(symbol).toEqual(tokenSymbol); }); @@ -172,10 +170,10 @@ describe('Token', () => { zkapp.sign(zkappKey); }); tx.send(); - const balance = Mina.getBalance({ - publicKey: tokenAccount1, - tokenId: zkapp.token().id, - }).value.toBigInt(); + const balance = Mina.getBalance( + tokenAccount1, + zkapp.token().id + ).value.toBigInt(); expect(balance).toEqual(BigInt(100_000)); }); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 31d68668c3..c92443b405 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -648,10 +648,7 @@ function addFeePayer( feePayerKey = PrivateKey.fromBase58(feePayerKey); let senderAddress = feePayerKey.toPublicKey(); if (feePayerNonce === undefined) { - let senderAccount = Mina.getAccount({ - publicKey: senderAddress, - tokenId: getDefaultTokenId(), - }); + let senderAccount = Mina.getAccount(senderAddress, getDefaultTokenId()); feePayerNonce = senderAccount.nonce.toString(); } let newMemo = memo; @@ -677,10 +674,7 @@ function signFeePayer( feePayerKey = PrivateKey.fromBase58(feePayerKey); let senderAddress = feePayerKey.toPublicKey(); if (feePayerNonce === undefined) { - let senderAccount = Mina.getAccount({ - publicKey: senderAddress, - tokenId: getDefaultTokenId(), - }); + let senderAccount = Mina.getAccount(senderAddress, getDefaultTokenId()); feePayerNonce = senderAccount.nonce.toString(); } if (feePayerMemo) parties.memo = Ledger.memoToBase58(feePayerMemo); From ece0c8258e9632a75836408500dec26dc13382bd Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 18 Jul 2022 11:58:36 -0700 Subject: [PATCH 31/49] Implement burn and send tests --- src/lib/token.test.ts | 197 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 170 insertions(+), 27 deletions(-) diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 6110b6624b..1c277f7667 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -3,9 +3,7 @@ import { isReady, State, state, - Field, UInt64, - UInt32, SmartContract, Mina, PrivateKey, @@ -16,7 +14,6 @@ import { Permissions, Token, Ledger, - getDefaultTokenId, } from '../../dist/server'; const tokenSymbol = 'MY_TOKEN'; @@ -94,9 +91,9 @@ let tokenAccount1: PublicKey; let tokenAccount2Key: PrivateKey; let tokenAccount2: PublicKey; -beforeAll(async () => { - // set up local blockchain, create zkapp keys, deploy the contract - await isReady; +// Call `setupLocal` before running each test to reset the ledger state. +async function setupLocal() { + // Set up local blockchain, create zkapp keys, token account keys, deploy the contract let Local = Mina.LocalBlockchain(); Mina.setActiveInstance(Local); feePayer = Local.testAccounts[0].privateKey; @@ -116,12 +113,18 @@ beforeAll(async () => { zkapp.deploy({ zkappKey }); }); tx.send(); -}); - -afterAll(() => setTimeout(shutdown, 0)); +} describe('Token', () => { + beforeAll(async () => await isReady); + + afterAll(() => setTimeout(shutdown, 0)); + describe('Create existing token', () => { + beforeEach(async () => { + await setupLocal(); + }); + it('should have a valid custom token id', async () => { const tokenId = zkapp.token().id; const expectedTokenId = new Token({ tokenOwner: zkappAddress }).id; @@ -163,18 +166,22 @@ describe('Token', () => { }); describe('Mint token', () => { + beforeEach(async () => { + await setupLocal(); + }); + it('should change the balance of a token account after token owner mints', async () => { - let tx = await Mina.transaction(feePayer, () => { - Party.fundNewAccount(feePayer); - zkapp.mint(tokenAccount1, UInt64.from(100_000)); - zkapp.sign(zkappKey); - }); - tx.send(); - const balance = Mina.getBalance( - tokenAccount1, - zkapp.token().id - ).value.toBigInt(); - expect(balance).toEqual(BigInt(100_000)); + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(100_000)); + zkapp.sign(zkappKey); + }) + ).send(); + + expect( + Mina.getBalance(tokenAccount1, zkapp.token().id).value.toBigInt() + ).toEqual(BigInt(100_000)); }); it('should error if token owner mints more tokens than allowed', async () => { @@ -199,14 +206,150 @@ describe('Token', () => { }); describe('Burn token', () => { - it('should change the balance of a token account after token owner burns', async () => {}); - it('should error if token owner burns more tokens than token account has', async () => {}); - it('should error if token owner burns negative amount tokens', async () => {}); + beforeEach(async () => { + await setupLocal(); + }); + + it('should change the balance of a token account after token owner burns', async () => { + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(100_000)); + zkapp.sign(zkappKey); + }) + ).send(); + ( + await Mina.transaction(feePayer, () => { + zkapp.burn(tokenAccount1, UInt64.from(10_000)); + zkapp.sign(zkappKey); + }) + ) + .sign([tokenAccount1Key]) + .send(); + + expect( + Mina.getBalance(tokenAccount1, zkapp.token().id).value.toBigInt() + ).toEqual(BigInt(90_000)); + }); + + it('should error if token owner burns more tokens than token account has', async () => { + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(1_000)); + zkapp.sign(zkappKey); + }) + ).send(); + + let tx = ( + await Mina.transaction(feePayer, () => { + zkapp.burn(tokenAccount1, UInt64.from(10_000)); + zkapp.sign(zkappKey); + }) + ).sign([tokenAccount1Key]); + + expect(() => { + tx.send(); + }).toThrow(); + }); + + it('should error if token owner burns negative amount tokens', async () => { + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.burn(tokenAccount1, UInt64.from(-100_000)); + zkapp.sign(zkappKey); + }).catch((e) => { + expect(e).toBeDefined(); + }); + }); }); describe('Send token', () => { - it('should change the balance of a token account after sending', async () => {}); - it('should error creating a token account if no account creation fee is specified', async () => {}); - it('should error if sender sends more tokens than they have', async () => {}); - it('should error if sender sends negative amount of tokens', async () => {}); + beforeEach(async () => { + await setupLocal(); + }); + + it('should change the balance of a token account after sending', async () => { + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(100_000)); + zkapp.sign(zkappKey); + }) + ).send(); + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.send(tokenAccount1, tokenAccount2, UInt64.from(10_000)); + zkapp.sign(zkappKey); + }) + ) + .sign([tokenAccount1Key]) + .send(); + + expect( + Mina.getBalance(tokenAccount1, zkapp.token().id).value.toBigInt() + ).toEqual(BigInt(90_000)); + expect( + Mina.getBalance(tokenAccount2, zkapp.token().id).value.toBigInt() + ).toEqual(BigInt(10_000)); + }); + + it('should error creating a token account if no account creation fee is specified', async () => { + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(100_000)); + zkapp.sign(zkappKey); + }) + ).send(); + + let tx = ( + await Mina.transaction(feePayer, () => { + zkapp.send(tokenAccount1, tokenAccount2, UInt64.from(10_000)); + zkapp.sign(zkappKey); + }) + ).sign([tokenAccount1Key]); + + expect(() => { + tx.send(); + }).toThrow(); + }); + + it('should error if sender sends more tokens than they have', async () => { + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(100_000)); + zkapp.sign(zkappKey); + }) + ).send(); + + let tx = ( + await Mina.transaction(feePayer, () => { + zkapp.send(tokenAccount1, tokenAccount2, UInt64.from(100_000_000)); + zkapp.sign(zkappKey); + }) + ).sign([tokenAccount1Key]); + + expect(() => { + tx.send(); + }).toThrow(); + }); + + it('should error if sender sends negative amount of tokens', async () => { + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.mint(tokenAccount1, UInt64.from(100_000)); + zkapp.sign(zkappKey); + }) + ).send(); + await Mina.transaction(feePayer, () => { + zkapp.send(tokenAccount1, tokenAccount2, UInt64.from(-10_000)); + zkapp.sign(zkappKey); + }).catch((e) => { + expect(e).toBeDefined(); + }); + }); }); }); From 6ff6785e220768fd49f36c1df4b437a4483bf8d2 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 18 Jul 2022 12:07:01 -0700 Subject: [PATCH 32/49] Fix fetchAccount type parameters --- src/lib/fetch.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 50859ace4a..ce594d1995 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -46,16 +46,24 @@ function setGraphqlEndpoint(graphqlEndpoint: string) { * @returns zkapp information on the specified account or an error is thrown */ async function fetchAccount( - accountInfo: { publicKey: PublicKey; tokenId?: string }, + accountInfo: { publicKey: string | PublicKey; tokenId?: string }, graphqlEndpoint = defaultGraphqlEndpoint, { timeout = defaultTimeout } = {} ): Promise< | { account: Account; error: undefined } | { account: undefined; error: FetchError } > { - let response = await fetchAccountInternal(accountInfo, graphqlEndpoint, { - timeout, - }); + let publicKeyBase58 = + accountInfo.publicKey instanceof PublicKey + ? accountInfo.publicKey.toBase58() + : accountInfo.publicKey; + let response = await fetchAccountInternal( + { publicKey: publicKeyBase58, tokenId: accountInfo.tokenId }, + graphqlEndpoint, + { + timeout, + } + ); return response.error === undefined ? { account: parseFetchedAccount(response.account), @@ -67,13 +75,13 @@ async function fetchAccount( // internal version of fetchAccount which does the same, but returns the original JSON version // of the account, to save some back-and-forth conversions when caching accounts async function fetchAccountInternal( - accountInfo: { publicKey: PublicKey; tokenId?: string }, + accountInfo: { publicKey: string; tokenId?: string }, graphqlEndpoint = defaultGraphqlEndpoint, config?: FetchConfig ) { const { publicKey, tokenId } = accountInfo; let [response, error] = await makeGraphqlRequest( - accountQuery(publicKey.toBase58(), tokenId ?? getDefaultTokenId()), + accountQuery(publicKey, tokenId ?? getDefaultTokenId()), graphqlEndpoint, config ); @@ -85,7 +93,7 @@ async function fetchAccountInternal( account: undefined, error: { statusCode: 404, - statusText: `fetchAccount: Account with public key ${publicKey.toBase58()} does not exist.`, + statusText: `fetchAccount: Account with public key ${publicKey} does not exist.`, }, }; } @@ -306,7 +314,7 @@ async function fetchMissingData(graphqlEndpoint: string) { }); let promises = accounts.map(async ([key, { publicKey, tokenId }]) => { let response = await fetchAccountInternal( - { publicKey: PublicKey.fromBase58(publicKey), tokenId }, + { publicKey, tokenId }, graphqlEndpoint ); if (response.error === undefined) delete accountsToFetch[key]; From f5178ce93135a6a2122a50d760ae247f357bcbd7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 18 Jul 2022 13:04:46 -0700 Subject: [PATCH 33/49] Use init method for zkApp state --- src/lib/token.test.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 1c277f7667..63d6d0006d 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -28,6 +28,9 @@ class TokenContract extends SmartContract { ...Permissions.default(), editState: Permissions.proofOrSignature(), }); + } + + @method init() { this.tokenSymbol.set(tokenSymbol); this.totalAmountInCirculation.set(UInt64.zero); this.maxAmountInCirculation.set(UInt64.from(100_000_000)); @@ -108,11 +111,13 @@ async function setupLocal() { tokenAccount2Key = Local.testAccounts[2].privateKey; tokenAccount2 = tokenAccount2Key.toPublicKey(); - let tx = await Mina.transaction(feePayer, () => { - Party.fundNewAccount(feePayer); - zkapp.deploy({ zkappKey }); - }); - tx.send(); + ( + await Mina.transaction(feePayer, () => { + Party.fundNewAccount(feePayer); + zkapp.deploy({ zkappKey }); + zkapp.init(); + }) + ).send(); } describe('Token', () => { From d94b75f25d62c183d02355416f382e9b56c634d6 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 19 Jul 2022 10:18:24 -0700 Subject: [PATCH 34/49] Add support for checked tokenId, convert tokenId type to Field, additional feedback --- src/lib/fetch.ts | 13 +++++---- src/lib/mina.ts | 39 +++++++++++++++------------ src/lib/party.ts | 44 +++++++++++++++++-------------- src/lib/precondition.ts | 2 +- src/lib/state.ts | 4 +-- src/lib/token.test.ts | 15 ++--------- src/lib/zkapp.ts | 2 +- src/snarky.d.ts | 5 ++-- src/snarky/snarky-class-spec.json | 6 ++++- 9 files changed, 67 insertions(+), 63 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index ce594d1995..8a335b7277 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,5 +1,5 @@ import 'isomorphic-fetch'; -import { Bool, Field, Types } from '../snarky'; +import { Bool, Field, Types, Ledger } from '../snarky'; import { UInt32, UInt64 } from './int'; import { getDefaultTokenId, @@ -81,7 +81,10 @@ async function fetchAccountInternal( ) { const { publicKey, tokenId } = accountInfo; let [response, error] = await makeGraphqlRequest( - accountQuery(publicKey, tokenId ?? getDefaultTokenId()), + accountQuery( + publicKey, + tokenId ?? Ledger.fieldToBase58(getDefaultTokenId()) + ), graphqlEndpoint, config ); @@ -163,7 +166,7 @@ type Account = { publicKey: PublicKey; nonce: UInt32; balance: UInt64; - tokenId: string; + tokenId: Field; tokenSymbol: string; zkapp?: { appState: Field[] }; permissions?: Permissions; @@ -176,7 +179,7 @@ type Account = { type FlexibleAccount = { publicKey: PublicKey | string; nonce: UInt32 | string | number; - tokenId: string; + tokenId?: string; tokenSymbol?: string; balance?: UInt64 | string | number; zkapp?: { appState: (Field | string | number)[] }; @@ -261,7 +264,7 @@ function stringifyAccount(account: FlexibleAccount): FetchedAccount { zkapp?.appState.map((s) => s.toString()) ?? Array(ZkappStateLength).fill('0'), balance: { total: balance?.toString() ?? '0' }, - tokenId, + tokenId: tokenId ?? Ledger.fieldToBase58(getDefaultTokenId()), tokenSymbol: tokenSymbol ?? '', }; } diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 16c568cdf1..299b072f01 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -178,7 +178,7 @@ function createTransaction( interface Mina { transaction(sender: SenderSpec, f: () => void): Promise; currentSlot(): UInt32; - getAccount(publicKey: PublicKey, tokenId?: string): Account; + getAccount(publicKey: PublicKey, tokenId?: Field): Account; getNetworkState(): NetworkValue; accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): TransactionId; @@ -228,15 +228,14 @@ function LocalBlockchain({ }, getAccount( publicKey: PublicKey, - tokenId: string = getDefaultTokenId() + tokenId: Field = getDefaultTokenId() ): Account { - let ledgerAccount = ledger.getAccount( - publicKey, - Ledger.fieldOfBase58(tokenId) - ); + let ledgerAccount = ledger.getAccount(publicKey, tokenId); if (ledgerAccount == undefined) { throw new Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${tokenId}` + `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${Ledger.fieldToBase58( + tokenId + )}` ); } else { return { @@ -301,13 +300,17 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { }, getAccount( publicKey: PublicKey, - tokenId: string = getDefaultTokenId() + tokenId: Field = getDefaultTokenId() ): Account { if (currentTransaction?.fetchMode === 'test') { - Fetch.markAccountToBeFetched(publicKey, tokenId, graphqlEndpoint); + Fetch.markAccountToBeFetched( + publicKey, + Ledger.fieldToBase58(tokenId), + graphqlEndpoint + ); let account = Fetch.getCachedAccount( publicKey, - tokenId, + Ledger.fieldToBase58(tokenId), graphqlEndpoint ); return account ?? dummyAccount(publicKey); @@ -318,13 +321,15 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { ) { let account = Fetch.getCachedAccount( publicKey, - tokenId, + Ledger.fieldToBase58(tokenId), graphqlEndpoint ); if (account !== undefined) return account; } throw Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${tokenId}.\nGraphql endpoint: ${graphqlEndpoint}` + `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${Ledger.fieldToBase58( + tokenId + )}.\nGraphql endpoint: ${graphqlEndpoint}` ); }, getNetworkState() { @@ -398,12 +403,12 @@ let activeInstance: Mina = { }, getAccount( publicKey: PublicKey, - tokenId: string = getDefaultTokenId() + tokenId: Field = getDefaultTokenId() ): Account { if (currentTransaction?.fetchMode === 'test') { Fetch.markAccountToBeFetched( publicKey, - tokenId, + Ledger.fieldToBase58(tokenId), Fetch.defaultGraphqlEndpoint ); return dummyAccount(publicKey); @@ -414,7 +419,7 @@ let activeInstance: Mina = { ) { let account = Fetch.getCachedAccount( publicKey, - tokenId, + Ledger.fieldToBase58(tokenId), Fetch.defaultGraphqlEndpoint ); if (account === undefined) @@ -484,7 +489,7 @@ function currentSlot(): UInt32 { /** * @return The account data associated to the given public key. */ -function getAccount(publicKey: PublicKey, tokenId?: string): Account { +function getAccount(publicKey: PublicKey, tokenId?: Field): Account { return activeInstance.getAccount(publicKey, tokenId); } @@ -498,7 +503,7 @@ function getNetworkState() { /** * @return The balance associated to the given public key. */ -function getBalance(publicKey: PublicKey, tokenId?: string) { +function getBalance(publicKey: PublicKey, tokenId?: Field) { return activeInstance.getAccount(publicKey, tokenId).balance; } diff --git a/src/lib/party.ts b/src/lib/party.ts index 277d12d0f1..77af3075b5 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -12,7 +12,7 @@ import { PrivateKey, PublicKey } from './signature'; import { UInt64, UInt32, Int64 } from './int'; import * as Mina from './mina'; import { SmartContract } from './zkapp'; -import { withContextAsync } from './global-context'; +import { inCheckedComputation, withContextAsync } from './global-context'; import * as Precondition from './precondition'; import { Proof } from './proof_system'; import { emptyHashWithPrefix, hashWithPrefix, prefixes } from './hash'; @@ -242,7 +242,7 @@ let Permissions = { }), }; -const getDefaultTokenId = () => Ledger.fieldToBase58(Field.one); +const getDefaultTokenId = () => Field.one; type Event = Field[]; @@ -356,11 +356,11 @@ const Body = { return { publicKey, update: Body.noUpdate(), - tokenId: Ledger.fieldOfBase58(getDefaultTokenId()), + tokenId: getDefaultTokenId(), balanceChange: Int64.zero, events: Events.empty(), sequenceEvents: Events.empty(), - caller: Ledger.fieldOfBase58(getDefaultTokenId()), + caller: getDefaultTokenId(), callData: Field.zero, // TODO new MerkleList(), callDepth: 0, preconditions: Preconditions.ignoreAll(), @@ -504,11 +504,11 @@ type UnfinishedSignature = undefined | LazySignature | string; type LazyControl = Control | LazySignature | LazyProof; class Token { - readonly id: string; - readonly parentTokenId: string; + readonly id: Field; + readonly parentTokenId: Field; readonly tokenOwner: PublicKey; - constructor(options: { tokenOwner: PublicKey; parentTokenId?: string }) { + constructor(options: { tokenOwner: PublicKey; parentTokenId?: Field }) { let { tokenOwner, parentTokenId } = options ?? {}; // Reassign to default tokenId if undefined @@ -516,25 +516,29 @@ class Token { // Check if we can deserialize the parentTokenId try { - Ledger.fieldOfBase58(parentTokenId); + Ledger.fieldToBase58(parentTokenId); } catch (e) { - throw new Error(`Invalid parentTokenId\n(error: ${(e).message})`); + throw new Error( + `Invalid parentTokenId\n(error: ${(e as Error).message})` + ); } // Check if we can create a custom tokenId try { - Ledger.customTokenID(tokenOwner, Ledger.fieldOfBase58(parentTokenId)); + Ledger.customTokenId(tokenOwner, parentTokenId); } catch (e) { throw new Error( - `Could not create a custom token id:\n(error: ${(e).message})` + `Could not create a custom token id:\n(error: ${(e as Error).message})` ); } + this.parentTokenId = parentTokenId; this.tokenOwner = tokenOwner; - this.id = Ledger.customTokenID( - tokenOwner, - Ledger.fieldOfBase58(this.parentTokenId) - ); + if (inCheckedComputation()) { + this.id = Ledger.customTokenIdChecked(tokenOwner, this.parentTokenId); + } else { + this.id = Ledger.customTokenId(tokenOwner, this.parentTokenId); + } } } @@ -573,7 +577,7 @@ class Party { return new (Party as any)(body, authorization, party.isSelf); } - token(tokenId?: string) { + token(tokenId?: Field) { let thisParty = this; let customToken = new Token({ tokenOwner: thisParty.body.publicKey, @@ -829,8 +833,8 @@ class Party { static createUnsigned( publicKey: PublicKey, options?: { - caller?: string; - tokenId?: string; + caller?: Field; + tokenId?: Field; callDepth?: number; useFullCommitment?: Bool; } @@ -846,8 +850,8 @@ class Party { const pk = publicKey; const body: Body = Body.keepAll(pk); const { caller, tokenId, callDepth, useFullCommitment } = options ?? {}; - body.caller = caller ? Ledger.fieldOfBase58(caller) : body.caller; - body.tokenId = tokenId ? Ledger.fieldOfBase58(tokenId) : body.tokenId; + body.caller = caller ?? body.caller; + body.tokenId = tokenId ?? body.tokenId; body.callDepth = callDepth ?? body.callDepth; body.useFullCommitment = useFullCommitment ?? body.useFullCommitment; diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index 674ce7103d..96f6cd167f 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -178,7 +178,7 @@ To write a correct circuit, you must avoid any dependency on the concrete value let value: U; if (accountOrNetwork === 'account') { let address = party.body.publicKey; - let account: any = Mina.getAccount(address, getDefaultTokenId()); + let account: any = Mina.getAccount(address, party.body.tokenId); value = account[key]; if (value === undefined) throw Error( diff --git a/src/lib/state.ts b/src/lib/state.ts index ac7aab1222..1ea2c63386 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -1,4 +1,4 @@ -import { Circuit, Field, AsFieldElements } from '../snarky'; +import { Circuit, Field, AsFieldElements, Ledger } from '../snarky'; import { circuitArray } from './circuit_value'; import { getDefaultTokenId, Party } from './party'; import { PublicKey } from './signature'; @@ -257,7 +257,7 @@ To write a correct circuit, you must avoid any dependency on the concrete value let address: PublicKey = this._contract.instance.address; let { account } = await fetchAccount({ publicKey: address, - tokenId: getDefaultTokenId(), + tokenId: Ledger.fieldToBase58(getDefaultTokenId()), }); if (account === undefined) return undefined; let stateAsFields: Field[]; diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 63d6d0006d..fb44bae8b4 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -1,4 +1,5 @@ import { + Field, shutdown, isReady, State, @@ -144,22 +145,10 @@ describe('Token', () => { }); it('should create a valid token with a different parentTokenId', async () => { - const newTokenId = Ledger.customTokenID( - tokenAccount1, - Ledger.fieldOfBase58(zkapp.token().id) - ); + const newTokenId = Ledger.customTokenId(tokenAccount1, zkapp.token().id); expect(newTokenId).toBeDefined(); }); - it('should error if passing in an invalid parentTokenId', async () => { - expect(() => { - new Token({ - tokenOwner: zkappAddress, - parentTokenId: 'invalid', - }); - }).toThrow(); - }); - it('should error if passing in an invalid tokenSymbol', async () => { await Mina.transaction(feePayer, () => { zkapp.setInvalidTokenSymbol(); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index c92443b405..2a76d769c9 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -334,7 +334,7 @@ class SmartContract { return this.self.network; } - token(tokenId?: string) { + token(tokenId?: Field) { return this.self.token(tokenId); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 029401b258..a6e4c852a5 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,5 +1,3 @@ -import { TokenId } from 'snarky/parties-leaves-json'; - export { Field, Bool, @@ -759,7 +757,8 @@ declare class Ledger { i: number ): string; - static customTokenID(publicKey: { g: Group }, tokenId: Field): string; + static customTokenId(publicKey: { g: Group }, tokenId: Field): Field; + static customTokenIdChecked(publicKey: { g: Group }, tokenId: Field): Field; static createTokenAccount(publicKey: { g: Group }, tokenId: Field): string; static publicKeyToString(publicKey: { g: Group }): string; diff --git a/src/snarky/snarky-class-spec.json b/src/snarky/snarky-class-spec.json index 5f32b17ca8..6ca8404288 100644 --- a/src/snarky/snarky-class-spec.json +++ b/src/snarky/snarky-class-spec.json @@ -385,7 +385,11 @@ "type": "function" }, { - "name": "customTokenID", + "name": "customTokenId", + "type": "function" + }, + { + "name": "customTokenIdChecked", "type": "function" }, { From 48c91013fd67b04a363ebbff91a74ef7e56fa055 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 19 Jul 2022 14:04:15 -0700 Subject: [PATCH 35/49] Refactor checked computation check and error messages --- src/lib/circuit_value.ts | 4 ++-- src/lib/party.ts | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index b8b526dc4d..07102be31b 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -77,8 +77,8 @@ abstract class CircuitValue { Circuit.assertEqual(this, x); } - isConstant(x: this) { - return x.toFields().every((x) => x.isConstant()); + toConstant(): this { + return (this.constructor as any).toConstant(this); } static ofFields( diff --git a/src/lib/party.ts b/src/lib/party.ts index 77af3075b5..f1af07049c 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -1,4 +1,4 @@ -import { circuitValue, cloneCircuitValue } from './circuit_value'; +import { CircuitValue, circuitValue, cloneCircuitValue } from './circuit_value'; import { Field, Bool, @@ -518,9 +518,7 @@ class Token { try { Ledger.fieldToBase58(parentTokenId); } catch (e) { - throw new Error( - `Invalid parentTokenId\n(error: ${(e as Error).message})` - ); + throw new Error(`Invalid parentTokenId\nError: ${(e as Error).message}`); } // Check if we can create a custom tokenId @@ -528,13 +526,13 @@ class Token { Ledger.customTokenId(tokenOwner, parentTokenId); } catch (e) { throw new Error( - `Could not create a custom token id:\n(error: ${(e as Error).message})` + `Could not create a custom token id:\nError: ${(e as Error).message}` ); } this.parentTokenId = parentTokenId; this.tokenOwner = tokenOwner; - if (inCheckedComputation()) { + if (tokenOwner.toConstant() && parentTokenId.isConstant()) { this.id = Ledger.customTokenIdChecked(tokenOwner, this.parentTokenId); } else { this.id = Ledger.customTokenId(tokenOwner, this.parentTokenId); @@ -885,7 +883,7 @@ class Party { // TODO: getAccount could always be used if we had a generic way to add account info prior to creating transactions if (nonce === undefined) { - let account = Mina.getAccount(publicKey, getDefaultTokenId()); + let account = Mina.getAccount(publicKey, body.tokenId); nonce = account.nonce; } From 4802f959d8c969c24557f47c63b151c1019978b4 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 19 Jul 2022 14:08:14 -0700 Subject: [PATCH 36/49] Update bindings --- MINA_COMMIT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MINA_COMMIT b/MINA_COMMIT index 9ed28fe275..d916a4822c 100644 --- a/MINA_COMMIT +++ b/MINA_COMMIT @@ -1,2 +1,2 @@ The mina commit used to generate the backends for node and chrome is -97ff72a1d43cda02225d972031cdb0639f0abf84 +e8e36ccff42f706e555efa1687c3f3310f46069f From cc3d69060cb99997ef58ad83948c6ccfe50f7505 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Tue, 19 Jul 2022 15:50:12 -0700 Subject: [PATCH 37/49] PR feedback --- src/lib/circuit_value.ts | 4 +-- src/lib/party.ts | 72 ++++++++++++++-------------------------- src/lib/state.ts | 5 ++- src/lib/token.test.ts | 44 +++--------------------- src/lib/zkapp.ts | 4 +-- 5 files changed, 36 insertions(+), 93 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 07102be31b..b8b526dc4d 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -77,8 +77,8 @@ abstract class CircuitValue { Circuit.assertEqual(this, x); } - toConstant(): this { - return (this.constructor as any).toConstant(this); + isConstant(x: this) { + return x.toFields().every((x) => x.isConstant()); } static ofFields( diff --git a/src/lib/party.ts b/src/lib/party.ts index f1af07049c..f683d2d850 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -1,18 +1,10 @@ -import { CircuitValue, circuitValue, cloneCircuitValue } from './circuit_value'; -import { - Field, - Bool, - Ledger, - Circuit, - Pickles, - Types, - Poseidon, -} from '../snarky'; +import { circuitValue, cloneCircuitValue } from './circuit_value'; +import { Field, Bool, Ledger, Circuit, Pickles, Types } from '../snarky'; import { PrivateKey, PublicKey } from './signature'; import { UInt64, UInt32, Int64 } from './int'; import * as Mina from './mina'; import { SmartContract } from './zkapp'; -import { inCheckedComputation, withContextAsync } from './global-context'; +import { withContextAsync } from './global-context'; import * as Precondition from './precondition'; import { Proof } from './proof_system'; import { emptyHashWithPrefix, hashWithPrefix, prefixes } from './hash'; @@ -514,13 +506,6 @@ class Token { // Reassign to default tokenId if undefined parentTokenId ??= getDefaultTokenId(); - // Check if we can deserialize the parentTokenId - try { - Ledger.fieldToBase58(parentTokenId); - } catch (e) { - throw new Error(`Invalid parentTokenId\nError: ${(e as Error).message}`); - } - // Check if we can create a custom tokenId try { Ledger.customTokenId(tokenOwner, parentTokenId); @@ -532,7 +517,10 @@ class Token { this.parentTokenId = parentTokenId; this.tokenOwner = tokenOwner; - if (tokenOwner.toConstant() && parentTokenId.isConstant()) { + if ( + tokenOwner.toFields().every((x) => x.isConstant()) && + parentTokenId.isConstant() + ) { this.id = Ledger.customTokenIdChecked(tokenOwner, this.parentTokenId); } else { this.id = Ledger.customTokenId(tokenOwner, this.parentTokenId); @@ -575,12 +563,12 @@ class Party { return new (Party as any)(body, authorization, party.isSelf); } - token(tokenId?: Field) { + token() { let thisParty = this; let customToken = new Token({ tokenOwner: thisParty.body.publicKey, - parentTokenId: tokenId, }); + return { id: customToken.id, parentTokenId: customToken.parentTokenId, @@ -591,7 +579,6 @@ class Party { caller: this.id, tokenId: this.id, callDepth: 1, - useFullCommitment: Bool(true), }); // Add the amount to mint to the receiver's account @@ -618,37 +605,26 @@ class Party { }, send({ from, to, amount }: SendParams) { - // Check if the sender party is the current zkApp address - if (from === thisParty.publicKey) { - // If so, create a new party for the zkApp to send the amount to the receiver - let senderParty = Party.createUnsigned(thisParty.publicKey, { - caller: this.id, - tokenId: this.id, - }); - senderParty.body.balanceChange = - senderParty.body.balanceChange.sub(amount); - } else { - // If not, create a new party for the sender to send the amount to the receiver - let senderParty = Party.createUnsigned(from, { - caller: this.id, - tokenId: this.id, - callDepth: 1, - useFullCommitment: Bool(true), - }); - senderParty.body.balanceChange = - senderParty.body.balanceChange.sub(amount); - - // Require signature if the sender party is not the zkApp - senderParty.authorization = { - kind: 'lazy-signature', - }; - } + // Create a new party for the sender to send the amount to the receiver + let senderParty = Party.createUnsigned(from, { + caller: this.id, + tokenId: this.id, + callDepth: 1, + useFullCommitment: Bool(true), + }); + + senderParty.body.balanceChange = + senderParty.body.balanceChange.sub(amount); + + // Require signature from the sender party + senderParty.authorization = { + kind: 'lazy-signature', + }; let receiverParty = Party.createUnsigned(to, { caller: this.id, tokenId: this.id, callDepth: 1, - useFullCommitment: Bool(true), }); // Add the amount to send to the receiver's account diff --git a/src/lib/state.ts b/src/lib/state.ts index 1ea2c63386..ae678edb30 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -202,7 +202,10 @@ function createState(): InternalStateType { if (!GlobalContext.inCompile() && !GlobalContext.inAnalyze()) { let account: Account; try { - account = Mina.getAccount(address, getDefaultTokenId()); + account = Mina.getAccount( + address, + this._contract.instance.self.body.tokenId + ); } catch (err) { // TODO: there should also be a reasonable error here if (inProver) { diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index fb44bae8b4..05e5bb695a 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -175,7 +175,7 @@ describe('Token', () => { expect( Mina.getBalance(tokenAccount1, zkapp.token().id).value.toBigInt() - ).toEqual(BigInt(100_000)); + ).toEqual(100_000n); }); it('should error if token owner mints more tokens than allowed', async () => { @@ -187,16 +187,6 @@ describe('Token', () => { expect(e).toBeDefined(); }); }); - - it('should error if token owner mints negative amount tokens', async () => { - await Mina.transaction(feePayer, () => { - Party.fundNewAccount(feePayer); - zkapp.mint(tokenAccount1, UInt64.from(-100_000)); - zkapp.sign(zkappKey); - }).catch((e) => { - expect(e).toBeDefined(); - }); - }); }); describe('Burn token', () => { @@ -223,7 +213,7 @@ describe('Token', () => { expect( Mina.getBalance(tokenAccount1, zkapp.token().id).value.toBigInt() - ).toEqual(BigInt(90_000)); + ).toEqual(90_000n); }); it('should error if token owner burns more tokens than token account has', async () => { @@ -246,16 +236,6 @@ describe('Token', () => { tx.send(); }).toThrow(); }); - - it('should error if token owner burns negative amount tokens', async () => { - await Mina.transaction(feePayer, () => { - Party.fundNewAccount(feePayer); - zkapp.burn(tokenAccount1, UInt64.from(-100_000)); - zkapp.sign(zkappKey); - }).catch((e) => { - expect(e).toBeDefined(); - }); - }); }); describe('Send token', () => { beforeEach(async () => { @@ -282,10 +262,10 @@ describe('Token', () => { expect( Mina.getBalance(tokenAccount1, zkapp.token().id).value.toBigInt() - ).toEqual(BigInt(90_000)); + ).toEqual(90_000n); expect( Mina.getBalance(tokenAccount2, zkapp.token().id).value.toBigInt() - ).toEqual(BigInt(10_000)); + ).toEqual(10_000n); }); it('should error creating a token account if no account creation fee is specified', async () => { @@ -329,21 +309,5 @@ describe('Token', () => { tx.send(); }).toThrow(); }); - - it('should error if sender sends negative amount of tokens', async () => { - ( - await Mina.transaction(feePayer, () => { - Party.fundNewAccount(feePayer); - zkapp.mint(tokenAccount1, UInt64.from(100_000)); - zkapp.sign(zkappKey); - }) - ).send(); - await Mina.transaction(feePayer, () => { - zkapp.send(tokenAccount1, tokenAccount2, UInt64.from(-10_000)); - zkapp.sign(zkappKey); - }).catch((e) => { - expect(e).toBeDefined(); - }); - }); }); }); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 2a76d769c9..a3a9f06776 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -334,8 +334,8 @@ class SmartContract { return this.self.network; } - token(tokenId?: Field) { - return this.self.token(tokenId); + token() { + return this.self.token(); } get tokenSymbol() { From 92be6a427933a8ef1cb0118f7f94520fbaaf72f8 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 20 Jul 2022 11:10:16 -0700 Subject: [PATCH 38/49] Fix parentTokenId parameter for new Token() --- src/lib/party.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/party.ts b/src/lib/party.ts index f683d2d850..b3139e3984 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -567,6 +567,7 @@ class Party { let thisParty = this; let customToken = new Token({ tokenOwner: thisParty.body.publicKey, + parentTokenId: thisParty.body.tokenId, }); return { From 3f7894536210391b7a5c8af644f4eccc65de425a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 20 Jul 2022 11:25:35 -0700 Subject: [PATCH 39/49] Add getter for tokenId on Party structure --- src/lib/party.ts | 4 ++++ src/lib/zkapp.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/lib/party.ts b/src/lib/party.ts index b3139e3984..ce823ea384 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -635,6 +635,10 @@ class Party { }; } + get tokenId() { + return this.body.tokenId; + } + get tokenSymbol() { let party = this; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index a3a9f06776..04a88d5e7b 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -338,6 +338,10 @@ class SmartContract { return this.self.token(); } + get tokenId() { + return this.self.tokenId; + } + get tokenSymbol() { return this.self.tokenSymbol; } From 122545258d34b56e8483b21a90db0b2f45f3f0e4 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 20 Jul 2022 11:26:52 -0700 Subject: [PATCH 40/49] Update bindings --- MINA_COMMIT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MINA_COMMIT b/MINA_COMMIT index d916a4822c..e723b48240 100644 --- a/MINA_COMMIT +++ b/MINA_COMMIT @@ -1,2 +1,2 @@ The mina commit used to generate the backends for node and chrome is -e8e36ccff42f706e555efa1687c3f3310f46069f +2df63cc94756f94a8daaea4cc6659c3ffd51447e From fe55f6eee26a07072291c31ba768cc68a0741824 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 20 Jul 2022 11:27:38 -0700 Subject: [PATCH 41/49] Fix CircuitValue.isConstant --- src/lib/circuit_value.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index b8b526dc4d..cb24ba93c5 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -77,8 +77,8 @@ abstract class CircuitValue { Circuit.assertEqual(this, x); } - isConstant(x: this) { - return x.toFields().every((x) => x.isConstant()); + isConstant() { + return this.toFields().every((x) => x.isConstant()); } static ofFields( From c359962353f21d856a7f0bf3b68d858ae3394098 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 20 Jul 2022 13:29:26 -0700 Subject: [PATCH 42/49] Update for isNew --- src/lib/party.ts | 1 + src/snarky/gen/js-layout.ts | 18 ++++++++++++++++++ src/snarky/gen/parties-json.ts | 2 ++ src/snarky/gen/parties.ts | 2 ++ 4 files changed, 23 insertions(+) diff --git a/src/lib/party.ts b/src/lib/party.ts index ce823ea384..faf88fd468 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -462,6 +462,7 @@ const AccountPrecondition = { state: appState, sequenceState: Events.emptySequenceState(), provedState: ignore(Bool(false)), + isNew: ignore(Bool(false)), }; }, nonce(nonce: UInt32): AccountPrecondition { diff --git a/src/snarky/gen/js-layout.ts b/src/snarky/gen/js-layout.ts index a4b7d40ca4..351bc0e1d0 100644 --- a/src/snarky/gen/js-layout.ts +++ b/src/snarky/gen/js-layout.ts @@ -888,6 +888,15 @@ let jsLayout = { }, docs: null, }, + { + key: 'isNew', + value: { + type: 'option', + optionType: 'flaggedOption', + inner: { type: 'Bool' }, + }, + docs: null, + }, ], }, docs: null, @@ -1751,6 +1760,15 @@ let jsLayout = { }, docs: null, }, + { + key: 'isNew', + value: { + type: 'option', + optionType: 'flaggedOption', + inner: { type: 'Bool' }, + }, + docs: null, + }, ], }, docs: null, diff --git a/src/snarky/gen/parties-json.ts b/src/snarky/gen/parties-json.ts index 20663c1935..7588cf9d0e 100644 --- a/src/snarky/gen/parties-json.ts +++ b/src/snarky/gen/parties-json.ts @@ -142,6 +142,7 @@ type Parties = { state: (Field | null)[]; sequenceState: Field | null; provedState: Bool | null; + isNew: Bool | null; }; }; useFullCommitment: Bool; @@ -273,6 +274,7 @@ type Party = { state: (Field | null)[]; sequenceState: Field | null; provedState: Bool | null; + isNew: Bool | null; }; }; useFullCommitment: Bool; diff --git a/src/snarky/gen/parties.ts b/src/snarky/gen/parties.ts index 2987df7720..4b1e953c12 100644 --- a/src/snarky/gen/parties.ts +++ b/src/snarky/gen/parties.ts @@ -201,6 +201,7 @@ type Parties = { state: { isSome: Bool; value: Field }[]; sequenceState: Field; provedState: { isSome: Bool; value: Bool }; + isNew: { isSome: Bool; value: Bool }; }; }; useFullCommitment: Bool; @@ -368,6 +369,7 @@ type Party = { state: { isSome: Bool; value: Field }[]; sequenceState: Field; provedState: { isSome: Bool; value: Bool }; + isNew: { isSome: Bool; value: Bool }; }; }; useFullCommitment: Bool; From d870ae1579a13bc1c989dd291d4d32a3b30726f1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 20 Jul 2022 18:57:11 -0700 Subject: [PATCH 43/49] Fix intg tests --- src/lib/fetch.ts | 13 ++++++++++--- src/lib/mina.ts | 10 ++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 8a335b7277..20d3d76093 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -229,6 +229,8 @@ function parseFetchedAccount({ delegateAccount, receiptChainHash, sequenceEvents, + tokenId, + tokenSymbol, }: Partial): Partial { return { publicKey: @@ -251,6 +253,8 @@ function parseFetchedAccount({ // : undefined, delegate: delegateAccount && PublicKey.fromBase58(delegateAccount.publicKey), + tokenId: tokenId !== undefined ? Ledger.fieldOfBase58(tokenId) : undefined, + tokenSymbol: tokenSymbol !== undefined ? tokenSymbol : undefined, }; } @@ -344,12 +348,15 @@ async function fetchMissingData(graphqlEndpoint: string) { function getCachedAccount( publicKey: PublicKey, - tokenId: string, + tokenId: Field, graphqlEndpoint = defaultGraphqlEndpoint ) { let account = - accountCache[`${publicKey.toBase58()};${tokenId}${graphqlEndpoint}`] - ?.account; + accountCache[ + `${publicKey.toBase58()};${Ledger.fieldToBase58( + tokenId + )};${graphqlEndpoint}` + ]?.account; if (account !== undefined) return parseFetchedAccount(account); } function getCachedNetwork(graphqlEndpoint = defaultGraphqlEndpoint) { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 299b072f01..bb512a25cf 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -310,7 +310,7 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { ); let account = Fetch.getCachedAccount( publicKey, - Ledger.fieldToBase58(tokenId), + tokenId, graphqlEndpoint ); return account ?? dummyAccount(publicKey); @@ -321,7 +321,7 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { ) { let account = Fetch.getCachedAccount( publicKey, - Ledger.fieldToBase58(tokenId), + tokenId, graphqlEndpoint ); if (account !== undefined) return account; @@ -419,12 +419,14 @@ let activeInstance: Mina = { ) { let account = Fetch.getCachedAccount( publicKey, - Ledger.fieldToBase58(tokenId), + tokenId, Fetch.defaultGraphqlEndpoint ); if (account === undefined) throw Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${tokenId}.\nEither call Mina.setActiveInstance first or explicitly add the account with addCachedAccount` + `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${Ledger.fieldToBase58( + tokenId + )}.\nEither call Mina.setActiveInstance first or explicitly add the account with addCachedAccount` ); return account; } From e73a4f69adaa053b8f32384c4fec9bd1aeff7460 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 21 Jul 2022 13:25:12 -0700 Subject: [PATCH 44/49] Use createChildParty instead of createUnsigned --- src/index.ts | 9 ++++++++- src/lib/party.ts | 45 ++++++++++++++++++++------------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/index.ts b/src/index.ts index ba522cf7c8..d05b888459 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,7 +36,14 @@ export { } from './lib/zkapp'; export { state, State, declareState } from './lib/state'; export { Proof, SelfProof, ZkProgram, verify } from './lib/proof_system'; -export { Party, Permissions, ZkappPublicInput } from './lib/party'; +export { + Token, + Party, + Permissions, + ZkappPublicInput, + getDefaultTokenId, + partiesToJson, +} from './lib/party'; export { fetchAccount, fetchLastBlock, diff --git a/src/lib/party.ts b/src/lib/party.ts index fb4a92fc00..1d4562a1bd 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -32,8 +32,8 @@ export { ZkappStateLength, Events, partyToPublicInput, - Token, getDefaultTokenId, + Token, CallForest, createChildParty, }; @@ -584,10 +584,9 @@ class Party { tokenOwner: customToken.tokenOwner, mint({ address, amount }: MintOrBurnParams) { - let receiverParty = Party.createUnsigned(address, { + let receiverParty = createChildParty(thisParty, address, { caller: this.id, tokenId: this.id, - callDepth: 1, }); // Add the amount to mint to the receiver's account @@ -596,10 +595,9 @@ class Party { }, burn({ address, amount }: MintOrBurnParams) { - let receiverParty = Party.createUnsigned(address, { + let receiverParty = createChildParty(thisParty, address, { caller: this.id, tokenId: this.id, - callDepth: 1, useFullCommitment: Bool(true), }); @@ -615,10 +613,9 @@ class Party { send({ from, to, amount }: SendParams) { // Create a new party for the sender to send the amount to the receiver - let senderParty = Party.createUnsigned(from, { + let senderParty = createChildParty(thisParty, from, { caller: this.id, tokenId: this.id, - callDepth: 1, useFullCommitment: Bool(true), }); @@ -630,10 +627,9 @@ class Party { kind: 'lazy-signature', }; - let receiverParty = Party.createUnsigned(to, { + let receiverParty = createChildParty(thisParty, to, { caller: this.id, tokenId: this.id, - callDepth: 1, }); // Add the amount to send to the receiver's account @@ -835,22 +831,8 @@ class Party { return { body, authorization: undefined }; } - static createUnsigned( - publicKey: PublicKey, - options?: { - caller?: Field; - tokenId?: Field; - callDepth?: number; - useFullCommitment?: Bool; - } - ) { + static createUnsigned(publicKey: PublicKey) { let party = Party.defaultParty(publicKey); - const { caller, tokenId, callDepth, useFullCommitment } = options ?? {}; - party.body.caller = caller ?? party.body.caller; - party.body.tokenId = tokenId ?? party.body.tokenId; - party.body.callDepth = callDepth ?? party.body.callDepth; - party.body.useFullCommitment = - useFullCommitment ?? party.body.useFullCommitment; Mina.currentTransaction()?.parties.push(party); return party; } @@ -954,9 +936,22 @@ const CallForest = { }, }; -function createChildParty(parent: Party, childAddress: PublicKey) { +function createChildParty( + parent: Party, + childAddress: PublicKey, + options?: { + caller?: Field; + tokenId?: Field; + useFullCommitment?: Bool; + } +) { let child = Party.defaultParty(childAddress); + const { caller, tokenId, useFullCommitment } = options ?? {}; child.body.callDepth = parent.body.callDepth + 1; + child.body.caller = caller ?? child.body.caller; + child.body.tokenId = tokenId ?? child.body.tokenId; + child.body.useFullCommitment = + useFullCommitment ?? child.body.useFullCommitment; child.parent = parent; parent.children.push(child); return child; From 8203973a26e95869fc78d74e834da639ba90ffe3 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 22 Jul 2022 11:35:59 -0700 Subject: [PATCH 45/49] Add formatted getAccount error --- src/lib/mina.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 7e19b30a8a..5fd4fad443 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -68,6 +68,14 @@ type SenderSpec = | { feePayerKey: PrivateKey; fee?: number | string | UInt64; memo?: string } | undefined; +function reportGetAccountError(publicKey: string, tokenId: string) { + if (tokenId === Ledger.fieldToBase58(getDefaultTokenId())) { + return `getAccount: Could not find account for public key ${publicKey}`; + } else { + return `getAccount: Could not find account for public key ${publicKey} with the tokenId ${tokenId}`; + } +} + function createUnsignedTransaction( f: () => unknown, { fetchMode = 'cached' as FetchMode } = {} @@ -240,9 +248,10 @@ function LocalBlockchain({ let ledgerAccount = ledger.getAccount(publicKey, tokenId); if (ledgerAccount == undefined) { throw new Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${Ledger.fieldToBase58( - tokenId - )}` + reportGetAccountError( + publicKey.toBase58(), + Ledger.fieldToBase58(tokenId) + ) ); } else { return { @@ -327,9 +336,10 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { if (account !== undefined) return account; } throw Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${Ledger.fieldToBase58( - tokenId - )}.\nGraphql endpoint: ${graphqlEndpoint}` + `${reportGetAccountError( + publicKey.toBase58(), + Ledger.fieldToBase58(tokenId) + )}\nGraphql endpoint: ${graphqlEndpoint}` ); }, getNetworkState() { @@ -421,9 +431,10 @@ let activeInstance: Mina = { ); if (account === undefined) throw Error( - `getAccount: Could not find account for public key ${publicKey.toBase58()} with the tokenId ${Ledger.fieldToBase58( - tokenId - )}.\nEither call Mina.setActiveInstance first or explicitly add the account with addCachedAccount` + `${reportGetAccountError( + publicKey.toBase58(), + Ledger.fieldToBase58(tokenId) + )}\n\nEither call Mina.setActiveInstance first or explicitly add the account with addCachedAccount` ); return account; } From dc1957959e70b138cc2698e4b4e459bf0ac165f0 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 22 Jul 2022 11:36:14 -0700 Subject: [PATCH 46/49] Fix checked and unchecked condition in token constructor --- src/lib/party.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/party.ts b/src/lib/party.ts index 1d4562a1bd..dd2f34b710 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -524,9 +524,9 @@ class Token { tokenOwner.toFields().every((x) => x.isConstant()) && parentTokenId.isConstant() ) { - this.id = Ledger.customTokenIdChecked(tokenOwner, this.parentTokenId); - } else { this.id = Ledger.customTokenId(tokenOwner, this.parentTokenId); + } else { + this.id = Ledger.customTokenIdChecked(tokenOwner, this.parentTokenId); } } } From 4788a9a490165129104f14b19b615df15a52177b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 22 Jul 2022 11:36:33 -0700 Subject: [PATCH 47/49] Fix GraphQL query parameter --- src/lib/fetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index d517cabba3..338563807f 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -187,7 +187,7 @@ type FlexibleAccount = { // TODO provedState const accountQuery = (publicKey: string, tokenId: string) => `{ - account(publicKey: "${publicKey}" tokenId: "${tokenId}") { + account(publicKey: "${publicKey}", tokenId: "${tokenId}") { publicKey nonce zkappUri From 997eecd0e80d182a057cdf88c65ebcbd0d2bd2c7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 10 May 2022 15:10:10 +0200 Subject: [PATCH 48/49] integrate `deploy()` with qanet --- src/lib/mina.ts | 22 +++++++++++----------- src/lib/zkapp.ts | 22 ++++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 5fd4fad443..436d3b7c2d 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -36,8 +36,8 @@ export { getNetworkState, accountCreationFee, sendTransaction, + FeePayerSpec, }; - interface TransactionId { wait(): Promise; } @@ -63,7 +63,7 @@ type CurrentTransaction = { let currentTransaction = Context.create(); -type SenderSpec = +type FeePayerSpec = | PrivateKey | { feePayerKey: PrivateKey; fee?: number | string | UInt64; memo?: string } | undefined; @@ -84,7 +84,7 @@ function createUnsignedTransaction( } function createTransaction( - feePayer: SenderSpec, + feePayer: FeePayerSpec, f: () => unknown, { fetchMode = 'cached' as FetchMode, isFinalRunOutsideCircuit = true } = {} ): Transaction { @@ -191,7 +191,7 @@ function createTransaction( } interface Mina { - transaction(sender: SenderSpec, f: () => void): Promise; + transaction(sender: FeePayerSpec, f: () => void): Promise; currentSlot(): UInt32; getAccount(publicKey: PublicKey, tokenId?: Field): Account; getNetworkState(): NetworkValue; @@ -279,7 +279,7 @@ function LocalBlockchain({ ); return { wait: async () => {} }; }, - async transaction(sender: SenderSpec, f: () => void) { + async transaction(sender: FeePayerSpec, f: () => void) { // bad hack: run transaction just to see whether it creates proofs // if it doesn't, this is the last chance to run SmartContract.runOutsideCircuit, which is supposed to run only once // TODO: this has obvious holes if multiple zkapps are involved, but not relevant currently because we can't prove with multiple parties @@ -383,7 +383,7 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina { }, }; }, - async transaction(sender: SenderSpec, f: () => void) { + async transaction(sender: FeePayerSpec, f: () => void) { let tx = createTransaction(sender, f, { fetchMode: 'test', isFinalRunOutsideCircuit: false, @@ -446,7 +446,7 @@ let activeInstance: Mina = { sendTransaction() { throw new Error('must call Mina.setActiveInstance first'); }, - async transaction(sender: SenderSpec, f: () => void) { + async transaction(sender: FeePayerSpec, f: () => void) { return createTransaction(sender, f); }, }; @@ -472,15 +472,15 @@ function setActiveInstance(m: Mina) { * @return A transaction that can subsequently be submitted to the chain. */ function transaction(f: () => void): Promise; -function transaction(sender: SenderSpec, f: () => void): Promise; +function transaction(sender: FeePayerSpec, f: () => void): Promise; function transaction( - senderOrF: SenderSpec | (() => void), + senderOrF: FeePayerSpec | (() => void), fOrUndefined?: () => void ): Promise { - let sender: SenderSpec; + let sender: FeePayerSpec; let f: () => void; if (fOrUndefined !== undefined) { - sender = senderOrF as SenderSpec; + sender = senderOrF as FeePayerSpec; f = fOrUndefined; } else { sender = undefined; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index c336dc5a69..a70e8356be 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -585,7 +585,16 @@ async function deploy( } ) { let address = zkappKey.toPublicKey(); - let tx = Mina.createUnsignedTransaction(() => { + let feePayerSpec: Mina.FeePayerSpec = undefined; + if (shouldSignFeePayer) { + if (feePayerKey === undefined || transactionFee === undefined) { + throw Error( + `When setting shouldSignFeePayer=true, you need to also supply feePayerKey (fee payer's private key) and transactionFee.` + ); + } + feePayerSpec = { feePayerKey, fee: transactionFee, memo: memo ?? '' }; + } + let tx = await Mina.transaction(feePayerSpec, () => { if (initialBalance !== undefined) { if (feePayerKey === undefined) throw Error( @@ -608,17 +617,6 @@ async function deploy( zkapp.self.balance.addInPlace(amount); } }); - tx.transaction.memo = memo ?? ''; - if (shouldSignFeePayer) { - if (feePayerKey === undefined || transactionFee === undefined) { - throw Error( - `When setting shouldSignFeePayer=true, you need to also supply feePayerKey (fee payer's private key) and transactionFee.` - ); - } - tx.transaction = addFeePayer(tx.transaction, feePayerKey, { - transactionFee, - }); - } // TODO modifying the json after calling to ocaml would avoid extra vk serialization.. but need to compute vk hash return tx.sign().toJSON(); } From c0035b69bb802b44c9b3b025e958a884690a9477 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 25 Jul 2022 16:46:18 +0200 Subject: [PATCH 49/49] fix integration test --- src/examples/deploy/deploy.ts | 2 +- src/lib/mina.ts | 8 -------- src/lib/zkapp.ts | 25 ++++++------------------- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/examples/deploy/deploy.ts b/src/examples/deploy/deploy.ts index 18f78a473f..2509b3ab7d 100644 --- a/src/examples/deploy/deploy.ts +++ b/src/examples/deploy/deploy.ts @@ -1,4 +1,4 @@ -import { isReady, PrivateKey, deploy, shutdown, compile } from 'snarkyjs'; +import { isReady, PrivateKey, deploy, shutdown } from 'snarkyjs'; import SimpleZkapp from './simple_zkapp'; import { readFileSync, existsSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 436d3b7c2d..206475e22b 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -22,7 +22,6 @@ import { Proof, snarkContext } from './proof_system'; import { Context } from './global-context'; export { - createUnsignedTransaction, createTransaction, BerkeleyQANet, LocalBlockchain, @@ -76,13 +75,6 @@ function reportGetAccountError(publicKey: string, tokenId: string) { } } -function createUnsignedTransaction( - f: () => unknown, - { fetchMode = 'cached' as FetchMode } = {} -) { - return createTransaction(undefined, f, { fetchMode }); -} - function createTransaction( feePayer: FeePayerSpec, f: () => unknown, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index a70e8356be..0b4733841a 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -570,35 +570,22 @@ async function deploy( zkappKey, verificationKey, initialBalance, - shouldSignFeePayer, - feePayerKey, - transactionFee, - memo, + feePayer, }: { zkappKey: PrivateKey; verificationKey: { data: string; hash: string | Field }; initialBalance?: number | string; - feePayerKey?: PrivateKey; - shouldSignFeePayer?: boolean; - transactionFee?: string | number; - memo?: string; + feePayer?: Mina.FeePayerSpec; } ) { let address = zkappKey.toPublicKey(); - let feePayerSpec: Mina.FeePayerSpec = undefined; - if (shouldSignFeePayer) { - if (feePayerKey === undefined || transactionFee === undefined) { - throw Error( - `When setting shouldSignFeePayer=true, you need to also supply feePayerKey (fee payer's private key) and transactionFee.` - ); - } - feePayerSpec = { feePayerKey, fee: transactionFee, memo: memo ?? '' }; - } - let tx = await Mina.transaction(feePayerSpec, () => { + let feePayerKey = + feePayer instanceof PrivateKey ? feePayer : feePayer?.feePayerKey; + let tx = await Mina.transaction(feePayer, () => { if (initialBalance !== undefined) { if (feePayerKey === undefined) throw Error( - `When using the optional initialBalance argument, you need to also supply the fee payer's private key feePayerKey to sign the initial balance funding.` + `When using the optional initialBalance argument, you need to also supply the fee payer's private key as part of the \`feePayer\` argument, to sign the initial balance funding.` ); // optional first party: the sender/fee payer who also funds the zkapp let amount = UInt64.fromString(String(initialBalance)).add(