@lfdecentralizedtrust/zeto-contracts 0.2.2 → 0.5.1

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 (164) hide show
  1. package/README.md +76 -162
  2. package/config/eip170.ts +30 -0
  3. package/contracts/factory.sol +30 -34
  4. package/contracts/factory_upgradeable.sol +11 -7
  5. package/contracts/lib/common/util.sol +40 -0
  6. package/contracts/lib/interfaces/ILockableCapability.sol +318 -0
  7. package/contracts/lib/interfaces/{izeto.sol → IZeto.sol} +13 -1
  8. package/contracts/lib/interfaces/{izeto_initializable.sol → IZetoInitializable.sol} +14 -12
  9. package/contracts/lib/interfaces/{izeto_kyc.sol → IZetoKyc.sol} +3 -3
  10. package/contracts/lib/interfaces/IZetoLockHooks.sol +42 -0
  11. package/contracts/lib/interfaces/IZetoLockableCapability.sol +237 -0
  12. package/contracts/lib/interfaces/IZetoStorage.sol +106 -0
  13. package/contracts/lib/interfaces/{izeto_lockable.sol → IZetoVerifier.sol} +8 -21
  14. package/contracts/lib/registry.sol +74 -26
  15. package/contracts/lib/storage/base.sol +210 -0
  16. package/contracts/lib/storage/nullifier.sol +166 -0
  17. package/contracts/lib/zeto_common.sol +277 -28
  18. package/contracts/lib/zeto_fungible.sol +443 -33
  19. package/contracts/lib/zeto_fungible_base.sol +69 -0
  20. package/contracts/lib/zeto_fungible_burn.sol +83 -53
  21. package/contracts/lib/zeto_fungible_burn_nullifier.sol +115 -72
  22. package/contracts/lib/zeto_fungible_nullifier.sol +167 -0
  23. package/contracts/lib/zeto_lockable.sol +219 -0
  24. package/contracts/lib/zeto_lockable_lib.sol +460 -0
  25. package/contracts/lib/zeto_lockable_storage.sol +43 -0
  26. package/contracts/lib/zeto_non_fungible.sol +228 -0
  27. package/contracts/lib/zeto_non_fungible_base.sol +61 -0
  28. package/contracts/lib/zeto_non_fungible_nullifier.sol +61 -0
  29. package/contracts/test/escrow1.sol +90 -34
  30. package/contracts/test/escrow2.sol +64 -29
  31. package/contracts/test/qurrency.sol +10 -2
  32. package/contracts/test/smt.sol +6 -1
  33. package/contracts/test/tendecimals.sol +14 -12
  34. package/contracts/verifiers/impl/anon.sol +189 -0
  35. package/contracts/verifiers/impl/anon_batch.sol +301 -0
  36. package/contracts/verifiers/impl/anon_enc.sol +266 -0
  37. package/contracts/verifiers/impl/anon_enc_batch.sol +602 -0
  38. package/contracts/verifiers/impl/anon_enc_nullifier.sol +287 -0
  39. package/contracts/verifiers/impl/anon_enc_nullifier_batch.sol +679 -0
  40. package/contracts/verifiers/impl/anon_enc_nullifier_kyc.sol +294 -0
  41. package/contracts/verifiers/impl/anon_enc_nullifier_kyc_batch.sol +686 -0
  42. package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation.sol +413 -0
  43. package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation_batch.sol +1141 -0
  44. package/contracts/verifiers/impl/anon_nullifier_kyc_transfer.sol +217 -0
  45. package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked.sol +196 -0
  46. package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked_batch.sol +308 -0
  47. package/contracts/verifiers/impl/anon_nullifier_kyc_transfer_batch.sol +385 -0
  48. package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer.sol +497 -0
  49. package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer_batch.sol +1001 -0
  50. package/contracts/verifiers/{verifier_anon_nullifier_transferLocked.sol → impl/anon_nullifier_transfer.sol} +12 -19
  51. package/contracts/verifiers/{verifier_anon_nullifier_transferLocked_batch.sol → impl/anon_nullifier_transfer_batch.sol} +44 -51
  52. package/contracts/verifiers/impl/burn.sol +182 -0
  53. package/contracts/verifiers/impl/burn_batch.sol +238 -0
  54. package/contracts/verifiers/impl/burn_nullifier.sol +203 -0
  55. package/contracts/verifiers/impl/burn_nullifier_batch.sol +315 -0
  56. package/contracts/verifiers/impl/deposit.sol +182 -0
  57. package/contracts/verifiers/impl/deposit_kyc.sol +189 -0
  58. package/contracts/verifiers/impl/nf_anon.sol +175 -0
  59. package/contracts/verifiers/{verifier_nf_anon_nullifier_transferLocked.sol → impl/nf_anon_nullifier_transfer.sol} +6 -13
  60. package/contracts/verifiers/impl/withdraw.sol +189 -0
  61. package/contracts/verifiers/impl/withdraw_batch.sol +245 -0
  62. package/contracts/verifiers/impl/withdraw_nullifier.sol +210 -0
  63. package/contracts/verifiers/impl/withdraw_nullifier_batch.sol +322 -0
  64. package/contracts/verifiers/verifier_anon.sol +31 -186
  65. package/contracts/verifiers/verifier_anon_batch.sol +31 -298
  66. package/contracts/verifiers/verifier_anon_enc.sol +31 -263
  67. package/contracts/verifiers/verifier_anon_enc_batch.sol +31 -599
  68. package/contracts/verifiers/verifier_anon_enc_nullifier.sol +31 -284
  69. package/contracts/verifiers/verifier_anon_enc_nullifier_batch.sol +33 -676
  70. package/contracts/verifiers/verifier_anon_enc_nullifier_kyc.sol +31 -291
  71. package/contracts/verifiers/verifier_anon_enc_nullifier_kyc_batch.sol +33 -683
  72. package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation.sol +33 -410
  73. package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation_batch.sol +33 -1138
  74. package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer.sol +33 -214
  75. package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked.sol +33 -221
  76. package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked_batch.sol +33 -389
  77. package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer_batch.sol +33 -382
  78. package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer.sol +33 -221
  79. package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer_batch.sol +33 -389
  80. package/contracts/verifiers/verifier_anon_nullifier_transfer.sol +33 -207
  81. package/contracts/verifiers/verifier_anon_nullifier_transfer_batch.sol +33 -375
  82. package/contracts/verifiers/verifier_burn.sol +31 -179
  83. package/contracts/verifiers/verifier_burn_batch.sol +31 -235
  84. package/contracts/verifiers/verifier_burn_nullifier.sol +31 -200
  85. package/contracts/verifiers/verifier_burn_nullifier_batch.sol +31 -312
  86. package/contracts/verifiers/verifier_deposit.sol +31 -179
  87. package/contracts/verifiers/verifier_deposit_kyc.sol +34 -0
  88. package/contracts/verifiers/verifier_nf_anon.sol +31 -172
  89. package/contracts/verifiers/verifier_nf_anon_nullifier_transfer.sol +33 -179
  90. package/contracts/verifiers/verifier_withdraw.sol +31 -186
  91. package/contracts/verifiers/verifier_withdraw_batch.sol +31 -242
  92. package/contracts/verifiers/verifier_withdraw_nullifier.sol +31 -207
  93. package/contracts/verifiers/verifier_withdraw_nullifier_batch.sol +33 -319
  94. package/contracts/zeto_anon.sol +77 -231
  95. package/contracts/zeto_anon_burnable.sol +56 -12
  96. package/contracts/zeto_anon_enc.sol +93 -190
  97. package/contracts/zeto_anon_enc_nullifier.sol +249 -195
  98. package/contracts/zeto_anon_enc_nullifier_kyc.sol +51 -232
  99. package/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +231 -238
  100. package/contracts/zeto_anon_nullifier.sol +164 -298
  101. package/contracts/zeto_anon_nullifier_burnable.sol +68 -18
  102. package/contracts/zeto_anon_nullifier_kyc.sol +40 -345
  103. package/contracts/zeto_anon_nullifier_qurrency.sol +217 -361
  104. package/contracts/zeto_nf_anon.sol +55 -130
  105. package/contracts/zeto_nf_anon_nullifier.sol +90 -152
  106. package/hardhat.config.ts +18 -3
  107. package/ignition/modules/lib/deps.ts +13 -0
  108. package/ignition/modules/test/tendecimals.ts +7 -1
  109. package/ignition/modules/zeto_anon.ts +3 -0
  110. package/ignition/modules/zeto_anon_burnable.ts +11 -7
  111. package/ignition/modules/zeto_anon_enc.ts +3 -0
  112. package/ignition/modules/zeto_anon_enc_nullifier.ts +34 -0
  113. package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +5 -2
  114. package/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +3 -0
  115. package/ignition/modules/zeto_anon_nullifier.ts +31 -4
  116. package/ignition/modules/zeto_anon_nullifier_burnable.ts +53 -16
  117. package/ignition/modules/zeto_anon_nullifier_kyc.ts +30 -12
  118. package/ignition/modules/zeto_anon_nullifier_qurrency.ts +3 -0
  119. package/ignition/modules/zeto_nf_anon.ts +3 -1
  120. package/ignition/modules/zeto_nf_anon_nullifier.ts +17 -12
  121. package/package.json +7 -3
  122. package/scripts/deploy_cloneable.ts +19 -10
  123. package/scripts/deploy_upgradeable.ts +9 -5
  124. package/scripts/lib/zeto_libraries.ts +47 -0
  125. package/scripts/tokens/Zeto_Anon.ts +6 -3
  126. package/scripts/tokens/Zeto_AnonBurnable.ts +4 -1
  127. package/scripts/tokens/Zeto_AnonEnc.ts +15 -3
  128. package/scripts/tokens/Zeto_AnonEncNullifier.ts +11 -8
  129. package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +7 -6
  130. package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +7 -6
  131. package/scripts/tokens/Zeto_AnonNullifier.ts +7 -6
  132. package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +7 -6
  133. package/scripts/tokens/Zeto_AnonNullifierKyc.ts +7 -6
  134. package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +7 -8
  135. package/scripts/tokens/Zeto_NfAnon.ts +5 -3
  136. package/scripts/tokens/Zeto_NfAnonNullifier.ts +7 -7
  137. package/test/factory.ts +55 -73
  138. package/test/lib/anon_nullifier_helpers.ts +89 -0
  139. package/test/lib/anon_zeto_helpers.ts +76 -0
  140. package/test/lib/deploy.ts +5 -2
  141. package/test/lib/eip170.ts +23 -0
  142. package/test/lib/utils.ts +74 -35
  143. package/test/test/escrow1.ts +185 -58
  144. package/test/test/escrow2.ts +200 -107
  145. package/test/test/qurrency.ts +13 -33
  146. package/test/usdc-shielding.ts +39 -27
  147. package/test/utils.ts +144 -21
  148. package/test/zeto_anon.ts +956 -465
  149. package/test/zeto_anon_enc.ts +881 -143
  150. package/test/zeto_anon_enc_nullifier.ts +850 -39
  151. package/test/zeto_anon_enc_nullifier_kyc.ts +123 -182
  152. package/test/zeto_anon_enc_nullifier_non_repudiation.ts +80 -47
  153. package/test/zeto_anon_nullifier.ts +1212 -954
  154. package/test/zeto_anon_nullifier_kyc.ts +677 -656
  155. package/test/zeto_anon_nullifier_qurrency.ts +455 -307
  156. package/test/zeto_nf_anon.ts +737 -138
  157. package/test/zeto_nf_anon_nullifier.ts +876 -247
  158. package/contracts/lib/zeto_base.sol +0 -293
  159. package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
  160. package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
  161. package/contracts/lib/zeto_nullifier.sol +0 -340
  162. package/contracts/zkDvP.sol_ +0 -273
  163. package/test/zkDvP.ts_ +0 -455
  164. /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
+ }