o1js
o1js copied to clipboard
Accessing two token accounts in one SmartContract's @method causes FieldVector.get(): Index out of bounds error or get() function returns incorrect token balance value
When @method accesses one token Account(address, tokenId), everything works as expected. When trying to access two token accounts, the following error appears while proving:
● MinaNFT contract › should issue badges, iteration 1
FieldVector.get(): Index out of bounds, got 314229/314229
at get (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:2119:13)
at external_values (src/lib/crypto/kimchi_backend/pasta/vesta_based_plonk.ml:95:33)
at ../../../../../../workspace_root/src/lib/crypto/kimchi_backend/common/plonk_constraint_system.ml:990:44
at iteri$1 (ocaml/base/array0.ml:50:18)
at _oy4_ (src/lib/crypto/kimchi_backend/common/plonk_constraint_system.ml:984:9)
at ../../../../../../home/gregor/.opam/4.14.0/lib/base/list.ml:544:8
at fold_left$0 (ocaml/ocaml/list.ml:121:25)
at fold_left$2 (ocaml/base/list0.ml:21:23)
at iteri$2 (ocaml/base/list.ml:543:6)
at compute_witness (src/lib/crypto/kimchi_backend/common/plonk_constraint_system.ml:983:5)
at create_async (src/lib/crypto/kimchi_backend/pasta/vesta_based_plonk.ml:117:7)
at create_proof (src/lib/pickles/step.ml:816:27)
at ../../../../../../workspace_root/src/lib/pickles/step.ml:822:27
at ../../../../../../workspace_root/src/lib/snarky/src/base/runners.ml:351:13
at ../../../../../../workspace_root/src/lib/snarky/src/base/runners.ml:305:34
at ../../../../../../workspace_root/src/lib/snarky/src/base/snark0.ml:1302:19
at finalize_is_running (src/lib/snarky/src/base/snark0.ml:1272:15)
at generate_witness_conv (src/lib/snarky/src/base/snark0.ml:1301:7)
at prove (src/lib/snarkyjs/src/bindings/ocaml/lib/pickles_bindings.ml:638:7)
at .yarn/cache/o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:13072:59
at withThreadPool (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:3168:20)
at prettifyStacktracePromise (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:2867:12)
at .yarn/cache/o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:16552:14
at Object.run (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:13311:16)
at createZkappProof (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:16545:21)
at addProof (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:16538:15)
at addMissingProofs (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:16510:42)
at Object.prove (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:16836:38)
at MinaNFTBadge.issue (src/minanftbadge.ts:277:5)
at Object.<anonymous> (tests/update.test.ts:181:20)
If method1, accessing token account A, calls method2, accessing token account B, the get function gives 0 for the balance of token account B even though the balance is not 0:
● MinaNFT contract › escrow should transfer NFT and funds
Field.assertEquals(): 0 != 1000000000
96 | const account = Account(address, this.token.id);
97 | const tokenBalance = account.balance.getAndAssertEquals();
> 98 | tokenBalance.assertEquals(UInt64.from(1_000_000_000));
| ^
99 | }
at _Field.assertEquals (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:4671:17)
at assertEqualImplicit (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:6466:12)
at Object.assertEqual (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:6456:12)
at _UInt64.assertEquals (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:7132:14)
at MinaNFTNameServiceContract.isNFT (src/contract/names.ts:98:18)
at MinaNFTNameServiceContract.transfer (src/contract/names.ts:199:10)
at runCalledContract (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:14514:29)
at .yarn/cache/o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:6440:21
at run_prover (src/lib/snarky/src/base/snark0.ml:1150:17)
at run$3 (src/lib/snarky/src/base/as_prover0.ml:15:17)
at run$4 (src/lib/snarky/src/base/as_prover0.ml:74:18)
at ../../../../../../workspace_root/src/lib/snarky/src/base/checked_runner.ml:260:15
at ../../../../../../workspace_root/src/lib/snarky/src/base/checked_runner.ml:54:30
at exists (src/lib/snarky/src/base/snark0.ml:1213:11)
at exists$12 (src/lib/snarkyjs/src/bindings/ocaml/lib/snarky_bindings.ml:17:15)
at Function.Class.<computed> [as exists] (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:3413:52)
at Object.witness (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:6439:35)
at Function.witness (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:16063:21)
at MinaNFTNameServiceContract.wrappedMethod (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:14541:78)
at Escrow.transfer (src/plugins/escrow.ts:81:13)
at Escrow.wrappedMethod (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:14446:30)
at src/escrow.ts:203:15
at .yarn/cache/o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:16769:13
at ../../../../../../workspace_root/src/lib/snarkyjs/src/bindings/ocaml/lib/snarky_bindings.ml:38:11
at ../../../../../../workspace_root/src/lib/snarky/src/base/snark0.ml:1315:40
at mark_active (src/lib/snarky/src/base/snark0.ml:1167:19)
at _piO_ (src/lib/snarky/src/base/snark0.ml:1314:17)
at as_stateful (src/lib/snarky/src/base/snark0.ml:755:15)
at run_and_check_exn (src/lib/snarky/src/base/runners.ml:115:16)
at ../../../../../../workspace_root/src/lib/snarky/src/base/snark0.ml:1313:13
at finalize_is_running (src/lib/snarky/src/base/snark0.ml:1272:15)
at run_and_check_exn (src/lib/snarky/src/base/snark0.ml:1311:7)
at Object.runUnchecked (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:4163:16)
at createTransaction (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:16768:20)
at Object.transaction (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:17208:16)
at Object.transaction (o1js-npm-0.14.1-6dd1d545d0-5ce9973c48.zip/node_modules/o1js/dist/node/index.cjs:17321:27)
at MinaNFTEscrow.transfer (src/escrow.ts:200:36)
at Object.<anonymous> (tests/escrow.test.ts:278:18)
In reality, the balance is not 0; it is 1000000000, as shown by several checks made before calling transfer() The bug exists while using testworld2; on the local blockchain, the function get() returns the correct result.
There is also a discussion on the hierarchy of Token accounts (i.e., many nested token accounts) on the discord that can be affected by this behavior: https://discord.com/channels/484437221055922177/1171202435663007804
Today, I checked my old code that throws this error, and the error is still there. I went to debug it and found out the error is being thrown when I use getAndRequireEquals() on token accounts:
@method checkFail(address: PublicKey, tokenId: Field) {
const version = this.version.getAndRequireEquals();
version.assertEquals(Field(1));
const token = new TokenAccount(address, tokenId);
// If you comment next two lines, the test will pass
const value = token.value.getAndRequireEquals();
value.assertEquals(Field(1));
}
When I use get(), tests pass:
@method checkPass(address: PublicKey, tokenId: Field) {
const version = this.version.getAndRequireEquals();
version.assertEquals(Field(1));
const token = new TokenAccount(address, tokenId);
const value = token.value.get();
value.assertEquals(Field(1));
}
The test code I use: https://github.com/dfstio/minanft-lib/blob/token-accounts/experimental/issue_token_accounts.test.ts
The discord discussion: https://discord.com/channels/484437221055922177/1261748801371373729/1262142233973362770