From bc6870099acd66ac82716e041feb5c8de4a2ddb1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 2 Feb 2024 08:23:53 +0100 Subject: [PATCH 01/69] normalize comment --- src/lib/account_update.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 810bdb21dd..3a7e7f343a 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -2008,17 +2008,16 @@ function dummySignature() { /** * The public input for zkApps consists of certain hashes of the proving - AccountUpdate (and its child accountUpdates) which is constructed during method - execution. - - For SmartContract proving, a method is run twice: First outside the proof, to - obtain the public input, and once in the prover, which takes the public input - as input. The current transaction is hashed again inside the prover, which - asserts that the result equals the input public input, as part of the snark - circuit. The block producer will also hash the transaction they receive and - pass it as a public input to the verifier. Thus, the transaction is fully - constrained by the proof - the proof couldn't be used to attest to a different - transaction. + * account update (and its child updates) which is constructed during method execution. + * + * For SmartContract proving, a method is run twice: First outside the proof, to + * obtain the public input, and once in the prover, which takes the public input + * as input. The current transaction is hashed again inside the prover, which + * asserts that the result equals the input public input, as part of the snark + * circuit. The block producer will also hash the transaction they receive and + * pass it as a public input to the verifier. Thus, the transaction is fully + * constrained by the proof - the proof couldn't be used to attest to a different + * transaction. */ type ZkappPublicInput = { accountUpdate: Field; From 2c52e8bef9c3d7726b90c44f5c667e0752d0e299 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 2 Feb 2024 17:11:25 +0100 Subject: [PATCH 02/69] switch over how we hash calls in the circuit --- src/lib/zkapp.ts | 16 +++--- tests/vk-regression/vk-regression.json | 72 +++++++++++++------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 466076c7a0..f91731ce53 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -19,6 +19,7 @@ import { LazyProof, CallForest, UnfinishedForest, + AccountUpdateForest, } from './account_update.js'; import { cloneCircuitValue, @@ -194,11 +195,11 @@ function wrapMethod( let blindingValue = Provable.witness(Field, getBlindingValue); // it's also good if we prove that we use the same blinding value across the method // that's why we pass the variable (not the constant) into a new context - let context = memoizationContext() ?? { + let memoCtx = memoizationContext() ?? { memoized: [], currentIndex: 0, }; - let id = memoizationContext.enter({ ...context, blindingValue }); + let id = memoizationContext.enter({ ...memoCtx, blindingValue }); let result: unknown; try { let clonedArgs = actualArgs.map(cloneCircuitValue); @@ -218,7 +219,8 @@ function wrapMethod( ProofAuthorization.setKind(accountUpdate); debugPublicInput(accountUpdate); - checkPublicInput(publicInput, accountUpdate); + let calls = UnfinishedForest.finalize(context.selfCalls); + checkPublicInput(publicInput, accountUpdate, calls); // check the self accountUpdate right after calling the method // TODO: this needs to be done in a unified way for all account updates that are created @@ -448,11 +450,11 @@ function wrapMethod( function checkPublicInput( { accountUpdate, calls }: ZkappPublicInput, - self: AccountUpdate + self: AccountUpdate, + selfCalls: AccountUpdateForest ) { - let otherInput = self.toPublicInput(); - accountUpdate.assertEquals(otherInput.accountUpdate); - calls.assertEquals(otherInput.calls); + accountUpdate.assertEquals(self.hash()); + calls.assertEquals(selfCalls.hash); } /** diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index f0459aa452..01cde4af40 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -1,22 +1,22 @@ { "Voting_": { - "digest": "5796cb30bf420c4052ee0ca04d6a7eb554ac0806183ee97c92c51bc1cc2976e", + "digest": "385ea3e72a679aad6a48b0734008d80da05f27a17854b9088edc8de420ad907d", "methods": { "voterRegistration": { - "rows": 1261, - "digest": "8b6b8083bb59f6dd4746b5b3da3e10e3" + "rows": 1259, + "digest": "a59bad21fc55ce7d8d099d24cb91d1e6" }, "candidateRegistration": { - "rows": 1261, - "digest": "73947a300094dcebedff84c86887a06d" + "rows": 1259, + "digest": "ee209f54f9f7996bae2bf5c0fd4bf9da" }, "approveRegistrations": { - "rows": 1149, - "digest": "8fa35ef942084cc163bbd27bdaccb468" + "rows": 1147, + "digest": "748673762a27be8b58ef15084bd49beb" }, "vote": { - "rows": 1675, - "digest": "9c607a19228a5913cd32278394d65f10" + "rows": 1672, + "digest": "0862eef1a5edb8ade4d1d5dab1f76abc" }, "countVotes": { "rows": 5796, @@ -24,16 +24,16 @@ } }, "verificationKey": { - "data": "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", - "hash": "18930773283376165145392075130641581795923440685773146685646791501279243480469" + "data": "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", + "hash": "10242427736291135326630618424974868967569697518211103043324256389485118856981" } }, "Membership_": { - "digest": "262bb745a13f7fac8f6ad1dd8ff13ae5789e0c26b63f4b41a5dbc43c6ac9df85", + "digest": "3b4598fa0324c27a3535ee0f187419dcd43cde203ac1053df4bcc5a108fd0c52", "methods": { "addEntry": { - "rows": 1355, - "digest": "9bf99a1ea1fa39ed9590c0b0a67bfeb7" + "rows": 1353, + "digest": "657f6ba31355b4d6daa80b84e86a5341" }, "isMember": { "rows": 469, @@ -45,8 +45,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "16174092625812619544041752898938261603039773654468059859161573381120826639333" + "data": "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", + "hash": "27077061932938200348860230517448044253628907171665029926735544979854107063304" } }, "HelloWorld": { @@ -63,15 +63,15 @@ } }, "TokenContract": { - "digest": "19aa0f5b442d011f2bbdd68ac189bf91cd017803f0f91a5ab6a61d50e3136c2", + "digest": "1b4cc9f8af1e49b7f727bde338ff93b3aa72d1be175669d51dc7991296ce7dbd", "methods": { "init": { - "rows": 655, - "digest": "3941ac88f0b92eec098dfcf46faa4e60" + "rows": 342, + "digest": "f9f73a136c5642d519ec0f73e033e6f3" }, "init2": { - "rows": 652, - "digest": "1ebb84a10bafd30accfd3e8046d2e20d" + "rows": 342, + "digest": "c4303618beb22bb040bffacff35286cb" }, "approveBase": { "rows": 13194, @@ -79,37 +79,37 @@ } }, "verificationKey": { - "data": "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", - "hash": "10629507172660088077994796699854359657108723627411391158715881834104929001219" + "data": "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", + "hash": "20122457815983542378345864836658988429829729096873540353338563912426952908135" } }, "Dex": { - "digest": "2289275173dc37af7005d29de2d1bc87ef6633a38aab74653528d62a3ea2c53b", + "digest": "272c50dc8fd51817806dc4e5e0c17e4315d5bad077b23f6b44dac5b999ceb9a2", "methods": { "supplyLiquidityBase": { - "rows": 2883, - "digest": "a5811e58fa24f5ebde41076fc94849b7" + "rows": 2566, + "digest": "13f3375abe98f94b605c5b079315f066" }, "swapX": { - "rows": 1564, - "digest": "f6b047df744d8dcf297bd7a4fb45c812" + "rows": 1562, + "digest": "c29354e0f9d6a787a9330730ba19e689" }, "swapY": { - "rows": 1564, - "digest": "3082a30634a7a8da1603a7994b0cd4af" + "rows": 1562, + "digest": "9d82e9d413a342d45179001358495e89" }, "burnLiquidity": { - "rows": 718, - "digest": "99fb50066d2aa2f3f7afe801009cb503" + "rows": 403, + "digest": "7e7c3df3049d99703023a39ab1bfe8e4" }, "transfer": { - "rows": 1044, - "digest": "7c188ff9cf8e7db108e2d24f8e097622" + "rows": 414, + "digest": "2a4ce4f790fedf38514d331fa0d57eb0" } }, "verificationKey": { - "data": "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", - "hash": "10792977795272926498471289159457543677416779853262843765284042897388782617920" + "data": "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", + "hash": "18952871976004328548139095054727555812687816649131669760777722323942382863169" } }, "Group Primitive": { From 40ed27a9c0a2508a0ae8ee5895c549dcadda241f Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 2 Feb 2024 17:57:08 +0100 Subject: [PATCH 03/69] add hint to problem --- src/lib/account_update.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index e6b800fcdf..c3fc5a819c 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -662,6 +662,7 @@ class AccountUpdate implements Types.AccountUpdate { if (accountLike instanceof PublicKey) { accountLike = AccountUpdate.defaultAccountUpdate(accountLike, id); makeChildAccountUpdate(thisAccountUpdate, accountLike); + // TODO existing API not affecting `UnfinishedForest` } if (!accountLike.label) accountLike.label = `${ From 9a032fc3ed29d5c58eebd3170b03573f3b775af6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 10:26:06 +0100 Subject: [PATCH 04/69] fixup not updating unfinished forest --- src/lib/account_update.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 28cecc9df5..6c807e7c6e 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -690,8 +690,7 @@ class AccountUpdate implements Types.AccountUpdate { } if (accountLike instanceof PublicKey) { accountLike = AccountUpdate.defaultAccountUpdate(accountLike, id); - makeChildAccountUpdate(thisAccountUpdate, accountLike); - // TODO existing API not affecting `UnfinishedForest` + thisAccountUpdate.adopt(accountLike); } if (!accountLike.label) accountLike.label = `${ @@ -1659,7 +1658,11 @@ const UnfinishedForest = { return { useHash: false, value: [] }; }, - witnessHash(forest: UnfinishedForest): UnfinishedForest { + witnessHash(forest: UnfinishedForest): { + readonly useHash: true; + hash: Field; + readonly value: UnfinishedTree[]; + } { let hash = Provable.witness(Field, () => { return UnfinishedForest.finalize(forest).hash; }); From af6956e6070b046423c7427e17728511dcb1623a Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 10:26:22 +0100 Subject: [PATCH 05/69] adapt token contract to new hashing --- src/lib/mina/token/token-contract.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 765fd184f9..64d3e21a2b 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -64,19 +64,22 @@ abstract class TokenContract extends SmartContract { `the supported limit of ${MAX_ACCOUNT_UPDATES}.\n` ); - // skip hashing our child account updates in the method wrapper - // since we just did that in the loop above - this.self.children.callsType = { - type: 'WitnessEquals', - value: updates.hash, - }; - // make top-level updates our children Provable.asProver(() => { updates.data.get().forEach((update) => { this.self.adopt(update.element.accountUpdate.value.get()); }); }); + + // skip hashing our child account updates in the method wrapper + // since we just did that in the loop above + let insideContract = smartContractContext.get(); + if (insideContract) { + insideContract.selfCalls = UnfinishedForest.witnessHash( + insideContract.selfCalls + ); + insideContract.selfCalls.hash.assertEquals(updates.hash); + } } /** From 755e7499eeb293202715b4ef930b3e0d1531bbc6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 10:57:50 +0100 Subject: [PATCH 06/69] add more unfinished forest helpers --- src/lib/account_update.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 6c807e7c6e..2803d7fedc 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1698,6 +1698,18 @@ const UnfinishedForest = { }); }, + pushTree(forest: UnfinishedForest, tree: AccountUpdateTree) { + let value = AccountUpdate.dummy(); + Provable.asProver(() => { + value = tree.accountUpdate.value.get(); + }); + forest.value.push({ + accountUpdate: { useHash: true, hash: tree.accountUpdate.hash, value }, + isDummy: Bool(false), + calls: UnfinishedForest.fromForest(tree.calls), + }); + }, + remove(forest: UnfinishedForest, accountUpdate: AccountUpdate) { // find account update by .id let index = forest.value.findIndex( @@ -1711,6 +1723,22 @@ const UnfinishedForest = { forest.value.splice(index, 1); }, + fromForest(forest: MerkleListBase): UnfinishedForest { + let value: UnfinishedTree[] = []; + Provable.asProver(() => { + value = forest.data.get().map(({ element: tree }) => ({ + accountUpdate: { + useHash: true, + hash: tree.accountUpdate.hash, + value: tree.accountUpdate.value.get(), + }, + isDummy: Bool(false), + calls: UnfinishedForest.fromForest(tree.calls), + })); + }); + return { useHash: true, hash: forest.hash, value }; + }, + finalize(forest: UnfinishedForest): AccountUpdateForest { if (forest.useHash) { let data = Unconstrained.witness(() => From ba293791f88bae887078c2bb14a96818a97fba63 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 10:58:34 +0100 Subject: [PATCH 07/69] fix adaptation of token contract --- src/lib/mina/token/token-contract.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 64d3e21a2b..53e71acc6e 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -65,9 +65,11 @@ abstract class TokenContract extends SmartContract { ); // make top-level updates our children + // TODO: this must not be necessary once we move everything to `selfCalls` Provable.asProver(() => { updates.data.get().forEach((update) => { - this.self.adopt(update.element.accountUpdate.value.get()); + let accountUpdate = update.element.accountUpdate.value.get(); + this.self.adopt(accountUpdate); }); }); @@ -75,10 +77,7 @@ abstract class TokenContract extends SmartContract { // since we just did that in the loop above let insideContract = smartContractContext.get(); if (insideContract) { - insideContract.selfCalls = UnfinishedForest.witnessHash( - insideContract.selfCalls - ); - insideContract.selfCalls.hash.assertEquals(updates.hash); + insideContract.selfCalls = UnfinishedForest.fromForest(updates); } } From 3e533a77af4ccd7da6fc640a44e6330ca5e6cc08 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 15:33:34 +0100 Subject: [PATCH 08/69] minor --- src/lib/mina/token/token-contract.unit-test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/mina/token/token-contract.unit-test.ts b/src/lib/mina/token/token-contract.unit-test.ts index 6bac61c881..b49d6b8190 100644 --- a/src/lib/mina/token/token-contract.unit-test.ts +++ b/src/lib/mina/token/token-contract.unit-test.ts @@ -37,9 +37,11 @@ class ExampleTokenContract extends TokenContract { let Local = Mina.LocalBlockchain({ proofsEnabled: false }); Mina.setActiveInstance(Local); -let { publicKey: sender, privateKey: senderKey } = Local.testAccounts[0]; -let { publicKey: tokenAddress, privateKey: tokenKey } = Local.testAccounts[1]; -let { publicKey: otherAddress } = Local.testAccounts[2]; +let [ + { publicKey: sender, privateKey: senderKey }, + { publicKey: tokenAddress, privateKey: tokenKey }, + { publicKey: otherAddress }, +] = Local.testAccounts; let token = new ExampleTokenContract(tokenAddress); let tokenId = token.token.id; From d69c73a3fe0752ef43816d84a7c9cd67abee347c Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 15:33:42 +0100 Subject: [PATCH 09/69] add failing unit test --- .../mina/account-update-layout.unit-test.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/lib/mina/account-update-layout.unit-test.ts diff --git a/src/lib/mina/account-update-layout.unit-test.ts b/src/lib/mina/account-update-layout.unit-test.ts new file mode 100644 index 0000000000..91fbfcab67 --- /dev/null +++ b/src/lib/mina/account-update-layout.unit-test.ts @@ -0,0 +1,42 @@ +import { Mina } from '../../index.js'; +import { AccountUpdate } from '../account_update.js'; +import { UInt64 } from '../int.js'; +import { SmartContract, method } from '../zkapp.js'; + +// smart contract which creates an account update that has a child of its own + +class NestedCall extends SmartContract { + @method deposit() { + const payerUpdate = AccountUpdate.createSigned(this.sender); + payerUpdate.send({ to: this.address, amount: UInt64.one }); + } +} + +// setup + +let Local = Mina.LocalBlockchain({ proofsEnabled: true }); +Mina.setActiveInstance(Local); + +let [ + { publicKey: sender, privateKey: senderKey }, + { publicKey: zkappAddress, privateKey: zkappKey }, +] = Local.testAccounts; + +await NestedCall.compile(); +let zkapp = new NestedCall(zkappAddress); + +// deploy zkapp + +await (await Mina.transaction(sender, () => zkapp.deploy())) + .sign([zkappKey, senderKey]) + .send(); + +// deposit call +let balanceBefore = Mina.getBalance(zkappAddress); + +let depositTx = await Mina.transaction(sender, () => zkapp.deposit()); +console.log(depositTx.toPretty()); +await depositTx.prove(); +await depositTx.sign([senderKey]).send(); + +Mina.getBalance(zkappAddress).assertEquals(balanceBefore.add(1)); From 118cc06cf85b0267cc3ea3503dbe34c4c3d606ac Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 17:49:17 +0100 Subject: [PATCH 10/69] move smart contract context and start account update layout class --- src/lib/account_update.ts | 26 ++-------- .../mina/account-update-layout.unit-test.ts | 1 + src/lib/mina/smart-contract-context.ts | 49 +++++++++++++++++++ src/lib/mina/token/token-contract.ts | 2 +- src/lib/zkapp.ts | 45 +++++++---------- 5 files changed, 75 insertions(+), 48 deletions(-) create mode 100644 src/lib/mina/smart-contract-context.ts diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 2803d7fedc..271637705a 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -46,7 +46,6 @@ import { prefixes, protocolVersions, } from '../bindings/crypto/constants.js'; -import { Context } from './global-context.js'; import { MlArray } from './ml/base.js'; import { Signature, signFieldElement } from '../mina-signer/src/signature.js'; import { MlFieldConstArray } from './ml/fields.js'; @@ -58,9 +57,9 @@ import { genericHash, MerkleList, MerkleListBase, - withHashes, } from './provable-types/merkle-list.js'; import { Hashed } from './provable-types/packed.js'; +import { smartContractContext } from './mina/smart-contract-context.js'; // external API export { @@ -72,7 +71,6 @@ export { }; // internal API export { - smartContractContext, SetOrKeep, Permission, Preconditions, @@ -82,7 +80,6 @@ export { ZkappCommand, addMissingSignatures, addMissingProofs, - ZkappStateLength, Events, Actions, TokenId, @@ -91,31 +88,19 @@ export { createChildAccountUpdate, AccountUpdatesLayout, zkAppProver, - SmartContractContext, dummySignature, LazyProof, AccountUpdateTree, UnfinishedForest, + UnfinishedTree, hashAccountUpdate, HashedAccountUpdate, }; -const ZkappStateLength = 8; - const TransactionVersion = { current: () => UInt32.from(protocolVersions.txnVersion), }; -type SmartContractContext = { - this: SmartContract; - methodCallDepth: number; - selfUpdate: AccountUpdate; - selfCalls: UnfinishedForest; -}; -let smartContractContext = Context.create({ - default: null, -}); - type ZkappProverData = { transaction: ZkappCommand; accountUpdate: AccountUpdate; @@ -799,7 +784,7 @@ class AccountUpdate implements Types.AccountUpdate { } else { receiver = AccountUpdate.defaultAccountUpdate(to, this.body.tokenId); receiver.label = `${this.label ?? 'Unlabeled'}.send()`; - this.approve(receiver); + this.adopt(receiver); } // Sub the amount from the sender's account @@ -814,8 +799,7 @@ class AccountUpdate implements Types.AccountUpdate { } /** - * Makes an {@link AccountUpdate} a child-{@link AccountUpdate} of this and - * approves it. + * Makes an {@link AccountUpdate} a child of this and approves it. */ approve( childUpdate: AccountUpdate, @@ -832,7 +816,7 @@ class AccountUpdate implements Types.AccountUpdate { } /** - * Makes an {@link AccountUpdate} a child-{@link AccountUpdate} of this. + * Makes an {@link AccountUpdate} a child of this. */ adopt(childUpdate: AccountUpdate) { makeChildAccountUpdate(this, childUpdate); diff --git a/src/lib/mina/account-update-layout.unit-test.ts b/src/lib/mina/account-update-layout.unit-test.ts index 91fbfcab67..f75f503194 100644 --- a/src/lib/mina/account-update-layout.unit-test.ts +++ b/src/lib/mina/account-update-layout.unit-test.ts @@ -32,6 +32,7 @@ await (await Mina.transaction(sender, () => zkapp.deploy())) .send(); // deposit call + let balanceBefore = Mina.getBalance(zkappAddress); let depositTx = await Mina.transaction(sender, () => zkapp.deposit()); diff --git a/src/lib/mina/smart-contract-context.ts b/src/lib/mina/smart-contract-context.ts new file mode 100644 index 0000000000..a11b87141c --- /dev/null +++ b/src/lib/mina/smart-contract-context.ts @@ -0,0 +1,49 @@ +import type { SmartContract } from '../zkapp.js'; +import type { + AccountUpdate, + UnfinishedForest, + UnfinishedTree, +} from '../account_update.js'; +import { Context } from '../global-context.js'; + +export { smartContractContext, SmartContractContext }; + +type SmartContractContext = { + this: SmartContract; + selfUpdate: AccountUpdate; + selfLayout: AccountUpdateLayout; + selfCalls: UnfinishedForest; +}; +let smartContractContext = Context.create({ + default: null, +}); + +const SmartContractContext = { + enter(self: SmartContract, selfUpdate: AccountUpdate) { + let context: SmartContractContext = { + this: self, + selfUpdate, + selfLayout: new AccountUpdateLayout(), + selfCalls: { useHash: false, value: [] }, + }; + let id = smartContractContext.enter(context); + return { id, context }; + }, + leave(id: number) { + smartContractContext.leave(id); + }, + stepOutside() { + return smartContractContext.enter(null); + }, + get() { + return smartContractContext.get(); + }, +}; + +class AccountUpdateLayout { + map: Map; + + constructor() { + this.map = new Map(); + } +} diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 53e71acc6e..df391553c5 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -9,10 +9,10 @@ import { AccountUpdateTree, HashedAccountUpdate, Permissions, - smartContractContext, } from '../../account_update.js'; import { DeployArgs, SmartContract } from '../../zkapp.js'; import { TokenAccountUpdateIterator } from './forest-iterator.js'; +import { smartContractContext } from '../smart-contract-context.js'; export { TokenContract }; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index f91731ce53..d1cbc72ba1 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -9,13 +9,10 @@ import { Permissions, Actions, SetOrKeep, - smartContractContext, TokenId, ZkappCommand, zkAppProver, ZkappPublicInput, - ZkappStateLength, - SmartContractContext, LazyProof, CallForest, UnfinishedForest, @@ -62,6 +59,8 @@ import { import { Cache } from './proof-system/cache.js'; import { assert } from './gadgets/common.js'; import { SmartContractBase } from './mina/smart-contract-base.js'; +import { SmartContractContext } from './mina/smart-contract-context.js'; +import { ZkappStateLength } from './mina/mina-instance.js'; // external API export { @@ -164,15 +163,12 @@ function wrapMethod( } }); - let insideContract = smartContractContext.get(); + let insideContract = SmartContractContext.get(); if (!insideContract) { - const context: SmartContractContext = { - this: this, - methodCallDepth: 0, - selfUpdate: selfAccountUpdate(this, methodName), - selfCalls: UnfinishedForest.empty(), - }; - let id = smartContractContext.enter(context); + const { id, context } = SmartContractContext.enter( + this, + selfAccountUpdate(this, methodName) + ); try { if (inCompile() || inProver() || inAnalyze()) { // important to run this with a fresh accountUpdate everytime, otherwise compile messes up our circuits @@ -304,20 +300,17 @@ function wrapMethod( return result; } } finally { - smartContractContext.leave(id); + SmartContractContext.leave(id); } } // if we're here, this method was called inside _another_ smart contract method let parentAccountUpdate = insideContract.this.self; - let methodCallDepth = insideContract.methodCallDepth; - let innerContext: SmartContractContext = { - this: this, - methodCallDepth: methodCallDepth + 1, - selfUpdate: selfAccountUpdate(this, methodName), - selfCalls: UnfinishedForest.empty(), - }; - let id = smartContractContext.enter(innerContext); + + let { id, context: innerContext } = SmartContractContext.enter( + this, + selfAccountUpdate(this, methodName) + ); try { // if the call result is not undefined but there's no known returnType, the returnType was probably not annotated properly, // so we have to explain to the user how to do that @@ -443,7 +436,7 @@ function wrapMethod( accountUpdate.body.callData.assertEquals(callData); return result; } finally { - smartContractContext.leave(id); + SmartContractContext.leave(id); } }; } @@ -800,7 +793,7 @@ super.init(); */ get self(): AccountUpdate { let inTransaction = Mina.currentTransaction.has(); - let inSmartContract = smartContractContext.get(); + let inSmartContract = SmartContractContext.get(); if (!inTransaction && !inSmartContract) { // TODO: it's inefficient to return a fresh account update everytime, would be better to return a constant "non-writable" account update, // or even expose the .get() methods independently of any account update (they don't need one) @@ -1141,8 +1134,8 @@ super.init(); throw err; } let id: number; - let insideSmartContract = !!smartContractContext.get(); - if (insideSmartContract) id = smartContractContext.enter(null); + let insideSmartContract = !!SmartContractContext.get(); + if (insideSmartContract) id = SmartContractContext.stepOutside(); try { for (let methodIntf of methodIntfs) { let accountUpdate: AccountUpdate; @@ -1169,7 +1162,7 @@ super.init(); if (printSummary) console.log(methodIntf.methodName, summary()); } } finally { - if (insideSmartContract) smartContractContext.leave(id!); + if (insideSmartContract) SmartContractContext.leave(id!); } } return methodMetadata; @@ -1463,7 +1456,7 @@ type DeployArgs = | undefined; function Account(address: PublicKey, tokenId?: Field) { - if (smartContractContext.get()) { + if (SmartContractContext.get()) { return AccountUpdate.create(address, tokenId).account; } else { return AccountUpdate.defaultAccountUpdate(address, tokenId).account; From 2bb10ac30995f118a9e7c3c8691b39c41ed9dc32 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 18:00:27 +0100 Subject: [PATCH 11/69] move some of it back --- src/lib/account_update.ts | 44 +++++++++++++++++++++++++- src/lib/mina/smart-contract-context.ts | 32 +------------------ src/lib/zkapp.ts | 2 +- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 271637705a..257c5fdfc9 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -59,7 +59,10 @@ import { MerkleListBase, } from './provable-types/merkle-list.js'; import { Hashed } from './provable-types/packed.js'; -import { smartContractContext } from './mina/smart-contract-context.js'; +import { + SmartContractContext, + smartContractContext, +} from './mina/smart-contract-context.js'; // external API export { @@ -91,10 +94,12 @@ export { dummySignature, LazyProof, AccountUpdateTree, + AccountUpdateLayout, UnfinishedForest, UnfinishedTree, hashAccountUpdate, HashedAccountUpdate, + SmartContractContext, }; const TransactionVersion = { @@ -1754,6 +1759,43 @@ function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { return { accountUpdate, isDummy: node.isDummy, calls }; } +const SmartContractContext = { + enter(self: SmartContract, selfUpdate: AccountUpdate) { + let context: SmartContractContext = { + this: self, + selfUpdate, + selfLayout: new AccountUpdateLayout(), + selfCalls: { useHash: false, value: [] }, + }; + let id = smartContractContext.enter(context); + return { id, context }; + }, + leave(id: number) { + smartContractContext.leave(id); + }, + stepOutside() { + return smartContractContext.enter(null); + }, + get() { + return smartContractContext.get(); + }, +}; + +class AccountUpdateLayout { + map: Map; + + constructor() { + this.map = new Map(); + } + + pushChild(parent: AccountUpdate, child: AccountUpdate) { + // let insideContract = smartContractContext.get(); + // if (insideContract && insideContract.selfUpdate.id === this.id) { + // UnfinishedForest.push(insideContract.selfCalls, childUpdate); + // } + } +} + const CallForest = { // similar to Mina_base.ZkappCommand.Call_forest.to_account_updates_list // takes a list of accountUpdates, which each can have children, so they form a "forest" (list of trees) diff --git a/src/lib/mina/smart-contract-context.ts b/src/lib/mina/smart-contract-context.ts index a11b87141c..eae9e58b13 100644 --- a/src/lib/mina/smart-contract-context.ts +++ b/src/lib/mina/smart-contract-context.ts @@ -2,7 +2,7 @@ import type { SmartContract } from '../zkapp.js'; import type { AccountUpdate, UnfinishedForest, - UnfinishedTree, + AccountUpdateLayout, } from '../account_update.js'; import { Context } from '../global-context.js'; @@ -17,33 +17,3 @@ type SmartContractContext = { let smartContractContext = Context.create({ default: null, }); - -const SmartContractContext = { - enter(self: SmartContract, selfUpdate: AccountUpdate) { - let context: SmartContractContext = { - this: self, - selfUpdate, - selfLayout: new AccountUpdateLayout(), - selfCalls: { useHash: false, value: [] }, - }; - let id = smartContractContext.enter(context); - return { id, context }; - }, - leave(id: number) { - smartContractContext.leave(id); - }, - stepOutside() { - return smartContractContext.enter(null); - }, - get() { - return smartContractContext.get(); - }, -}; - -class AccountUpdateLayout { - map: Map; - - constructor() { - this.map = new Map(); - } -} diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index d1cbc72ba1..2f7b1f9b87 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -17,6 +17,7 @@ import { CallForest, UnfinishedForest, AccountUpdateForest, + SmartContractContext, } from './account_update.js'; import { cloneCircuitValue, @@ -59,7 +60,6 @@ import { import { Cache } from './proof-system/cache.js'; import { assert } from './gadgets/common.js'; import { SmartContractBase } from './mina/smart-contract-base.js'; -import { SmartContractContext } from './mina/smart-contract-context.js'; import { ZkappStateLength } from './mina/mina-instance.js'; // external API From 996be22626a0afb857ab236554f11b46e3240141 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 22:10:42 +0100 Subject: [PATCH 12/69] minor --- src/lib/mina/transaction-logic/ledger.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/mina/transaction-logic/ledger.ts b/src/lib/mina/transaction-logic/ledger.ts index 266054ca50..daf7d3cb0d 100644 --- a/src/lib/mina/transaction-logic/ledger.ts +++ b/src/lib/mina/transaction-logic/ledger.ts @@ -2,10 +2,11 @@ * A ledger of accounts - simple model of a local blockchain. */ import { PublicKey } from '../../signature.js'; -import { type AccountUpdate, TokenId } from '../../account_update.js'; +import type { AccountUpdate } from '../../account_update.js'; import { Account, newAccount } from '../account.js'; import { Field } from '../../field.js'; import { applyAccountUpdate } from './apply.js'; +import { Types } from '../../../bindings/mina-transaction/types.js'; export { SimpleLedger }; @@ -20,7 +21,10 @@ class SimpleLedger { return new SimpleLedger(); } - exists({ publicKey, tokenId = TokenId.default }: InputAccountId): boolean { + exists({ + publicKey, + tokenId = Types.TokenId.empty(), + }: InputAccountId): boolean { return this.accounts.has(accountId({ publicKey, tokenId })); } @@ -30,7 +34,7 @@ class SimpleLedger { load({ publicKey, - tokenId = TokenId.default, + tokenId = Types.TokenId.empty(), }: InputAccountId): Account | undefined { let id = accountId({ publicKey, tokenId }); let account = this.accounts.get(id); From d2dd5fe834214a6acd3592621c62db867200f92f Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 22:13:13 +0100 Subject: [PATCH 13/69] develop account update layout enough to fix unit test --- src/lib/account_update.ts | 68 ++++++++++++++++++-------- src/lib/mina/smart-contract-context.ts | 6 ++- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 257c5fdfc9..58dc0cf35a 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -60,6 +60,7 @@ import { } from './provable-types/merkle-list.js'; import { Hashed } from './provable-types/packed.js'; import { + accountUpdates, SmartContractContext, smartContractContext, } from './mina/smart-contract-context.js'; @@ -812,12 +813,7 @@ class AccountUpdate implements Types.AccountUpdate { ) { makeChildAccountUpdate(this, childUpdate); AccountUpdate.witnessChildren(childUpdate, layout, { skipCheck: true }); - - // TODO: this is not as general as approve suggests - let insideContract = smartContractContext.get(); - if (insideContract && insideContract.selfUpdate.id === this.id) { - UnfinishedForest.push(insideContract.selfCalls, childUpdate); - } + accountUpdates()?.pushChild(this, childUpdate); } /** @@ -825,12 +821,7 @@ class AccountUpdate implements Types.AccountUpdate { */ adopt(childUpdate: AccountUpdate) { makeChildAccountUpdate(this, childUpdate); - - // TODO: this is not as general as adopt suggests - let insideContract = smartContractContext.get(); - if (insideContract && insideContract.selfUpdate.id === this.id) { - UnfinishedForest.push(insideContract.selfCalls, childUpdate); - } + accountUpdates()?.pushChild(this, childUpdate); } get balance() { @@ -1745,6 +1736,26 @@ const UnfinishedForest = { } return finalForest; }, + + print(forest: UnfinishedForest) { + let indent = 0; + let layout = ''; + + let toPretty = (a: UnfinishedForest) => { + indent += 2; + for (let tree of a.value) { + layout += + ' '.repeat(indent) + + `( ${tree.accountUpdate.value.label || ''} )` + + '\n'; + toPretty(tree.calls); + } + indent -= 2; + }; + + toPretty(forest); + console.log(layout); + }, }; function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { @@ -1761,11 +1772,16 @@ function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { const SmartContractContext = { enter(self: SmartContract, selfUpdate: AccountUpdate) { + let selfCalls = UnfinishedForest.empty(); let context: SmartContractContext = { this: self, selfUpdate, - selfLayout: new AccountUpdateLayout(), - selfCalls: { useHash: false, value: [] }, + selfLayout: new AccountUpdateLayout({ + accountUpdate: { useHash: false, value: selfUpdate }, + isDummy: Bool(false), + calls: selfCalls, + }), + selfCalls, }; let id = smartContractContext.enter(context); return { id, context }; @@ -1782,17 +1798,29 @@ const SmartContractContext = { }; class AccountUpdateLayout { - map: Map; + map: Map; - constructor() { + constructor(root: UnfinishedTree) { this.map = new Map(); + this.map.set(root.accountUpdate.value.id, root); + } + + getNode(update: AccountUpdate) { + let node = this.map.get(update.id); + if (node !== undefined) return node; + node = { + accountUpdate: { useHash: false, value: update }, + isDummy: update.isDummy(), + calls: UnfinishedForest.empty(), + }; + this.map.set(update.id, node); + return node; } pushChild(parent: AccountUpdate, child: AccountUpdate) { - // let insideContract = smartContractContext.get(); - // if (insideContract && insideContract.selfUpdate.id === this.id) { - // UnfinishedForest.push(insideContract.selfCalls, childUpdate); - // } + let parentNode = this.getNode(parent); + let childNode = this.getNode(child); + parentNode.calls.value.push(childNode); } } diff --git a/src/lib/mina/smart-contract-context.ts b/src/lib/mina/smart-contract-context.ts index eae9e58b13..de8b383657 100644 --- a/src/lib/mina/smart-contract-context.ts +++ b/src/lib/mina/smart-contract-context.ts @@ -6,7 +6,7 @@ import type { } from '../account_update.js'; import { Context } from '../global-context.js'; -export { smartContractContext, SmartContractContext }; +export { smartContractContext, SmartContractContext, accountUpdates }; type SmartContractContext = { this: SmartContract; @@ -17,3 +17,7 @@ type SmartContractContext = { let smartContractContext = Context.create({ default: null, }); + +function accountUpdates() { + return smartContractContext.get()?.selfLayout; +} From c8d439ad0fbce363854af7ddea58bd7ea01a7dff Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 6 Feb 2024 22:26:45 +0100 Subject: [PATCH 14/69] continue expanding account update layout --- src/lib/account_update.ts | 23 +++++++++++++++++++++-- src/lib/mina/token/token-contract.ts | 10 +++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 58dc0cf35a..4d6ae0b9b9 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1799,13 +1799,21 @@ const SmartContractContext = { class AccountUpdateLayout { map: Map; + root: UnfinishedTree; constructor(root: UnfinishedTree) { this.map = new Map(); this.map.set(root.accountUpdate.value.id, root); + this.root = root; } - getNode(update: AccountUpdate) { + getNode(update: AccountUpdate | UnfinishedTree): UnfinishedTree { + if (!(update instanceof AccountUpdate)) { + if (!this.map.has(update.accountUpdate.value.id)) { + this.map.set(update.accountUpdate.value.id, update); + } + return update; + } let node = this.map.get(update.id); if (node !== undefined) return node; node = { @@ -1817,11 +1825,22 @@ class AccountUpdateLayout { return node; } - pushChild(parent: AccountUpdate, child: AccountUpdate) { + pushChild(parent: AccountUpdate | UnfinishedTree, child: AccountUpdate) { let parentNode = this.getNode(parent); let childNode = this.getNode(child); parentNode.calls.value.push(childNode); } + + setChildren( + parent: AccountUpdate | UnfinishedTree, + children: AccountUpdateForest + ) { + let parentNode = this.getNode(parent); + parentNode.calls = UnfinishedForest.fromForest(children); + } + setTopLevel(children: AccountUpdateForest) { + this.root.calls = UnfinishedForest.fromForest(children); + } } const CallForest = { diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index df391553c5..e2e58bde0e 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -12,7 +12,10 @@ import { } from '../../account_update.js'; import { DeployArgs, SmartContract } from '../../zkapp.js'; import { TokenAccountUpdateIterator } from './forest-iterator.js'; -import { smartContractContext } from '../smart-contract-context.js'; +import { + accountUpdates, + smartContractContext, +} from '../smart-contract-context.js'; export { TokenContract }; @@ -75,10 +78,7 @@ abstract class TokenContract extends SmartContract { // skip hashing our child account updates in the method wrapper // since we just did that in the loop above - let insideContract = smartContractContext.get(); - if (insideContract) { - insideContract.selfCalls = UnfinishedForest.fromForest(updates); - } + accountUpdates()?.setTopLevel(updates); } /** From 5e2b75645abe8273058df9237a8b1cacad5b4560 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 09:27:38 +0100 Subject: [PATCH 15/69] fixup set children --- src/lib/account_update.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 4d6ae0b9b9..d91d372257 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1626,7 +1626,8 @@ type UnfinishedForest = HashOrValue; type UnfinishedTree = { accountUpdate: HashOrValue; isDummy: Bool; - calls: UnfinishedForest; + // `children` must be readonly since it's referenced in each child's siblings + readonly calls: UnfinishedForest; }; type HashOrValue = @@ -1798,8 +1799,8 @@ const SmartContractContext = { }; class AccountUpdateLayout { - map: Map; - root: UnfinishedTree; + readonly map: Map; + readonly root: UnfinishedTree; constructor(root: UnfinishedTree) { this.map = new Map(); @@ -1836,10 +1837,12 @@ class AccountUpdateLayout { children: AccountUpdateForest ) { let parentNode = this.getNode(parent); - parentNode.calls = UnfinishedForest.fromForest(children); + // we're not allowed to switch parentNode.calls, it must stay the same reference + // so we mutate it in place + Object.assign(parentNode.calls, UnfinishedForest.fromForest(children)); } setTopLevel(children: AccountUpdateForest) { - this.root.calls = UnfinishedForest.fromForest(children); + this.setChildren(this.root, children); } } From ef4b154a5cf4470efa88b4122297be52d20d6d60 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 10:28:52 +0100 Subject: [PATCH 16/69] change name to avoid clash with ts keywored --- src/lib/testing/constraint-system.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 1f8ad9ba52..064b49d863 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -19,7 +19,7 @@ export { not, and, or, - satisfies, + fulfills, equals, contains, allConstant, @@ -186,7 +186,7 @@ function or(...tests: ConstraintSystemTest[]): ConstraintSystemTest { /** * General test */ -function satisfies( +function fulfills( label: string, run: (cs: Gate[], inputs: TypeAndValue[]) => boolean ): ConstraintSystemTest { @@ -276,10 +276,7 @@ function ifNotAllConstant(test: ConstraintSystemTest): ConstraintSystemTest { /** * Test whether constraint system is empty. */ -const isEmpty = satisfies( - 'constraint system is empty', - (cs) => cs.length === 0 -); +const isEmpty = fulfills('constraint system is empty', (cs) => cs.length === 0); /** * Modifies a test so that it runs on the constraint system with generic gates filtered out. From 599caca29978abf9ab7089fa58c17950968b26b9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 10:35:32 +0100 Subject: [PATCH 17/69] introduce siblings to be able to disattach --- src/lib/account_update.ts | 74 ++++++++++++++++++++++++++------------- src/lib/zkapp.ts | 7 +++- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index d91d372257..1b6cdf8892 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1628,54 +1628,57 @@ type UnfinishedTree = { isDummy: Bool; // `children` must be readonly since it's referenced in each child's siblings readonly calls: UnfinishedForest; + siblings?: UnfinishedForest; }; - -type HashOrValue = - | { readonly useHash: true; hash: Field; readonly value: T } - | { readonly useHash: false; value: T }; +type UseHash = { readonly useHash: true; hash: Field; readonly value: T }; +type PlainValue = { readonly useHash: false; value: T }; +type HashOrValue = UseHash | PlainValue; const UnfinishedForest = { - empty(): UnfinishedForest { + empty(): PlainValue { return { useHash: false, value: [] }; }, - witnessHash(forest: UnfinishedForest): { - readonly useHash: true; - hash: Field; - readonly value: UnfinishedTree[]; - } { + setHash(forest: UnfinishedForest, hash: Field): UseHash { + return Object.assign(forest, { useHash: true as const, hash }); + }, + + witnessHash(forest: UnfinishedForest): UseHash { let hash = Provable.witness(Field, () => { return UnfinishedForest.finalize(forest).hash; }); - return { useHash: true, hash, value: forest.value }; + return UnfinishedForest.setHash(forest, hash); }, fromArray(updates: AccountUpdate[], useHash = false): UnfinishedForest { if (useHash) { - let forest = UnfinishedForest.empty(); + let forest: UnfinishedForest = UnfinishedForest.empty(); Provable.asProver(() => (forest = UnfinishedForest.fromArray(updates))); return UnfinishedForest.witnessHash(forest); } - let nodes = updates.map((update): UnfinishedTree => { + let forest = UnfinishedForest.empty(); + forest.value = updates.map((update): UnfinishedTree => { return { accountUpdate: { useHash: false, value: update }, isDummy: update.isDummy(), calls: UnfinishedForest.fromArray(update.children.accountUpdates), + siblings: forest, }; }); - return { useHash: false, value: nodes }; + return forest; }, push( forest: UnfinishedForest, accountUpdate: AccountUpdate, - calls?: UnfinishedForest + children?: UnfinishedForest ) { forest.value.push({ accountUpdate: { useHash: false, value: accountUpdate }, isDummy: accountUpdate.isDummy(), - calls: calls ?? UnfinishedForest.empty(), + calls: children ?? UnfinishedForest.empty(), + siblings: forest, }); }, @@ -1688,6 +1691,7 @@ const UnfinishedForest = { accountUpdate: { useHash: true, hash: tree.accountUpdate.hash, value }, isDummy: Bool(false), calls: UnfinishedForest.fromForest(tree.calls), + siblings: forest, }); }, @@ -1705,9 +1709,9 @@ const UnfinishedForest = { }, fromForest(forest: MerkleListBase): UnfinishedForest { - let value: UnfinishedTree[] = []; + let unfinished = UnfinishedForest.empty(); Provable.asProver(() => { - value = forest.data.get().map(({ element: tree }) => ({ + unfinished.value = forest.data.get().map(({ element: tree }) => ({ accountUpdate: { useHash: true, hash: tree.accountUpdate.hash, @@ -1715,9 +1719,11 @@ const UnfinishedForest = { }, isDummy: Bool(false), calls: UnfinishedForest.fromForest(tree.calls), + siblings: unfinished, })); }); - return { useHash: true, hash: forest.hash, value }; + Object.assign(unfinished, { useHash: true, hash: forest.hash }); + return unfinished; }, finalize(forest: UnfinishedForest): AccountUpdateForest { @@ -1808,7 +1814,14 @@ class AccountUpdateLayout { this.root = root; } - getNode(update: AccountUpdate | UnfinishedTree): UnfinishedTree { + get(update: AccountUpdate) { + return this.map.get(update.id); + } + + getOrCreate( + update: AccountUpdate | UnfinishedTree, + siblings?: UnfinishedForest + ): UnfinishedTree { if (!(update instanceof AccountUpdate)) { if (!this.map.has(update.accountUpdate.value.id)) { this.map.set(update.accountUpdate.value.id, update); @@ -1821,29 +1834,42 @@ class AccountUpdateLayout { accountUpdate: { useHash: false, value: update }, isDummy: update.isDummy(), calls: UnfinishedForest.empty(), + siblings, }; this.map.set(update.id, node); return node; } pushChild(parent: AccountUpdate | UnfinishedTree, child: AccountUpdate) { - let parentNode = this.getNode(parent); - let childNode = this.getNode(child); + let parentNode = this.getOrCreate(parent); + let childNode = this.getOrCreate(child, parentNode.calls); parentNode.calls.value.push(childNode); } + pushTopLevel(child: AccountUpdate) { + this.pushChild(this.root, child); + } + setChildren( parent: AccountUpdate | UnfinishedTree, children: AccountUpdateForest ) { - let parentNode = this.getNode(parent); - // we're not allowed to switch parentNode.calls, it must stay the same reference + let parentNode = this.getOrCreate(parent); + // we're not allowed to switch parentNode.children, it must stay the same reference // so we mutate it in place Object.assign(parentNode.calls, UnfinishedForest.fromForest(children)); } + setTopLevel(children: AccountUpdateForest) { this.setChildren(this.root, children); } + + disattach(update: AccountUpdate) { + let node = this.get(update); + if (node?.siblings === undefined) return; + UnfinishedForest.remove(node.siblings, update); + node.siblings = undefined; + } } const CallForest = { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 2f7b1f9b87..385c089d21 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -410,10 +410,15 @@ function wrapMethod( // nothing is asserted about them -- it's the callee's task to check their children accountUpdate.children.callsType = { type: 'Witness' }; parentAccountUpdate.children.accountUpdates.push(accountUpdate); + + let grandchildren = UnfinishedForest.fromArray( + accountUpdate.children.accountUpdates, + true + ); UnfinishedForest.push( insideContract.selfCalls, accountUpdate, - UnfinishedForest.fromArray(accountUpdate.children.accountUpdates, true) + grandchildren ); // assert that we really called the right zkapp From 43a34d50dda29abebc1c411deb59765f38b7f826 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 11:03:37 +0100 Subject: [PATCH 18/69] override `instanceof`? seriously? have I become a hard-core OO programmer now who will bend over backwards just to use OO idioms? --- src/lib/account_update.ts | 7 +++++-- src/lib/provable-types/merkle-list.ts | 10 ++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 1b6cdf8892..adb089d9df 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1852,12 +1852,15 @@ class AccountUpdateLayout { setChildren( parent: AccountUpdate | UnfinishedTree, - children: AccountUpdateForest + children: AccountUpdateForest | UnfinishedForest ) { let parentNode = this.getOrCreate(parent); // we're not allowed to switch parentNode.children, it must stay the same reference // so we mutate it in place - Object.assign(parentNode.calls, UnfinishedForest.fromForest(children)); + if (children instanceof AccountUpdateForest) { + children = UnfinishedForest.fromForest(children); + } + Object.assign(parentNode.calls, children); } setTopLevel(children: AccountUpdateForest) { diff --git a/src/lib/provable-types/merkle-list.ts b/src/lib/provable-types/merkle-list.ts index a06e19c563..701dc314ae 100644 --- a/src/lib/provable-types/merkle-list.ts +++ b/src/lib/provable-types/merkle-list.ts @@ -202,10 +202,10 @@ class MerkleList implements MerkleListBase { from: (array: T[]) => MerkleList; provable: ProvableHashable>; } { - return class MerkleList_ extends MerkleList { + class MerkleListTBase extends MerkleList { static _innerProvable = type; - static _provable = provableFromClass(MerkleList_, { + static _provable = provableFromClass(MerkleListTBase, { hash: Field, data: Unconstrained.provable, }) as ProvableHashable>; @@ -225,6 +225,12 @@ class MerkleList implements MerkleListBase { assert(this._provable !== undefined, 'MerkleList not initialized'); return this._provable; } + } + // override `instanceof` for subclasses + return class MerkleListT extends MerkleListTBase { + static [Symbol.hasInstance](x: any): boolean { + return x instanceof MerkleListTBase; + } }; } From 54f596ffd6ed76e9b0dc2cf36c49fd2d4b409712 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 12:04:51 +0100 Subject: [PATCH 19/69] write to au layout in zkapp calls --- src/lib/zkapp.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 385c089d21..f115c7e468 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -411,14 +411,10 @@ function wrapMethod( accountUpdate.children.callsType = { type: 'Witness' }; parentAccountUpdate.children.accountUpdates.push(accountUpdate); - let grandchildren = UnfinishedForest.fromArray( - accountUpdate.children.accountUpdates, - true - ); - UnfinishedForest.push( - insideContract.selfCalls, + insideContract.selfLayout.pushTopLevel(accountUpdate); + insideContract.selfLayout.setChildren( accountUpdate, - grandchildren + UnfinishedForest.fromArray(accountUpdate.children.accountUpdates, true) ); // assert that we really called the right zkapp From 71a420050d631f31015785299510844c61b08df3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 12:12:29 +0100 Subject: [PATCH 20/69] get from layout in token contract --- src/lib/mina/token/token-contract.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index e2e58bde0e..d5f17d8a47 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -12,10 +12,7 @@ import { } from '../../account_update.js'; import { DeployArgs, SmartContract } from '../../zkapp.js'; import { TokenAccountUpdateIterator } from './forest-iterator.js'; -import { - accountUpdates, - smartContractContext, -} from '../smart-contract-context.js'; +import { accountUpdates } from '../smart-contract-context.js'; export { TokenContract }; @@ -153,15 +150,11 @@ function finalizeAccountUpdates(updates: AccountUpdate[]): AccountUpdateForest { function finalizeAccountUpdate(update: AccountUpdate): AccountUpdateTree { let calls: AccountUpdateForest; - let insideContract = smartContractContext.get(); - if (insideContract) { - let node = insideContract.selfCalls.value.find( - (c) => c.accountUpdate.value.id === update.id - ); - if (node !== undefined) { - calls = UnfinishedForest.finalize(node.calls); - } + let node = accountUpdates()?.get(update); + if (node !== undefined) { + calls = UnfinishedForest.finalize(node.calls); } + calls ??= AccountUpdateForest.fromArray(update.children.accountUpdates, { skipDummies: true, }); From 4c71d09009151422fa8c1a6a231035754c2ee57b Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 12:12:52 +0100 Subject: [PATCH 21/69] disattach from layout in unlink --- src/lib/account_update.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index adb089d9df..67d9049489 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1149,10 +1149,7 @@ class AccountUpdate implements Types.AccountUpdate { */ static unlink(accountUpdate: AccountUpdate) { // TODO duplicate logic - let insideContract = smartContractContext.get(); - if (insideContract) { - UnfinishedForest.remove(insideContract.selfCalls, accountUpdate); - } + accountUpdates()?.disattach(accountUpdate); let siblings = accountUpdate.parent?.children.accountUpdates ?? currentTransaction()?.accountUpdates; From 6b6b64361b9a6f2f991ce05fe2af8235658535e3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 12:20:45 +0100 Subject: [PATCH 22/69] remove selfCalls --- src/lib/account_update.ts | 5 ++--- src/lib/mina/smart-contract-context.ts | 7 +------ src/lib/mina/token/token-contract.ts | 2 +- src/lib/zkapp.ts | 4 +++- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 67d9049489..9ce41f88c4 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1776,16 +1776,15 @@ function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { const SmartContractContext = { enter(self: SmartContract, selfUpdate: AccountUpdate) { - let selfCalls = UnfinishedForest.empty(); + let calls = UnfinishedForest.empty(); let context: SmartContractContext = { this: self, selfUpdate, selfLayout: new AccountUpdateLayout({ accountUpdate: { useHash: false, value: selfUpdate }, isDummy: Bool(false), - calls: selfCalls, + calls, }), - selfCalls, }; let id = smartContractContext.enter(context); return { id, context }; diff --git a/src/lib/mina/smart-contract-context.ts b/src/lib/mina/smart-contract-context.ts index de8b383657..603bab43c4 100644 --- a/src/lib/mina/smart-contract-context.ts +++ b/src/lib/mina/smart-contract-context.ts @@ -1,9 +1,5 @@ import type { SmartContract } from '../zkapp.js'; -import type { - AccountUpdate, - UnfinishedForest, - AccountUpdateLayout, -} from '../account_update.js'; +import type { AccountUpdate, AccountUpdateLayout } from '../account_update.js'; import { Context } from '../global-context.js'; export { smartContractContext, SmartContractContext, accountUpdates }; @@ -12,7 +8,6 @@ type SmartContractContext = { this: SmartContract; selfUpdate: AccountUpdate; selfLayout: AccountUpdateLayout; - selfCalls: UnfinishedForest; }; let smartContractContext = Context.create({ default: null, diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index d5f17d8a47..3eaa92a209 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -65,7 +65,7 @@ abstract class TokenContract extends SmartContract { ); // make top-level updates our children - // TODO: this must not be necessary once we move everything to `selfCalls` + // TODO: this must not be necessary once we move everything to `selfLayout` Provable.asProver(() => { updates.data.get().forEach((update) => { let accountUpdate = update.element.accountUpdate.value.get(); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index f115c7e468..453335867e 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -215,7 +215,9 @@ function wrapMethod( ProofAuthorization.setKind(accountUpdate); debugPublicInput(accountUpdate); - let calls = UnfinishedForest.finalize(context.selfCalls); + let calls = UnfinishedForest.finalize( + context.selfLayout.root.calls + ); checkPublicInput(publicInput, accountUpdate, calls); // check the self accountUpdate right after calling the method From 3884e6e2b0bfa7e74af927520425cfec9bdf4ccf Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 12:40:53 +0100 Subject: [PATCH 23/69] finalize layout --- src/lib/account_update.ts | 11 +++++++++++ src/lib/mina/token/token-contract.ts | 9 +++------ src/lib/zkapp.ts | 4 +--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 9ce41f88c4..8dcab458ab 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1869,6 +1869,17 @@ class AccountUpdateLayout { UnfinishedForest.remove(node.siblings, update); node.siblings = undefined; } + + finalizeAndRemove(update: AccountUpdate) { + let node = this.get(update); + if (node === undefined) return; + this.disattach(update); + return UnfinishedForest.finalize(node.calls); + } + + finalize() { + return UnfinishedForest.finalize(this.root.calls); + } } const CallForest = { diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 3eaa92a209..afcbeeb6b2 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -148,17 +148,14 @@ function finalizeAccountUpdates(updates: AccountUpdate[]): AccountUpdateForest { } function finalizeAccountUpdate(update: AccountUpdate): AccountUpdateTree { - let calls: AccountUpdateForest; + let calls = accountUpdates()?.finalizeAndRemove(update); - let node = accountUpdates()?.get(update); - if (node !== undefined) { - calls = UnfinishedForest.finalize(node.calls); - } + // TODO remove once everything lives in `selfLayout` + AccountUpdate.unlink(update); calls ??= AccountUpdateForest.fromArray(update.children.accountUpdates, { skipDummies: true, }); - AccountUpdate.unlink(update); return { accountUpdate: HashedAccountUpdate.hash(update), calls }; } diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 453335867e..3266ac7026 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -215,9 +215,7 @@ function wrapMethod( ProofAuthorization.setKind(accountUpdate); debugPublicInput(accountUpdate); - let calls = UnfinishedForest.finalize( - context.selfLayout.root.calls - ); + let calls = context.selfLayout.finalize(); checkPublicInput(publicInput, accountUpdate, calls); // check the self accountUpdate right after calling the method From 1ae3d640f5489714ef28d214087d50b193116452 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 13:39:33 +0100 Subject: [PATCH 24/69] don't store variables in aux data --- src/lib/account_update.ts | 4 +++- src/lib/circuit_value.ts | 8 +++++++ src/lib/provable-types/merkle-list.ts | 34 ++++++++++++++++++++------- src/lib/provable-types/packed.ts | 13 +++++++--- src/lib/provable.ts | 10 ++++++++ 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 8dcab458ab..5da8ddbc08 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1766,7 +1766,9 @@ function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { let accountUpdate = node.accountUpdate.useHash ? new HashedAccountUpdate( node.accountUpdate.hash, - Unconstrained.from(node.accountUpdate.value) + Unconstrained.witness(() => + Provable.toConstant(AccountUpdate, node.accountUpdate.value) + ) ) : HashedAccountUpdate.hash(node.accountUpdate.value); diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index a24e58e83a..ab6f802684 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -547,6 +547,14 @@ and Provable.asProver() blocks, which execute outside the proof. /** * Create an `Unconstrained` with the given `value`. + * + * Note: If `T` contains provable types, `Unconstrained.from` is an anti-pattern, + * because it stores witnesses in a space that's intended to be used outside the proof. + * Something like the following should be used instead: + * + * ```ts + * let xWrapped = Unconstrained.witness(() => Provable.toConstant(type, x)); + * ``` */ static from(value: T) { return new Unconstrained(true, value); diff --git a/src/lib/provable-types/merkle-list.ts b/src/lib/provable-types/merkle-list.ts index 701dc314ae..dd62f3a375 100644 --- a/src/lib/provable-types/merkle-list.ts +++ b/src/lib/provable-types/merkle-list.ts @@ -26,6 +26,12 @@ type WithHash = { previousHash: Field; element: T }; function WithHash(type: ProvableHashable): ProvableHashable> { return Struct({ previousHash: Field, element: type }); } +function toConstant(type: Provable, node: WithHash): WithHash { + return { + previousHash: node.previousHash.toConstant(), + element: Provable.toConstant(type, node.element), + }; +} /** * Common base type for {@link MerkleList} and {@link MerkleListIterator} @@ -88,7 +94,10 @@ class MerkleList implements MerkleListBase { push(element: T) { let previousHash = this.hash; this.hash = this.nextHash(previousHash, element); - this.data.updateAsProver((data) => [{ previousHash, element }, ...data]); + this.data.updateAsProver((data) => [ + toConstant(this.innerProvable, { previousHash, element }), + ...data, + ]); } /** @@ -102,7 +111,9 @@ class MerkleList implements MerkleListBase { previousHash ); this.data.updateAsProver((data) => - condition.toBoolean() ? [{ previousHash, element }, ...data] : data + condition.toBoolean() + ? [toConstant(this.innerProvable, { previousHash, element }), ...data] + : data ); } @@ -161,11 +172,12 @@ class MerkleList implements MerkleListBase { let element = this.pop(); // if the condition is false, we restore the original state - this.data.updateAsProver((data) => - condition.toBoolean() + this.data.updateAsProver((data) => { + let node = { previousHash: this.hash, element }; + return condition.toBoolean() ? data - : [{ previousHash: this.hash, element }, ...data] - ); + : [toConstant(this.innerProvable, node), ...data]; + }); this.hash = Provable.if(condition, this.hash, originalHash); return element; @@ -218,7 +230,10 @@ class MerkleList implements MerkleListBase { static from(array: T[]): MerkleList { let { hash, data } = withHashes(array, nextHash); - return new this({ data: Unconstrained.from(data), hash }); + let unconstrained = Unconstrained.witness(() => + data.map((x) => toConstant(type, x)) + ); + return new this({ data: unconstrained, hash }); } static get provable(): ProvableHashable> { @@ -401,7 +416,10 @@ class MerkleListIterator implements MerkleListIteratorBase { static from(array: T[]): MerkleListIterator { let { hash, data } = withHashes(array, nextHash); - return this.startIterating({ data: Unconstrained.from(data), hash }); + let unconstrained = Unconstrained.witness(() => + data.map((x) => toConstant(type, x)) + ); + return this.startIterating({ data: unconstrained, hash }); } static startIterating({ diff --git a/src/lib/provable-types/packed.ts b/src/lib/provable-types/packed.ts index eb0a04184e..9389de406a 100644 --- a/src/lib/provable-types/packed.ts +++ b/src/lib/provable-types/packed.ts @@ -79,9 +79,13 @@ class Packed { * Pack a value. */ static pack(x: T): Packed { - let input = this.innerProvable.toInput(x); + let type = this.innerProvable; + let input = type.toInput(x); let packed = packToFields(input); - return new this(packed, Unconstrained.from(x)); + let unconstrained = Unconstrained.witness(() => + Provable.toConstant(type, x) + ); + return new this(packed, unconstrained); } /** @@ -211,7 +215,10 @@ class Hashed { */ static hash(value: T): Hashed { let hash = this._hash(value); - return new this(hash, Unconstrained.from(value)); + let unconstrained = Unconstrained.witness(() => + Provable.toConstant(this.innerProvable, value) + ); + return new this(hash, unconstrained); } /** diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 961f723451..6d1eac028a 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -196,6 +196,16 @@ const Provable = { * ``` */ inCheckedComputation, + + /** + * Returns a constant version of a provable type. + */ + toConstant(type: Provable, value: T) { + return type.fromFields( + type.toFields(value).map((x) => x.toConstant()), + type.toAuxiliary(value) + ); + }, }; function witness = FlexibleProvable>( From f68dfcfb4f641e49b952c5a1ac816e4462064a0e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 14:33:54 +0100 Subject: [PATCH 25/69] use layout to process zkapp calls --- src/lib/account_update.ts | 56 ++++++++++++++-------------- src/lib/mina/token/token-contract.ts | 1 - src/lib/zkapp.ts | 26 ++++++++----- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 5da8ddbc08..20a6635ae8 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -64,6 +64,7 @@ import { SmartContractContext, smartContractContext, } from './mina/smart-contract-context.js'; +import { assert } from './util/assert.js'; // external API export { @@ -96,8 +97,6 @@ export { LazyProof, AccountUpdateTree, AccountUpdateLayout, - UnfinishedForest, - UnfinishedTree, hashAccountUpdate, HashedAccountUpdate, SmartContractContext, @@ -1590,6 +1589,19 @@ class AccountUpdateForest extends MerkleList.create( return forest; } + + // TODO this comes from paranoia and might be removed later + static assertConstant(forest: MerkleListBase) { + Provable.asProver(() => { + forest.data.get().forEach(({ element: tree }) => { + assert( + Provable.isConstant(AccountUpdate, tree.accountUpdate.value.get()), + 'account update not constant' + ); + AccountUpdateForest.assertConstant(tree.calls); + }); + }); + } } // how to hash a forest @@ -1647,25 +1659,6 @@ const UnfinishedForest = { return UnfinishedForest.setHash(forest, hash); }, - fromArray(updates: AccountUpdate[], useHash = false): UnfinishedForest { - if (useHash) { - let forest: UnfinishedForest = UnfinishedForest.empty(); - Provable.asProver(() => (forest = UnfinishedForest.fromArray(updates))); - return UnfinishedForest.witnessHash(forest); - } - - let forest = UnfinishedForest.empty(); - forest.value = updates.map((update): UnfinishedTree => { - return { - accountUpdate: { useHash: false, value: update }, - isDummy: update.isDummy(), - calls: UnfinishedForest.fromArray(update.children.accountUpdates), - siblings: forest, - }; - }); - return forest; - }, - push( forest: UnfinishedForest, accountUpdate: AccountUpdate, @@ -1705,21 +1698,26 @@ const UnfinishedForest = { forest.value.splice(index, 1); }, - fromForest(forest: MerkleListBase): UnfinishedForest { + fromForest( + forest: MerkleListBase, + recursiveCall = false + ): UnfinishedForest { let unfinished = UnfinishedForest.empty(); Provable.asProver(() => { unfinished.value = forest.data.get().map(({ element: tree }) => ({ accountUpdate: { useHash: true, - hash: tree.accountUpdate.hash, + hash: tree.accountUpdate.hash.toConstant(), value: tree.accountUpdate.value.get(), }, isDummy: Bool(false), - calls: UnfinishedForest.fromForest(tree.calls), + calls: UnfinishedForest.fromForest(tree.calls, true), siblings: unfinished, })); }); - Object.assign(unfinished, { useHash: true, hash: forest.hash }); + if (!recursiveCall) { + Object.assign(unfinished, { useHash: true, hash: forest.hash }); + } return unfinished; }, @@ -1805,6 +1803,7 @@ const SmartContractContext = { class AccountUpdateLayout { readonly map: Map; readonly root: UnfinishedTree; + final?: AccountUpdateForest; constructor(root: UnfinishedTree) { this.map = new Map(); @@ -1879,8 +1878,11 @@ class AccountUpdateLayout { return UnfinishedForest.finalize(node.calls); } - finalize() { - return UnfinishedForest.finalize(this.root.calls); + finalizeChildren() { + let final = UnfinishedForest.finalize(this.root.calls); + this.final = final; + AccountUpdateForest.assertConstant(final); + return final; } } diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index afcbeeb6b2..4617e011c5 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -5,7 +5,6 @@ import { PublicKey } from '../../signature.js'; import { AccountUpdate, AccountUpdateForest, - UnfinishedForest, AccountUpdateTree, HashedAccountUpdate, Permissions, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 3266ac7026..e7469fce42 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -15,7 +15,6 @@ import { ZkappPublicInput, LazyProof, CallForest, - UnfinishedForest, AccountUpdateForest, SmartContractContext, } from './account_update.js'; @@ -215,7 +214,7 @@ function wrapMethod( ProofAuthorization.setKind(accountUpdate); debugPublicInput(accountUpdate); - let calls = context.selfLayout.finalize(); + let calls = context.selfLayout.finalizeChildren(); checkPublicInput(publicInput, accountUpdate, calls); // check the self accountUpdate right after calling the method @@ -388,12 +387,24 @@ function wrapMethod( Mina.currentTransaction()!.accountUpdates ); } - return { accountUpdate, result: result ?? null }; + // extract callee's account update layout + let children = innerContext.selfLayout.finalizeChildren(); + + return { + accountUpdate, + result: { result: result ?? null, children }, + }; }; // we have to run the called contract inside a witness block, to not affect the caller's circuit - let { accountUpdate, result } = AccountUpdate.witness( - returnType ?? provable(null), + let { + accountUpdate, + result: { result, children }, + } = AccountUpdate.witness<{ result: any; children: AccountUpdateForest }>( + provable({ + result: returnType ?? provable(null), + children: AccountUpdateForest.provable, + }), runCalledContract, { skipCheck: true } ); @@ -412,10 +423,7 @@ function wrapMethod( parentAccountUpdate.children.accountUpdates.push(accountUpdate); insideContract.selfLayout.pushTopLevel(accountUpdate); - insideContract.selfLayout.setChildren( - accountUpdate, - UnfinishedForest.fromArray(accountUpdate.children.accountUpdates, true) - ); + insideContract.selfLayout.setChildren(accountUpdate, children); // assert that we really called the right zkapp accountUpdate.body.publicKey.assertEquals(this.address); From 3778fa2dcc40db4912fffdf96b76d4671a029fbb Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 15:17:21 +0100 Subject: [PATCH 26/69] fix token test (don't use create child account update) --- src/lib/account_update.ts | 2 +- src/lib/token.test.ts | 43 +++++++++++++-------------------------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 20a6635ae8..c340b9cc13 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -676,7 +676,7 @@ class AccountUpdate implements Types.AccountUpdate { } if (accountLike instanceof AccountUpdate) { accountLike.tokenId.assertEquals(id); - thisAccountUpdate.approve(accountLike); + thisAccountUpdate.adopt(accountLike); } if (accountLike instanceof PublicKey) { accountLike = AccountUpdate.defaultAccountUpdate(accountLike, id); diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 5f8502f902..961d20770e 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -12,7 +12,6 @@ import { Permissions, VerificationKey, Field, - Experimental, Int64, TokenId, } from 'o1js'; @@ -84,38 +83,31 @@ class TokenContract extends SmartContract { this.totalAmountInCirculation.set(newTotalAmountInCirculation); } - @method approveTransferCallback( + @method approveTransfer( senderAddress: PublicKey, receiverAddress: PublicKey, amount: UInt64, - callback: Experimental.Callback + senderAccountUpdate: AccountUpdate ) { - let layout = AccountUpdate.Layout.NoChildren; // Allow only 1 accountUpdate with no children - let senderAccountUpdate = this.approve(callback, layout); - let negativeAmount = Int64.fromObject( - senderAccountUpdate.body.balanceChange - ); + this.self.adopt(senderAccountUpdate); + let negativeAmount = senderAccountUpdate.balanceChange; negativeAmount.assertEquals(Int64.from(amount).neg()); let tokenId = this.token.id; senderAccountUpdate.body.tokenId.assertEquals(tokenId); senderAccountUpdate.body.publicKey.assertEquals(senderAddress); - let receiverAccountUpdate = Experimental.createChildAccountUpdate( - this.self, - receiverAddress, - tokenId - ); + let receiverAccountUpdate = AccountUpdate.create(receiverAddress, tokenId); receiverAccountUpdate.balance.addInPlace(amount); } } class ZkAppB extends SmartContract { - @method approveZkapp(amount: UInt64) { + @method approveSend(amount: UInt64) { this.balance.subInPlace(amount); } } class ZkAppC extends SmartContract { - @method approveZkapp(amount: UInt64) { + @method approveSend(amount: UInt64) { this.balance.subInPlace(amount); } @@ -535,16 +527,13 @@ describe('Token', () => { await tx.sign([feePayerKey, tokenZkappKey]).send(); tx = await Mina.transaction(feePayer, () => { - let approveSendingCallback = Experimental.Callback.create( - zkAppB, - 'approveZkapp', - [UInt64.from(10_000)] - ); - tokenZkapp.approveTransferCallback( + zkAppB.approveSend(UInt64.from(10_000)); + + tokenZkapp.approveTransfer( zkAppBAddress, zkAppCAddress, UInt64.from(10_000), - approveSendingCallback + zkAppB.self ); }); await tx.prove(); @@ -570,16 +559,12 @@ describe('Token', () => { await expect(() => Mina.transaction(feePayer, () => { - let approveSendingCallback = Experimental.Callback.create( - zkAppC, - 'approveIncorrectLayout', - [UInt64.from(10_000)] - ); - tokenZkapp.approveTransferCallback( + zkAppC.approveIncorrectLayout(UInt64.from(10_000)); + tokenZkapp.approveTransfer( zkAppBAddress, zkAppCAddress, UInt64.from(10_000), - approveSendingCallback + zkAppC.self ); }) ).rejects.toThrow(); From 756ade0ea74dcc4d70daa54388527465386a56ee Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 15:22:20 +0100 Subject: [PATCH 27/69] dump vks --- tests/vk-regression/vk-regression.json | 72 +++++++++++++------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 9eb6b8b65e..6e8a9a738d 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -1,22 +1,22 @@ { "Voting_": { - "digest": "385ea3e72a679aad6a48b0734008d80da05f27a17854b9088edc8de420ad907d", + "digest": "232c8378880bbb68117f00d34e442aa804f42255a6f02d343b738b8171d6ce65", "methods": { "voterRegistration": { "rows": 1259, - "digest": "a59bad21fc55ce7d8d099d24cb91d1e6" + "digest": "de76523858f6497e67b359668a14f51d" }, "candidateRegistration": { "rows": 1259, - "digest": "ee209f54f9f7996bae2bf5c0fd4bf9da" + "digest": "737730be005f7d071d339e036353ef7b" }, "approveRegistrations": { - "rows": 1147, - "digest": "748673762a27be8b58ef15084bd49beb" + "rows": 1148, + "digest": "08b40e8d2a9ea7369c371afd2e4e1f54" }, "vote": { - "rows": 1672, - "digest": "0862eef1a5edb8ade4d1d5dab1f76abc" + "rows": 1673, + "digest": "37fdfc514d42b21cbe6f0b914bd88efa" }, "countVotes": { "rows": 5796, @@ -24,16 +24,16 @@ } }, "verificationKey": { - "data": "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", - "hash": "10242427736291135326630618424974868967569697518211103043324256389485118856981" + "data": "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", + "hash": "15776151091008092402671254671490268418790844901048023495826534500844684898367" } }, "Membership_": { - "digest": "3b4598fa0324c27a3535ee0f187419dcd43cde203ac1053df4bcc5a108fd0c52", + "digest": "35aa3fb2f5114cb29b575c70c3cef15e6538c66074876997d0005cd51925cf9", "methods": { "addEntry": { "rows": 1353, - "digest": "657f6ba31355b4d6daa80b84e86a5341" + "digest": "9c4e2d1faf08ecf2ab6d46e29d3e7063" }, "isMember": { "rows": 469, @@ -45,8 +45,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "27077061932938200348860230517448044253628907171665029926735544979854107063304" + "data": "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", + "hash": "26245886907208238580957987655381478268353243870285421747846360000722576368055" } }, "HelloWorld": { @@ -63,53 +63,53 @@ } }, "TokenContract": { - "digest": "1b4cc9f8af1e49b7f727bde338ff93b3aa72d1be175669d51dc7991296ce7dbd", + "digest": "13926c08b6e9f7e6dd936edc1d019db122882c357b6e5f3baf416e06530c608a", "methods": { "init": { - "rows": 342, - "digest": "f9f73a136c5642d519ec0f73e033e6f3" + "rows": 655, + "digest": "d535a589774b32cd36e2d6c3707afd30" }, "init2": { - "rows": 342, - "digest": "c4303618beb22bb040bffacff35286cb" + "rows": 652, + "digest": "4a781d299f945218e93f3d9235549373" }, "approveBase": { - "rows": 13194, - "digest": "8f8ad42a6586936a28b570877403960c" + "rows": 13244, + "digest": "4dd81f1cc1a06b617677e08883e50fbd" } }, "verificationKey": { - "data": "AACwuS3vTWCwpRIX/QlJQqJcmPO9nPm4+sCfcrqiY1NUMiV9k6Pc8kFkMsbGLst78T8uAnYwc1Ql49kq0I2GizwshS9xkBcfxRTAAMBHXhf8KDkK39AalVocKIrfWMV0MSShinj0bCxPCc10K0cya4Voy8fud4+hktDOuwjaAstpEJSbKRHMIki77xHmJWlFUYdkgPg30MU4Ta3ev/h+mcMWmofyhLSQqUbaV6hM95n3Y0Wcn2LRNxJP8TRwHndIcylleqPsGMh3P+A+N9c32N4kl29nreMJJdcUrCXK90GLPAFOB9mHIjKk9+9o3eZc3cGQ+jppXoN3zwO91DeT/GYvXqCZTAudLxIwuJU11UBThG5CKKABa9ulQ1bYGXj9Eydy0vPxfojDeFrnKMi9GKSjiSMzmOLbIw7Dt+g9ggjsHM5rPrT7dY1VV4ZT9shjlcX3029xnk3Bjz4Q9PiK+A8o6f7L6aVB07I+QY2iDtwSQWuXYPohrk85I1UbPfY+giWqFXBtHaN45PMWCyBx0TKaozETCmv0kA5KGTzesYQCECPQ8F2DM+oXz8xly+z9/Ypt/Zx9NvF7wute/1s6Q/QuALwRpuVzjcE9XI9dcXT3cOq9gBOXywj0ksyrtIyrhdIEOk/3SNVzUY5cDljDSvMI/LBLlvt4hlMNQKnEaMcf5AiualxBHmDFBY3jj2ar6dP2OIfn7prilChVTkVooq8LAzjcuUVNl/dxWgt+lNKIpiBegEFHA4Xr0XI0orQZjCIBEBXWMCQE5kfK476bQgrLeKJfQ45PZfgB688DGwaYAxWbcxBV822/aAsA55ijFY1Xf7S+DiytY4a/u0bellKMDUQqTOq9VwmbDv868zXscUwKpNVR3wy2En/q9M/HJJc4BZyuuQvlQSR59m0gL4hKHf5Dci/YVvM6ACHmg+5SxCr1pUNKbyy2lsIa5Ma40ZmsTpT4/lQczmGENQSQXA9bFibT0Q+Vj885p9heLOCCXyAujC4DhAdYmT1MQ7v4Ixckgxm+qa8j6OG0KVKedxqGnK/IPv+9yLpKDOv1JvR6DQ1x9D3zHQ3BHcmggl4spHL1IXHXiyCzRnepiK87kZ8vAho0xbIGur+GDXy1b4gGJ96UgYKiK3xxeCxDYemF2LMS1VslxqO9ysOQImTmUlVV/VW8vGvnmVoAwkp+WDm7QAz6l24QrqQAp0ebGEbpXqv21bhlr6dYBsculE2VU9SuGJ2g6yuuKf4+lfJ2V5TkIxFvlgw5cxTXNQ010JYug38++ZDV+MibXPzg+cODE5wfZ3jon5wVNkAiG642DzXzNj67x80zBWLdt3UKnFZs9dpa1fYpTjlJg8T+dnJJiKf2IvmvF8xyi1HAwAFyhDL2dn/w/pDE2Kl9QdpZpQYDEBQgCCkegsZszQ+2mjxU9pLXzz5GSoqz8jABW5Qo3abBAhvYKKaAs6NoRgeAD6SadFDbQmXaftE+Y1MVOtjnaZDUBdwahWiJMlkfZpxW1aubEc/GSX8WzCZ8h9HeakcRc7kcN0CR8kmfER3eiZ2JMbt5cQl/afNjwGGAmeXzTaR34AgFjiw/RlZJkhYm9jyf18M8yP94QGBMxd6Y6wrNvOmJHzEnp8aitJsDlZklm8LKbjumlSbLcbBokpIDhFBBKfwP2qsQX7eHLCZ/3mztoFKoIiYXgrHWG8m2SzIJ/ljn6Rg7AxIsPjzZyEw1eXAOC7A1FCT/757ygMsnk+rLlpDTBYLmhJtQdt61MQFDi5BuCmQ/PY9C/74/k4APl5htiNcCZty/1JElFwjuCQFjvAiMPUMyqp7/ALFapsTZqhSs1g6jd8uhuJoTNEqLDvKUUbs0kMvGy8BOG0YXNxmNccabGwBzxmijv6LF/Xinecl4aD8FCh6opY98TJnOHd3XSYL1DbLqmmc6CXEM+g5iDGnXr/CkI2Jy37OkF8X03jz4AH0Yj0+J63yH4IS+PrNpKZEXKh7PvXNaLGGKsFcKEi63/xKPKH0G4RzvFKbkp+IWqtIYjMiwIJMwzmfS1NLLXqqpFiD364eFcXINR2rrDKcoTUp1JkVZVfXfKwaRUPWSGFYIYMtwPh2w8ZfubAmXZFpyzstORhFyg9rtVAAy0lcDhQwWVlhFFkR2qbdoy0EFLBrfKqUIkd1N6vDQQYL1RGaTAv/ybregrJsFo+VP3ZatlR6LnKYWp1m7vPkGm3I6Pus/mvp1k10QGk8nhFuR31DjsG3lzZ4gXSs1oSv0qbxD2S6g5+Y6cPbITEGX3uQjsunXnQ9PHd22Mk+fqbDakTiCJh6aFqqPNShiAXkGSuC1oXJHX3zqnbn75dWO0UVhBNAbjYkSnQeyka1wnZb12sR+PlRMvWQVcd93t5L/FiE0ORo=", - "hash": "20122457815983542378345864836658988429829729096873540353338563912426952908135" + "data": "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", + "hash": "8425314086810278038786818130066444259495740778554070932241514249764148251692" } }, "Dex": { - "digest": "272c50dc8fd51817806dc4e5e0c17e4315d5bad077b23f6b44dac5b999ceb9a2", + "digest": "1adce234400fe0b0ea8b1e625256538660d9ed6e15767e2ac78bb7a59d83a3af", "methods": { "supplyLiquidityBase": { - "rows": 2566, - "digest": "13f3375abe98f94b605c5b079315f066" + "rows": 2882, + "digest": "9930f9a0b82eff6247cded6430c6356f" }, "swapX": { - "rows": 1562, - "digest": "c29354e0f9d6a787a9330730ba19e689" + "rows": 1563, + "digest": "e6c2a260178af42268a1020fc8e6113d" }, "swapY": { - "rows": 1562, - "digest": "9d82e9d413a342d45179001358495e89" + "rows": 1563, + "digest": "8a3959ec3394716f1979cf013a9a4ced" }, "burnLiquidity": { - "rows": 403, - "digest": "7e7c3df3049d99703023a39ab1bfe8e4" + "rows": 718, + "digest": "6c406099fe2d2493bd216f9bbe3ba934" }, "transfer": { - "rows": 414, - "digest": "2a4ce4f790fedf38514d331fa0d57eb0" + "rows": 1044, + "digest": "1df1d01485d388ee364156f940214d23" } }, "verificationKey": { - "data": "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", - "hash": "18952871976004328548139095054727555812687816649131669760777722323942382863169" + "data": "AADgDFCYyznG8hH/Z695+WW86B544SmJFzz5ObrizTJ4KMqy+pfsOR2Mt2yGViXSJPpAR76RNHNga83UB8/9OPQIB+uHOnxXH7vN8sUeDQi50gWdXzRlzSS1jsT9t+XsQwHNWgMQp04pKmF+0clYz1zwOO95BwHGcQ/olrSYW4tbJN6KW0hN2eESQfUJcwfB6uUzwvGtkFs+aiUykn7KUgUgXQkKgdHHdyFioNHNPmkpiAre/Ts8BKwwvf5hCa1MtBF6ax6ymlATB4YBL0ETiEPTE/Qk1zGWUSL2UB6aY45/LlfTLCKlyLq7cR3HOucFfBncVfzI7D8j5n4wVqY+vAI4cf+Yv7iVRLbeFcycXtsuPQntgBzKa/mcqcWuVM7p2SYRrtKdX8EKvOO6NhfLx4x0atAi8pKf+vZR76LSP4iOA8hwXvk6MNvPt1fxCS96ZAKuAzZnAcK+MH1OcKeLj+EHtZmf40WRb3AEG5TWRKuD6DT5noDclZsE8ROZKUSOKAUGIBvt7MpzOWPPchmnromWEevmXo3GoPUZCKnWX6ZLAtJwAszLUgiVS8rx3JnLXuXrtcVFto5FFQhwSHZyzuYZAIDAYK7Q5B4vfVRO2sKtcsdvqGaN8PqBI0wk/ztCG24fs6Y8bh/c3VE5+aYOpXHrg48pkPU0BALhn9HBXRD4zAEX158Ec7dRasnw88ilp3WCDpjKgqPkM2k6lr/GtZEqJPVdN/OQieSqy7+nA/QJOMD0KJw/f/BRjQK8pl5w1+YDJ5M/KjfmCc2/EsnV7Mhax350ZtrXdzh/HWIWzEZKKxcbERFbRtf+fkMOOLNpNov1FEFvKOU612vDOIbrVHeBN9mwuepUrJctcfgLc0Mi3Sxs3+NA0I74qm5ktjmplDwgUtKzIs3IrVFv6b1pg/J32HmwNzJZw2fYzpFE1LDjBSK/SX3axwMy5yEd8+jl4uAdQZpa9UQQIHu1Y1ZMgJSDDicXz6D1bZMA1Q2/lU+8AYbldgQVmlLq/lzr63krX+AMui2aEHwEFHenVnLoyu8b9mrtq35xqy228mqECf8YRQtjf5x4cYfXeDfwEqyfh+J9Wau/9pflXra/iQpHqoJlPruN7YPBiXekC30QeageThlYM/EdNZbgPSCxaKiLvdkrysX/B10Phr5p9KLUclsaeQrwr3taDhHobZe2LxxKVCz59l19FcR35ItoigIxtMfkv3rdlCOeBVI93oVl5esiH8AvYGHhulWIvrNfKol3Viir41zv4qMBOcQg8+ygqjwqREU5+qiYeJlQ2AtT0/PVeZWg4mHC39uz1Lld3N2hyyxRo+Z0nC/8220uuf9gAnQ+JFixgyYW0NowUtuFj+uYAV9Dh/Zpe4LyAOkU0kBW4CEuOxNr+gz+9h0BoPfBHlMuuQAUc5L8uMunJC7uBKZiL+/tT1ZGfyIuqU47fEP9Hghxmip8v7gpf+4wB0MVUUwav9QRe9g88ER1HcJPqYb4EIOc2kbYSX75bT0mAFqR8lwZrj6lbQtNS0QQboG5fzoyYGi8YnSXhC2T5fFDpGJ319GHUsna58o5wk8LMwKWNTxq+FN6XiRgu0BFOrtG6MtT1OxYE9Dti6WatGDsWv+KMLDHjxUK1bhiSRnvkWYNcnuDJ0Ry+PRGHNUijVU0SbchntC2JHdhwKbwIofwKHE8HhvlK8FgQ1VOLDioA26UFzr23LpCTqwSJ7/sAqttNGcPR8MSeeR9TQvXNYQPKrA7Gh720X+7LD6BuHdy4vkcr9EKBU0ccUJ2ABBiyPdji+AgEbUCL/wrp6/GX8pui5YJGWx3XmIFj/RnYS2Je5FZ7w74JclD3XhLUo5Dhpq5RznHplpLB9mNdZdm5269US/XCgC/ZKyUxW3+0ajdBY1cLzF6qglitaYTp3MVUENVOkACM2RyKw6jIK2Leq3qLp6AUz21VXj4WznZcdI8MXqT9v8HxjXbAI9dtbhLRZRpJmu/129vrVmwSTHvsVoA7vXyYh/iO3ZMcy+D1x+HZU6Q/oDYCicqOPHxpSc9QGehmNyeGzI//524Gz3RudkU7s6MPdLWqZrieRTnWsTIrCDieu4ValfP8BFz7asYUv0t9jMWpv3yjbY7c5h8N/m7IUXwTQCzFpjPV7HC72BjVwPaYqh5/oAQsSNcv5I3c2GsCGj5C4hFFoT7eWfVtu/6ibQl0COhRDsegnOBtZ7NGfybI8IIO/4yrgel92bypb3eSxeMvdE5wzURluGDkBVVIACD8C5W1MzqrejUiiTfc3mkLhQ0xKRRhT0qqkmYWlbGN5hmMOA9YaYx8OFTgMys1WbzdidWgEkyvvdkWctGlges6eg/lJE61tJ8wGxvJfKtpyDW/2MRvsnO1+2EXIQ2eV3hkxg=", + "hash": "23594323045578615602880853374590447788338441806100547122393736875331781522763" } }, "Group Primitive": { @@ -250,4 +250,4 @@ "hash": "22296391645667701199385692837408020819294441951376164803693884547686842878882" } } -} +} \ No newline at end of file From 1928e17f1521ef8f75cefe0e94fa77ef420790e6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 16:07:24 +0100 Subject: [PATCH 28/69] [no children} fromFlatArray which doesn't rely on account update nesting --- src/lib/account_update.ts | 23 +++++- .../mina/token/forest-iterator.unit-test.ts | 80 +++++-------------- 2 files changed, 42 insertions(+), 61 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index c340b9cc13..ea6bbc3cd9 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -49,7 +49,11 @@ import { import { MlArray } from './ml/base.js'; import { Signature, signFieldElement } from '../mina-signer/src/signature.js'; import { MlFieldConstArray } from './ml/fields.js'; -import { transactionCommitments } from '../mina-signer/src/sign-zkapp-command.js'; +import { + accountUpdatesToCallForest, + CallForest, + transactionCommitments, +} from '../mina-signer/src/sign-zkapp-command.js'; import { currentTransaction } from './mina/transaction-context.js'; import { isSmartContract } from './mina/smart-contract-base.js'; import { activeInstance } from './mina/mina-instance.js'; @@ -1560,6 +1564,23 @@ class AccountUpdateForest extends MerkleList.create( AccountUpdateTree, merkleListHash ) { + static fromFlatArray(updates: AccountUpdate[]): AccountUpdateForest { + let simpleForest = accountUpdatesToCallForest(updates); + return this.fromSimpleForest(simpleForest); + } + + private static fromSimpleForest( + simpleForest: CallForest + ): AccountUpdateForest { + let nodes = simpleForest.map((node) => { + let accountUpdate = HashedAccountUpdate.hash(node.accountUpdate); + let calls = AccountUpdateForest.fromSimpleForest(node.children); + return { accountUpdate, calls }; + }); + return AccountUpdateForest.from(nodes); + } + + // TODO remove static fromArray( updates: AccountUpdate[], { skipDummies = false } = {} diff --git a/src/lib/mina/token/forest-iterator.unit-test.ts b/src/lib/mina/token/forest-iterator.unit-test.ts index 06c9981847..c9a92117a8 100644 --- a/src/lib/mina/token/forest-iterator.unit-test.ts +++ b/src/lib/mina/token/forest-iterator.unit-test.ts @@ -4,7 +4,6 @@ import { TokenAccountUpdateIterator } from './forest-iterator.js'; import { AccountUpdate, AccountUpdateForest, - CallForest, TokenId, hashAccountUpdate, } from '../../account_update.js'; @@ -13,7 +12,6 @@ import { Pickles } from '../../../snarky.js'; import { accountUpdatesToCallForest, callForestHash, - CallForest as SimpleCallForest, } from '../../../mina-signer/src/sign-zkapp-command.js'; import assert from 'assert'; import { Field, Bool } from '../../core.js'; @@ -60,40 +58,19 @@ test.custom({ timeBudget: 1000 })( let forestBigint = accountUpdatesToCallForest(flatUpdatesBigint); let expectedHash = callForestHash(forestBigint); - // convert to o1js-style list of nested `AccountUpdate`s let flatUpdates = flatUpdatesBigint.map(accountUpdateFromBigint); - let updates = callForestToNestedArray( - accountUpdatesToCallForest(flatUpdates) - ); - - let forest = AccountUpdateForest.fromArray(updates); + let forest = AccountUpdateForest.fromFlatArray(flatUpdates); forest.hash.assertEquals(expectedHash); } ); -// can recover flat account updates from nested updates -// this is here to assert that we compute `updates` correctly in the other tests - -test(flatAccountUpdates, (flatUpdates) => { - let updates = callForestToNestedArray( - accountUpdatesToCallForest(flatUpdates) - ); - let flatUpdates2 = CallForest.toFlatList(updates, false); - let n = flatUpdates.length; - for (let i = 0; i < n; i++) { - assert.deepStrictEqual(flatUpdates2[i], flatUpdates[i]); - } -}); - // traverses the top level of a call forest in correct order // i.e., CallForestArray works test.custom({ timeBudget: 1000 })(flatAccountUpdates, (flatUpdates) => { // prepare call forest from flat account updates - let updates = callForestToNestedArray( - accountUpdatesToCallForest(flatUpdates) - ); - let forest = AccountUpdateForest.fromArray(updates).startIterating(); + let forest = AccountUpdateForest.fromFlatArray(flatUpdates).startIterating(); + let updates = flatUpdates.filter((u) => u.body.callDepth === 0); // step through top-level by calling forest.next() repeatedly let n = updates.length; @@ -116,10 +93,7 @@ test.custom({ timeBudget: 5000 })(flatAccountUpdates, (flatUpdates) => { let tokenId = TokenId.default; // prepare forest iterator from flat account updates - let updates = callForestToNestedArray( - accountUpdatesToCallForest(flatUpdates) - ); - let forest = AccountUpdateForest.fromArray(updates); + let forest = AccountUpdateForest.fromFlatArray(flatUpdates); let forestIterator = TokenAccountUpdateIterator.create(forest, tokenId); // step through forest iterator and compare against expected updates @@ -154,28 +128,26 @@ test.custom({ timeBudget: 5000 })( }); let tokenId = TokenId.derive(tokenOwner); - // prepare forest iterator from flat account updates - let updates = callForestToNestedArray( - accountUpdatesToCallForest(flatUpdates) - ); - // make all top-level updates inaccessible - updates.forEach((u, i) => { - if (i % 3 === 0) { - u.body.mayUseToken = AccountUpdate.MayUseToken.No; - } else if (i % 3 === 1) { - u.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; - } else { - u.body.publicKey = tokenOwner; - u.body.tokenId = TokenId.default; - } - }); + flatUpdates + .filter((u) => u.body.callDepth === 0) + .forEach((u, i) => { + if (i % 3 === 0) { + u.body.mayUseToken = AccountUpdate.MayUseToken.No; + } else if (i % 3 === 1) { + u.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; + } else { + u.body.publicKey = tokenOwner; + u.body.tokenId = TokenId.default; + } + }); - let forest = AccountUpdateForest.fromArray(updates); + // prepare forest iterator from flat account updates + let forest = AccountUpdateForest.fromFlatArray(flatUpdates); let forestIterator = TokenAccountUpdateIterator.create(forest, tokenId); // step through forest iterator and compare against expected updates - let expectedUpdates = updates; + let expectedUpdates = flatUpdates.filter((u) => u.body.callDepth === 0); let n = flatUpdates.length; for (let i = 0; i < n; i++) { @@ -214,10 +186,7 @@ test.custom({ timeBudget: 5000 })( }); // prepare forest iterator from flat account updates - let updates = callForestToNestedArray( - accountUpdatesToCallForest(flatUpdates) - ); - let forest = AccountUpdateForest.fromArray(updates); + let forest = AccountUpdateForest.fromFlatArray(flatUpdates); let forestIterator = TokenAccountUpdateIterator.create(forest, tokenId); // step through forest iterator and compare against expected updates @@ -239,15 +208,6 @@ function accountUpdateFromBigint(a: TypesBigint.AccountUpdate): AccountUpdate { return AccountUpdate.fromJSON(TypesBigint.AccountUpdate.toJSON(a)); } -function callForestToNestedArray( - forest: SimpleCallForest -): AccountUpdate[] { - return forest.map(({ accountUpdate, children }) => { - accountUpdate.children.accountUpdates = callForestToNestedArray(children); - return accountUpdate; - }); -} - function assertEqual(actual: AccountUpdate, expected: AccountUpdate) { let actualHash = hashAccountUpdate(actual).toBigInt(); let expectedHash = hashAccountUpdate(expected).toBigInt(); From 0f917b1e77e69ebaf590709047f2079fe5775fa3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 17:56:37 +0100 Subject: [PATCH 29/69] minor fix --- src/lib/account_update.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index ea6bbc3cd9..a10c19bb02 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1797,14 +1797,13 @@ function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { const SmartContractContext = { enter(self: SmartContract, selfUpdate: AccountUpdate) { - let calls = UnfinishedForest.empty(); let context: SmartContractContext = { this: self, selfUpdate, selfLayout: new AccountUpdateLayout({ accountUpdate: { useHash: false, value: selfUpdate }, isDummy: Bool(false), - calls, + calls: UnfinishedForest.empty(), }), }; let id = smartContractContext.enter(context); @@ -1836,10 +1835,7 @@ class AccountUpdateLayout { return this.map.get(update.id); } - getOrCreate( - update: AccountUpdate | UnfinishedTree, - siblings?: UnfinishedForest - ): UnfinishedTree { + getOrCreate(update: AccountUpdate | UnfinishedTree): UnfinishedTree { if (!(update instanceof AccountUpdate)) { if (!this.map.has(update.accountUpdate.value.id)) { this.map.set(update.accountUpdate.value.id, update); @@ -1852,7 +1848,6 @@ class AccountUpdateLayout { accountUpdate: { useHash: false, value: update }, isDummy: update.isDummy(), calls: UnfinishedForest.empty(), - siblings, }; this.map.set(update.id, node); return node; @@ -1860,7 +1855,8 @@ class AccountUpdateLayout { pushChild(parent: AccountUpdate | UnfinishedTree, child: AccountUpdate) { let parentNode = this.getOrCreate(parent); - let childNode = this.getOrCreate(child, parentNode.calls); + let childNode = this.getOrCreate(child); + childNode.siblings = parentNode.calls; parentNode.calls.value.push(childNode); } From d5ec3fb1fc716446ad707ef62adf23e4a60da683 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 18:19:29 +0100 Subject: [PATCH 30/69] introduce (not yet used) layout for Mina.transaction --- src/lib/account_update.ts | 18 ++++++++++-------- src/lib/mina.ts | 2 ++ src/lib/mina/smart-contract-context.ts | 9 ++++++++- src/lib/mina/transaction-context.ts | 3 ++- src/lib/zkapp.ts | 4 ++++ 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index a10c19bb02..3684d17695 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1800,11 +1800,7 @@ const SmartContractContext = { let context: SmartContractContext = { this: self, selfUpdate, - selfLayout: new AccountUpdateLayout({ - accountUpdate: { useHash: false, value: selfUpdate }, - isDummy: Bool(false), - calls: UnfinishedForest.empty(), - }), + selfLayout: new AccountUpdateLayout(selfUpdate), }; let id = smartContractContext.enter(context); return { id, context }; @@ -1825,10 +1821,16 @@ class AccountUpdateLayout { readonly root: UnfinishedTree; final?: AccountUpdateForest; - constructor(root: UnfinishedTree) { + constructor(root?: AccountUpdate) { this.map = new Map(); - this.map.set(root.accountUpdate.value.id, root); - this.root = root; + root ??= AccountUpdate.dummy(); + let rootTree: UnfinishedTree = { + accountUpdate: { useHash: false, value: root }, + isDummy: Bool(false), + calls: UnfinishedForest.empty(), + }; + this.map.set(root.id, rootTree); + this.root = rootTree; } get(update: AccountUpdate) { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 22c83fe9bb..e39c2b0dec 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -15,6 +15,7 @@ import { Actions, Events, dummySignature, + AccountUpdateLayout, } from './account_update.js'; import * as Fetch from './fetch.js'; import { assertPreconditionInvariants, NetworkValue } from './precondition.js'; @@ -182,6 +183,7 @@ function createTransaction( let transactionId = currentTransaction.enter({ sender, accountUpdates: [], + layout: new AccountUpdateLayout(), fetchMode, isFinalRunOutsideCircuit, numberOfRuns, diff --git a/src/lib/mina/smart-contract-context.ts b/src/lib/mina/smart-contract-context.ts index 603bab43c4..a276a2ff1e 100644 --- a/src/lib/mina/smart-contract-context.ts +++ b/src/lib/mina/smart-contract-context.ts @@ -1,6 +1,7 @@ import type { SmartContract } from '../zkapp.js'; import type { AccountUpdate, AccountUpdateLayout } from '../account_update.js'; import { Context } from '../global-context.js'; +import { currentTransaction } from './transaction-context.js'; export { smartContractContext, SmartContractContext, accountUpdates }; @@ -14,5 +15,11 @@ let smartContractContext = Context.create({ }); function accountUpdates() { - return smartContractContext.get()?.selfLayout; + // in a smart contract, return the layout currently created in the contract call + let layout = smartContractContext.get()?.selfLayout; + + // if not in a smart contract but in a transaction, return the layout of the transaction + layout ??= currentTransaction()?.layout; + + return layout; } diff --git a/src/lib/mina/transaction-context.ts b/src/lib/mina/transaction-context.ts index a3bb040b1c..e007079522 100644 --- a/src/lib/mina/transaction-context.ts +++ b/src/lib/mina/transaction-context.ts @@ -1,4 +1,4 @@ -import type { AccountUpdate } from '../account_update.js'; +import type { AccountUpdate, AccountUpdateLayout } from '../account_update.js'; import type { PublicKey } from '../signature.js'; import { Context } from '../global-context.js'; @@ -8,6 +8,7 @@ type FetchMode = 'fetch' | 'cached' | 'test'; type CurrentTransaction = { sender?: PublicKey; accountUpdates: AccountUpdate[]; + layout: AccountUpdateLayout; fetchMode: FetchMode; isFinalRunOutsideCircuit: boolean; numberOfRuns: 0 | 1 | undefined; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index e7469fce42..1fbdae114c 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -17,6 +17,7 @@ import { CallForest, AccountUpdateForest, SmartContractContext, + AccountUpdateLayout, } from './account_update.js'; import { cloneCircuitValue, @@ -60,6 +61,7 @@ import { Cache } from './proof-system/cache.js'; import { assert } from './gadgets/common.js'; import { SmartContractBase } from './mina/smart-contract-base.js'; import { ZkappStateLength } from './mina/mina-instance.js'; +import { accountUpdates } from './mina/smart-contract-context.js'; // external API export { @@ -176,6 +178,8 @@ function wrapMethod( let txId = Mina.currentTransaction.enter({ sender: proverData?.transaction.feePayer.body.publicKey, accountUpdates: [], + // TODO could pass an update with the fee payer's content here? probably not bc it's not accessed + layout: new AccountUpdateLayout(), fetchMode: inProver() ? 'cached' : 'test', isFinalRunOutsideCircuit: false, numberOfRuns: undefined, From f3e9169b30ba5bf4f2299d3eba0bfd0a229b1066 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 19:47:21 +0100 Subject: [PATCH 31/69] match transaction.accountUpdates everywhere --- src/lib/account_update.ts | 61 ++++++++++++++++++++++++++++++++++++++- src/lib/mina.ts | 1 + src/lib/zkapp.ts | 27 +++++++++-------- 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 3684d17695..950817dac1 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1122,7 +1122,8 @@ class AccountUpdate implements Types.AccountUpdate { } > AccountUpdate.create()`; } else { currentTransaction()?.accountUpdates.push(accountUpdate); - accountUpdate.label = `Mina.transaction > AccountUpdate.create()`; + currentTransaction()?.layout.pushTopLevel(accountUpdate); + accountUpdate.label = `Mina.transaction() > AccountUpdate.create()`; } return accountUpdate; } @@ -1145,6 +1146,7 @@ class AccountUpdate implements Types.AccountUpdate { if (!updates.find((update) => update.id === accountUpdate.id)) { updates.push(accountUpdate); } + currentTransaction.get().layout.pushTopLevel(accountUpdate); } } /** @@ -1779,6 +1781,39 @@ const UnfinishedForest = { toPretty(forest); console.log(layout); }, + + toFlatList( + forest: UnfinishedForest, + mutate = true, + depth = 0 + ): AccountUpdate[] { + let flatUpdates: AccountUpdate[] = []; + for (let node of forest.value) { + if (node.isDummy.toBoolean()) continue; + let update = node.accountUpdate.value; + if (mutate) update.body.callDepth = depth; + let children = UnfinishedForest.toFlatList(node.calls, mutate, depth + 1); + flatUpdates.push(update, ...children); + } + return flatUpdates; + }, + + toConstantInPlace(forest: UnfinishedForest) { + for (let node of forest.value) { + // `as any` to override readonly - this method is explicit about its mutability + (node.accountUpdate as any).value = Provable.toConstant( + AccountUpdate, + node.accountUpdate.value + ); + if (node.accountUpdate.useHash) { + node.accountUpdate.hash = node.accountUpdate.hash.toConstant(); + } + UnfinishedForest.toConstantInPlace(node.calls); + } + if (forest.useHash) { + forest.hash = forest.hash.toConstant(); + } + }, }; function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { @@ -1858,6 +1893,11 @@ class AccountUpdateLayout { pushChild(parent: AccountUpdate | UnfinishedTree, child: AccountUpdate) { let parentNode = this.getOrCreate(parent); let childNode = this.getOrCreate(child); + if (childNode.siblings === parentNode.calls) return; + assert( + childNode.siblings === undefined, + 'AccountUpdateLayout.pushChild(): child already has another parent.' + ); childNode.siblings = parentNode.calls; parentNode.calls.value.push(childNode); } @@ -1903,6 +1943,25 @@ class AccountUpdateLayout { AccountUpdateForest.assertConstant(final); return final; } + + toFlatList({ mutate }: { mutate: boolean }) { + return UnfinishedForest.toFlatList(this.root.calls, mutate); + } + + forEachPredecessor( + update: AccountUpdate, + callback: (update: AccountUpdate) => void + ) { + let updates = this.toFlatList({ mutate: false }); + for (let otherUpdate of updates) { + if (otherUpdate.id === update.id) return; + callback(otherUpdate); + } + } + + toConstantInPlace() { + UnfinishedForest.toConstantInPlace(this.root.calls); + } } const CallForest = { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index e39c2b0dec..376f5ba7bd 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -208,6 +208,7 @@ function createTransaction( tx.accountUpdates = CallForest.map(tx.accountUpdates, (a) => toConstant(AccountUpdate, a) ); + tx.layout.toConstantInPlace(); }); }); } else { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 1fbdae114c..666d11785c 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -243,7 +243,8 @@ function wrapMethod( // called smart contract at the top level, in a transaction! // => attach ours to the current list of account updates let accountUpdate = context.selfUpdate; - Mina.currentTransaction()?.accountUpdates.push(accountUpdate); + Mina.currentTransaction.get().accountUpdates.push(accountUpdate); + Mina.currentTransaction.get().layout.pushTopLevel(accountUpdate); // first, clone to protect against the method modifying arguments! // TODO: double-check that this works on all possible inputs, e.g. CircuitValue, o1js primitives @@ -297,7 +298,7 @@ function wrapMethod( memoized, blindingValue, }, - Mina.currentTransaction()!.accountUpdates + Mina.currentTransaction.get().layout ); } return result; @@ -388,7 +389,7 @@ function wrapMethod( memoized, blindingValue: constantBlindingValue, }, - Mina.currentTransaction()!.accountUpdates + Mina.currentTransaction()?.layout ?? new AccountUpdateLayout() ); } // extract callee's account update layout @@ -1526,7 +1527,10 @@ const Reducer: (< ) as any; const ProofAuthorization = { - setKind({ body, id }: AccountUpdate, priorAccountUpdates?: AccountUpdate[]) { + setKind( + { body, id }: AccountUpdate, + priorAccountUpdates?: AccountUpdateLayout + ) { body.authorizationKind.isSigned = Bool(false); body.authorizationKind.isProved = Bool(true); let hash = Provable.witness(Field, () => { @@ -1534,17 +1538,16 @@ const ProofAuthorization = { let isProver = proverData !== undefined; assert( isProver || priorAccountUpdates !== undefined, - 'Called `setProofAuthorizationKind()` outside the prover without passing in `priorAccountUpdates`.' + 'Called `setKind()` outside the prover without passing in `priorAccountUpdates`.' ); let myAccountUpdateId = isProver ? proverData.accountUpdate.id : id; - priorAccountUpdates ??= proverData.transaction.accountUpdates; - priorAccountUpdates = priorAccountUpdates.filter( + let priorAccountUpdatesFlat = priorAccountUpdates?.toFlatList({ + mutate: false, + }); + priorAccountUpdatesFlat ??= proverData.transaction.accountUpdates; + priorAccountUpdatesFlat = priorAccountUpdatesFlat.filter( (a) => a.id !== myAccountUpdateId ); - let priorAccountUpdatesFlat = CallForest.toFlatList( - priorAccountUpdates, - false - ); let accountUpdate = [...priorAccountUpdatesFlat] .reverse() .find((body_) => @@ -1568,7 +1571,7 @@ const ProofAuthorization = { setLazyProof( accountUpdate: AccountUpdate, proof: Omit, - priorAccountUpdates: AccountUpdate[] + priorAccountUpdates: AccountUpdateLayout ) { this.setKind(accountUpdate, priorAccountUpdates); accountUpdate.authorization = {}; From f43dc3d3f634a8ea53ab9d291d416ba48e1b89dd Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 20:20:35 +0100 Subject: [PATCH 32/69] throw error if layout doesn't match list length --- src/lib/account_update.ts | 1 + src/lib/mina.ts | 17 ++++++++++++++++- src/lib/zkapp.ts | 14 +++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 950817dac1..f663ebc1f9 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1805,6 +1805,7 @@ const UnfinishedForest = { AccountUpdate, node.accountUpdate.value ); + node.isDummy = Provable.toConstant(Bool, node.isDummy); if (node.accountUpdate.useHash) { node.accountUpdate.hash = node.accountUpdate.hash.toConstant(); } diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 376f5ba7bd..6d6c8c4d09 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -208,7 +208,6 @@ function createTransaction( tx.accountUpdates = CallForest.map(tx.accountUpdates, (a) => toConstant(AccountUpdate, a) ); - tx.layout.toConstantInPlace(); }); }); } else { @@ -229,6 +228,22 @@ function createTransaction( // CallForest.addCallers(accountUpdates); accountUpdates = CallForest.toFlatList(accountUpdates); + let otherAccountUpdates = currentTransaction + .get() + .layout.toFlatList({ mutate: true }); + + if (otherAccountUpdates.length !== accountUpdates.length) { + console.log( + 'expected', + accountUpdates.map((a) => a.toPretty()) + ); + console.log( + 'actual ', + otherAccountUpdates.map((a) => a.toPretty()) + ); + throw Error('mismatch'); + } + try { // check that on-chain values weren't used without setting a precondition for (let accountUpdate of accountUpdates) { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 666d11785c..b5d7f661d6 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -53,6 +53,7 @@ import { PrivateKey, PublicKey } from './signature.js'; import { assertStatePrecondition, cleanStatePrecondition } from './state.js'; import { inAnalyze, + inCheckedComputation, inCompile, inProver, snarkContext, @@ -244,7 +245,6 @@ function wrapMethod( // => attach ours to the current list of account updates let accountUpdate = context.selfUpdate; Mina.currentTransaction.get().accountUpdates.push(accountUpdate); - Mina.currentTransaction.get().layout.pushTopLevel(accountUpdate); // first, clone to protect against the method modifying arguments! // TODO: double-check that this works on all possible inputs, e.g. CircuitValue, o1js primitives @@ -301,6 +301,18 @@ function wrapMethod( Mina.currentTransaction.get().layout ); } + + // transfer layout from the smart contract context to the transaction + if (inCheckedComputation()) { + Provable.asProver(() => { + accountUpdate = Provable.toConstant(AccountUpdate, accountUpdate); + context.selfLayout.toConstantInPlace(); + }); + } + let txLayout = Mina.currentTransaction.get().layout; + txLayout.pushTopLevel(accountUpdate); + txLayout.setChildren(accountUpdate, context.selfLayout.root.calls); + return result; } } finally { From 6da02d72348d550dd79178a3ea157497e0cc4bd7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 20:46:52 +0100 Subject: [PATCH 33/69] remove nested account updates from tx context --- src/lib/account_update.ts | 34 +++++++---------------------- src/lib/mina.ts | 23 ++----------------- src/lib/mina/transaction-context.ts | 1 - src/lib/zkapp.ts | 2 -- 4 files changed, 10 insertions(+), 50 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index f663ebc1f9..5ab281e77d 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1003,17 +1003,14 @@ class AccountUpdate implements Types.AccountUpdate { if (isSameAsFeePayer) nonce++; // now, we check how often this account update already updated its nonce in // this tx, and increase nonce from `getAccount` by that amount - CallForest.forEachPredecessor( - currentTransaction.get().accountUpdates, - update as AccountUpdate, - (otherUpdate) => { - let shouldIncreaseNonce = otherUpdate.publicKey - .equals(publicKey) - .and(otherUpdate.tokenId.equals(tokenId)) - .and(otherUpdate.body.incrementNonce); - if (shouldIncreaseNonce.toBoolean()) nonce++; - } - ); + let layout = currentTransaction.get().layout; + layout.forEachPredecessor(update as AccountUpdate, (otherUpdate) => { + let shouldIncreaseNonce = otherUpdate.publicKey + .equals(publicKey) + .and(otherUpdate.tokenId.equals(tokenId)) + .and(otherUpdate.body.incrementNonce); + if (shouldIncreaseNonce.toBoolean()) nonce++; + }); return { nonce: UInt32.from(nonce), isSameAsFeePayer: Bool(isSameAsFeePayer), @@ -1121,7 +1118,6 @@ class AccountUpdate implements Types.AccountUpdate { self.label || 'Unlabeled' } > AccountUpdate.create()`; } else { - currentTransaction()?.accountUpdates.push(accountUpdate); currentTransaction()?.layout.pushTopLevel(accountUpdate); accountUpdate.label = `Mina.transaction() > AccountUpdate.create()`; } @@ -1142,10 +1138,6 @@ class AccountUpdate implements Types.AccountUpdate { insideContract.this.self.approve(accountUpdate); } else { if (!currentTransaction.has()) return; - let updates = currentTransaction.get().accountUpdates; - if (!updates.find((update) => update.id === accountUpdate.id)) { - updates.push(accountUpdate); - } currentTransaction.get().layout.pushTopLevel(accountUpdate); } } @@ -1153,17 +1145,7 @@ class AccountUpdate implements Types.AccountUpdate { * Disattach an account update from where it's currently located in the transaction */ static unlink(accountUpdate: AccountUpdate) { - // TODO duplicate logic accountUpdates()?.disattach(accountUpdate); - let siblings = - accountUpdate.parent?.children.accountUpdates ?? - currentTransaction()?.accountUpdates; - if (siblings === undefined) return; - let i = siblings?.findIndex((update) => update.id === accountUpdate.id); - if (i !== undefined && i !== -1) { - siblings!.splice(i, 1); - } - accountUpdate.parent === undefined; } /** diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 6d6c8c4d09..9b1386e806 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -182,7 +182,6 @@ function createTransaction( let transactionId = currentTransaction.enter({ sender, - accountUpdates: [], layout: new AccountUpdateLayout(), fetchMode, isFinalRunOutsideCircuit, @@ -205,9 +204,7 @@ function createTransaction( f(); Provable.asProver(() => { let tx = currentTransaction.get(); - tx.accountUpdates = CallForest.map(tx.accountUpdates, (a) => - toConstant(AccountUpdate, a) - ); + tx.layout.toConstantInPlace(); }); }); } else { @@ -223,27 +220,11 @@ function createTransaction( currentTransaction.leave(transactionId); throw err; } - let accountUpdates = currentTransaction.get().accountUpdates; - // TODO: I'll be back - // CallForest.addCallers(accountUpdates); - accountUpdates = CallForest.toFlatList(accountUpdates); - let otherAccountUpdates = currentTransaction + let accountUpdates = currentTransaction .get() .layout.toFlatList({ mutate: true }); - if (otherAccountUpdates.length !== accountUpdates.length) { - console.log( - 'expected', - accountUpdates.map((a) => a.toPretty()) - ); - console.log( - 'actual ', - otherAccountUpdates.map((a) => a.toPretty()) - ); - throw Error('mismatch'); - } - try { // check that on-chain values weren't used without setting a precondition for (let accountUpdate of accountUpdates) { diff --git a/src/lib/mina/transaction-context.ts b/src/lib/mina/transaction-context.ts index e007079522..47c96a04fa 100644 --- a/src/lib/mina/transaction-context.ts +++ b/src/lib/mina/transaction-context.ts @@ -7,7 +7,6 @@ export { currentTransaction, CurrentTransaction, FetchMode }; type FetchMode = 'fetch' | 'cached' | 'test'; type CurrentTransaction = { sender?: PublicKey; - accountUpdates: AccountUpdate[]; layout: AccountUpdateLayout; fetchMode: FetchMode; isFinalRunOutsideCircuit: boolean; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index b5d7f661d6..d0f88d7875 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -178,7 +178,6 @@ function wrapMethod( let proverData = inProver() ? zkAppProver.getData() : undefined; let txId = Mina.currentTransaction.enter({ sender: proverData?.transaction.feePayer.body.publicKey, - accountUpdates: [], // TODO could pass an update with the fee payer's content here? probably not bc it's not accessed layout: new AccountUpdateLayout(), fetchMode: inProver() ? 'cached' : 'test', @@ -244,7 +243,6 @@ function wrapMethod( // called smart contract at the top level, in a transaction! // => attach ours to the current list of account updates let accountUpdate = context.selfUpdate; - Mina.currentTransaction.get().accountUpdates.push(accountUpdate); // first, clone to protect against the method modifying arguments! // TODO: double-check that this works on all possible inputs, e.g. CircuitValue, o1js primitives From 5c138eebc7d17e89776074794c6ede180aad4cc7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 20:56:03 +0100 Subject: [PATCH 34/69] put back some code that was premature to remove --- src/lib/account_update.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 5ab281e77d..2c14519443 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1146,6 +1146,14 @@ class AccountUpdate implements Types.AccountUpdate { */ static unlink(accountUpdate: AccountUpdate) { accountUpdates()?.disattach(accountUpdate); + + let siblings = accountUpdate.parent?.children.accountUpdates; + if (siblings === undefined) return; + let i = siblings?.findIndex((update) => update.id === accountUpdate.id); + if (i !== undefined && i !== -1) { + siblings!.splice(i, 1); + } + accountUpdate.parent === undefined; } /** From f96e6a9be2b4d0e982b08efe1364c8ad8b726694 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 21:10:44 +0100 Subject: [PATCH 35/69] [no children] remove unnecessary logic in token contract --- src/lib/mina/token/token-contract.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 4617e011c5..de46d0c4bc 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -148,13 +148,7 @@ function finalizeAccountUpdates(updates: AccountUpdate[]): AccountUpdateForest { function finalizeAccountUpdate(update: AccountUpdate): AccountUpdateTree { let calls = accountUpdates()?.finalizeAndRemove(update); - - // TODO remove once everything lives in `selfLayout` - AccountUpdate.unlink(update); - - calls ??= AccountUpdateForest.fromArray(update.children.accountUpdates, { - skipDummies: true, - }); + calls ??= AccountUpdateForest.empty(); return { accountUpdate: HashedAccountUpdate.hash(update), calls }; } From 030646e4197894eab65462207486779c9d6ea5a9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 22:08:42 +0100 Subject: [PATCH 36/69] implement to public input w/o children --- src/lib/account_update.ts | 31 ++++++++++++++++++++--- src/lib/account_update.unit-test.ts | 7 +++-- src/lib/mina.ts | 4 ++- src/mina-signer/src/sign-zkapp-command.ts | 23 ++++++++++++++--- 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 2c14519443..1a8061c140 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -52,12 +52,14 @@ import { MlFieldConstArray } from './ml/fields.js'; import { accountUpdatesToCallForest, CallForest, + callForestHashGeneric, transactionCommitments, } from '../mina-signer/src/sign-zkapp-command.js'; import { currentTransaction } from './mina/transaction-context.js'; import { isSmartContract } from './mina/smart-contract-base.js'; import { activeInstance } from './mina/mina-instance.js'; import { + emptyHash, genericHash, MerkleList, MerkleListBase, @@ -1045,9 +1047,32 @@ class AccountUpdate implements Types.AccountUpdate { } } - toPublicInput(): ZkappPublicInput { + toPublicInput({ + accountUpdates, + }: { + accountUpdates: AccountUpdate[]; + }): ZkappPublicInput { let accountUpdate = this.hash(); - let calls = CallForest.hashChildren(this); + + // collect this update's descendants + let descendants: AccountUpdate[] = []; + let callDepth = this.body.callDepth; + let i = accountUpdates.findIndex((a) => a.id === this.id); + assert(i !== -1, 'Account update not found in transaction'); + for (i++; i < accountUpdates.length; i++) { + let update = accountUpdates[i]; + if (update.body.callDepth <= callDepth) break; + descendants.push(update); + } + + // call forest hash + let forest = accountUpdatesToCallForest(descendants, callDepth + 1); + let calls = callForestHashGeneric( + forest, + (a) => a.hash(), + Poseidon.hashWithPrefix, + emptyHash + ); return { accountUpdate, calls }; } @@ -2350,7 +2375,7 @@ async function createZkappProof( }: LazyProof, { transaction, accountUpdate, index }: ZkappProverData ): Promise> { - let publicInput = accountUpdate.toPublicInput(); + let publicInput = accountUpdate.toPublicInput(transaction); let publicInputFields = MlFieldConstArray.to( ZkappPublicInput.toFields(publicInput) ); diff --git a/src/lib/account_update.unit-test.ts b/src/lib/account_update.unit-test.ts index a280b22712..845cd25a48 100644 --- a/src/lib/account_update.unit-test.ts +++ b/src/lib/account_update.unit-test.ts @@ -71,9 +71,12 @@ function createAccountUpdate() { let otherAddress = PrivateKey.random().toPublicKey(); let accountUpdate = AccountUpdate.create(address); - accountUpdate.approve(AccountUpdate.create(otherAddress)); + let otherUpdate = AccountUpdate.create(otherAddress); + accountUpdate.approve(otherUpdate); - let publicInput = accountUpdate.toPublicInput(); + let publicInput = accountUpdate.toPublicInput({ + accountUpdates: [accountUpdate, otherUpdate], + }); // create transaction JSON with the same accountUpdate structure, for ocaml version let tx = await Mina.transaction(() => { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 9b1386e806..b00075b40f 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -427,9 +427,11 @@ function LocalBlockchain({ // TODO: verify account update even if the account doesn't exist yet, using a default initial account if (account !== undefined) { + let publicInput = update.toPublicInput(txn.transaction); await verifyAccountUpdate( account, update, + publicInput, commitments, this.proofsEnabled, this.getNetworkId() @@ -1162,6 +1164,7 @@ function defaultNetworkState(): NetworkValue { async function verifyAccountUpdate( account: Account, accountUpdate: AccountUpdate, + publicInput: ZkappPublicInput, transactionCommitments: { commitment: bigint; fullCommitment: bigint }, proofsEnabled: boolean, networkId: NetworkId @@ -1243,7 +1246,6 @@ async function verifyAccountUpdate( if (accountUpdate.authorization.proof && proofsEnabled) { try { - let publicInput = accountUpdate.toPublicInput(); let publicInputFields = ZkappPublicInput.toFields(publicInput); let proof: JsonProof = { diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 004a3f0940..0dc1f79a95 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -28,6 +28,7 @@ export { verifyAccountUpdateSignature, accountUpdatesToCallForest, callForestHash, + callForestHashGeneric, accountUpdateHash, feePayerHash, createFeePayer, @@ -156,11 +157,25 @@ function accountUpdateHash(update: AccountUpdate) { return hashWithPrefix(prefixes.body, fields); } -function callForestHash(forest: CallForest): Field { - let stackHash = 0n; +function callForestHash(forest: CallForest): bigint { + return callForestHashGeneric(forest, accountUpdateHash, hashWithPrefix, 0n); +} + +function callForestHashGeneric( + forest: CallForest, + hash: (a: A) => F, + hashWithPrefix: (prefix: string, input: F[]) => F, + emptyHash: F +): F { + let stackHash = emptyHash; for (let callTree of [...forest].reverse()) { - let calls = callForestHash(callTree.children); - let treeHash = accountUpdateHash(callTree.accountUpdate); + let calls = callForestHashGeneric( + callTree.children, + hash, + hashWithPrefix, + emptyHash + ); + let treeHash = hash(callTree.accountUpdate); let nodeHash = hashWithPrefix(prefixes.accountUpdateNode, [ treeHash, calls, From 09922c05b24463e8a51165d80fdedf21605e3da9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 22:13:57 +0100 Subject: [PATCH 37/69] [no children] refactor recursive diff --- src/lib/zkapp.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index d0f88d7875..5994ff93e1 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1620,10 +1620,12 @@ function diffRecursive( ) { let { transaction, index, accountUpdate: input } = inputData; diff(transaction, index, prover.toPretty(), input.toPretty()); - let nChildren = input.children.accountUpdates.length; + let inputChildren = accountUpdates()!.get(input)!.calls.value; + let proverChildren = accountUpdates()!.get(prover)!.calls.value; + let nChildren = inputChildren.length; for (let i = 0; i < nChildren; i++) { - let inputChild = input.children.accountUpdates[i]; - let child = prover.children.accountUpdates[i]; + let inputChild = inputChildren[i].accountUpdate.value; + let child = proverChildren[i].accountUpdate.value; if (!inputChild || !child) return; diffRecursive(child, { transaction, index, accountUpdate: inputChild }); } From 1cbaa487651e6a3cb3781e0597aede793b36efb3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 22:25:22 +0100 Subject: [PATCH 38/69] remove AccountUpdate.children --- src/examples/zkapps/token_with_proofs.ts | 6 +- src/lib/account_update.ts | 286 +----------------- .../mina/token/token-contract.unit-test.ts | 7 +- src/lib/zkapp.ts | 16 +- 4 files changed, 13 insertions(+), 302 deletions(-) diff --git a/src/examples/zkapps/token_with_proofs.ts b/src/examples/zkapps/token_with_proofs.ts index b685c8610c..17c3d1bee0 100644 --- a/src/examples/zkapps/token_with_proofs.ts +++ b/src/examples/zkapps/token_with_proofs.ts @@ -56,10 +56,8 @@ class TokenContract extends SmartContract { receiverAddress: PublicKey, callback: Experimental.Callback ) { - let senderAccountUpdate = this.approve( - callback, - AccountUpdate.Layout.AnyChildren - ); + // TODO use token contract methods for approve + let senderAccountUpdate = this.approve(callback); let amount = UInt64.from(1_000); let negativeAmount = Int64.fromObject( senderAccountUpdate.body.balanceChange diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 1a8061c140..4044bb0723 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -616,18 +616,6 @@ class AccountUpdate implements Types.AccountUpdate { account: Account; network: Network; currentSlot: CurrentSlot; - children: { - callsType: - | { type: 'None' } - | { type: 'Witness' } - | { type: 'Equals'; value: Field } - | { type: 'WitnessEquals'; value: Field }; - accountUpdates: AccountUpdate[]; - } = { - callsType: { type: 'None' }, - accountUpdates: [], - }; - parent: AccountUpdate | undefined = undefined; private isSelf: boolean; @@ -657,13 +645,8 @@ class AccountUpdate implements Types.AccountUpdate { accountUpdate.isSelf ); cloned.lazyAuthorization = accountUpdate.lazyAuthorization; - cloned.children.callsType = accountUpdate.children.callsType; - cloned.children.accountUpdates = accountUpdate.children.accountUpdates.map( - AccountUpdate.clone - ); cloned.id = accountUpdate.id; cloned.label = accountUpdate.label; - cloned.parent = accountUpdate.parent; return cloned; } @@ -812,12 +795,8 @@ class AccountUpdate implements Types.AccountUpdate { /** * Makes an {@link AccountUpdate} a child of this and approves it. */ - approve( - childUpdate: AccountUpdate, - layout: AccountUpdatesLayout = AccountUpdate.Layout.NoChildren - ) { + approve(childUpdate: AccountUpdate) { makeChildAccountUpdate(this, childUpdate); - AccountUpdate.witnessChildren(childUpdate, layout, { skipCheck: true }); accountUpdates()?.pushChild(this, childUpdate); } @@ -1077,27 +1056,9 @@ class AccountUpdate implements Types.AccountUpdate { } toPrettyLayout() { - let indent = 0; - let layout = ''; - let i = 0; - - let print = (a: AccountUpdate) => { - layout += - ' '.repeat(indent) + - `AccountUpdate(${i}, ${a.label || ''}, ${ - a.children.callsType.type - })` + - '\n'; - i++; - indent += 2; - for (let child of a.children.accountUpdates) { - print(child); - } - indent -= 2; - }; - - print(this); - return layout; + let node = accountUpdates()?.get(this); + assert(node !== undefined, 'AccountUpdate not found in layout'); + UnfinishedForest.print(node.calls); } static defaultAccountUpdate(address: PublicKey, tokenId?: Field) { @@ -1171,14 +1132,6 @@ class AccountUpdate implements Types.AccountUpdate { */ static unlink(accountUpdate: AccountUpdate) { accountUpdates()?.disattach(accountUpdate); - - let siblings = accountUpdate.parent?.children.accountUpdates; - if (siblings === undefined) return; - let i = siblings?.findIndex((update) => update.id === accountUpdate.id); - if (i !== undefined && i !== -1) { - siblings!.splice(i, 1); - } - accountUpdate.parent === undefined; } /** @@ -1263,21 +1216,10 @@ class AccountUpdate implements Types.AccountUpdate { static toFields = Types.AccountUpdate.toFields; static toAuxiliary(a?: AccountUpdate) { let aux = Types.AccountUpdate.toAuxiliary(a); - let children: AccountUpdate['children'] = { - callsType: { type: 'None' }, - accountUpdates: [], - }; let lazyAuthorization = a && a.lazyAuthorization; - if (a) { - children.callsType = a.children.callsType; - children.accountUpdates = a.children.accountUpdates.map( - AccountUpdate.clone - ); - } - let parent = a?.parent; let id = a?.id ?? Math.random(); let label = a?.label ?? ''; - return [{ lazyAuthorization, children, parent, id, label }, aux]; + return [{ lazyAuthorization, id, label }, aux]; } static toInput = Types.AccountUpdate.toInput; static empty() { @@ -1308,72 +1250,6 @@ class AccountUpdate implements Types.AccountUpdate { return Provable.witness(combinedType, compute); } - static witnessChildren( - accountUpdate: AccountUpdate, - childLayout: AccountUpdatesLayout, - options?: { skipCheck: boolean } - ) { - // just witness children's hash if childLayout === null - if (childLayout === AccountUpdate.Layout.AnyChildren) { - accountUpdate.children.callsType = { type: 'Witness' }; - return; - } - if (childLayout === AccountUpdate.Layout.NoDelegation) { - accountUpdate.children.callsType = { type: 'Witness' }; - accountUpdate.body.mayUseToken.parentsOwnToken.assertFalse(); - accountUpdate.body.mayUseToken.inheritFromParent.assertFalse(); - return; - } - accountUpdate.children.callsType = { type: 'None' }; - let childArray: AccountUpdatesLayout[] = - typeof childLayout === 'number' - ? Array(childLayout).fill(AccountUpdate.Layout.NoChildren) - : childLayout; - let n = childArray.length; - for (let i = 0; i < n; i++) { - accountUpdate.children.accountUpdates[i] = AccountUpdate.witnessTree( - provable(null), - childArray[i], - () => ({ - accountUpdate: - accountUpdate.children.accountUpdates[i] ?? AccountUpdate.dummy(), - result: null, - }), - options - ).accountUpdate; - } - if (n === 0) { - accountUpdate.children.callsType = { - type: 'Equals', - value: CallForest.emptyHash(), - }; - } - } - - /** - * Like AccountUpdate.witness, but lets you specify a layout for the - * accountUpdate's children, which also get witnessed - */ - static witnessTree( - resultType: FlexibleProvable, - childLayout: AccountUpdatesLayout, - compute: () => { - accountUpdate: AccountUpdate; - result: T; - }, - options?: { skipCheck: boolean } - ) { - // witness the root accountUpdate - let { accountUpdate, result } = AccountUpdate.witness( - resultType, - compute, - options - ); - // witness child account updates - AccountUpdate.witnessChildren(accountUpdate, childLayout, options); - return { accountUpdate, result }; - } - /** * Describes the children of an account update, which are laid out in a tree. * @@ -1597,37 +1473,6 @@ class AccountUpdateForest extends MerkleList.create( return AccountUpdateForest.from(nodes); } - // TODO remove - static fromArray( - updates: AccountUpdate[], - { skipDummies = false } = {} - ): AccountUpdateForest { - if (skipDummies) return AccountUpdateForest.fromArraySkipDummies(updates); - - let nodes = updates.map((update) => { - let accountUpdate = HashedAccountUpdate.hash(update); - let calls = AccountUpdateForest.fromArray(update.children.accountUpdates); - return { accountUpdate, calls }; - }); - return AccountUpdateForest.from(nodes); - } - - private static fromArraySkipDummies( - updates: AccountUpdate[] - ): AccountUpdateForest { - let forest = AccountUpdateForest.empty(); - - for (let update of [...updates].reverse()) { - let accountUpdate = HashedAccountUpdate.hash(update); - let calls = AccountUpdateForest.fromArraySkipDummies( - update.children.accountUpdates - ); - forest.pushIf(update.isDummy().not(), { accountUpdate, calls }); - } - - return forest; - } - // TODO this comes from paranoia and might be removed later static assertConstant(forest: MerkleListBase) { Provable.asProver(() => { @@ -1980,116 +1825,6 @@ class AccountUpdateLayout { } } -const CallForest = { - // similar to Mina_base.ZkappCommand.Call_forest.to_account_updates_list - // takes a list of accountUpdates, which each can have children, so they form a "forest" (list of trees) - // returns a flattened list, with `accountUpdate.body.callDepth` specifying positions in the forest - // also removes any "dummy" accountUpdates - toFlatList( - forest: AccountUpdate[], - mutate = true, - depth = 0 - ): AccountUpdate[] { - let accountUpdates = []; - for (let accountUpdate of forest) { - if (accountUpdate.isDummy().toBoolean()) continue; - if (mutate) accountUpdate.body.callDepth = depth; - let children = accountUpdate.children.accountUpdates; - accountUpdates.push( - accountUpdate, - ...CallForest.toFlatList(children, mutate, depth + 1) - ); - } - return accountUpdates; - }, - - // Mina_base.Zkapp_command.Digest.Forest.empty - emptyHash() { - return Field(0); - }, - - // similar to Mina_base.Zkapp_command.Call_forest.accumulate_hashes - // hashes a accountUpdate's children (and their children, and ...) to compute - // the `calls` field of ZkappPublicInput - hashChildren(update: AccountUpdate): Field { - if (!Provable.inCheckedComputation()) { - return CallForest.hashChildrenBase(update); - } - - let { callsType } = update.children; - // compute hash outside the circuit if callsType is "Witness" - // i.e., allowing accountUpdates with arbitrary children - if (callsType.type === 'Witness') { - return Provable.witness(Field, () => CallForest.hashChildrenBase(update)); - } - if (callsType.type === 'WitnessEquals') { - return callsType.value; - } - let calls = CallForest.hashChildrenBase(update); - if (callsType.type === 'Equals') { - calls.assertEquals(callsType.value); - } - return calls; - }, - - hashChildrenBase({ children }: AccountUpdate) { - let stackHash = CallForest.emptyHash(); - for (let accountUpdate of [...children.accountUpdates].reverse()) { - let calls = CallForest.hashChildren(accountUpdate); - let nodeHash = hashWithPrefix(prefixes.accountUpdateNode, [ - accountUpdate.hash(), - calls, - ]); - let newHash = hashWithPrefix(prefixes.accountUpdateCons, [ - nodeHash, - stackHash, - ]); - // skip accountUpdate if it's a dummy - stackHash = Provable.if(accountUpdate.isDummy(), stackHash, newHash); - } - return stackHash; - }, - - computeCallDepth(update: AccountUpdate) { - for (let callDepth = 0; ; callDepth++) { - if (update.parent === undefined) return callDepth; - update = update.parent; - } - }, - - map(updates: AccountUpdate[], map: (update: AccountUpdate) => AccountUpdate) { - let newUpdates: AccountUpdate[] = []; - for (let update of updates) { - let newUpdate = map(update); - newUpdate.children.accountUpdates = CallForest.map( - update.children.accountUpdates, - map - ); - newUpdates.push(newUpdate); - } - return newUpdates; - }, - - forEach(updates: AccountUpdate[], callback: (update: AccountUpdate) => void) { - for (let update of updates) { - callback(update); - CallForest.forEach(update.children.accountUpdates, callback); - } - }, - - forEachPredecessor( - updates: AccountUpdate[], - update: AccountUpdate, - callback: (update: AccountUpdate) => void - ) { - let isPredecessor = true; - CallForest.forEach(updates, (otherUpdate) => { - if (otherUpdate.id === update.id) isPredecessor = false; - if (isPredecessor) callback(otherUpdate); - }); - }, -}; - function createChildAccountUpdate( parent: AccountUpdate, childAddress: PublicKey, @@ -2101,16 +1836,7 @@ function createChildAccountUpdate( } function makeChildAccountUpdate(parent: AccountUpdate, child: AccountUpdate) { child.body.callDepth = parent.body.callDepth + 1; - let wasChildAlready = parent.children.accountUpdates.find( - (update) => update.id === child.id - ); - // add to our children if not already here - if (!wasChildAlready) { - parent.children.accountUpdates.push(child); - // remove the child from the top level list / its current parent - AccountUpdate.unlink(child); - } - child.parent = parent; + AccountUpdate.unlink(child); } // authorization diff --git a/src/lib/mina/token/token-contract.unit-test.ts b/src/lib/mina/token/token-contract.unit-test.ts index b49d6b8190..2c9c059946 100644 --- a/src/lib/mina/token/token-contract.unit-test.ts +++ b/src/lib/mina/token/token-contract.unit-test.ts @@ -72,15 +72,14 @@ update1.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken; let update2 = AccountUpdate.create(otherAddress); update2.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; +update2.body.callDepth = 1; let update3 = AccountUpdate.create(otherAddress, tokenId); update3.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; update3.balanceChange = Int64.one; +update3.body.callDepth = 2; -update1.adopt(update2); -update2.adopt(update3); - -let forest = AccountUpdateForest.fromArray([update1]); +let forest = AccountUpdateForest.fromFlatArray([update1, update2, update3]); await assert.rejects( () => Mina.transaction(sender, () => token.approveBase(forest)), diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 5994ff93e1..0826d944aa 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -351,7 +351,6 @@ function wrapMethod( let constantBlindingValue = blindingValue.toConstant(); let accountUpdate = this.self; accountUpdate.body.callDepth = parentAccountUpdate.body.callDepth + 1; - accountUpdate.parent = parentAccountUpdate; let memoContext = { memoized: [], @@ -431,11 +430,6 @@ function wrapMethod( // connect accountUpdate to our own. outside Provable.witness so compile knows the right structure when hashing children accountUpdate.body.callDepth = parentAccountUpdate.body.callDepth + 1; - accountUpdate.parent = parentAccountUpdate; - // beware: we don't include the callee's children in the caller circuit - // nothing is asserted about them -- it's the callee's task to check their children - accountUpdate.children.callsType = { type: 'Witness' }; - parentAccountUpdate.children.accountUpdates.push(accountUpdate); insideContract.selfLayout.pushTopLevel(accountUpdate); insideContract.selfLayout.setChildren(accountUpdate, children); @@ -928,22 +922,16 @@ super.init(); * * Under the hood, "approving" just means that the account update is made a child of the zkApp in the * tree of account updates that forms the transaction. - * The second parameter `layout` allows you to also make assertions about the approved update's _own_ children, - * by specifying a certain expected layout of children. See {@link AccountUpdate.Layout}. * * @param updateOrCallback - * @param layout * @returns The account update that was approved (needed when passing in a Callback) */ - approve( - updateOrCallback: AccountUpdate | Callback, - layout?: AccountUpdatesLayout - ) { + approve(updateOrCallback: AccountUpdate | Callback) { let accountUpdate = updateOrCallback instanceof AccountUpdate ? updateOrCallback : Provable.witness(AccountUpdate, () => updateOrCallback.accountUpdate); - this.self.approve(accountUpdate, layout); + this.self.approve(accountUpdate); return accountUpdate; } From e3a66d61b29eb64847e3caf0540df6ddcfbc707e Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 22:32:10 +0100 Subject: [PATCH 39/69] some cleanup --- src/lib/account_update.ts | 46 ----------------------------- src/lib/mina/transaction-context.ts | 2 +- src/lib/zkapp.ts | 2 -- 3 files changed, 1 insertion(+), 49 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 4044bb0723..9860a2619b 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -97,7 +97,6 @@ export { Token, CallForest, createChildAccountUpdate, - AccountUpdatesLayout, zkAppProver, dummySignature, LazyProof, @@ -1250,45 +1249,6 @@ class AccountUpdate implements Types.AccountUpdate { return Provable.witness(combinedType, compute); } - /** - * Describes the children of an account update, which are laid out in a tree. - * - * The tree layout is described recursively by using a combination of `AccountUpdate.Layout.NoChildren`, `AccountUpdate.Layout.StaticChildren(...)` and `AccountUpdate.Layout.AnyChildren`. - * - `NoChildren` means an account update that can't have children - * - `AnyChildren` means an account update can have an arbitrary amount of children, which means you can't access those children in your circuit (because the circuit is static). - * - `StaticChildren` means the account update must have a certain static amount of children and expects as arguments a description of each of those children. - * As a shortcut, you can also pass `StaticChildren` a number, which means it has that amount of children but no grandchildren. - * - * This is best understood by examples: - * - * ```ts - * let { NoChildren, AnyChildren, StaticChildren } = AccounUpdate.Layout; - * - * NoChildren // an account update with no children - * AnyChildren // an account update with arbitrary children - * StaticChildren(NoChildren) // an account update with 1 child, which doesn't have children itself - * StaticChildren(1) // shortcut for StaticChildren(NoChildren) - * StaticChildren(2) // shortcut for StaticChildren(NoChildren, NoChildren) - * StaticChildren(0) // equivalent to NoChildren - * - * // an update with 2 children, of which one has arbitrary children and the other has exactly 1 descendant - * StaticChildren(AnyChildren, StaticChildren(1)) - * ``` - */ - static Layout = { - StaticChildren: ((...args: any[]) => { - if (args.length === 1 && typeof args[0] === 'number') return args[0]; - if (args.length === 0) return 0; - return args; - }) as { - (n: number): AccountUpdatesLayout; - (...args: AccountUpdatesLayout[]): AccountUpdatesLayout; - }, - NoChildren: 0, - AnyChildren: 'AnyChildren' as const, - NoDelegation: 'NoDelegation' as const, - }; - static get MayUseToken() { return { type: provablePure({ parentsOwnToken: Bool, inheritFromParent: Bool }), @@ -1414,12 +1374,6 @@ class AccountUpdate implements Types.AccountUpdate { } } -type AccountUpdatesLayout = - | number - | 'AnyChildren' - | 'NoDelegation' - | AccountUpdatesLayout[]; - // call forest stuff function hashAccountUpdate(update: AccountUpdate) { diff --git a/src/lib/mina/transaction-context.ts b/src/lib/mina/transaction-context.ts index 47c96a04fa..d981384085 100644 --- a/src/lib/mina/transaction-context.ts +++ b/src/lib/mina/transaction-context.ts @@ -1,4 +1,4 @@ -import type { AccountUpdate, AccountUpdateLayout } from '../account_update.js'; +import type { AccountUpdateLayout } from '../account_update.js'; import type { PublicKey } from '../signature.js'; import { Context } from '../global-context.js'; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 0826d944aa..57d841bd96 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -2,7 +2,6 @@ import { Gate, Pickles, ProvablePure } from '../snarky.js'; import { Field, Bool } from './core.js'; import { AccountUpdate, - AccountUpdatesLayout, Authorization, Body, Events, @@ -14,7 +13,6 @@ import { zkAppProver, ZkappPublicInput, LazyProof, - CallForest, AccountUpdateForest, SmartContractContext, AccountUpdateLayout, From bfa8fc94fc1ddd771f01f856b4ef66c18ebfe9dd Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 7 Feb 2024 22:33:47 +0100 Subject: [PATCH 40/69] remove obsolete method --- src/lib/account_update.ts | 14 +++----------- src/lib/mina/token/token-contract.ts | 2 +- src/lib/token.test.ts | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 9860a2619b..e2185e6166 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -664,11 +664,11 @@ class AccountUpdate implements Types.AccountUpdate { } if (accountLike instanceof AccountUpdate) { accountLike.tokenId.assertEquals(id); - thisAccountUpdate.adopt(accountLike); + thisAccountUpdate.approve(accountLike); } if (accountLike instanceof PublicKey) { accountLike = AccountUpdate.defaultAccountUpdate(accountLike, id); - thisAccountUpdate.adopt(accountLike); + thisAccountUpdate.approve(accountLike); } if (!accountLike.label) accountLike.label = `${ @@ -777,7 +777,7 @@ class AccountUpdate implements Types.AccountUpdate { } else { receiver = AccountUpdate.defaultAccountUpdate(to, this.body.tokenId); receiver.label = `${this.label ?? 'Unlabeled'}.send()`; - this.adopt(receiver); + this.approve(receiver); } // Sub the amount from the sender's account @@ -799,14 +799,6 @@ class AccountUpdate implements Types.AccountUpdate { accountUpdates()?.pushChild(this, childUpdate); } - /** - * Makes an {@link AccountUpdate} a child of this. - */ - adopt(childUpdate: AccountUpdate) { - makeChildAccountUpdate(this, childUpdate); - accountUpdates()?.pushChild(this, childUpdate); - } - get balance() { let accountUpdate = this; diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index de46d0c4bc..210b3d1a86 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -68,7 +68,7 @@ abstract class TokenContract extends SmartContract { Provable.asProver(() => { updates.data.get().forEach((update) => { let accountUpdate = update.element.accountUpdate.value.get(); - this.self.adopt(accountUpdate); + this.approve(accountUpdate); }); }); diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 961d20770e..e6c2ee1f5b 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -89,7 +89,7 @@ class TokenContract extends SmartContract { amount: UInt64, senderAccountUpdate: AccountUpdate ) { - this.self.adopt(senderAccountUpdate); + this.approve(senderAccountUpdate); let negativeAmount = senderAccountUpdate.balanceChange; negativeAmount.assertEquals(Int64.from(amount).neg()); let tokenId = this.token.id; From 202116e1078a3d7a85657fbf8f689fd86ea85fff Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 10:55:59 +0100 Subject: [PATCH 41/69] make unfinished tree a class --- src/lib/account_update.ts | 236 +++++++++++++++++++++----------------- 1 file changed, 129 insertions(+), 107 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index e2185e6166..47456378fd 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1049,7 +1049,7 @@ class AccountUpdate implements Types.AccountUpdate { toPrettyLayout() { let node = accountUpdates()?.get(this); assert(node !== undefined, 'AccountUpdate not found in layout'); - UnfinishedForest.print(node.calls); + node.calls.print(); } static defaultAccountUpdate(address: PublicKey, tokenId?: Field) { @@ -1459,83 +1459,57 @@ function hashCons(forestHash: Field, nodeHash: Field) { * everything immediately. Instead, we maintain a structure consisting of either hashes or full account * updates that can be hashed into a final call forest at the end. */ -type UnfinishedForest = HashOrValue; +type UnfinishedForestBase = HashOrValue; +type UnfinishedForestPlain = UnfinishedForestCommon & + PlainValue; +type UnfinishedForestHashed = UnfinishedForestCommon & + UseHash; type UnfinishedTree = { accountUpdate: HashOrValue; isDummy: Bool; // `children` must be readonly since it's referenced in each child's siblings - readonly calls: UnfinishedForest; - siblings?: UnfinishedForest; + readonly calls: UnfinishedForestCommon; + siblings?: UnfinishedForestCommon; }; -type UseHash = { readonly useHash: true; hash: Field; readonly value: T }; -type PlainValue = { readonly useHash: false; value: T }; -type HashOrValue = UseHash | PlainValue; +type UseHash = { hash: Field; value: T }; +type PlainValue = { hash?: undefined; value: T }; +type HashOrValue = { hash?: Field; value: T }; -const UnfinishedForest = { - empty(): PlainValue { - return { useHash: false, value: [] }; - }, - - setHash(forest: UnfinishedForest, hash: Field): UseHash { - return Object.assign(forest, { useHash: true as const, hash }); - }, - - witnessHash(forest: UnfinishedForest): UseHash { - let hash = Provable.witness(Field, () => { - return UnfinishedForest.finalize(forest).hash; - }); - return UnfinishedForest.setHash(forest, hash); - }, +class UnfinishedForestCommon implements UnfinishedForestBase { + hash?: Field; + value: UnfinishedTree[]; // TODO: make private - push( - forest: UnfinishedForest, - accountUpdate: AccountUpdate, - children?: UnfinishedForest - ) { - forest.value.push({ - accountUpdate: { useHash: false, value: accountUpdate }, - isDummy: accountUpdate.isDummy(), - calls: children ?? UnfinishedForest.empty(), - siblings: forest, - }); - }, - - pushTree(forest: UnfinishedForest, tree: AccountUpdateTree) { - let value = AccountUpdate.dummy(); - Provable.asProver(() => { - value = tree.accountUpdate.value.get(); - }); - forest.value.push({ - accountUpdate: { useHash: true, hash: tree.accountUpdate.hash, value }, - isDummy: Bool(false), - calls: UnfinishedForest.fromForest(tree.calls), - siblings: forest, - }); - }, + usesHash(): this is UnfinishedForestHashed { + return this.hash !== undefined; + } + mutable(): this is UnfinishedForest { + return this instanceof UnfinishedForest && this.hash === undefined; + } - remove(forest: UnfinishedForest, accountUpdate: AccountUpdate) { - // find account update by .id - let index = forest.value.findIndex( - (node) => node.accountUpdate.value.id === accountUpdate.id - ); + constructor(value: UnfinishedTree[], hash?: Field) { + this.value = value; + this.hash = hash; + } - // nothing to do if it's not there - if (index === -1) return; + setHash(hash: Field): UnfinishedForestHashed { + this.hash = hash; + return this as any; // TODO? + } - // remove it - forest.value.splice(index, 1); - }, + witnessHash(): UnfinishedForestHashed { + let hash = Provable.witness(Field, () => this.finalize().hash); + return this.setHash(hash); + } - fromForest( + static fromForest( forest: MerkleListBase, recursiveCall = false - ): UnfinishedForest { + ): UnfinishedForestCommon { let unfinished = UnfinishedForest.empty(); Provable.asProver(() => { unfinished.value = forest.data.get().map(({ element: tree }) => ({ accountUpdate: { - useHash: true, hash: tree.accountUpdate.hash.toConstant(), value: tree.accountUpdate.value.get(), }, @@ -1545,34 +1519,34 @@ const UnfinishedForest = { })); }); if (!recursiveCall) { - Object.assign(unfinished, { useHash: true, hash: forest.hash }); + unfinished.setHash(forest.hash); } return unfinished; - }, + } - finalize(forest: UnfinishedForest): AccountUpdateForest { - if (forest.useHash) { + finalize(): AccountUpdateForest { + if (this.usesHash()) { let data = Unconstrained.witness(() => - UnfinishedForest.finalize({ ...forest, useHash: false }).data.get() + new UnfinishedForest(this.value).finalize().data.get() ); - return new AccountUpdateForest({ hash: forest.hash, data }); + return new AccountUpdateForest({ hash: this.hash, data }); } // not using the hash means we calculate it in-circuit - let nodes = forest.value.map(toTree); + let nodes = this.value.map(toTree); let finalForest = AccountUpdateForest.empty(); for (let { isDummy, ...tree } of [...nodes].reverse()) { finalForest.pushIf(isDummy.not(), tree); } return finalForest; - }, + } - print(forest: UnfinishedForest) { + print() { let indent = 0; let layout = ''; - let toPretty = (a: UnfinishedForest) => { + let toPretty = (a: UnfinishedForestBase) => { indent += 2; for (let tree of a.value) { layout += @@ -1584,56 +1558,99 @@ const UnfinishedForest = { indent -= 2; }; - toPretty(forest); + toPretty(this); console.log(layout); - }, + } - toFlatList( - forest: UnfinishedForest, - mutate = true, - depth = 0 - ): AccountUpdate[] { + toFlatList(mutate = true, depth = 0): AccountUpdate[] { let flatUpdates: AccountUpdate[] = []; - for (let node of forest.value) { + for (let node of this.value) { if (node.isDummy.toBoolean()) continue; let update = node.accountUpdate.value; if (mutate) update.body.callDepth = depth; - let children = UnfinishedForest.toFlatList(node.calls, mutate, depth + 1); + let children = node.calls.toFlatList(mutate, depth + 1); flatUpdates.push(update, ...children); } return flatUpdates; - }, + } - toConstantInPlace(forest: UnfinishedForest) { - for (let node of forest.value) { + toConstantInPlace() { + for (let node of this.value) { // `as any` to override readonly - this method is explicit about its mutability (node.accountUpdate as any).value = Provable.toConstant( AccountUpdate, node.accountUpdate.value ); node.isDummy = Provable.toConstant(Bool, node.isDummy); - if (node.accountUpdate.useHash) { + if (node.accountUpdate.hash !== undefined) { node.accountUpdate.hash = node.accountUpdate.hash.toConstant(); } - UnfinishedForest.toConstantInPlace(node.calls); + node.calls.toConstantInPlace(); } - if (forest.useHash) { - forest.hash = forest.hash.toConstant(); + if (this.usesHash()) { + this.hash = this.hash.toConstant(); } - }, -}; + } +} + +class UnfinishedForest + extends UnfinishedForestCommon + implements UnfinishedForestPlain +{ + hash?: undefined; + value: UnfinishedTree[]; + + static empty() { + return new UnfinishedForest([]); + } + + push(accountUpdate: AccountUpdate, children?: UnfinishedForest) { + this.value.push({ + accountUpdate: { value: accountUpdate }, + isDummy: accountUpdate.isDummy(), + calls: children ?? UnfinishedForest.empty(), + siblings: this, + }); + } + + pushTree(tree: AccountUpdateTree) { + let value = AccountUpdate.dummy(); + Provable.asProver(() => { + value = tree.accountUpdate.value.get(); + }); + this.value.push({ + accountUpdate: { hash: tree.accountUpdate.hash, value }, + isDummy: Bool(false), + calls: UnfinishedForest.fromForest(tree.calls), + siblings: this, + }); + } + + remove(accountUpdate: AccountUpdate) { + // find account update by .id + let index = this.value.findIndex( + (node) => node.accountUpdate.value.id === accountUpdate.id + ); + + // nothing to do if it's not there + if (index === -1) return; + + // remove it + this.value.splice(index, 1); + } +} function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { - let accountUpdate = node.accountUpdate.useHash - ? new HashedAccountUpdate( - node.accountUpdate.hash, - Unconstrained.witness(() => - Provable.toConstant(AccountUpdate, node.accountUpdate.value) + let accountUpdate = + node.accountUpdate.hash !== undefined + ? new HashedAccountUpdate( + node.accountUpdate.hash, + Unconstrained.witness(() => + Provable.toConstant(AccountUpdate, node.accountUpdate.value) + ) ) - ) - : HashedAccountUpdate.hash(node.accountUpdate.value); - - let calls = UnfinishedForest.finalize(node.calls); + : HashedAccountUpdate.hash(node.accountUpdate.value); + let calls = node.calls.finalize(); return { accountUpdate, isDummy: node.isDummy, calls }; } @@ -1667,7 +1684,7 @@ class AccountUpdateLayout { this.map = new Map(); root ??= AccountUpdate.dummy(); let rootTree: UnfinishedTree = { - accountUpdate: { useHash: false, value: root }, + accountUpdate: { value: root }, isDummy: Bool(false), calls: UnfinishedForest.empty(), }; @@ -1689,7 +1706,7 @@ class AccountUpdateLayout { let node = this.map.get(update.id); if (node !== undefined) return node; node = { - accountUpdate: { useHash: false, value: update }, + accountUpdate: { value: update }, isDummy: update.isDummy(), calls: UnfinishedForest.empty(), }; @@ -1715,14 +1732,15 @@ class AccountUpdateLayout { setChildren( parent: AccountUpdate | UnfinishedTree, - children: AccountUpdateForest | UnfinishedForest + children: AccountUpdateForest | UnfinishedForestCommon ) { let parentNode = this.getOrCreate(parent); - // we're not allowed to switch parentNode.children, it must stay the same reference - // so we mutate it in place + if (children instanceof AccountUpdateForest) { children = UnfinishedForest.fromForest(children); } + // we're not allowed to switch parentNode.children, it must stay the same reference + // so we mutate it in place Object.assign(parentNode.calls, children); } @@ -1733,7 +1751,11 @@ class AccountUpdateLayout { disattach(update: AccountUpdate) { let node = this.get(update); if (node?.siblings === undefined) return; - UnfinishedForest.remove(node.siblings, update); + assert( + node.siblings.mutable(), + 'Cannot disattach from an immutable layout' + ); + node.siblings.remove(update); node.siblings = undefined; } @@ -1741,18 +1763,18 @@ class AccountUpdateLayout { let node = this.get(update); if (node === undefined) return; this.disattach(update); - return UnfinishedForest.finalize(node.calls); + return node.calls.finalize(); } finalizeChildren() { - let final = UnfinishedForest.finalize(this.root.calls); + let final = this.root.calls.finalize(); this.final = final; AccountUpdateForest.assertConstant(final); return final; } toFlatList({ mutate }: { mutate: boolean }) { - return UnfinishedForest.toFlatList(this.root.calls, mutate); + return this.root.calls.toFlatList(mutate); } forEachPredecessor( @@ -1767,7 +1789,7 @@ class AccountUpdateLayout { } toConstantInPlace() { - UnfinishedForest.toConstantInPlace(this.root.calls); + this.root.calls.toConstantInPlace(); } } From a55136957c997e1fbc73cdcede9a33fc1011a977 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 10:58:44 +0100 Subject: [PATCH 42/69] move smart contract context --- src/lib/account_update.ts | 21 --------------------- src/lib/zkapp.ts | 26 +++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 47456378fd..b3ea598824 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1654,27 +1654,6 @@ function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { return { accountUpdate, isDummy: node.isDummy, calls }; } -const SmartContractContext = { - enter(self: SmartContract, selfUpdate: AccountUpdate) { - let context: SmartContractContext = { - this: self, - selfUpdate, - selfLayout: new AccountUpdateLayout(selfUpdate), - }; - let id = smartContractContext.enter(context); - return { id, context }; - }, - leave(id: number) { - smartContractContext.leave(id); - }, - stepOutside() { - return smartContractContext.enter(null); - }, - get() { - return smartContractContext.get(); - }, -}; - class AccountUpdateLayout { readonly map: Map; readonly root: UnfinishedTree; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 57d841bd96..cf1d9e8cde 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -60,7 +60,10 @@ import { Cache } from './proof-system/cache.js'; import { assert } from './gadgets/common.js'; import { SmartContractBase } from './mina/smart-contract-base.js'; import { ZkappStateLength } from './mina/mina-instance.js'; -import { accountUpdates } from './mina/smart-contract-context.js'; +import { + accountUpdates, + smartContractContext, +} from './mina/smart-contract-context.js'; // external API export { @@ -1458,6 +1461,27 @@ type ExecutionState = { accountUpdate: AccountUpdate; }; +const SmartContractContext = { + enter(self: SmartContract, selfUpdate: AccountUpdate) { + let context: SmartContractContext = { + this: self, + selfUpdate, + selfLayout: new AccountUpdateLayout(selfUpdate), + }; + let id = smartContractContext.enter(context); + return { id, context }; + }, + leave(id: number) { + smartContractContext.leave(id); + }, + stepOutside() { + return smartContractContext.enter(null); + }, + get() { + return smartContractContext.get(); + }, +}; + type DeployArgs = | { verificationKey?: { data: string; hash: string | Field }; From a4c22163780ec5f94f22745c5e7f2c1777a008b0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 10:59:50 +0100 Subject: [PATCH 43/69] finish moving smart contract context --- src/lib/account_update.ts | 2 -- src/lib/zkapp.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index b3ea598824..827a64c3d4 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -67,7 +67,6 @@ import { import { Hashed } from './provable-types/packed.js'; import { accountUpdates, - SmartContractContext, smartContractContext, } from './mina/smart-contract-context.js'; import { assert } from './util/assert.js'; @@ -104,7 +103,6 @@ export { AccountUpdateLayout, hashAccountUpdate, HashedAccountUpdate, - SmartContractContext, }; const TransactionVersion = { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index cf1d9e8cde..aa32403f11 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -14,7 +14,6 @@ import { ZkappPublicInput, LazyProof, AccountUpdateForest, - SmartContractContext, AccountUpdateLayout, } from './account_update.js'; import { @@ -61,6 +60,7 @@ import { assert } from './gadgets/common.js'; import { SmartContractBase } from './mina/smart-contract-base.js'; import { ZkappStateLength } from './mina/mina-instance.js'; import { + SmartContractContext, accountUpdates, smartContractContext, } from './mina/smart-contract-context.js'; From 70302c8a5246c36d4ee94668b22f601b50854ca5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 11:21:40 +0100 Subject: [PATCH 44/69] play with ensuring invariants more strongly --- src/lib/account_update.ts | 50 +++++++++++++++++++-------------------- src/lib/zkapp.ts | 5 ++-- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 827a64c3d4..ea28897455 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1474,11 +1474,11 @@ type UseHash = { hash: Field; value: T }; type PlainValue = { hash?: undefined; value: T }; type HashOrValue = { hash?: Field; value: T }; -class UnfinishedForestCommon implements UnfinishedForestBase { +class UnfinishedForestCommon { hash?: Field; - value: UnfinishedTree[]; // TODO: make private + private value: UnfinishedTree[]; // TODO: make private - usesHash(): this is UnfinishedForestHashed { + usesHash(): this is { hash: Field } & UnfinishedForestCommon { return this.hash !== undefined; } mutable(): this is UnfinishedForest { @@ -1544,7 +1544,7 @@ class UnfinishedForestCommon implements UnfinishedForestBase { let indent = 0; let layout = ''; - let toPretty = (a: UnfinishedForestBase) => { + let toPretty = (a: UnfinishedForestCommon) => { indent += 2; for (let tree of a.value) { layout += @@ -1591,24 +1591,27 @@ class UnfinishedForestCommon implements UnfinishedForestBase { } } -class UnfinishedForest - extends UnfinishedForestCommon - implements UnfinishedForestPlain -{ +class UnfinishedForest extends UnfinishedForestCommon { hash?: undefined; - value: UnfinishedTree[]; static empty() { return new UnfinishedForest([]); } - push(accountUpdate: AccountUpdate, children?: UnfinishedForest) { - this.value.push({ - accountUpdate: { value: accountUpdate }, - isDummy: accountUpdate.isDummy(), - calls: children ?? UnfinishedForest.empty(), - siblings: this, - }); + private getValue(): UnfinishedTree[] { + // don't know how to do this differently, but this perfectly protects our invariant: + // only mutable forests can be modified + return (this as any).value; + } + + push(node: UnfinishedTree) { + if (node.siblings === this) return; + assert( + node.siblings === undefined, + 'Cannot push node that already has a parent.' + ); + node.siblings = this; + this.getValue().push(node); } pushTree(tree: AccountUpdateTree) { @@ -1616,7 +1619,7 @@ class UnfinishedForest Provable.asProver(() => { value = tree.accountUpdate.value.get(); }); - this.value.push({ + this.getValue().push({ accountUpdate: { hash: tree.accountUpdate.hash, value }, isDummy: Bool(false), calls: UnfinishedForest.fromForest(tree.calls), @@ -1626,7 +1629,7 @@ class UnfinishedForest remove(accountUpdate: AccountUpdate) { // find account update by .id - let index = this.value.findIndex( + let index = this.getValue().findIndex( (node) => node.accountUpdate.value.id === accountUpdate.id ); @@ -1634,7 +1637,7 @@ class UnfinishedForest if (index === -1) return; // remove it - this.value.splice(index, 1); + this.getValue().splice(index, 1); } } @@ -1694,13 +1697,8 @@ class AccountUpdateLayout { pushChild(parent: AccountUpdate | UnfinishedTree, child: AccountUpdate) { let parentNode = this.getOrCreate(parent); let childNode = this.getOrCreate(child); - if (childNode.siblings === parentNode.calls) return; - assert( - childNode.siblings === undefined, - 'AccountUpdateLayout.pushChild(): child already has another parent.' - ); - childNode.siblings = parentNode.calls; - parentNode.calls.value.push(childNode); + assert(parentNode.calls.mutable(), 'Cannot push to an immutable layout'); + parentNode.calls.push(childNode); } pushTopLevel(child: AccountUpdate) { diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index aa32403f11..7aeb218a7b 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1630,8 +1630,9 @@ function diffRecursive( ) { let { transaction, index, accountUpdate: input } = inputData; diff(transaction, index, prover.toPretty(), input.toPretty()); - let inputChildren = accountUpdates()!.get(input)!.calls.value; - let proverChildren = accountUpdates()!.get(prover)!.calls.value; + // TODO + let inputChildren = (accountUpdates()!.get(input)!.calls as any).value; + let proverChildren = (accountUpdates()!.get(prover)!.calls as any).value; let nChildren = inputChildren.length; for (let i = 0; i < nChildren; i++) { let inputChild = inputChildren[i].accountUpdate.value; From ef9c18c932af39e53d80542fd745cf34a9c07003 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 11:31:13 +0100 Subject: [PATCH 45/69] rename calls to children --- src/lib/account_update.ts | 34 +++++++++++++++++----------------- src/lib/zkapp.ts | 6 +++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index ea28897455..74118046ce 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1047,7 +1047,7 @@ class AccountUpdate implements Types.AccountUpdate { toPrettyLayout() { let node = accountUpdates()?.get(this); assert(node !== undefined, 'AccountUpdate not found in layout'); - node.calls.print(); + node.children.print(); } static defaultAccountUpdate(address: PublicKey, tokenId?: Field) { @@ -1467,7 +1467,7 @@ type UnfinishedTree = { accountUpdate: HashOrValue; isDummy: Bool; // `children` must be readonly since it's referenced in each child's siblings - readonly calls: UnfinishedForestCommon; + readonly children: UnfinishedForestCommon; siblings?: UnfinishedForestCommon; }; type UseHash = { hash: Field; value: T }; @@ -1512,7 +1512,7 @@ class UnfinishedForestCommon { value: tree.accountUpdate.value.get(), }, isDummy: Bool(false), - calls: UnfinishedForest.fromForest(tree.calls, true), + children: UnfinishedForest.fromForest(tree.calls, true), siblings: unfinished, })); }); @@ -1551,7 +1551,7 @@ class UnfinishedForestCommon { ' '.repeat(indent) + `( ${tree.accountUpdate.value.label || ''} )` + '\n'; - toPretty(tree.calls); + toPretty(tree.children); } indent -= 2; }; @@ -1566,7 +1566,7 @@ class UnfinishedForestCommon { if (node.isDummy.toBoolean()) continue; let update = node.accountUpdate.value; if (mutate) update.body.callDepth = depth; - let children = node.calls.toFlatList(mutate, depth + 1); + let children = node.children.toFlatList(mutate, depth + 1); flatUpdates.push(update, ...children); } return flatUpdates; @@ -1583,7 +1583,7 @@ class UnfinishedForestCommon { if (node.accountUpdate.hash !== undefined) { node.accountUpdate.hash = node.accountUpdate.hash.toConstant(); } - node.calls.toConstantInPlace(); + node.children.toConstantInPlace(); } if (this.usesHash()) { this.hash = this.hash.toConstant(); @@ -1622,7 +1622,7 @@ class UnfinishedForest extends UnfinishedForestCommon { this.getValue().push({ accountUpdate: { hash: tree.accountUpdate.hash, value }, isDummy: Bool(false), - calls: UnfinishedForest.fromForest(tree.calls), + children: UnfinishedForest.fromForest(tree.calls), siblings: this, }); } @@ -1651,7 +1651,7 @@ function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { ) ) : HashedAccountUpdate.hash(node.accountUpdate.value); - let calls = node.calls.finalize(); + let calls = node.children.finalize(); return { accountUpdate, isDummy: node.isDummy, calls }; } @@ -1666,7 +1666,7 @@ class AccountUpdateLayout { let rootTree: UnfinishedTree = { accountUpdate: { value: root }, isDummy: Bool(false), - calls: UnfinishedForest.empty(), + children: UnfinishedForest.empty(), }; this.map.set(root.id, rootTree); this.root = rootTree; @@ -1688,7 +1688,7 @@ class AccountUpdateLayout { node = { accountUpdate: { value: update }, isDummy: update.isDummy(), - calls: UnfinishedForest.empty(), + children: UnfinishedForest.empty(), }; this.map.set(update.id, node); return node; @@ -1697,8 +1697,8 @@ class AccountUpdateLayout { pushChild(parent: AccountUpdate | UnfinishedTree, child: AccountUpdate) { let parentNode = this.getOrCreate(parent); let childNode = this.getOrCreate(child); - assert(parentNode.calls.mutable(), 'Cannot push to an immutable layout'); - parentNode.calls.push(childNode); + assert(parentNode.children.mutable(), 'Cannot push to an immutable layout'); + parentNode.children.push(childNode); } pushTopLevel(child: AccountUpdate) { @@ -1716,7 +1716,7 @@ class AccountUpdateLayout { } // we're not allowed to switch parentNode.children, it must stay the same reference // so we mutate it in place - Object.assign(parentNode.calls, children); + Object.assign(parentNode.children, children); } setTopLevel(children: AccountUpdateForest) { @@ -1738,18 +1738,18 @@ class AccountUpdateLayout { let node = this.get(update); if (node === undefined) return; this.disattach(update); - return node.calls.finalize(); + return node.children.finalize(); } finalizeChildren() { - let final = this.root.calls.finalize(); + let final = this.root.children.finalize(); this.final = final; AccountUpdateForest.assertConstant(final); return final; } toFlatList({ mutate }: { mutate: boolean }) { - return this.root.calls.toFlatList(mutate); + return this.root.children.toFlatList(mutate); } forEachPredecessor( @@ -1764,7 +1764,7 @@ class AccountUpdateLayout { } toConstantInPlace() { - this.root.calls.toConstantInPlace(); + this.root.children.toConstantInPlace(); } } diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 7aeb218a7b..5629f6ed45 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -310,7 +310,7 @@ function wrapMethod( } let txLayout = Mina.currentTransaction.get().layout; txLayout.pushTopLevel(accountUpdate); - txLayout.setChildren(accountUpdate, context.selfLayout.root.calls); + txLayout.setChildren(accountUpdate, context.selfLayout.root.children); return result; } @@ -1631,8 +1631,8 @@ function diffRecursive( let { transaction, index, accountUpdate: input } = inputData; diff(transaction, index, prover.toPretty(), input.toPretty()); // TODO - let inputChildren = (accountUpdates()!.get(input)!.calls as any).value; - let proverChildren = (accountUpdates()!.get(prover)!.calls as any).value; + let inputChildren = (accountUpdates()!.get(input)!.children as any).value; + let proverChildren = (accountUpdates()!.get(prover)!.children as any).value; let nChildren = inputChildren.length; for (let i = 0; i < nChildren; i++) { let inputChild = inputChildren[i].accountUpdate.value; From c03554bbed5e3f12fde4946ed1f5eb5d7e308794 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 12:03:44 +0100 Subject: [PATCH 46/69] start refactoring unfinished forest --- src/lib/account_update.ts | 101 ++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 58 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 74118046ce..300347363c 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1457,53 +1457,52 @@ function hashCons(forestHash: Field, nodeHash: Field) { * everything immediately. Instead, we maintain a structure consisting of either hashes or full account * updates that can be hashed into a final call forest at the end. */ -type UnfinishedForestBase = HashOrValue; -type UnfinishedForestPlain = UnfinishedForestCommon & - PlainValue; -type UnfinishedForestHashed = UnfinishedForestCommon & - UseHash; +type UnfinishedForestFinal = { final: AccountUpdateForest } & UnfinishedForest; +type UnfinishedForestMutable = { + final?: undefined; + value: UnfinishedTree[]; +} & UnfinishedForest; type UnfinishedTree = { accountUpdate: HashOrValue; isDummy: Bool; // `children` must be readonly since it's referenced in each child's siblings - readonly children: UnfinishedForestCommon; - siblings?: UnfinishedForestCommon; + readonly children: UnfinishedForest; + siblings?: UnfinishedForest; }; -type UseHash = { hash: Field; value: T }; -type PlainValue = { hash?: undefined; value: T }; type HashOrValue = { hash?: Field; value: T }; -class UnfinishedForestCommon { - hash?: Field; - private value: UnfinishedTree[]; // TODO: make private +class UnfinishedForest { + final?: AccountUpdateForest; + readonly value: readonly Readonly[]; // TODO: make private - usesHash(): this is { hash: Field } & UnfinishedForestCommon { - return this.hash !== undefined; + isFinal(): this is UnfinishedForestFinal { + return this.final !== undefined; } - mutable(): this is UnfinishedForest { - return this instanceof UnfinishedForest && this.hash === undefined; + isMutable(): this is UnfinishedForestMutable { + return this.final === undefined; } - constructor(value: UnfinishedTree[], hash?: Field) { + constructor(value: UnfinishedTree[], final?: AccountUpdateForest) { this.value = value; - this.hash = hash; + this.final = final; } - setHash(hash: Field): UnfinishedForestHashed { - this.hash = hash; - return this as any; // TODO? + setFinal(final: AccountUpdateForest): UnfinishedForestFinal { + return Object.assign(this, { final }); } - witnessHash(): UnfinishedForestHashed { - let hash = Provable.witness(Field, () => this.finalize().hash); - return this.setHash(hash); + witnessHash(): UnfinishedForestFinal { + let final = Provable.witness(AccountUpdateForest.provable, () => + this.finalize() + ); + return this.setFinal(final); } static fromForest( forest: MerkleListBase, recursiveCall = false - ): UnfinishedForestCommon { + ): UnfinishedForest { let unfinished = UnfinishedForest.empty(); Provable.asProver(() => { unfinished.value = forest.data.get().map(({ element: tree }) => ({ @@ -1517,17 +1516,14 @@ class UnfinishedForestCommon { })); }); if (!recursiveCall) { - unfinished.setHash(forest.hash); + unfinished.setFinal(new AccountUpdateForest(forest)); } return unfinished; } finalize(): AccountUpdateForest { - if (this.usesHash()) { - let data = Unconstrained.witness(() => - new UnfinishedForest(this.value).finalize().data.get() - ); - return new AccountUpdateForest({ hash: this.hash, data }); + if (this.isFinal()) { + return this.final; } // not using the hash means we calculate it in-circuit @@ -1537,6 +1533,7 @@ class UnfinishedForestCommon { for (let { isDummy, ...tree } of [...nodes].reverse()) { finalForest.pushIf(isDummy.not(), tree); } + this.setFinal(finalForest); return finalForest; } @@ -1544,7 +1541,7 @@ class UnfinishedForestCommon { let indent = 0; let layout = ''; - let toPretty = (a: UnfinishedForestCommon) => { + let toPretty = (a: UnfinishedForest) => { indent += 2; for (let tree of a.value) { layout += @@ -1575,33 +1572,23 @@ class UnfinishedForestCommon { toConstantInPlace() { for (let node of this.value) { // `as any` to override readonly - this method is explicit about its mutability - (node.accountUpdate as any).value = Provable.toConstant( + node.accountUpdate.value = Provable.toConstant( AccountUpdate, node.accountUpdate.value ); - node.isDummy = Provable.toConstant(Bool, node.isDummy); + (node as any).isDummy = Provable.toConstant(Bool, node.isDummy); if (node.accountUpdate.hash !== undefined) { node.accountUpdate.hash = node.accountUpdate.hash.toConstant(); } node.children.toConstantInPlace(); } - if (this.usesHash()) { - this.hash = this.hash.toConstant(); + if (this.isFinal()) { + this.final.hash = this.final.hash.toConstant(); } } -} - -class UnfinishedForest extends UnfinishedForestCommon { - hash?: undefined; - static empty() { - return new UnfinishedForest([]); - } - - private getValue(): UnfinishedTree[] { - // don't know how to do this differently, but this perfectly protects our invariant: - // only mutable forests can be modified - return (this as any).value; + static empty(): UnfinishedForestMutable { + return new UnfinishedForest([]) as any; } push(node: UnfinishedTree) { @@ -1611,7 +1598,8 @@ class UnfinishedForest extends UnfinishedForestCommon { 'Cannot push node that already has a parent.' ); node.siblings = this; - this.getValue().push(node); + assert(this.isMutable(), 'Cannot push to an immutable forest'); + this.value.push(node); } pushTree(tree: AccountUpdateTree) { @@ -1619,7 +1607,8 @@ class UnfinishedForest extends UnfinishedForestCommon { Provable.asProver(() => { value = tree.accountUpdate.value.get(); }); - this.getValue().push({ + assert(this.isMutable(), 'Cannot push to an immutable forest'); + this.value.push({ accountUpdate: { hash: tree.accountUpdate.hash, value }, isDummy: Bool(false), children: UnfinishedForest.fromForest(tree.calls), @@ -1629,7 +1618,7 @@ class UnfinishedForest extends UnfinishedForestCommon { remove(accountUpdate: AccountUpdate) { // find account update by .id - let index = this.getValue().findIndex( + let index = this.value.findIndex( (node) => node.accountUpdate.value.id === accountUpdate.id ); @@ -1637,7 +1626,8 @@ class UnfinishedForest extends UnfinishedForestCommon { if (index === -1) return; // remove it - this.getValue().splice(index, 1); + assert(this.isMutable(), 'Cannot remove from an immutable forest'); + this.value.splice(index, 1); } } @@ -1697,7 +1687,6 @@ class AccountUpdateLayout { pushChild(parent: AccountUpdate | UnfinishedTree, child: AccountUpdate) { let parentNode = this.getOrCreate(parent); let childNode = this.getOrCreate(child); - assert(parentNode.children.mutable(), 'Cannot push to an immutable layout'); parentNode.children.push(childNode); } @@ -1707,7 +1696,7 @@ class AccountUpdateLayout { setChildren( parent: AccountUpdate | UnfinishedTree, - children: AccountUpdateForest | UnfinishedForestCommon + children: AccountUpdateForest | UnfinishedForest ) { let parentNode = this.getOrCreate(parent); @@ -1726,10 +1715,6 @@ class AccountUpdateLayout { disattach(update: AccountUpdate) { let node = this.get(update); if (node?.siblings === undefined) return; - assert( - node.siblings.mutable(), - 'Cannot disattach from an immutable layout' - ); node.siblings.remove(update); node.siblings = undefined; } From f48955a24c682f8deaf0925f083314e80554814b Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 8 Feb 2024 12:30:24 +0100 Subject: [PATCH 47/69] reorder stuff --- src/lib/account_update.ts | 152 +++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 300347363c..0063e7395f 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1488,10 +1488,30 @@ class UnfinishedForest { this.final = final; } - setFinal(final: AccountUpdateForest): UnfinishedForestFinal { + static empty(): UnfinishedForestMutable { + return new UnfinishedForest([]) as any; + } + + private setFinal(final: AccountUpdateForest): UnfinishedForestFinal { return Object.assign(this, { final }); } + finalize(): AccountUpdateForest { + if (this.isFinal()) { + return this.final; + } + + // not using the hash means we calculate it in-circuit + let nodes = this.value.map(toTree); + let finalForest = AccountUpdateForest.empty(); + + for (let { isDummy, ...tree } of [...nodes].reverse()) { + finalForest.pushIf(isDummy.not(), tree); + } + this.setFinal(finalForest); + return finalForest; + } + witnessHash(): UnfinishedForestFinal { let final = Provable.witness(AccountUpdateForest.provable, () => this.finalize() @@ -1499,6 +1519,45 @@ class UnfinishedForest { return this.setFinal(final); } + push(node: UnfinishedTree) { + if (node.siblings === this) return; + assert( + node.siblings === undefined, + 'Cannot push node that already has a parent.' + ); + node.siblings = this; + assert(this.isMutable(), 'Cannot push to an immutable forest'); + this.value.push(node); + } + + pushTree(tree: AccountUpdateTree) { + assert(this.isMutable(), 'Cannot push to an immutable forest'); + let value = AccountUpdate.dummy(); + Provable.asProver(() => { + value = tree.accountUpdate.value.get(); + }); + this.value.push({ + accountUpdate: { hash: tree.accountUpdate.hash, value }, + isDummy: Bool(false), + children: UnfinishedForest.fromForest(tree.calls), + siblings: this, + }); + } + + remove(accountUpdate: AccountUpdate) { + // find account update by .id + let index = this.value.findIndex( + (node) => node.accountUpdate.value.id === accountUpdate.id + ); + + // nothing to do if it's not there + if (index === -1) return; + + // remove it + assert(this.isMutable(), 'Cannot remove from an immutable forest'); + this.value.splice(index, 1); + } + static fromForest( forest: MerkleListBase, recursiveCall = false @@ -1521,42 +1580,6 @@ class UnfinishedForest { return unfinished; } - finalize(): AccountUpdateForest { - if (this.isFinal()) { - return this.final; - } - - // not using the hash means we calculate it in-circuit - let nodes = this.value.map(toTree); - let finalForest = AccountUpdateForest.empty(); - - for (let { isDummy, ...tree } of [...nodes].reverse()) { - finalForest.pushIf(isDummy.not(), tree); - } - this.setFinal(finalForest); - return finalForest; - } - - print() { - let indent = 0; - let layout = ''; - - let toPretty = (a: UnfinishedForest) => { - indent += 2; - for (let tree of a.value) { - layout += - ' '.repeat(indent) + - `( ${tree.accountUpdate.value.label || ''} )` + - '\n'; - toPretty(tree.children); - } - indent -= 2; - }; - - toPretty(this); - console.log(layout); - } - toFlatList(mutate = true, depth = 0): AccountUpdate[] { let flatUpdates: AccountUpdate[] = []; for (let node of this.value) { @@ -1587,47 +1610,24 @@ class UnfinishedForest { } } - static empty(): UnfinishedForestMutable { - return new UnfinishedForest([]) as any; - } - - push(node: UnfinishedTree) { - if (node.siblings === this) return; - assert( - node.siblings === undefined, - 'Cannot push node that already has a parent.' - ); - node.siblings = this; - assert(this.isMutable(), 'Cannot push to an immutable forest'); - this.value.push(node); - } - - pushTree(tree: AccountUpdateTree) { - let value = AccountUpdate.dummy(); - Provable.asProver(() => { - value = tree.accountUpdate.value.get(); - }); - assert(this.isMutable(), 'Cannot push to an immutable forest'); - this.value.push({ - accountUpdate: { hash: tree.accountUpdate.hash, value }, - isDummy: Bool(false), - children: UnfinishedForest.fromForest(tree.calls), - siblings: this, - }); - } - - remove(accountUpdate: AccountUpdate) { - // find account update by .id - let index = this.value.findIndex( - (node) => node.accountUpdate.value.id === accountUpdate.id - ); + print() { + let indent = 0; + let layout = ''; - // nothing to do if it's not there - if (index === -1) return; + let toPretty = (a: UnfinishedForest) => { + indent += 2; + for (let tree of a.value) { + layout += + ' '.repeat(indent) + + `( ${tree.accountUpdate.value.label || ''} )` + + '\n'; + toPretty(tree.children); + } + indent -= 2; + }; - // remove it - assert(this.isMutable(), 'Cannot remove from an immutable forest'); - this.value.splice(index, 1); + toPretty(this); + console.log(layout); } } From b7a74c41509539dda22cd0ba0873857ad80ffdeb Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 8 Feb 2024 12:55:12 +0100 Subject: [PATCH 48/69] tweak remove --- src/lib/account_update.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 0063e7395f..f7571553e9 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1497,11 +1497,8 @@ class UnfinishedForest { } finalize(): AccountUpdateForest { - if (this.isFinal()) { - return this.final; - } + if (this.isFinal()) return this.final; - // not using the hash means we calculate it in-circuit let nodes = this.value.map(toTree); let finalForest = AccountUpdateForest.empty(); @@ -1530,6 +1527,7 @@ class UnfinishedForest { this.value.push(node); } + // TODO this isn't quite right pushTree(tree: AccountUpdateTree) { assert(this.isMutable(), 'Cannot push to an immutable forest'); let value = AccountUpdate.dummy(); @@ -1544,10 +1542,10 @@ class UnfinishedForest { }); } - remove(accountUpdate: AccountUpdate) { - // find account update by .id + remove(node: UnfinishedTree) { + // find by .id let index = this.value.findIndex( - (node) => node.accountUpdate.value.id === accountUpdate.id + (n) => n.accountUpdate.value.id === node.accountUpdate.value.id ); // nothing to do if it's not there @@ -1555,6 +1553,7 @@ class UnfinishedForest { // remove it assert(this.isMutable(), 'Cannot remove from an immutable forest'); + node.siblings = undefined; this.value.splice(index, 1); } @@ -1714,9 +1713,7 @@ class AccountUpdateLayout { disattach(update: AccountUpdate) { let node = this.get(update); - if (node?.siblings === undefined) return; - node.siblings.remove(update); - node.siblings = undefined; + node?.siblings?.remove(node); } finalizeAndRemove(update: AccountUpdate) { From cbc774c3ade14c60ac34dde4dc2bc24c80d0ef21 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Thu, 8 Feb 2024 16:04:14 +0100 Subject: [PATCH 49/69] better set children logic --- src/lib/account_update.ts | 43 ++++++++++++++-------------- src/lib/mina/token/token-contract.ts | 9 ------ src/lib/zkapp.ts | 9 ++++-- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index f7571553e9..c9e1afc2ed 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1557,26 +1557,33 @@ class UnfinishedForest { this.value.splice(index, 1); } - static fromForest( - forest: MerkleListBase, - recursiveCall = false - ): UnfinishedForest { - let unfinished = UnfinishedForest.empty(); + setToForest(forest: MerkleListBase) { + if (this.isMutable()) { + assert( + this.value.length === 0, + 'Replacing a mutable forest that has existing children might be a mistake.' + ); + } + let value: UnfinishedTree[] = []; Provable.asProver(() => { - unfinished.value = forest.data.get().map(({ element: tree }) => ({ + value = forest.data.get().map(({ element: tree }) => ({ accountUpdate: { hash: tree.accountUpdate.hash.toConstant(), value: tree.accountUpdate.value.get(), }, isDummy: Bool(false), - children: UnfinishedForest.fromForest(tree.calls, true), - siblings: unfinished, + children: UnfinishedForest.fromForest(tree.calls), + siblings: this, })); }); - if (!recursiveCall) { - unfinished.setFinal(new AccountUpdateForest(forest)); - } - return unfinished; + Object.assign(this, { value }); + return this.setFinal(new AccountUpdateForest(forest)); + } + + static fromForest( + forest: MerkleListBase + ): UnfinishedForestFinal { + return UnfinishedForest.empty().setToForest(forest); } toFlatList(mutate = true, depth = 0): AccountUpdate[] { @@ -1665,7 +1672,7 @@ class AccountUpdateLayout { return this.map.get(update.id); } - getOrCreate(update: AccountUpdate | UnfinishedTree): UnfinishedTree { + private getOrCreate(update: AccountUpdate | UnfinishedTree): UnfinishedTree { if (!(update instanceof AccountUpdate)) { if (!this.map.has(update.accountUpdate.value.id)) { this.map.set(update.accountUpdate.value.id, update); @@ -1695,16 +1702,10 @@ class AccountUpdateLayout { setChildren( parent: AccountUpdate | UnfinishedTree, - children: AccountUpdateForest | UnfinishedForest + children: AccountUpdateForest ) { let parentNode = this.getOrCreate(parent); - - if (children instanceof AccountUpdateForest) { - children = UnfinishedForest.fromForest(children); - } - // we're not allowed to switch parentNode.children, it must stay the same reference - // so we mutate it in place - Object.assign(parentNode.children, children); + parentNode.children.setToForest(children); } setTopLevel(children: AccountUpdateForest) { diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 210b3d1a86..cb18a6c906 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -63,15 +63,6 @@ abstract class TokenContract extends SmartContract { `the supported limit of ${MAX_ACCOUNT_UPDATES}.\n` ); - // make top-level updates our children - // TODO: this must not be necessary once we move everything to `selfLayout` - Provable.asProver(() => { - updates.data.get().forEach((update) => { - let accountUpdate = update.element.accountUpdate.value.get(); - this.approve(accountUpdate); - }); - }); - // skip hashing our child account updates in the method wrapper // since we just did that in the loop above accountUpdates()?.setTopLevel(updates); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 5629f6ed45..35d334d769 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -310,7 +310,10 @@ function wrapMethod( } let txLayout = Mina.currentTransaction.get().layout; txLayout.pushTopLevel(accountUpdate); - txLayout.setChildren(accountUpdate, context.selfLayout.root.children); + txLayout.setChildren( + accountUpdate, + context.selfLayout.finalizeChildren() + ); return result; } @@ -1631,8 +1634,8 @@ function diffRecursive( let { transaction, index, accountUpdate: input } = inputData; diff(transaction, index, prover.toPretty(), input.toPretty()); // TODO - let inputChildren = (accountUpdates()!.get(input)!.children as any).value; - let proverChildren = (accountUpdates()!.get(prover)!.children as any).value; + let inputChildren = accountUpdates()!.get(input)!.children.value; + let proverChildren = accountUpdates()!.get(prover)!.children.value; let nChildren = inputChildren.length; for (let i = 0; i < nChildren; i++) { let inputChild = inputChildren[i].accountUpdate.value; From c4b016a5f2974d8220877ec22af3417814f7cad6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 18:39:08 +0100 Subject: [PATCH 50/69] completely get rid of unconstrained array after finalizing --- src/lib/account_update.ts | 70 ++++++++++++++++++++++----------------- src/lib/zkapp.ts | 4 +-- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index c9e1afc2ed..b81f214e3e 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1405,6 +1405,20 @@ class AccountUpdateForest extends MerkleList.create( let simpleForest = accountUpdatesToCallForest(updates); return this.fromSimpleForest(simpleForest); } + static toFlatArray( + forest: MerkleListBase, + mutate = true, + depth = 0 + ) { + let flat: AccountUpdate[] = []; + for (let { element: tree } of forest.data.get()) { + let update = tree.accountUpdate.value.get(); + if (mutate) update.body.callDepth = depth; + flat.push(update); + flat.push(...this.toFlatArray(tree.calls, mutate, depth + 1)); + } + return flat; + } private static fromSimpleForest( simpleForest: CallForest @@ -1451,13 +1465,16 @@ function hashCons(forestHash: Field, nodeHash: Field) { } /** - * Structure for constructing the forest of child account updates, from a circuit. + * UnfinishedForest / UnfinishedTree are structures for constructing the forest of child account updates from a circuit. * * The circuit can mutate account updates and change their array of children, so here we can't hash * everything immediately. Instead, we maintain a structure consisting of either hashes or full account * updates that can be hashed into a final call forest at the end. */ -type UnfinishedForestFinal = { final: AccountUpdateForest } & UnfinishedForest; +type UnfinishedForestFinal = { + final: AccountUpdateForest; + value?: undefined; +} & UnfinishedForest; type UnfinishedForestMutable = { final?: undefined; value: UnfinishedTree[]; @@ -1474,13 +1491,13 @@ type HashOrValue = { hash?: Field; value: T }; class UnfinishedForest { final?: AccountUpdateForest; - readonly value: readonly Readonly[]; // TODO: make private + value?: UnfinishedTree[]; isFinal(): this is UnfinishedForestFinal { return this.final !== undefined; } isMutable(): this is UnfinishedForestMutable { - return this.final === undefined; + return this.value !== undefined; } constructor(value: UnfinishedTree[], final?: AccountUpdateForest) { @@ -1493,11 +1510,12 @@ class UnfinishedForest { } private setFinal(final: AccountUpdateForest): UnfinishedForestFinal { - return Object.assign(this, { final }); + return Object.assign(this, { final, value: undefined }); } finalize(): AccountUpdateForest { if (this.isFinal()) return this.final; + assert(this.isMutable(), 'final or mutable'); let nodes = this.value.map(toTree); let finalForest = AccountUpdateForest.empty(); @@ -1543,6 +1561,7 @@ class UnfinishedForest { } remove(node: UnfinishedTree) { + assert(this.isMutable(), 'Cannot remove from an immutable forest'); // find by .id let index = this.value.findIndex( (n) => n.accountUpdate.value.id === node.accountUpdate.value.id @@ -1552,7 +1571,6 @@ class UnfinishedForest { if (index === -1) return; // remove it - assert(this.isMutable(), 'Cannot remove from an immutable forest'); node.siblings = undefined; this.value.splice(index, 1); } @@ -1564,65 +1582,55 @@ class UnfinishedForest { 'Replacing a mutable forest that has existing children might be a mistake.' ); } - let value: UnfinishedTree[] = []; - Provable.asProver(() => { - value = forest.data.get().map(({ element: tree }) => ({ - accountUpdate: { - hash: tree.accountUpdate.hash.toConstant(), - value: tree.accountUpdate.value.get(), - }, - isDummy: Bool(false), - children: UnfinishedForest.fromForest(tree.calls), - siblings: this, - })); - }); - Object.assign(this, { value }); return this.setFinal(new AccountUpdateForest(forest)); } - static fromForest( - forest: MerkleListBase - ): UnfinishedForestFinal { + static fromForest(forest: MerkleListBase) { return UnfinishedForest.empty().setToForest(forest); } - toFlatList(mutate = true, depth = 0): AccountUpdate[] { + toFlatArray(mutate = true, depth = 0): AccountUpdate[] { + if (this.isFinal()) + return AccountUpdateForest.toFlatArray(this.final, mutate, depth); + assert(this.isMutable(), 'final or mutable'); let flatUpdates: AccountUpdate[] = []; for (let node of this.value) { if (node.isDummy.toBoolean()) continue; let update = node.accountUpdate.value; if (mutate) update.body.callDepth = depth; - let children = node.children.toFlatList(mutate, depth + 1); + let children = node.children.toFlatArray(mutate, depth + 1); flatUpdates.push(update, ...children); } return flatUpdates; } toConstantInPlace() { + if (this.isFinal()) { + this.final.hash = this.final.hash.toConstant(); + return; + } + assert(this.isMutable(), 'final or mutable'); for (let node of this.value) { - // `as any` to override readonly - this method is explicit about its mutability node.accountUpdate.value = Provable.toConstant( AccountUpdate, node.accountUpdate.value ); - (node as any).isDummy = Provable.toConstant(Bool, node.isDummy); + node.isDummy = Provable.toConstant(Bool, node.isDummy); if (node.accountUpdate.hash !== undefined) { node.accountUpdate.hash = node.accountUpdate.hash.toConstant(); } node.children.toConstantInPlace(); } - if (this.isFinal()) { - this.final.hash = this.final.hash.toConstant(); - } } print() { + assert(this.isMutable(), 'print(): unimplemented for final forests'); let indent = 0; let layout = ''; let toPretty = (a: UnfinishedForest) => { indent += 2; - for (let tree of a.value) { + for (let tree of a.value!) { layout += ' '.repeat(indent) + `( ${tree.accountUpdate.value.label || ''} )` + @@ -1732,7 +1740,7 @@ class AccountUpdateLayout { } toFlatList({ mutate }: { mutate: boolean }) { - return this.root.children.toFlatList(mutate); + return this.root.children.toFlatArray(mutate); } forEachPredecessor( diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 35d334d769..357e6b3849 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1634,8 +1634,8 @@ function diffRecursive( let { transaction, index, accountUpdate: input } = inputData; diff(transaction, index, prover.toPretty(), input.toPretty()); // TODO - let inputChildren = accountUpdates()!.get(input)!.children.value; - let proverChildren = accountUpdates()!.get(prover)!.children.value; + let inputChildren = accountUpdates()!.get(input)!.children.value!; + let proverChildren = accountUpdates()!.get(prover)!.children.value!; let nChildren = inputChildren.length; for (let i = 0; i < nChildren; i++) { let inputChild = inputChildren[i].accountUpdate.value; From 1e6278e9f31d84cc978723d8aa7d23d8409cc73f Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 18:56:35 +0100 Subject: [PATCH 51/69] cleanup, rename --- src/lib/account_update.ts | 69 +++++++++++++++++++++++---------------- src/lib/zkapp.ts | 4 +-- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index b81f214e3e..5f7019f457 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1465,44 +1465,56 @@ function hashCons(forestHash: Field, nodeHash: Field) { } /** - * UnfinishedForest / UnfinishedTree are structures for constructing the forest of child account updates from a circuit. + * `UnfinishedForest` / `UnfinishedTree` are structures for constructing the forest of child account updates from a circuit. * * The circuit can mutate account updates and change their array of children, so here we can't hash * everything immediately. Instead, we maintain a structure consisting of either hashes or full account * updates that can be hashed into a final call forest at the end. + * + * `UnfinishedForest` behaves like a tagged enum type: + * ``` + * type UnfinishedForest = + * | Mutable of UnfinishedTree[] + * | Final of AccountUpdateForest; + * ``` */ -type UnfinishedForestFinal = { - final: AccountUpdateForest; - value?: undefined; -} & UnfinishedForest; -type UnfinishedForestMutable = { - final?: undefined; - value: UnfinishedTree[]; -} & UnfinishedForest; - type UnfinishedTree = { - accountUpdate: HashOrValue; + // TODO it would be nice to make this closer to `Final of Hashed | Mutable of AccountUpdate` + accountUpdate: { hash?: Field; value: AccountUpdate }; isDummy: Bool; // `children` must be readonly since it's referenced in each child's siblings readonly children: UnfinishedForest; siblings?: UnfinishedForest; }; -type HashOrValue = { hash?: Field; value: T }; + +type UnfinishedForestFinal = UnfinishedForest & { + final: AccountUpdateForest; + mutable?: undefined; +}; + +type UnfinishedForestMutable = UnfinishedForest & { + final?: undefined; + mutable: UnfinishedTree[]; +}; class UnfinishedForest { final?: AccountUpdateForest; - value?: UnfinishedTree[]; + mutable?: UnfinishedTree[]; isFinal(): this is UnfinishedForestFinal { return this.final !== undefined; } isMutable(): this is UnfinishedForestMutable { - return this.value !== undefined; + return this.mutable !== undefined; } - constructor(value: UnfinishedTree[], final?: AccountUpdateForest) { - this.value = value; + constructor(mutable?: UnfinishedTree[], final?: AccountUpdateForest) { + assert( + (final === undefined) !== (mutable === undefined), + 'final or mutable' + ); this.final = final; + this.mutable = mutable; } static empty(): UnfinishedForestMutable { @@ -1510,14 +1522,14 @@ class UnfinishedForest { } private setFinal(final: AccountUpdateForest): UnfinishedForestFinal { - return Object.assign(this, { final, value: undefined }); + return Object.assign(this, { final, mutable: undefined }); } finalize(): AccountUpdateForest { if (this.isFinal()) return this.final; assert(this.isMutable(), 'final or mutable'); - let nodes = this.value.map(toTree); + let nodes = this.mutable.map(toTree); let finalForest = AccountUpdateForest.empty(); for (let { isDummy, ...tree } of [...nodes].reverse()) { @@ -1542,17 +1554,17 @@ class UnfinishedForest { ); node.siblings = this; assert(this.isMutable(), 'Cannot push to an immutable forest'); - this.value.push(node); + this.mutable.push(node); } - // TODO this isn't quite right + // TODO this isn't quite right - shouldn't have to save the value in a circuit-accessible way if it's hashed pushTree(tree: AccountUpdateTree) { assert(this.isMutable(), 'Cannot push to an immutable forest'); let value = AccountUpdate.dummy(); Provable.asProver(() => { value = tree.accountUpdate.value.get(); }); - this.value.push({ + this.mutable.push({ accountUpdate: { hash: tree.accountUpdate.hash, value }, isDummy: Bool(false), children: UnfinishedForest.fromForest(tree.calls), @@ -1563,7 +1575,7 @@ class UnfinishedForest { remove(node: UnfinishedTree) { assert(this.isMutable(), 'Cannot remove from an immutable forest'); // find by .id - let index = this.value.findIndex( + let index = this.mutable.findIndex( (n) => n.accountUpdate.value.id === node.accountUpdate.value.id ); @@ -1572,13 +1584,13 @@ class UnfinishedForest { // remove it node.siblings = undefined; - this.value.splice(index, 1); + this.mutable.splice(index, 1); } setToForest(forest: MerkleListBase) { if (this.isMutable()) { assert( - this.value.length === 0, + this.mutable.length === 0, 'Replacing a mutable forest that has existing children might be a mistake.' ); } @@ -1594,7 +1606,7 @@ class UnfinishedForest { return AccountUpdateForest.toFlatArray(this.final, mutate, depth); assert(this.isMutable(), 'final or mutable'); let flatUpdates: AccountUpdate[] = []; - for (let node of this.value) { + for (let node of this.mutable) { if (node.isDummy.toBoolean()) continue; let update = node.accountUpdate.value; if (mutate) update.body.callDepth = depth; @@ -1610,7 +1622,7 @@ class UnfinishedForest { return; } assert(this.isMutable(), 'final or mutable'); - for (let node of this.value) { + for (let node of this.mutable) { node.accountUpdate.value = Provable.toConstant( AccountUpdate, node.accountUpdate.value @@ -1624,13 +1636,14 @@ class UnfinishedForest { } print() { - assert(this.isMutable(), 'print(): unimplemented for final forests'); let indent = 0; let layout = ''; let toPretty = (a: UnfinishedForest) => { + if (a.isFinal()) layout += ' '.repeat(indent) + ' ( finalized forest )\n'; + assert(a.isMutable(), 'final or mutable'); indent += 2; - for (let tree of a.value!) { + for (let tree of a.mutable) { layout += ' '.repeat(indent) + `( ${tree.accountUpdate.value.label || ''} )` + diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 357e6b3849..7d2fbd7e23 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1634,8 +1634,8 @@ function diffRecursive( let { transaction, index, accountUpdate: input } = inputData; diff(transaction, index, prover.toPretty(), input.toPretty()); // TODO - let inputChildren = accountUpdates()!.get(input)!.children.value!; - let proverChildren = accountUpdates()!.get(prover)!.children.value!; + let inputChildren = accountUpdates()!.get(input)!.children.mutable!; + let proverChildren = accountUpdates()!.get(prover)!.children.mutable!; let nChildren = inputChildren.length; for (let i = 0; i < nChildren; i++) { let inputChild = inputChildren[i].accountUpdate.value; From ed0423b176bff28a3a999bad0976b9d2824ca828 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 21:13:47 +0100 Subject: [PATCH 52/69] extract tree --- src/lib/account_update.ts | 22 +++++++++++++--------- src/lib/mina/token/token-contract.ts | 25 +++++++++++-------------- src/lib/provable-types/packed.ts | 10 ++++++++-- src/lib/zkapp.ts | 2 +- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 5f7019f457..adab08e9b0 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1050,6 +1050,14 @@ class AccountUpdate implements Types.AccountUpdate { node.children.print(); } + extractTree(): AccountUpdateTree { + let layout = accountUpdates(); + let hash = layout?.get(this)?.accountUpdate.hash; + let calls = layout?.finalizeAndRemove(this) ?? AccountUpdateForest.empty(); + let accountUpdate = HashedAccountUpdate.hash(this, hash); + return { accountUpdate, calls }; + } + static defaultAccountUpdate(address: PublicKey, tokenId?: Field) { return new AccountUpdate(Body.keepAll(address, tokenId)); } @@ -1659,15 +1667,10 @@ class UnfinishedForest { } function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { - let accountUpdate = - node.accountUpdate.hash !== undefined - ? new HashedAccountUpdate( - node.accountUpdate.hash, - Unconstrained.witness(() => - Provable.toConstant(AccountUpdate, node.accountUpdate.value) - ) - ) - : HashedAccountUpdate.hash(node.accountUpdate.value); + let accountUpdate = HashedAccountUpdate.hash( + node.accountUpdate.value, + node.accountUpdate.hash + ); let calls = node.children.finalize(); return { accountUpdate, isDummy: node.isDummy, calls }; } @@ -1736,6 +1739,7 @@ class AccountUpdateLayout { disattach(update: AccountUpdate) { let node = this.get(update); node?.siblings?.remove(node); + return node; } finalizeAndRemove(update: AccountUpdate) { diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index cb18a6c906..09950a2702 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -89,16 +89,16 @@ abstract class TokenContract extends SmartContract { /** * Approve a single account update (with arbitrarily many children). */ - approveAccountUpdate(accountUpdate: AccountUpdate) { - let forest = finalizeAccountUpdates([accountUpdate]); + approveAccountUpdate(accountUpdate: AccountUpdate | AccountUpdateTree) { + let forest = toForest([accountUpdate]); this.approveBase(forest); } /** * Approve a list of account updates (with arbitrarily many children). */ - approveAccountUpdates(accountUpdates: AccountUpdate[]) { - let forest = finalizeAccountUpdates(accountUpdates); + approveAccountUpdates(accountUpdates: (AccountUpdate | AccountUpdateTree)[]) { + let forest = toForest(accountUpdates); this.approveBase(forest); } @@ -127,19 +127,16 @@ abstract class TokenContract extends SmartContract { from.balanceChange = Int64.from(amount).neg(); to.balanceChange = Int64.from(amount); - let forest = finalizeAccountUpdates([from, to]); + let forest = toForest([from, to]); this.approveBase(forest); } } -function finalizeAccountUpdates(updates: AccountUpdate[]): AccountUpdateForest { - let trees = updates.map(finalizeAccountUpdate); +function toForest( + updates: (AccountUpdate | AccountUpdateTree)[] +): AccountUpdateForest { + let trees = updates.map((a) => + a instanceof AccountUpdate ? a.extractTree() : a + ); return AccountUpdateForest.from(trees); } - -function finalizeAccountUpdate(update: AccountUpdate): AccountUpdateTree { - let calls = accountUpdates()?.finalizeAndRemove(update); - calls ??= AccountUpdateForest.empty(); - - return { accountUpdate: HashedAccountUpdate.hash(update), calls }; -} diff --git a/src/lib/provable-types/packed.ts b/src/lib/provable-types/packed.ts index 9389de406a..d0b7ddef4f 100644 --- a/src/lib/provable-types/packed.ts +++ b/src/lib/provable-types/packed.ts @@ -212,9 +212,15 @@ class Hashed { /** * Wrap a value, and represent it by its hash in provable code. + * + * ```ts + * let hashed = HashedType.hash(value); + * ``` + * + * Optionally, if you already have the hash, you can pass it in and avoid recomputing it. */ - static hash(value: T): Hashed { - let hash = this._hash(value); + static hash(value: T, hash?: Field): Hashed { + hash ??= this._hash(value); let unconstrained = Unconstrained.witness(() => Provable.toConstant(this.innerProvable, value) ); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 7d2fbd7e23..dcba0348b9 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -844,7 +844,7 @@ super.init(); this.#executionState = { transactionId, accountUpdate }; return accountUpdate; } - // same as this.self, but explicitly creates a _new_ account update + /** * Same as `SmartContract.self` but explicitly creates a new {@link AccountUpdate}. */ From acbb260b92be14a97aea6ec1cf1bca75879bb802 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 22:27:19 +0100 Subject: [PATCH 53/69] minor --- src/lib/account_update.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index adab08e9b0..e25327ce57 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -790,11 +790,19 @@ class AccountUpdate implements Types.AccountUpdate { } /** - * Makes an {@link AccountUpdate} a child of this and approves it. + * Makes another {@link AccountUpdate} a child of this one. + * + * The parent-child relationship means that the child becomes part of the "statement" + * of the parent, and goes into the commitment that is authorized by either a signature + * or a proof. + * + * For a proof in particular, child account updates are contained in the public input + * of the proof that authorizes the parent account update. */ - approve(childUpdate: AccountUpdate) { - makeChildAccountUpdate(this, childUpdate); - accountUpdates()?.pushChild(this, childUpdate); + approve(child: AccountUpdate) { + child.body.callDepth = this.body.callDepth + 1; + accountUpdates()?.disattach(child); + accountUpdates()?.pushChild(this, child); } get balance() { @@ -1776,18 +1784,16 @@ class AccountUpdateLayout { } } +// TODO remove function createChildAccountUpdate( parent: AccountUpdate, childAddress: PublicKey, tokenId?: Field ) { let child = AccountUpdate.defaultAccountUpdate(childAddress, tokenId); - makeChildAccountUpdate(parent, child); - return child; -} -function makeChildAccountUpdate(parent: AccountUpdate, child: AccountUpdate) { child.body.callDepth = parent.body.callDepth + 1; AccountUpdate.unlink(child); + return child; } // authorization From d437c7e876eb3441b710e7a3a4128e2c7ff21b5b Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Feb 2024 22:44:35 +0100 Subject: [PATCH 54/69] not sure if this is a good idea yet --- src/lib/account_update.ts | 3 +++ src/lib/provable-types/auxiliary.ts | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/lib/provable-types/auxiliary.ts diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index e25327ce57..1b88d258d8 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -70,6 +70,7 @@ import { smartContractContext, } from './mina/smart-contract-context.js'; import { assert } from './util/assert.js'; +import { RandomId } from './provable-types/auxiliary.js'; // external API export { @@ -1392,10 +1393,12 @@ class HashedAccountUpdate extends Hashed.create( ) {} type AccountUpdateTree = { + // id: number; accountUpdate: Hashed; calls: MerkleListBase; }; const AccountUpdateTree: ProvableHashable = Struct({ + // id: RandomId, accountUpdate: HashedAccountUpdate.provable, calls: MerkleListBase(), }); diff --git a/src/lib/provable-types/auxiliary.ts b/src/lib/provable-types/auxiliary.ts new file mode 100644 index 0000000000..46ee535526 --- /dev/null +++ b/src/lib/provable-types/auxiliary.ts @@ -0,0 +1,13 @@ +import type { ProvableHashable } from '../hash.js'; + +export { RandomId }; + +const RandomId: ProvableHashable = { + sizeInFields: () => 0, + toFields: () => [], + toAuxiliary: (v = Math.random()) => [v], + fromFields: (_, [v]) => v, + check: () => {}, + toInput: () => ({}), + empty: () => Math.random(), +}; From fa935cc930f46f370e4222e05f44c2e2d4f18efd Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 09:33:18 +0100 Subject: [PATCH 55/69] refactor unfinished tree similarly --- src/lib/account_update.ts | 78 +++++++++++++++++++-------------------- src/lib/zkapp.ts | 4 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 1b88d258d8..bd1ec734b6 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1061,10 +1061,11 @@ class AccountUpdate implements Types.AccountUpdate { extractTree(): AccountUpdateTree { let layout = accountUpdates(); - let hash = layout?.get(this)?.accountUpdate.hash; + let hash = layout?.get(this)?.final?.hash; + let id = this.id; let calls = layout?.finalizeAndRemove(this) ?? AccountUpdateForest.empty(); let accountUpdate = HashedAccountUpdate.hash(this, hash); - return { accountUpdate, calls }; + return { accountUpdate, id, calls }; } static defaultAccountUpdate(address: PublicKey, tokenId?: Field) { @@ -1393,12 +1394,12 @@ class HashedAccountUpdate extends Hashed.create( ) {} type AccountUpdateTree = { - // id: number; + id: number; accountUpdate: Hashed; calls: MerkleListBase; }; const AccountUpdateTree: ProvableHashable = Struct({ - // id: RandomId, + id: RandomId, accountUpdate: HashedAccountUpdate.provable, calls: MerkleListBase(), }); @@ -1445,7 +1446,7 @@ class AccountUpdateForest extends MerkleList.create( let nodes = simpleForest.map((node) => { let accountUpdate = HashedAccountUpdate.hash(node.accountUpdate); let calls = AccountUpdateForest.fromSimpleForest(node.children); - return { accountUpdate, calls }; + return { accountUpdate, calls, id: node.accountUpdate.id }; }); return AccountUpdateForest.from(nodes); } @@ -1490,21 +1491,28 @@ function hashCons(forestHash: Field, nodeHash: Field) { * everything immediately. Instead, we maintain a structure consisting of either hashes or full account * updates that can be hashed into a final call forest at the end. * - * `UnfinishedForest` behaves like a tagged enum type: + * `UnfinishedForest` and `UnfinishedTree` behave like a tagged enum type: * ``` * type UnfinishedForest = * | Mutable of UnfinishedTree[] * | Final of AccountUpdateForest; + * + * type UnfinishedTree = ( + * | Mutable of AccountUpdate + * | Final of HashedAccountUpdate) + * ) & { children: UnfinishedForest, ... } * ``` */ type UnfinishedTree = { - // TODO it would be nice to make this closer to `Final of Hashed | Mutable of AccountUpdate` - accountUpdate: { hash?: Field; value: AccountUpdate }; + id: number; isDummy: Bool; // `children` must be readonly since it's referenced in each child's siblings readonly children: UnfinishedForest; siblings?: UnfinishedForest; -}; +} & ( + | { final: HashedAccountUpdate; mutable?: undefined } + | { final?: undefined; mutable: AccountUpdate } +); type UnfinishedForestFinal = UnfinishedForest & { final: AccountUpdateForest; @@ -1576,15 +1584,11 @@ class UnfinishedForest { this.mutable.push(node); } - // TODO this isn't quite right - shouldn't have to save the value in a circuit-accessible way if it's hashed pushTree(tree: AccountUpdateTree) { assert(this.isMutable(), 'Cannot push to an immutable forest'); - let value = AccountUpdate.dummy(); - Provable.asProver(() => { - value = tree.accountUpdate.value.get(); - }); this.mutable.push({ - accountUpdate: { hash: tree.accountUpdate.hash, value }, + final: tree.accountUpdate, + id: tree.id, isDummy: Bool(false), children: UnfinishedForest.fromForest(tree.calls), siblings: this, @@ -1594,9 +1598,7 @@ class UnfinishedForest { remove(node: UnfinishedTree) { assert(this.isMutable(), 'Cannot remove from an immutable forest'); // find by .id - let index = this.mutable.findIndex( - (n) => n.accountUpdate.value.id === node.accountUpdate.value.id - ); + let index = this.mutable.findIndex((n) => n.id === node.id); // nothing to do if it's not there if (index === -1) return; @@ -1627,7 +1629,7 @@ class UnfinishedForest { let flatUpdates: AccountUpdate[] = []; for (let node of this.mutable) { if (node.isDummy.toBoolean()) continue; - let update = node.accountUpdate.value; + let update = node.mutable ?? node.final.value.get(); if (mutate) update.body.callDepth = depth; let children = node.children.toFlatArray(mutate, depth + 1); flatUpdates.push(update, ...children); @@ -1642,14 +1644,12 @@ class UnfinishedForest { } assert(this.isMutable(), 'final or mutable'); for (let node of this.mutable) { - node.accountUpdate.value = Provable.toConstant( - AccountUpdate, - node.accountUpdate.value - ); - node.isDummy = Provable.toConstant(Bool, node.isDummy); - if (node.accountUpdate.hash !== undefined) { - node.accountUpdate.hash = node.accountUpdate.hash.toConstant(); + if (node.mutable !== undefined) { + node.mutable = Provable.toConstant(AccountUpdate, node.mutable); + } else { + node.final.hash = node.final.hash.toConstant(); } + node.isDummy = Provable.toConstant(Bool, node.isDummy); node.children.toConstantInPlace(); } } @@ -1663,10 +1663,11 @@ class UnfinishedForest { assert(a.isMutable(), 'final or mutable'); indent += 2; for (let tree of a.mutable) { - layout += - ' '.repeat(indent) + - `( ${tree.accountUpdate.value.label || ''} )` + - '\n'; + let label = tree.mutable?.label || ''; + if (tree.final !== undefined) { + Provable.asProver(() => (label = tree.final!.value.get().label)); + } + layout += ' '.repeat(indent) + `( ${label} )` + '\n'; toPretty(tree.children); } indent -= 2; @@ -1678,12 +1679,9 @@ class UnfinishedForest { } function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { - let accountUpdate = HashedAccountUpdate.hash( - node.accountUpdate.value, - node.accountUpdate.hash - ); + let accountUpdate = node.final ?? HashedAccountUpdate.hash(node.mutable); let calls = node.children.finalize(); - return { accountUpdate, isDummy: node.isDummy, calls }; + return { accountUpdate, id: node.id, isDummy: node.isDummy, calls }; } class AccountUpdateLayout { @@ -1695,7 +1693,8 @@ class AccountUpdateLayout { this.map = new Map(); root ??= AccountUpdate.dummy(); let rootTree: UnfinishedTree = { - accountUpdate: { value: root }, + mutable: root, + id: root.id, isDummy: Bool(false), children: UnfinishedForest.empty(), }; @@ -1709,15 +1708,16 @@ class AccountUpdateLayout { private getOrCreate(update: AccountUpdate | UnfinishedTree): UnfinishedTree { if (!(update instanceof AccountUpdate)) { - if (!this.map.has(update.accountUpdate.value.id)) { - this.map.set(update.accountUpdate.value.id, update); + if (!this.map.has(update.id)) { + this.map.set(update.id, update); } return update; } let node = this.map.get(update.id); if (node !== undefined) return node; node = { - accountUpdate: { value: update }, + mutable: update, + id: update.id, isDummy: update.isDummy(), children: UnfinishedForest.empty(), }; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index dcba0348b9..24cfbaf882 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1638,8 +1638,8 @@ function diffRecursive( let proverChildren = accountUpdates()!.get(prover)!.children.mutable!; let nChildren = inputChildren.length; for (let i = 0; i < nChildren; i++) { - let inputChild = inputChildren[i].accountUpdate.value; - let child = proverChildren[i].accountUpdate.value; + let inputChild = inputChildren[i].mutable; + let child = proverChildren[i].mutable; if (!inputChild || !child) return; diffRecursive(child, { transaction, index, accountUpdate: inputChild }); } From 44bf6c9835bf8623508c10b359dd759705a55a02 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 09:33:58 +0100 Subject: [PATCH 56/69] fix account update create signed outside tx --- src/lib/account_update.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index bd1ec734b6..c0eb98faf4 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -982,8 +982,8 @@ class AccountUpdate implements Types.AccountUpdate { if (isSameAsFeePayer) nonce++; // now, we check how often this account update already updated its nonce in // this tx, and increase nonce from `getAccount` by that amount - let layout = currentTransaction.get().layout; - layout.forEachPredecessor(update as AccountUpdate, (otherUpdate) => { + let layout = currentTransaction()?.layout; + layout?.forEachPredecessor(update as AccountUpdate, (otherUpdate) => { let shouldIncreaseNonce = otherUpdate.publicKey .equals(publicKey) .and(otherUpdate.tokenId.equals(tokenId)) From 0570bfb81bd5722bba6d8188535b2289de5cd667 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 09:34:10 +0100 Subject: [PATCH 57/69] another test case for token contract --- .../mina/token/token-contract.unit-test.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/lib/mina/token/token-contract.unit-test.ts b/src/lib/mina/token/token-contract.unit-test.ts index 2c9c059946..bcbe2bc882 100644 --- a/src/lib/mina/token/token-contract.unit-test.ts +++ b/src/lib/mina/token/token-contract.unit-test.ts @@ -40,7 +40,7 @@ Mina.setActiveInstance(Local); let [ { publicKey: sender, privateKey: senderKey }, { publicKey: tokenAddress, privateKey: tokenKey }, - { publicKey: otherAddress }, + { publicKey: otherAddress, privateKey: otherKey }, ] = Local.testAccounts; let token = new ExampleTokenContract(tokenAddress); @@ -85,3 +85,20 @@ await assert.rejects( () => Mina.transaction(sender, () => token.approveBase(forest)), /Field\.assertEquals\(\): 1 != 0/ ); + +// succeeds to approve deep account update tree with zero balance sum +let update4 = AccountUpdate.createSigned(otherAddress, tokenId); +update4.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; +update4.balanceChange = Int64.minusOne; +update4.body.callDepth = 2; + +forest = AccountUpdateForest.fromFlatArray([ + update1, + update2, + update3, + update4, +]); + +let approveTx = await Mina.transaction(sender, () => token.approveBase(forest)); +await approveTx.prove(); +await approveTx.sign([senderKey, otherKey]).send(); From e62be7e8284380f241cbf3712df1baf895ae68e8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 10:46:17 +0100 Subject: [PATCH 58/69] approve a whole tree --- src/lib/account_update.ts | 75 +++++++++++++++---- .../mina/account-update-layout.unit-test.ts | 41 +++++++++- src/lib/zkapp.ts | 9 ++- 3 files changed, 103 insertions(+), 22 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index c0eb98faf4..d1f41c7984 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -800,8 +800,10 @@ class AccountUpdate implements Types.AccountUpdate { * For a proof in particular, child account updates are contained in the public input * of the proof that authorizes the parent account update. */ - approve(child: AccountUpdate) { - child.body.callDepth = this.body.callDepth + 1; + approve(child: AccountUpdate | AccountUpdateTree) { + if (child instanceof AccountUpdate) { + child.body.callDepth = this.body.callDepth + 1; + } accountUpdates()?.disattach(child); accountUpdates()?.pushChild(this, child); } @@ -1659,7 +1661,10 @@ class UnfinishedForest { let layout = ''; let toPretty = (a: UnfinishedForest) => { - if (a.isFinal()) layout += ' '.repeat(indent) + ' ( finalized forest )\n'; + if (a.isFinal()) { + layout += ' '.repeat(indent) + ' ( finalized forest )\n'; + return; + } assert(a.isMutable(), 'final or mutable'); indent += 2; for (let tree of a.mutable) { @@ -1684,6 +1689,12 @@ function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { return { accountUpdate, id: node.id, isDummy: node.isDummy, calls }; } +function isUnfinished( + input: AccountUpdate | AccountUpdateTree | UnfinishedTree +): input is UnfinishedTree { + return 'final' in input || 'mutable' in input; +} + class AccountUpdateLayout { readonly map: Map; readonly root: UnfinishedTree; @@ -1702,30 +1713,62 @@ class AccountUpdateLayout { this.root = rootTree; } - get(update: AccountUpdate) { + get(update: AccountUpdate | AccountUpdateTree) { return this.map.get(update.id); } - private getOrCreate(update: AccountUpdate | UnfinishedTree): UnfinishedTree { - if (!(update instanceof AccountUpdate)) { + private getOrCreate( + update: AccountUpdate | AccountUpdateTree | UnfinishedTree + ): UnfinishedTree { + if (isUnfinished(update)) { if (!this.map.has(update.id)) { this.map.set(update.id, update); } return update; } let node = this.map.get(update.id); - if (node !== undefined) return node; - node = { - mutable: update, - id: update.id, - isDummy: update.isDummy(), - children: UnfinishedForest.empty(), - }; + if (node !== undefined) { + // might have to change node + if (update instanceof AccountUpdate) { + if (node.final !== undefined) { + Object.assign(node, { + mutable: update, + final: undefined, + children: UnfinishedForest.empty(), + }); + } + } else if (node.mutable !== undefined) { + Object.assign(node, { + mutable: undefined, + final: update.accountUpdate, + children: UnfinishedForest.fromForest(update.calls), + }); + } + return node; + } + if (update instanceof AccountUpdate) { + node = { + mutable: update, + id: update.id, + isDummy: update.isDummy(), + children: UnfinishedForest.empty(), + }; + } else { + node = { + final: update.accountUpdate, + id: update.id, + isDummy: Bool(false), + children: UnfinishedForest.fromForest(update.calls), + }; + } this.map.set(update.id, node); return node; } - pushChild(parent: AccountUpdate | UnfinishedTree, child: AccountUpdate) { + pushChild( + parent: AccountUpdate | UnfinishedTree, + child: AccountUpdate | AccountUpdateTree + ) { let parentNode = this.getOrCreate(parent); let childNode = this.getOrCreate(child); parentNode.children.push(childNode); @@ -1747,13 +1790,13 @@ class AccountUpdateLayout { this.setChildren(this.root, children); } - disattach(update: AccountUpdate) { + disattach(update: AccountUpdate | AccountUpdateTree) { let node = this.get(update); node?.siblings?.remove(node); return node; } - finalizeAndRemove(update: AccountUpdate) { + finalizeAndRemove(update: AccountUpdate | AccountUpdateTree) { let node = this.get(update); if (node === undefined) return; this.disattach(update); diff --git a/src/lib/mina/account-update-layout.unit-test.ts b/src/lib/mina/account-update-layout.unit-test.ts index f75f503194..71638976f2 100644 --- a/src/lib/mina/account-update-layout.unit-test.ts +++ b/src/lib/mina/account-update-layout.unit-test.ts @@ -1,5 +1,10 @@ import { Mina } from '../../index.js'; -import { AccountUpdate } from '../account_update.js'; +import { + AccountUpdate, + AccountUpdateForest, + AccountUpdateTree, + HashedAccountUpdate, +} from '../account_update.js'; import { UInt64 } from '../int.js'; import { SmartContract, method } from '../zkapp.js'; @@ -7,9 +12,30 @@ import { SmartContract, method } from '../zkapp.js'; class NestedCall extends SmartContract { @method deposit() { - const payerUpdate = AccountUpdate.createSigned(this.sender); + let payerUpdate = AccountUpdate.createSigned(this.sender); payerUpdate.send({ to: this.address, amount: UInt64.one }); } + + @method depositUsingTree() { + let payerUpdate = AccountUpdate.createSigned(this.sender); + let receiverUpdate = AccountUpdate.defaultAccountUpdate(this.address); + payerUpdate.send({ to: receiverUpdate, amount: UInt64.one }); + + // TODO make this super easy + let calls = AccountUpdateForest.empty(); + let tree: AccountUpdateTree = { + accountUpdate: HashedAccountUpdate.hash(payerUpdate), + id: payerUpdate.id, + calls, + }; + calls.push({ + accountUpdate: HashedAccountUpdate.hash(receiverUpdate), + id: receiverUpdate.id, + calls: AccountUpdateForest.empty(), + }); + + this.approve(tree); + } } // setup @@ -41,3 +67,14 @@ await depositTx.prove(); await depositTx.sign([senderKey]).send(); Mina.getBalance(zkappAddress).assertEquals(balanceBefore.add(1)); + +// deposit call using tree + +balanceBefore = balanceBefore.add(1); + +depositTx = await Mina.transaction(sender, () => zkapp.depositUsingTree()); +console.log(depositTx.toPretty()); +await depositTx.prove(); +await depositTx.sign([senderKey]).send(); + +Mina.getBalance(zkappAddress).assertEquals(balanceBefore.add(1)); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 24cfbaf882..b604efabca 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -15,6 +15,7 @@ import { LazyProof, AccountUpdateForest, AccountUpdateLayout, + AccountUpdateTree, } from './account_update.js'; import { cloneCircuitValue, @@ -930,11 +931,11 @@ super.init(); * @param updateOrCallback * @returns The account update that was approved (needed when passing in a Callback) */ - approve(updateOrCallback: AccountUpdate | Callback) { + approve(updateOrCallback: AccountUpdate | AccountUpdateTree | Callback) { let accountUpdate = - updateOrCallback instanceof AccountUpdate - ? updateOrCallback - : Provable.witness(AccountUpdate, () => updateOrCallback.accountUpdate); + updateOrCallback instanceof Callback + ? Provable.witness(AccountUpdate, () => updateOrCallback.accountUpdate) + : updateOrCallback; this.self.approve(accountUpdate); return accountUpdate; } From ddde1de2de62a5029d7cfa1d644fc4da2dda1a18 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 11:04:20 +0100 Subject: [PATCH 59/69] proper type for packed/hashed.provable --- src/lib/provable-types/packed.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/lib/provable-types/packed.ts b/src/lib/provable-types/packed.ts index d0b7ddef4f..c770b28145 100644 --- a/src/lib/provable-types/packed.ts +++ b/src/lib/provable-types/packed.ts @@ -52,7 +52,9 @@ class Packed { /** * Create a packed representation of `type`. You can then use `PackedType.pack(x)` to pack a value. */ - static create(type: ProvableExtended): typeof Packed { + static create(type: ProvableExtended): typeof Packed & { + provable: ProvableHashable>; + } { // compute size of packed representation let input = type.toInput(type.empty()); let packedSize = countFields(input); @@ -67,6 +69,11 @@ class Packed { static empty(): Packed { return Packed_.pack(type.empty()); } + + static get provable() { + assert(this._provable !== undefined, 'Packed not initialized'); + return this._provable; + } }; } @@ -118,10 +125,6 @@ class Packed { return this.constructor as typeof Packed; } - static get provable(): ProvableHashable> { - assert(this._provable !== undefined, 'Packed not initialized'); - return this._provable; - } static get innerProvable(): ProvableExtended { assert(this._innerProvable !== undefined, 'Packed not initialized'); return this._innerProvable; @@ -181,7 +184,9 @@ class Hashed { static create( type: ProvableHashable, hash?: (t: T) => Field - ): typeof Hashed { + ): typeof Hashed & { + provable: ProvableHashable>; + } { let _hash = hash ?? ((t: T) => Poseidon.hashPacked(type, t)); let dummyHash = _hash(type.empty()); @@ -198,6 +203,11 @@ class Hashed { static empty(): Hashed { return new this(dummyHash, Unconstrained.from(type.empty())); } + + static get provable() { + assert(this._provable !== undefined, 'Hashed not initialized'); + return this._provable; + } }; } @@ -254,10 +264,6 @@ class Hashed { return this.constructor as typeof Hashed; } - static get provable(): ProvableHashable> { - assert(this._provable !== undefined, 'Hashed not initialized'); - return this._provable; - } static get innerProvable(): ProvableHashable { assert(this._innerProvable !== undefined, 'Hashed not initialized'); return this._innerProvable; From fe85eb088c85a6dc6ed88e85185b42b596df5371 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 11:04:40 +0100 Subject: [PATCH 60/69] struct how it should be --- src/lib/circuit_value.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index ab6f802684..d1d24e9959 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -32,6 +32,7 @@ export { Struct, FlexibleProvable, FlexibleProvablePure, + Unconstrained, }; // internal API @@ -45,7 +46,7 @@ export { HashInput, InferJson, InferredProvable, - Unconstrained, + StructNoJson, }; type ProvableExtension = { @@ -477,6 +478,24 @@ function Struct< return Struct_ as any; } +function StructNoJson< + A, + T extends InferProvable = InferProvable, + Pure extends boolean = IsPure +>( + type: A +): (new (value: T) => T) & { _isStruct: true } & (Pure extends true + ? ProvablePure + : Provable) & { + toInput: (x: T) => { + fields?: Field[] | undefined; + packed?: [Field, number][] | undefined; + }; + empty: () => T; + } { + return Struct(type) satisfies Provable as any; +} + /** * Container which holds an unconstrained value. This can be used to pass values * between the out-of-circuit blocks in provable code. From e12edc73ef08c78aa03dd57f5248bd9c2b7e7acd Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 11:52:21 +0100 Subject: [PATCH 61/69] make account update tree a usable class --- src/index.ts | 1 + src/lib/account_update.ts | 98 ++++++++++++++----- .../mina/account-update-layout.unit-test.ts | 17 +--- src/lib/mina/token/forest-iterator.ts | 4 +- 4 files changed, 82 insertions(+), 38 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3d3e7ca924..97240671ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,6 +76,7 @@ export { ZkappPublicInput, TransactionVersion, AccountUpdateForest, + AccountUpdateTree, } from './lib/account_update.js'; export { TokenAccountUpdateIterator } from './lib/mina/token/forest-iterator.js'; diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index d1f41c7984..ed751763be 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -3,8 +3,7 @@ import { FlexibleProvable, provable, provablePure, - Struct, - Unconstrained, + StructNoJson, } from './circuit_value.js'; import { memoizationContext, memoizeWitness, Provable } from './provable.js'; import { Field, Bool } from './core.js'; @@ -35,12 +34,7 @@ import { Actions, } from '../bindings/mina-transaction/transaction-leaves.js'; import { TokenId as Base58TokenId } from './base58-encodings.js'; -import { - hashWithPrefix, - packToFields, - Poseidon, - ProvableHashable, -} from './hash.js'; +import { hashWithPrefix, packToFields, Poseidon } from './hash.js'; import { mocks, prefixes, @@ -79,6 +73,7 @@ export { ZkappPublicInput, TransactionVersion, AccountUpdateForest, + AccountUpdateTree, }; // internal API export { @@ -100,7 +95,7 @@ export { zkAppProver, dummySignature, LazyProof, - AccountUpdateTree, + AccountUpdateTreeBase, AccountUpdateLayout, hashAccountUpdate, HashedAccountUpdate, @@ -1067,7 +1062,7 @@ class AccountUpdate implements Types.AccountUpdate { let id = this.id; let calls = layout?.finalizeAndRemove(this) ?? AccountUpdateForest.empty(); let accountUpdate = HashedAccountUpdate.hash(this, hash); - return { accountUpdate, id, calls }; + return new AccountUpdateTree({ accountUpdate, id, calls }); } static defaultAccountUpdate(address: PublicKey, tokenId?: Field) { @@ -1395,15 +1390,17 @@ class HashedAccountUpdate extends Hashed.create( hashAccountUpdate ) {} -type AccountUpdateTree = { +type AccountUpdateTreeBase = { id: number; accountUpdate: Hashed; - calls: MerkleListBase; + calls: AccountUpdateForestBase; }; -const AccountUpdateTree: ProvableHashable = Struct({ +type AccountUpdateForestBase = MerkleListBase; + +const AccountUpdateTreeBase = StructNoJson({ id: RandomId, accountUpdate: HashedAccountUpdate.provable, - calls: MerkleListBase(), + calls: MerkleListBase(), }); /** @@ -1420,7 +1417,7 @@ const AccountUpdateTree: ProvableHashable = Struct({ * ``` */ class AccountUpdateForest extends MerkleList.create( - AccountUpdateTree, + AccountUpdateTreeBase, merkleListHash ) { static fromFlatArray(updates: AccountUpdate[]): AccountUpdateForest { @@ -1428,7 +1425,7 @@ class AccountUpdateForest extends MerkleList.create( return this.fromSimpleForest(simpleForest); } static toFlatArray( - forest: MerkleListBase, + forest: AccountUpdateForestBase, mutate = true, depth = 0 ) { @@ -1454,7 +1451,7 @@ class AccountUpdateForest extends MerkleList.create( } // TODO this comes from paranoia and might be removed later - static assertConstant(forest: MerkleListBase) { + static assertConstant(forest: AccountUpdateForestBase) { Provable.asProver(() => { forest.data.get().forEach(({ element: tree }) => { assert( @@ -1467,13 +1464,68 @@ class AccountUpdateForest extends MerkleList.create( } } +/** + * Class which represents a tree of account updates, + * in a compressed way which allows iterating and selectively witnessing the account updates. + * + * The (recursive) type signature is: + * ``` + * type AccountUpdateTree = { + * accountUpdate: Hashed; + * calls: AccountUpdateForest; + * }; + * type AccountUpdateForest = MerkleList; + * ``` + */ +class AccountUpdateTree extends StructNoJson({ + id: RandomId, + accountUpdate: HashedAccountUpdate.provable, + calls: AccountUpdateForest.provable, +}) { + /** + * Create a tree of account updates which only consists of a root. + */ + static from(update: AccountUpdate, hash?: Field) { + return new AccountUpdateTree({ + accountUpdate: HashedAccountUpdate.hash(update, hash), + id: update.id, + calls: AccountUpdateForest.empty(), + }); + } + + /** + * Add an {@link AccountUpdate} or {@link AccountUpdateTree} to the children of this tree's root. + * + * See {@link AccountUpdate.approve}. + */ + approve(update: AccountUpdate | AccountUpdateTree, hash?: Field) { + accountUpdates()?.disattach(update); + if (update instanceof AccountUpdate) { + this.calls.pushIf( + update.isDummy().not(), + AccountUpdateTree.from(update, hash) + ); + } else { + this.calls.push(update); + } + } + + // fix Struct type + static fromFields(fields: Field[], aux: any) { + return new AccountUpdateTree(super.fromFields(fields, aux)); + } + static empty() { + return new AccountUpdateTree(super.empty()); + } +} + // how to hash a forest -function merkleListHash(forestHash: Field, tree: AccountUpdateTree) { +function merkleListHash(forestHash: Field, tree: AccountUpdateTreeBase) { return hashCons(forestHash, hashNode(tree)); } -function hashNode(tree: AccountUpdateTree) { +function hashNode(tree: AccountUpdateTreeBase) { return Poseidon.hashWithPrefix(prefixes.accountUpdateNode, [ tree.accountUpdate.hash, tree.calls.hash, @@ -1610,7 +1662,7 @@ class UnfinishedForest { this.mutable.splice(index, 1); } - setToForest(forest: MerkleListBase) { + setToForest(forest: AccountUpdateForestBase) { if (this.isMutable()) { assert( this.mutable.length === 0, @@ -1620,7 +1672,7 @@ class UnfinishedForest { return this.setFinal(new AccountUpdateForest(forest)); } - static fromForest(forest: MerkleListBase) { + static fromForest(forest: AccountUpdateForestBase) { return UnfinishedForest.empty().setToForest(forest); } @@ -1683,7 +1735,9 @@ class UnfinishedForest { } } -function toTree(node: UnfinishedTree): AccountUpdateTree & { isDummy: Bool } { +function toTree( + node: UnfinishedTree +): AccountUpdateTreeBase & { isDummy: Bool } { let accountUpdate = node.final ?? HashedAccountUpdate.hash(node.mutable); let calls = node.children.finalize(); return { accountUpdate, id: node.id, isDummy: node.isDummy, calls }; diff --git a/src/lib/mina/account-update-layout.unit-test.ts b/src/lib/mina/account-update-layout.unit-test.ts index 71638976f2..76964ae423 100644 --- a/src/lib/mina/account-update-layout.unit-test.ts +++ b/src/lib/mina/account-update-layout.unit-test.ts @@ -18,22 +18,11 @@ class NestedCall extends SmartContract { @method depositUsingTree() { let payerUpdate = AccountUpdate.createSigned(this.sender); - let receiverUpdate = AccountUpdate.defaultAccountUpdate(this.address); + let receiverUpdate = AccountUpdate.create(this.address); payerUpdate.send({ to: receiverUpdate, amount: UInt64.one }); - // TODO make this super easy - let calls = AccountUpdateForest.empty(); - let tree: AccountUpdateTree = { - accountUpdate: HashedAccountUpdate.hash(payerUpdate), - id: payerUpdate.id, - calls, - }; - calls.push({ - accountUpdate: HashedAccountUpdate.hash(receiverUpdate), - id: receiverUpdate.id, - calls: AccountUpdateForest.empty(), - }); - + let tree = AccountUpdateTree.from(payerUpdate); + tree.approve(receiverUpdate); this.approve(tree); } } diff --git a/src/lib/mina/token/forest-iterator.ts b/src/lib/mina/token/forest-iterator.ts index 441ae00951..8d0949e7a4 100644 --- a/src/lib/mina/token/forest-iterator.ts +++ b/src/lib/mina/token/forest-iterator.ts @@ -1,7 +1,7 @@ import { AccountUpdate, AccountUpdateForest, - AccountUpdateTree, + AccountUpdateTreeBase, TokenId, } from '../../account_update.js'; import { Field } from '../../core.js'; @@ -57,7 +57,7 @@ class TokenAccountUpdateIterator { selfToken: Field; constructor( - forest: MerkleListIterator, + forest: MerkleListIterator, mayUseToken: MayUseToken, selfToken: Field ) { From 24f9fb7315fa8da9789a3f83cdaa61d58cf701f6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 12:02:13 +0100 Subject: [PATCH 62/69] allow approving forests and some other polish --- src/lib/account_update.ts | 9 +++++++-- src/lib/mina/account-update-layout.unit-test.ts | 7 +------ src/lib/mina/token/token-contract.ts | 8 ++++---- src/lib/zkapp.ts | 8 +++++++- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index ed751763be..28e0d97701 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -795,7 +795,11 @@ class AccountUpdate implements Types.AccountUpdate { * For a proof in particular, child account updates are contained in the public input * of the proof that authorizes the parent account update. */ - approve(child: AccountUpdate | AccountUpdateTree) { + approve(child: AccountUpdate | AccountUpdateTree | AccountUpdateForest) { + if (child instanceof AccountUpdateForest) { + accountUpdates()?.setChildren(this, child); + return; + } if (child instanceof AccountUpdate) { child.body.callDepth = this.body.callDepth + 1; } @@ -1485,7 +1489,8 @@ class AccountUpdateTree extends StructNoJson({ /** * Create a tree of account updates which only consists of a root. */ - static from(update: AccountUpdate, hash?: Field) { + static from(update: AccountUpdate | AccountUpdateTree, hash?: Field) { + if (update instanceof AccountUpdateTree) return update; return new AccountUpdateTree({ accountUpdate: HashedAccountUpdate.hash(update, hash), id: update.id, diff --git a/src/lib/mina/account-update-layout.unit-test.ts b/src/lib/mina/account-update-layout.unit-test.ts index 76964ae423..7fd21e50b0 100644 --- a/src/lib/mina/account-update-layout.unit-test.ts +++ b/src/lib/mina/account-update-layout.unit-test.ts @@ -1,10 +1,5 @@ import { Mina } from '../../index.js'; -import { - AccountUpdate, - AccountUpdateForest, - AccountUpdateTree, - HashedAccountUpdate, -} from '../account_update.js'; +import { AccountUpdate, AccountUpdateTree } from '../account_update.js'; import { UInt64 } from '../int.js'; import { SmartContract, method } from '../zkapp.js'; diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 09950a2702..f1fe0a0fc1 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -49,23 +49,23 @@ abstract class TokenContract extends SmartContract { updates: AccountUpdateForest, callback: (update: AccountUpdate, usesToken: Bool) => void ) { - let forest = TokenAccountUpdateIterator.create(updates, this.token.id); + let iterator = TokenAccountUpdateIterator.create(updates, this.token.id); // iterate through the forest and apply user-defined logc for (let i = 0; i < MAX_ACCOUNT_UPDATES; i++) { - let { accountUpdate, usesThisToken } = forest.next(); + let { accountUpdate, usesThisToken } = iterator.next(); callback(accountUpdate, usesThisToken); } // prove that we checked all updates - forest.assertFinished( + iterator.assertFinished( `Number of account updates to approve exceed ` + `the supported limit of ${MAX_ACCOUNT_UPDATES}.\n` ); // skip hashing our child account updates in the method wrapper // since we just did that in the loop above - accountUpdates()?.setTopLevel(updates); + this.approve(updates); } /** diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index b604efabca..0c001e7c79 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -931,7 +931,13 @@ super.init(); * @param updateOrCallback * @returns The account update that was approved (needed when passing in a Callback) */ - approve(updateOrCallback: AccountUpdate | AccountUpdateTree | Callback) { + approve( + updateOrCallback: + | AccountUpdate + | AccountUpdateTree + | AccountUpdateForest + | Callback + ) { let accountUpdate = updateOrCallback instanceof Callback ? Provable.witness(AccountUpdate, () => updateOrCallback.accountUpdate) From adc23a2530dacf6058d2f3ad102b8e93c8b47559 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 12:13:02 +0100 Subject: [PATCH 63/69] calls -> children --- src/lib/account_update.ts | 41 ++++++++++++++------------- src/lib/mina/token/forest-iterator.ts | 4 +-- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 28e0d97701..aebba25314 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1064,9 +1064,10 @@ class AccountUpdate implements Types.AccountUpdate { let layout = accountUpdates(); let hash = layout?.get(this)?.final?.hash; let id = this.id; - let calls = layout?.finalizeAndRemove(this) ?? AccountUpdateForest.empty(); + let children = + layout?.finalizeAndRemove(this) ?? AccountUpdateForest.empty(); let accountUpdate = HashedAccountUpdate.hash(this, hash); - return new AccountUpdateTree({ accountUpdate, id, calls }); + return new AccountUpdateTree({ accountUpdate, id, children }); } static defaultAccountUpdate(address: PublicKey, tokenId?: Field) { @@ -1397,14 +1398,14 @@ class HashedAccountUpdate extends Hashed.create( type AccountUpdateTreeBase = { id: number; accountUpdate: Hashed; - calls: AccountUpdateForestBase; + children: AccountUpdateForestBase; }; type AccountUpdateForestBase = MerkleListBase; const AccountUpdateTreeBase = StructNoJson({ id: RandomId, accountUpdate: HashedAccountUpdate.provable, - calls: MerkleListBase(), + children: MerkleListBase(), }); /** @@ -1416,7 +1417,7 @@ const AccountUpdateTreeBase = StructNoJson({ * type AccountUpdateForest = MerkleList; * type AccountUpdateTree = { * accountUpdate: Hashed; - * calls: AccountUpdateForest; + * children: AccountUpdateForest; * }; * ``` */ @@ -1438,7 +1439,7 @@ class AccountUpdateForest extends MerkleList.create( let update = tree.accountUpdate.value.get(); if (mutate) update.body.callDepth = depth; flat.push(update); - flat.push(...this.toFlatArray(tree.calls, mutate, depth + 1)); + flat.push(...this.toFlatArray(tree.children, mutate, depth + 1)); } return flat; } @@ -1448,8 +1449,8 @@ class AccountUpdateForest extends MerkleList.create( ): AccountUpdateForest { let nodes = simpleForest.map((node) => { let accountUpdate = HashedAccountUpdate.hash(node.accountUpdate); - let calls = AccountUpdateForest.fromSimpleForest(node.children); - return { accountUpdate, calls, id: node.accountUpdate.id }; + let children = AccountUpdateForest.fromSimpleForest(node.children); + return { accountUpdate, children, id: node.accountUpdate.id }; }); return AccountUpdateForest.from(nodes); } @@ -1462,7 +1463,7 @@ class AccountUpdateForest extends MerkleList.create( Provable.isConstant(AccountUpdate, tree.accountUpdate.value.get()), 'account update not constant' ); - AccountUpdateForest.assertConstant(tree.calls); + AccountUpdateForest.assertConstant(tree.children); }); }); } @@ -1476,7 +1477,7 @@ class AccountUpdateForest extends MerkleList.create( * ``` * type AccountUpdateTree = { * accountUpdate: Hashed; - * calls: AccountUpdateForest; + * children: AccountUpdateForest; * }; * type AccountUpdateForest = MerkleList; * ``` @@ -1484,7 +1485,7 @@ class AccountUpdateForest extends MerkleList.create( class AccountUpdateTree extends StructNoJson({ id: RandomId, accountUpdate: HashedAccountUpdate.provable, - calls: AccountUpdateForest.provable, + children: AccountUpdateForest.provable, }) { /** * Create a tree of account updates which only consists of a root. @@ -1494,7 +1495,7 @@ class AccountUpdateTree extends StructNoJson({ return new AccountUpdateTree({ accountUpdate: HashedAccountUpdate.hash(update, hash), id: update.id, - calls: AccountUpdateForest.empty(), + children: AccountUpdateForest.empty(), }); } @@ -1506,12 +1507,12 @@ class AccountUpdateTree extends StructNoJson({ approve(update: AccountUpdate | AccountUpdateTree, hash?: Field) { accountUpdates()?.disattach(update); if (update instanceof AccountUpdate) { - this.calls.pushIf( + this.children.pushIf( update.isDummy().not(), AccountUpdateTree.from(update, hash) ); } else { - this.calls.push(update); + this.children.push(update); } } @@ -1533,7 +1534,7 @@ function merkleListHash(forestHash: Field, tree: AccountUpdateTreeBase) { function hashNode(tree: AccountUpdateTreeBase) { return Poseidon.hashWithPrefix(prefixes.accountUpdateNode, [ tree.accountUpdate.hash, - tree.calls.hash, + tree.children.hash, ]); } function hashCons(forestHash: Field, nodeHash: Field) { @@ -1649,7 +1650,7 @@ class UnfinishedForest { final: tree.accountUpdate, id: tree.id, isDummy: Bool(false), - children: UnfinishedForest.fromForest(tree.calls), + children: UnfinishedForest.fromForest(tree.children), siblings: this, }); } @@ -1744,8 +1745,8 @@ function toTree( node: UnfinishedTree ): AccountUpdateTreeBase & { isDummy: Bool } { let accountUpdate = node.final ?? HashedAccountUpdate.hash(node.mutable); - let calls = node.children.finalize(); - return { accountUpdate, id: node.id, isDummy: node.isDummy, calls }; + let children = node.children.finalize(); + return { accountUpdate, id: node.id, isDummy: node.isDummy, children }; } function isUnfinished( @@ -1800,7 +1801,7 @@ class AccountUpdateLayout { Object.assign(node, { mutable: undefined, final: update.accountUpdate, - children: UnfinishedForest.fromForest(update.calls), + children: UnfinishedForest.fromForest(update.children), }); } return node; @@ -1817,7 +1818,7 @@ class AccountUpdateLayout { final: update.accountUpdate, id: update.id, isDummy: Bool(false), - children: UnfinishedForest.fromForest(update.calls), + children: UnfinishedForest.fromForest(update.children), }; } this.map.set(update.id, node); diff --git a/src/lib/mina/token/forest-iterator.ts b/src/lib/mina/token/forest-iterator.ts index 8d0949e7a4..62b717e94a 100644 --- a/src/lib/mina/token/forest-iterator.ts +++ b/src/lib/mina/token/forest-iterator.ts @@ -87,8 +87,8 @@ class TokenAccountUpdateIterator { */ next() { // get next account update from the current forest (might be a dummy) - let { accountUpdate, calls } = this.currentLayer.forest.next(); - let childForest = AccountUpdateIterator.startIterating(calls); + let { accountUpdate, children } = this.currentLayer.forest.next(); + let childForest = AccountUpdateIterator.startIterating(children); let childLayer = { forest: childForest, mayUseToken: MayUseToken.InheritFromParent, From 314ab5fa0a731ae52d496b388b15cd4c1b08c964 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 12:14:46 +0100 Subject: [PATCH 64/69] more explicit name for globally accessible accout update layout --- src/lib/account_update.ts | 16 ++++++++-------- src/lib/mina/smart-contract-context.ts | 4 ++-- src/lib/mina/token/token-contract.ts | 2 -- src/lib/zkapp.ts | 6 +++--- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index aebba25314..2f6cb5925a 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -60,7 +60,7 @@ import { } from './provable-types/merkle-list.js'; import { Hashed } from './provable-types/packed.js'; import { - accountUpdates, + accountUpdateLayout, smartContractContext, } from './mina/smart-contract-context.js'; import { assert } from './util/assert.js'; @@ -797,14 +797,14 @@ class AccountUpdate implements Types.AccountUpdate { */ approve(child: AccountUpdate | AccountUpdateTree | AccountUpdateForest) { if (child instanceof AccountUpdateForest) { - accountUpdates()?.setChildren(this, child); + accountUpdateLayout()?.setChildren(this, child); return; } if (child instanceof AccountUpdate) { child.body.callDepth = this.body.callDepth + 1; } - accountUpdates()?.disattach(child); - accountUpdates()?.pushChild(this, child); + accountUpdateLayout()?.disattach(child); + accountUpdateLayout()?.pushChild(this, child); } get balance() { @@ -1055,13 +1055,13 @@ class AccountUpdate implements Types.AccountUpdate { } toPrettyLayout() { - let node = accountUpdates()?.get(this); + let node = accountUpdateLayout()?.get(this); assert(node !== undefined, 'AccountUpdate not found in layout'); node.children.print(); } extractTree(): AccountUpdateTree { - let layout = accountUpdates(); + let layout = accountUpdateLayout(); let hash = layout?.get(this)?.final?.hash; let id = this.id; let children = @@ -1140,7 +1140,7 @@ class AccountUpdate implements Types.AccountUpdate { * Disattach an account update from where it's currently located in the transaction */ static unlink(accountUpdate: AccountUpdate) { - accountUpdates()?.disattach(accountUpdate); + accountUpdateLayout()?.disattach(accountUpdate); } /** @@ -1505,7 +1505,7 @@ class AccountUpdateTree extends StructNoJson({ * See {@link AccountUpdate.approve}. */ approve(update: AccountUpdate | AccountUpdateTree, hash?: Field) { - accountUpdates()?.disattach(update); + accountUpdateLayout()?.disattach(update); if (update instanceof AccountUpdate) { this.children.pushIf( update.isDummy().not(), diff --git a/src/lib/mina/smart-contract-context.ts b/src/lib/mina/smart-contract-context.ts index a276a2ff1e..96f93c4e8c 100644 --- a/src/lib/mina/smart-contract-context.ts +++ b/src/lib/mina/smart-contract-context.ts @@ -3,7 +3,7 @@ import type { AccountUpdate, AccountUpdateLayout } from '../account_update.js'; import { Context } from '../global-context.js'; import { currentTransaction } from './transaction-context.js'; -export { smartContractContext, SmartContractContext, accountUpdates }; +export { smartContractContext, SmartContractContext, accountUpdateLayout }; type SmartContractContext = { this: SmartContract; @@ -14,7 +14,7 @@ let smartContractContext = Context.create({ default: null, }); -function accountUpdates() { +function accountUpdateLayout() { // in a smart contract, return the layout currently created in the contract call let layout = smartContractContext.get()?.selfLayout; diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index f1fe0a0fc1..38a2a64843 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -6,12 +6,10 @@ import { AccountUpdate, AccountUpdateForest, AccountUpdateTree, - HashedAccountUpdate, Permissions, } from '../../account_update.js'; import { DeployArgs, SmartContract } from '../../zkapp.js'; import { TokenAccountUpdateIterator } from './forest-iterator.js'; -import { accountUpdates } from '../smart-contract-context.js'; export { TokenContract }; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 0c001e7c79..19c70e587c 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -62,7 +62,7 @@ import { SmartContractBase } from './mina/smart-contract-base.js'; import { ZkappStateLength } from './mina/mina-instance.js'; import { SmartContractContext, - accountUpdates, + accountUpdateLayout, smartContractContext, } from './mina/smart-contract-context.js'; @@ -1641,8 +1641,8 @@ function diffRecursive( let { transaction, index, accountUpdate: input } = inputData; diff(transaction, index, prover.toPretty(), input.toPretty()); // TODO - let inputChildren = accountUpdates()!.get(input)!.children.mutable!; - let proverChildren = accountUpdates()!.get(prover)!.children.mutable!; + let inputChildren = accountUpdateLayout()!.get(input)!.children.mutable!; + let proverChildren = accountUpdateLayout()!.get(prover)!.children.mutable!; let nChildren = inputChildren.length; for (let i = 0; i < nChildren; i++) { let inputChild = inputChildren[i].mutable; From e4a87290908a43140272dcf8281b579713ebe326 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 12:43:47 +0100 Subject: [PATCH 65/69] remove unnecessary changes --- src/lib/zkapp.ts | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 19c70e587c..3ac45d7307 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -167,7 +167,7 @@ function wrapMethod( } }); - let insideContract = SmartContractContext.get(); + let insideContract = smartContractContext.get(); if (!insideContract) { const { id, context } = SmartContractContext.enter( this, @@ -319,7 +319,7 @@ function wrapMethod( return result; } } finally { - SmartContractContext.leave(id); + smartContractContext.leave(id); } } @@ -459,7 +459,7 @@ function wrapMethod( accountUpdate.body.callData.assertEquals(callData); return result; } finally { - SmartContractContext.leave(id); + smartContractContext.leave(id); } }; } @@ -816,7 +816,7 @@ super.init(); */ get self(): AccountUpdate { let inTransaction = Mina.currentTransaction.has(); - let inSmartContract = SmartContractContext.get(); + let inSmartContract = smartContractContext.get(); if (!inTransaction && !inSmartContract) { // TODO: it's inefficient to return a fresh account update everytime, would be better to return a constant "non-writable" account update, // or even expose the .get() methods independently of any account update (they don't need one) @@ -1157,8 +1157,8 @@ super.init(); throw err; } let id: number; - let insideSmartContract = !!SmartContractContext.get(); - if (insideSmartContract) id = SmartContractContext.stepOutside(); + let insideSmartContract = !!smartContractContext.get(); + if (insideSmartContract) id = smartContractContext.enter(null); try { for (let methodIntf of methodIntfs) { let accountUpdate: AccountUpdate; @@ -1185,7 +1185,7 @@ super.init(); if (printSummary) console.log(methodIntf.methodName, summary()); } } finally { - if (insideSmartContract) SmartContractContext.leave(id!); + if (insideSmartContract) smartContractContext.leave(id!); } } return methodMetadata; @@ -1481,15 +1481,6 @@ const SmartContractContext = { let id = smartContractContext.enter(context); return { id, context }; }, - leave(id: number) { - smartContractContext.leave(id); - }, - stepOutside() { - return smartContractContext.enter(null); - }, - get() { - return smartContractContext.get(); - }, }; type DeployArgs = @@ -1500,7 +1491,7 @@ type DeployArgs = | undefined; function Account(address: PublicKey, tokenId?: Field) { - if (SmartContractContext.get()) { + if (smartContractContext.get()) { return AccountUpdate.create(address, tokenId).account; } else { return AccountUpdate.defaultAccountUpdate(address, tokenId).account; From aa37ee0902d51ea0883f36521dd190400477f5f0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 12:44:05 +0100 Subject: [PATCH 66/69] some code organization --- src/lib/account_update.ts | 110 +++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 2f6cb5925a..bc5ec31f9c 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1559,7 +1559,7 @@ function hashCons(forestHash: Field, nodeHash: Field) { * * type UnfinishedTree = ( * | Mutable of AccountUpdate - * | Final of HashedAccountUpdate) + * | Final of HashedAccountUpdate * ) & { children: UnfinishedForest, ... } * ``` */ @@ -1616,7 +1616,7 @@ class UnfinishedForest { if (this.isFinal()) return this.final; assert(this.isMutable(), 'final or mutable'); - let nodes = this.mutable.map(toTree); + let nodes = this.mutable.map(UnfinishedTree.finalize); let finalForest = AccountUpdateForest.empty(); for (let { isDummy, ...tree } of [...nodes].reverse()) { @@ -1644,17 +1644,6 @@ class UnfinishedForest { this.mutable.push(node); } - pushTree(tree: AccountUpdateTree) { - assert(this.isMutable(), 'Cannot push to an immutable forest'); - this.mutable.push({ - final: tree.accountUpdate, - id: tree.id, - isDummy: Bool(false), - children: UnfinishedForest.fromForest(tree.children), - siblings: this, - }); - } - remove(node: UnfinishedTree) { assert(this.isMutable(), 'Cannot remove from an immutable forest'); // find by .id @@ -1741,19 +1730,54 @@ class UnfinishedForest { } } -function toTree( - node: UnfinishedTree -): AccountUpdateTreeBase & { isDummy: Bool } { - let accountUpdate = node.final ?? HashedAccountUpdate.hash(node.mutable); - let children = node.children.finalize(); - return { accountUpdate, id: node.id, isDummy: node.isDummy, children }; -} +const UnfinishedTree = { + create(update: AccountUpdate | AccountUpdateTree): UnfinishedTree { + if (update instanceof AccountUpdate) { + return { + mutable: update, + id: update.id, + isDummy: update.isDummy(), + children: UnfinishedForest.empty(), + }; + } + return { + final: update.accountUpdate, + id: update.id, + isDummy: Bool(false), + children: UnfinishedForest.fromForest(update.children), + }; + }, -function isUnfinished( - input: AccountUpdate | AccountUpdateTree | UnfinishedTree -): input is UnfinishedTree { - return 'final' in input || 'mutable' in input; -} + setTo(node: UnfinishedTree, update: AccountUpdate | AccountUpdateTree) { + if (update instanceof AccountUpdate) { + if (node.final !== undefined) { + Object.assign(node, { + mutable: update, + final: undefined, + children: UnfinishedForest.empty(), + }); + } + } else if (node.mutable !== undefined) { + Object.assign(node, { + mutable: undefined, + final: update.accountUpdate, + children: UnfinishedForest.fromForest(update.children), + }); + } + }, + + finalize(node: UnfinishedTree): AccountUpdateTreeBase & { isDummy: Bool } { + let accountUpdate = node.final ?? HashedAccountUpdate.hash(node.mutable); + let children = node.children.finalize(); + return { accountUpdate, id: node.id, isDummy: node.isDummy, children }; + }, + + isUnfinished( + input: AccountUpdate | AccountUpdateTree | UnfinishedTree + ): input is UnfinishedTree { + return 'final' in input || 'mutable' in input; + }, +}; class AccountUpdateLayout { readonly map: Map; @@ -1780,47 +1804,21 @@ class AccountUpdateLayout { private getOrCreate( update: AccountUpdate | AccountUpdateTree | UnfinishedTree ): UnfinishedTree { - if (isUnfinished(update)) { + if (UnfinishedTree.isUnfinished(update)) { if (!this.map.has(update.id)) { this.map.set(update.id, update); } return update; } let node = this.map.get(update.id); + if (node !== undefined) { // might have to change node - if (update instanceof AccountUpdate) { - if (node.final !== undefined) { - Object.assign(node, { - mutable: update, - final: undefined, - children: UnfinishedForest.empty(), - }); - } - } else if (node.mutable !== undefined) { - Object.assign(node, { - mutable: undefined, - final: update.accountUpdate, - children: UnfinishedForest.fromForest(update.children), - }); - } + UnfinishedTree.setTo(node, update); return node; } - if (update instanceof AccountUpdate) { - node = { - mutable: update, - id: update.id, - isDummy: update.isDummy(), - children: UnfinishedForest.empty(), - }; - } else { - node = { - final: update.accountUpdate, - id: update.id, - isDummy: Bool(false), - children: UnfinishedForest.fromForest(update.children), - }; - } + + node = UnfinishedTree.create(update); this.map.set(update.id, node); return node; } From 694897dd06c3aad31e7c66ff30e4ac0144b75f2a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 12:45:52 +0100 Subject: [PATCH 67/69] fixup examples build --- src/examples/zkapps/token_with_proofs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/zkapps/token_with_proofs.ts b/src/examples/zkapps/token_with_proofs.ts index 17c3d1bee0..556354db00 100644 --- a/src/examples/zkapps/token_with_proofs.ts +++ b/src/examples/zkapps/token_with_proofs.ts @@ -57,7 +57,7 @@ class TokenContract extends SmartContract { callback: Experimental.Callback ) { // TODO use token contract methods for approve - let senderAccountUpdate = this.approve(callback); + let senderAccountUpdate = this.approve(callback) as AccountUpdate; let amount = UInt64.from(1_000); let negativeAmount = Int64.fromObject( senderAccountUpdate.body.balanceChange From 1cb30fa5d4e0f004090b8894851ca50d036a54cc Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 12 Feb 2024 12:50:54 +0100 Subject: [PATCH 68/69] changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca42cd21a..d6b9b5cbf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/834a44002...HEAD) +### Breaking changes + +- Remove `AccountUpdate.children` and `AccountUpdate.parent` properties https://github.com/o1-labs/o1js/pull/1402 + - Also removes the optional `AccountUpdatesLayout` argument to `approve()` + - Adds `AccountUpdateTree` and `AccountUpdateForest`, new classes that represent a layout of account updates explicitly + - Both of the new types are now accepted as inputs to `approve()` + - `accountUpdate.extractTree()` to obtain the tree associated with an account update in the current transaction context. + ### Added - `MerkleList` to enable provable operations on a dynamically-sized list https://github.com/o1-labs/o1js/pull/1398 From 24475ddb6c6149639e53f22d1118b472e3e0e45d Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 13 Feb 2024 10:33:28 +0100 Subject: [PATCH 69/69] bump live tests timeout --- .github/workflows/live-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/live-tests.yml b/.github/workflows/live-tests.yml index d6c3f72ff4..958c9bacfd 100644 --- a/.github/workflows/live-tests.yml +++ b/.github/workflows/live-tests.yml @@ -14,7 +14,7 @@ on: jobs: main-branch: - timeout-minutes: 25 + timeout-minutes: 45 runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') services: @@ -40,7 +40,7 @@ jobs: mina-branch-name: o1js-main berkeley-branch: - timeout-minutes: 25 + timeout-minutes: 45 runs-on: ubuntu-latest if: github.ref == 'refs/heads/berkeley' || (github.event_name == 'pull_request' && github.base_ref == 'berkeley') services: @@ -66,7 +66,7 @@ jobs: mina-branch-name: berkeley develop-branch: - timeout-minutes: 25 + timeout-minutes: 45 runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' || (github.event_name == 'pull_request' && github.base_ref == 'develop') services: