@lfdecentralizedtrust/zeto-contracts 0.2.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +70 -162
  2. package/contracts/factory.sol +30 -34
  3. package/contracts/factory_upgradeable.sol +11 -7
  4. package/contracts/lib/common/util.sol +40 -0
  5. package/contracts/lib/interfaces/ILockableCapability.sol +318 -0
  6. package/contracts/lib/interfaces/{izeto.sol → IZeto.sol} +13 -1
  7. package/contracts/lib/interfaces/{izeto_initializable.sol → IZetoInitializable.sol} +14 -12
  8. package/contracts/lib/interfaces/{izeto_kyc.sol → IZetoKyc.sol} +3 -3
  9. package/contracts/lib/interfaces/IZetoLockableCapability.sol +237 -0
  10. package/contracts/lib/interfaces/IZetoStorage.sol +106 -0
  11. package/contracts/lib/interfaces/{izeto_lockable.sol → IZetoVerifier.sol} +8 -21
  12. package/contracts/lib/registry.sol +74 -26
  13. package/contracts/lib/storage/base.sol +210 -0
  14. package/contracts/lib/storage/nullifier.sol +166 -0
  15. package/contracts/lib/zeto_common.sol +277 -28
  16. package/contracts/lib/zeto_fungible.sol +451 -33
  17. package/contracts/lib/zeto_fungible_base.sol +69 -0
  18. package/contracts/lib/zeto_fungible_burn.sol +83 -53
  19. package/contracts/lib/zeto_fungible_burn_nullifier.sol +115 -72
  20. package/contracts/lib/zeto_fungible_nullifier.sol +167 -0
  21. package/contracts/lib/zeto_lockable.sol +613 -0
  22. package/contracts/lib/zeto_non_fungible.sol +236 -0
  23. package/contracts/lib/zeto_non_fungible_base.sol +61 -0
  24. package/contracts/lib/zeto_non_fungible_nullifier.sol +61 -0
  25. package/contracts/test/escrow1.sol +90 -34
  26. package/contracts/test/escrow2.sol +64 -29
  27. package/contracts/test/qurrency.sol +10 -2
  28. package/contracts/test/smt.sol +6 -1
  29. package/contracts/test/tendecimals.sol +14 -12
  30. package/contracts/verifiers/impl/anon.sol +189 -0
  31. package/contracts/verifiers/impl/anon_batch.sol +301 -0
  32. package/contracts/verifiers/impl/anon_enc.sol +266 -0
  33. package/contracts/verifiers/impl/anon_enc_batch.sol +602 -0
  34. package/contracts/verifiers/impl/anon_enc_nullifier.sol +287 -0
  35. package/contracts/verifiers/impl/anon_enc_nullifier_batch.sol +679 -0
  36. package/contracts/verifiers/impl/anon_enc_nullifier_kyc.sol +294 -0
  37. package/contracts/verifiers/impl/anon_enc_nullifier_kyc_batch.sol +686 -0
  38. package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation.sol +413 -0
  39. package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation_batch.sol +1141 -0
  40. package/contracts/verifiers/impl/anon_nullifier_kyc_transfer.sol +217 -0
  41. package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked.sol +196 -0
  42. package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked_batch.sol +308 -0
  43. package/contracts/verifiers/impl/anon_nullifier_kyc_transfer_batch.sol +385 -0
  44. package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer.sol +497 -0
  45. package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer_batch.sol +1001 -0
  46. package/contracts/verifiers/{verifier_anon_nullifier_transferLocked.sol → impl/anon_nullifier_transfer.sol} +12 -19
  47. package/contracts/verifiers/{verifier_anon_nullifier_transferLocked_batch.sol → impl/anon_nullifier_transfer_batch.sol} +44 -51
  48. package/contracts/verifiers/impl/burn.sol +182 -0
  49. package/contracts/verifiers/impl/burn_batch.sol +238 -0
  50. package/contracts/verifiers/impl/burn_nullifier.sol +203 -0
  51. package/contracts/verifiers/impl/burn_nullifier_batch.sol +315 -0
  52. package/contracts/verifiers/impl/deposit.sol +182 -0
  53. package/contracts/verifiers/impl/deposit_kyc.sol +189 -0
  54. package/contracts/verifiers/impl/nf_anon.sol +175 -0
  55. package/contracts/verifiers/{verifier_nf_anon_nullifier_transferLocked.sol → impl/nf_anon_nullifier_transfer.sol} +6 -13
  56. package/contracts/verifiers/impl/withdraw.sol +189 -0
  57. package/contracts/verifiers/impl/withdraw_batch.sol +245 -0
  58. package/contracts/verifiers/impl/withdraw_nullifier.sol +210 -0
  59. package/contracts/verifiers/impl/withdraw_nullifier_batch.sol +322 -0
  60. package/contracts/verifiers/verifier_anon.sol +31 -186
  61. package/contracts/verifiers/verifier_anon_batch.sol +31 -298
  62. package/contracts/verifiers/verifier_anon_enc.sol +31 -263
  63. package/contracts/verifiers/verifier_anon_enc_batch.sol +31 -599
  64. package/contracts/verifiers/verifier_anon_enc_nullifier.sol +31 -284
  65. package/contracts/verifiers/verifier_anon_enc_nullifier_batch.sol +33 -676
  66. package/contracts/verifiers/verifier_anon_enc_nullifier_kyc.sol +31 -291
  67. package/contracts/verifiers/verifier_anon_enc_nullifier_kyc_batch.sol +33 -683
  68. package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation.sol +33 -410
  69. package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation_batch.sol +33 -1138
  70. package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer.sol +33 -214
  71. package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked.sol +33 -221
  72. package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked_batch.sol +33 -389
  73. package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer_batch.sol +33 -382
  74. package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer.sol +33 -221
  75. package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer_batch.sol +33 -389
  76. package/contracts/verifiers/verifier_anon_nullifier_transfer.sol +33 -207
  77. package/contracts/verifiers/verifier_anon_nullifier_transfer_batch.sol +33 -375
  78. package/contracts/verifiers/verifier_burn.sol +31 -179
  79. package/contracts/verifiers/verifier_burn_batch.sol +31 -235
  80. package/contracts/verifiers/verifier_burn_nullifier.sol +31 -200
  81. package/contracts/verifiers/verifier_burn_nullifier_batch.sol +31 -312
  82. package/contracts/verifiers/verifier_deposit.sol +31 -179
  83. package/contracts/verifiers/verifier_deposit_kyc.sol +34 -0
  84. package/contracts/verifiers/verifier_nf_anon.sol +31 -172
  85. package/contracts/verifiers/verifier_nf_anon_nullifier_transfer.sol +33 -179
  86. package/contracts/verifiers/verifier_withdraw.sol +31 -186
  87. package/contracts/verifiers/verifier_withdraw_batch.sol +31 -242
  88. package/contracts/verifiers/verifier_withdraw_nullifier.sol +31 -207
  89. package/contracts/verifiers/verifier_withdraw_nullifier_batch.sol +33 -319
  90. package/contracts/zeto_anon.sol +77 -231
  91. package/contracts/zeto_anon_burnable.sol +56 -12
  92. package/contracts/zeto_anon_enc.sol +93 -190
  93. package/contracts/zeto_anon_enc_nullifier.sol +249 -195
  94. package/contracts/zeto_anon_enc_nullifier_kyc.sol +51 -232
  95. package/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +231 -238
  96. package/contracts/zeto_anon_nullifier.sol +164 -298
  97. package/contracts/zeto_anon_nullifier_burnable.sol +68 -18
  98. package/contracts/zeto_anon_nullifier_kyc.sol +40 -345
  99. package/contracts/zeto_anon_nullifier_qurrency.sol +217 -361
  100. package/contracts/zeto_nf_anon.sol +55 -130
  101. package/contracts/zeto_nf_anon_nullifier.sol +90 -152
  102. package/hardhat.config.ts +9 -3
  103. package/ignition/modules/lib/deps.ts +8 -0
  104. package/ignition/modules/zeto_anon_burnable.ts +8 -7
  105. package/ignition/modules/zeto_anon_enc_nullifier.ts +31 -0
  106. package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +2 -2
  107. package/ignition/modules/zeto_anon_nullifier.ts +28 -4
  108. package/ignition/modules/zeto_anon_nullifier_burnable.ts +50 -16
  109. package/ignition/modules/zeto_anon_nullifier_kyc.ts +27 -12
  110. package/ignition/modules/zeto_nf_anon_nullifier.ts +14 -10
  111. package/package.json +4 -2
  112. package/scripts/deploy_cloneable.ts +19 -10
  113. package/scripts/deploy_upgradeable.ts +9 -5
  114. package/scripts/tokens/Zeto_Anon.ts +3 -3
  115. package/scripts/tokens/Zeto_AnonBurnable.ts +1 -1
  116. package/scripts/tokens/Zeto_AnonEnc.ts +12 -3
  117. package/scripts/tokens/Zeto_AnonEncNullifier.ts +5 -3
  118. package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +1 -1
  119. package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +1 -1
  120. package/scripts/tokens/Zeto_AnonNullifier.ts +1 -1
  121. package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +1 -1
  122. package/scripts/tokens/Zeto_AnonNullifierKyc.ts +1 -1
  123. package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +1 -1
  124. package/scripts/tokens/Zeto_NfAnon.ts +2 -2
  125. package/scripts/tokens/Zeto_NfAnonNullifier.ts +1 -1
  126. package/test/factory.ts +46 -73
  127. package/test/lib/anon_nullifier_helpers.ts +89 -0
  128. package/test/lib/anon_zeto_helpers.ts +76 -0
  129. package/test/lib/deploy.ts +3 -2
  130. package/test/lib/utils.ts +74 -35
  131. package/test/test/escrow1.ts +185 -58
  132. package/test/test/escrow2.ts +200 -107
  133. package/test/test/qurrency.ts +13 -33
  134. package/test/usdc-shielding.ts +39 -26
  135. package/test/utils.ts +144 -21
  136. package/test/zeto_anon.ts +956 -465
  137. package/test/zeto_anon_enc.ts +881 -142
  138. package/test/zeto_anon_enc_nullifier.ts +847 -38
  139. package/test/zeto_anon_enc_nullifier_kyc.ts +120 -181
  140. package/test/zeto_anon_enc_nullifier_non_repudiation.ts +77 -46
  141. package/test/zeto_anon_nullifier.ts +1212 -953
  142. package/test/zeto_anon_nullifier_kyc.ts +674 -655
  143. package/test/zeto_anon_nullifier_qurrency.ts +452 -306
  144. package/test/zeto_nf_anon.ts +737 -137
  145. package/test/zeto_nf_anon_nullifier.ts +876 -246
  146. package/contracts/lib/zeto_base.sol +0 -293
  147. package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
  148. package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
  149. package/contracts/lib/zeto_nullifier.sol +0 -340
  150. package/contracts/zkDvP.sol_ +0 -273
  151. package/test/zkDvP.ts_ +0 -455
  152. /package/contracts/lib/{common.sol → common/common.sol} +0 -0
@@ -15,11 +15,16 @@
15
15
  // limitations under the License.
16
16
 
17
17
  import { ethers, ignition, network } from "hardhat";
18
- import { Signer, encodeBytes32String, ZeroHash, lock } from "ethers";
18
+ import {
19
+ Signer,
20
+ AbiCoder,
21
+ BigNumberish,
22
+ ContractTransactionReceipt,
23
+ } from "ethers";
19
24
  import { expect } from "chai";
20
- import { loadCircuit, getProofHash } from "zeto-js";
25
+ import { loadCircuit } from "zeto-js";
21
26
  import zkEscrowModule from "../../ignition/modules/test/escrow1";
22
- import zetoAnonTests from "../zeto_anon";
27
+ import { prepareProof } from "../lib/anon_zeto_helpers";
23
28
  import {
24
29
  UTXO,
25
30
  User,
@@ -27,11 +32,36 @@ import {
27
32
  newUTXO,
28
33
  doMint,
29
34
  ZERO_UTXO,
30
- parseUTXOEvents,
35
+ logger,
31
36
  } from "../lib/utils";
32
37
  import { loadProvingKeys } from "../utils";
33
38
  import { deployZeto } from "../lib/deploy";
34
39
 
40
+ // ABI fragments for the ZetoLockableCapability *Args payloads.
41
+ // These mirror the canonical encodings used by every Zeto token that
42
+ // implements ILockableCapability.
43
+ const CREATE_ARGS_ABI =
44
+ "tuple(bytes32 txId, uint256[] inputs, uint256[] outputs, uint256[] lockedOutputs, bytes proof)";
45
+ const DELEGATE_ARGS_ABI = "tuple(bytes32 txId)";
46
+
47
+ function encodeCreateArgs(args: {
48
+ txId: string;
49
+ inputs: BigNumberish[];
50
+ outputs: BigNumberish[];
51
+ lockedOutputs: BigNumberish[];
52
+ proof: string;
53
+ }) {
54
+ return new AbiCoder().encode([CREATE_ARGS_ABI], [args]);
55
+ }
56
+
57
+ function encodeDelegateArgs(txId: string) {
58
+ return new AbiCoder().encode([DELEGATE_ARGS_ABI], [{ txId }]);
59
+ }
60
+
61
+ function randomBytes32(): string {
62
+ return ethers.hexlify(ethers.randomBytes(32));
63
+ }
64
+
35
65
  describe("Escrow flow for payment with Zeto_Anon", function () {
36
66
  let Alice: User;
37
67
  let Bob: User;
@@ -48,6 +78,7 @@ describe("Escrow flow for payment with Zeto_Anon", function () {
48
78
  // UTXOs involved in the escrow flow
49
79
  let lockedPayment1: UTXO;
50
80
  let paymentToBob: UTXO;
81
+ let lockId: string;
51
82
 
52
83
  // other variables
53
84
  let deployer: Signer;
@@ -66,11 +97,15 @@ describe("Escrow flow for payment with Zeto_Anon", function () {
66
97
  Bob = await newUser(b);
67
98
  Charlie = await newUser(c);
68
99
 
100
+ // The Zeto_Anon token uses the same `anon` circuit for both regular
101
+ // transfers and the locked-input branch (the lockVerifier is wired
102
+ // to Groth16Verifier_Anon in the ignition module). One circuit/key
103
+ // pair therefore covers both the createLock and spendLock proofs.
69
104
  circuit = await loadCircuit("anon");
70
105
  ({ provingKeyFile: provingKey } = loadProvingKeys("anon"));
71
106
 
72
107
  ({ deployer, zeto: zkPayment } = await deployZeto("Zeto_Anon"));
73
- console.log(`ZK Payment contract deployed at ${zkPayment.target}`);
108
+ logger.debug(`ZK Payment contract deployed at ${zkPayment.target}`);
74
109
  ({ zkEscrow } = await ignition.deploy(zkEscrowModule, {
75
110
  parameters: {
76
111
  zkEscrow1: {
@@ -85,62 +120,101 @@ describe("Escrow flow for payment with Zeto_Anon", function () {
85
120
  payment2 = newUTXO(20, Alice);
86
121
  const result = await doMint(zkPayment, deployer, [payment1, payment2]);
87
122
 
88
- // simulate Alice and Bob listening to minting events and updating his local merkle tree
123
+ // simulate Alice and Bob listening to minting events and updating
124
+ // their local merkle trees
89
125
  for (const log of result.logs) {
90
126
  const event = zkPayment.interface.parseLog(log as any);
91
127
  expect(event.args.outputs.length).to.equal(2);
92
128
  }
93
129
  });
94
130
 
95
- it("Alice locks some payment tokens and designates the escrow as the delegate", async function () {
131
+ it("Alice locks payment1 by calling createLock on the Zeto token", async function () {
132
+ // The locked output preserves the value but is a fresh commitment
133
+ // (new salt). The createLock proof attests to the standard transfer
134
+ // payment1 -> lockedPayment1.
96
135
  lockedPayment1 = newUTXO(payment1.value!, Alice);
97
- const { inputCommitments, outputCommitments, encodedProof } =
98
- await zetoAnonTests.prepareProof(
99
- circuit,
100
- provingKey,
101
- Alice,
102
- [payment1, ZERO_UTXO],
103
- [lockedPayment1, ZERO_UTXO],
104
- [Alice, {}],
105
- );
136
+ const encodedZkProof = await prepareProof(
137
+ circuit,
138
+ provingKey,
139
+ Alice,
140
+ [payment1, ZERO_UTXO],
141
+ [lockedPayment1, ZERO_UTXO],
142
+ [Alice, {}],
143
+ );
144
+
145
+ const createArgs = encodeCreateArgs({
146
+ txId: randomBytes32(),
147
+ inputs: [payment1.hash],
148
+ // For the fungible Zeto_Anon createLock, lockedOutputs holds the
149
+ // freshly-locked UTXO and outputs is empty (no change is being
150
+ // returned to Alice, since payment1's full value is moved into
151
+ // the lock).
152
+ outputs: [],
153
+ lockedOutputs: [lockedPayment1.hash],
154
+ proof: encodeProofOnly(encodedZkProof),
155
+ });
156
+
157
+ // Pre-compute the lockId off-chain to assert the contract derives
158
+ // the same value (deterministic from caller + txId).
159
+ lockId = await zkPayment.connect(Alice.signer).computeLockId(createArgs);
160
+
161
+ const tx = await zkPayment
162
+ .connect(Alice.signer)
163
+ .createLock(createArgs, ethers.ZeroHash, ethers.ZeroHash, "0x");
164
+ const result: ContractTransactionReceipt | null = await tx.wait();
165
+ logger.debug(`createLock() complete. Gas used: ${result?.gasUsed}`);
166
+
167
+ // Sanity-check the lock state after creation.
168
+ const info = await zkPayment.getLock(lockId);
169
+ expect(info.owner).to.equal(Alice.ethAddress);
170
+ expect(info.spender).to.equal(Alice.ethAddress);
171
+ expect(await zkPayment.locked(lockedPayment1.hash)).to.deep.equal([
172
+ true,
173
+ Alice.ethAddress,
174
+ ]);
175
+ });
176
+
177
+ it("Alice delegates the lock to the escrow contract", async function () {
106
178
  const tx = await zkPayment
107
179
  .connect(Alice.signer)
108
- .lock(
109
- inputCommitments,
110
- [],
111
- outputCommitments,
112
- encodedProof,
180
+ .delegateLock(
181
+ lockId,
182
+ encodeDelegateArgs(randomBytes32()),
113
183
  zkEscrow.target,
114
184
  "0x",
115
185
  );
116
- const result = await tx.wait();
117
- const events = parseUTXOEvents(zkPayment, result);
118
-
119
- // simulate Alice's tradinig partner listening to the locking events and verifying the committed (locked) value
120
- const lockedUTXO = events[0].lockedOutputs[0];
121
- const lockDelegate = events[0].delegate;
122
- // Alice's trading partner is sent the secrets for the locked UTXO (which is still owned by Alice)
123
- // in an off-chain message. so they can be used to verify the locked UTXO, and the lock delegate
124
- expect(lockedUTXO).to.equal(lockedPayment1.hash);
125
- expect(lockDelegate).to.equal(zkEscrow.target);
186
+ await tx.wait();
187
+
188
+ // After delegation the escrow is the sole spender; Alice can no
189
+ // longer spend the lock directly.
190
+ const info = await zkPayment.getLock(lockId);
191
+ expect(info.spender).to.equal(zkEscrow.target);
192
+ expect(await zkPayment.locked(lockedPayment1.hash)).to.deep.equal([
193
+ true,
194
+ zkEscrow.target,
195
+ ]);
126
196
  });
127
197
 
128
198
  it("Alice initiates a payment transaction to Bob through the escrow", async function () {
129
- paymentToBob = newUTXO(lockedPayment1.value!, Bob, lockedPayment1.salt);
199
+ paymentToBob = newUTXO(lockedPayment1.value!, Bob);
130
200
  const tx = await zkEscrow
131
201
  .connect(Alice.signer)
132
- .initiatePayment([lockedPayment1.hash], [paymentToBob.hash], "0x");
202
+ .initiatePayment(lockId, [paymentToBob.hash], "0x");
133
203
  const result = await tx.wait();
134
- const events = parseUTXOEvents(zkEscrow, result);
135
- // simulate Bob listening to the payment events and verifying the proposed payment
136
- const proposedPayment = events[0].outputs[0];
137
- const check = newUTXO(lockedPayment1.value!, Bob, lockedPayment1.salt);
138
- expect(proposedPayment).to.equal(check.hash);
139
- paymentId = events[0].paymentId;
204
+ const initiated = parseEscrowEvent(zkEscrow, result, "PaymentInitiated");
205
+ expect(initiated, "PaymentInitiated event not emitted").to.not.be.undefined;
206
+
207
+ // Bob listens to the payment events and verifies the proposed
208
+ // payment matches what Alice promised off-chain.
209
+ expect(initiated!.outputs[0]).to.equal(paymentToBob.hash);
210
+ expect(initiated!.lockId).to.equal(lockId);
211
+ paymentId = initiated!.paymentId;
140
212
  });
141
213
 
142
- it("Alice approves the payment by submitting a valid proof that can successfully verify the proposed payment", async function () {
143
- const { encodedProof } = await zetoAnonTests.prepareProof(
214
+ it("Alice approves the payment by submitting a valid locked-input proof", async function () {
215
+ // The spend proof transfers lockedPayment1 -> paymentToBob (now
216
+ // owned by Bob). Same `anon` circuit as the createLock step.
217
+ const encodedZkProof = await prepareProof(
144
218
  circuit,
145
219
  provingKey,
146
220
  Alice,
@@ -150,29 +224,82 @@ describe("Escrow flow for payment with Zeto_Anon", function () {
150
224
  );
151
225
  const tx = await zkEscrow
152
226
  .connect(Alice.signer)
153
- .approvePayment(paymentId, encodedProof, "0x");
227
+ .approvePayment(paymentId, encodeProofOnly(encodedZkProof), "0x");
154
228
  const result = await tx.wait();
155
- // simulate Bob listening to the escrow events and verifying the payment has been approved.
156
- // the escrow contract guaratees that the proof is valid
157
- const events = parseUTXOEvents(zkEscrow, result);
158
- const approvedPayment = events[0].paymentId;
159
- expect(approvedPayment).to.equal(paymentId);
229
+
230
+ // Bob listens to the escrow's PaymentApproved event; the escrow
231
+ // having stored the proof guarantees completePayment cannot fail
232
+ // on proof grounds later.
233
+ const approved = parseEscrowEvent(zkEscrow, result, "PaymentApproved");
234
+ expect(approved, "PaymentApproved event not emitted").to.not.be.undefined;
235
+ expect(approved!.paymentId).to.equal(paymentId);
160
236
  });
161
237
 
162
- it("Bob, or anyone, can call the escrow to finalize the payment and receive the locked UTXO", async function () {
238
+ it("Bob, or anyone, can call the escrow to finalize the payment and receive the unlocked UTXO", async function () {
163
239
  const tx = await zkEscrow
164
240
  .connect(Bob.signer)
165
241
  .completePayment(paymentId, "0x");
166
242
  const result = await tx.wait();
167
- // simulate Bob listening to the payment events and verifying
168
- // the expected UTXO has been transferred to him
169
- let events = parseUTXOEvents(zkPayment, result);
170
- const transferredPayment = events[0].outputs[0];
171
- const check = newUTXO(paymentToBob.value!, Bob, paymentToBob.salt);
172
- expect(transferredPayment).to.equal(check.hash);
173
- // simulate Bob listening to the escrow events and verifying the payment has been completed
174
- events = parseUTXOEvents(zkEscrow, result);
175
- const completedPayment = events[1].paymentId;
176
- expect(completedPayment).to.equal(paymentId);
243
+
244
+ // The Zeto token emits ZetoLockSpent containing the new unlocked
245
+ // outputs.
246
+ const lockSpent = result!.logs
247
+ .map((l) => {
248
+ try {
249
+ return zkPayment.interface.parseLog(l as any);
250
+ } catch (_e) {
251
+ return null;
252
+ }
253
+ })
254
+ .find((p: any) => p && p.name === "ZetoLockSpent");
255
+ expect(lockSpent, "ZetoLockSpent event not emitted").to.not.be.undefined;
256
+ expect(lockSpent!.args.outputs[0]).to.equal(paymentToBob.hash);
257
+
258
+ // Lock is consumed; the locked-UTXO ledger no longer carries
259
+ // lockedPayment1.
260
+ expect(await zkPayment.isLockActive(lockId)).to.equal(false);
261
+ expect((await zkPayment.locked(lockedPayment1.hash))[0]).to.be.false;
262
+
263
+ // Escrow's PaymentCompleted event fires for the same paymentId.
264
+ const completed = parseEscrowEvent(zkEscrow, result, "PaymentCompleted");
265
+ expect(completed, "PaymentCompleted event not emitted").to.not.be.undefined;
266
+ expect(completed!.paymentId).to.equal(paymentId);
177
267
  });
178
268
  });
269
+
270
+ // parseEscrowEvent extracts the first event of a given name from a
271
+ // transaction receipt by parsing logs through the escrow contract's
272
+ // own ABI. Unlike `parseUTXOEvents`, which only knows about the Zeto
273
+ // token's UTXO event family, this helper handles the escrow's
274
+ // custom Payment* events.
275
+ function parseEscrowEvent(
276
+ escrowContract: any,
277
+ receipt: ContractTransactionReceipt | null,
278
+ name: string,
279
+ ): any | undefined {
280
+ if (!receipt) return undefined;
281
+ for (const log of receipt.logs || []) {
282
+ let parsed;
283
+ try {
284
+ parsed = escrowContract.interface.parseLog(log as any);
285
+ } catch (_e) {
286
+ continue;
287
+ }
288
+ if (parsed && parsed.name === name) {
289
+ // Return the args Result directly; ethers exposes named fields
290
+ // (e.g. result.paymentId) alongside positional ones.
291
+ return parsed.args;
292
+ }
293
+ }
294
+ return undefined;
295
+ }
296
+
297
+ // encodeProofOnly mirrors the wire format that
298
+ // {Zeto_Anon.constructPublicInputs} expects: a bare `Commonlib.Proof`
299
+ // tuple (no merkle root prefix, since Zeto_Anon is non-nullifier).
300
+ function encodeProofOnly(proof: any) {
301
+ return new AbiCoder().encode(
302
+ ["tuple(uint256[2] pA, uint256[2][2] pB, uint256[2] pC)"],
303
+ [proof],
304
+ );
305
+ }