@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
@@ -0,0 +1,613 @@
1
+ // Copyright © 2024 Kaleido, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+ // Licensed under the Apache License, Version 2.0 (the "License");
6
+ // you may not use this file except in compliance with the License.
7
+ // You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing, software
12
+ // distributed under the License is distributed on an "AS IS" BASIS,
13
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ // See the License for the specific language governing permissions and
15
+ // limitations under the License.
16
+ pragma solidity ^0.8.27;
17
+
18
+ import {IZetoLockableCapability} from "./interfaces/IZetoLockableCapability.sol";
19
+ import {Commonlib} from "./common/common.sol";
20
+ import {ZetoCommon, ZetoCommonStorage} from "./zeto_common.sol";
21
+
22
+ /// @title ZetoLockable
23
+ /// @author Kaleido, Inc.
24
+ /// @dev Reusable mixin that wires the {IZetoLockableCapability} lock
25
+ /// lifecycle on top of {ZetoCommon}, agnostic of whether the
26
+ /// embedding token is fungible or non-fungible.
27
+ ///
28
+ /// The mixin owns:
29
+ /// * The lock-state mappings ({_locks}, {_txIds},
30
+ /// {_utxoDelegates}).
31
+ /// * The full create / update / delegate / spend / cancel
32
+ /// lifecycle entry points.
33
+ /// * The hash-domain separated unlock-commitment helpers.
34
+ /// * The {locked(uint256)} override that joins the storage
35
+ /// layer's "is locked" bit with the per-UTXO spender
36
+ /// projection.
37
+ ///
38
+ /// Descendants are responsible for the two ZK-circuit-specific
39
+ /// hooks {_doLockTransition} and {_transferLocked}, which
40
+ /// serialize a Zeto state transition into the public-inputs
41
+ /// shape expected by their concrete circuit family. Fungible
42
+ /// tokens pad inputs/outputs to the next supported batch size
43
+ /// via {ZetoCommon.checkAndPadCommitments}; non-fungible tokens
44
+ /// operate on fixed 1-in/1-out arrays. Keeping the hooks
45
+ /// virtual in this mixin lets both flavours share every other
46
+ /// moving piece of the lock lifecycle.
47
+ abstract contract ZetoLockable is ZetoCommon, IZetoLockableCapability {
48
+ /// @dev Lock state and replay protection live in {ZetoLockableStorage}
49
+ /// (ERC-7201 namespace `zeto.storage.ZetoLockable`).
50
+
51
+ modifier lockActive(bytes32 lockId) {
52
+ if (ZetoLockableStorage.layout().locks[lockId].owner == address(0)) {
53
+ revert LockNotActive(lockId);
54
+ }
55
+ _;
56
+ }
57
+
58
+ modifier onlyLockOwner(bytes32 lockId) {
59
+ address owner = ZetoLockableStorage.layout().locks[lockId].owner;
60
+ if (owner != msg.sender) {
61
+ revert LockUnauthorized(lockId, owner, msg.sender);
62
+ }
63
+ _;
64
+ }
65
+
66
+ modifier onlyLockSpender(bytes32 lockId) {
67
+ address spender = ZetoLockableStorage.layout().locks[lockId].spender;
68
+ if (spender != msg.sender) {
69
+ revert LockUnauthorized(lockId, spender, msg.sender);
70
+ }
71
+ _;
72
+ }
73
+
74
+ function _useTxId(bytes32 txId) internal {
75
+ if (ZetoLockableStorage.layout().txIds[msg.sender][txId]) {
76
+ revert DuplicateTransaction(txId);
77
+ }
78
+ ZetoLockableStorage.layout().txIds[msg.sender][txId] = true;
79
+ }
80
+
81
+ /// @dev Project the lock-level spender onto each UTXO in `utxos`.
82
+ /// Skips zero-padding entries so it is safe to pass calldata
83
+ /// arrays straight from the lock payloads.
84
+ function _setLockDelegates(
85
+ uint256[] memory utxos,
86
+ address spender
87
+ ) internal {
88
+ for (uint256 i = 0; i < utxos.length; ++i) {
89
+ if (utxos[i] != 0) {
90
+ ZetoLockableStorage.layout().utxoDelegates[utxos[i]] = spender;
91
+ }
92
+ }
93
+ }
94
+
95
+ /// @dev Wipe the per-UTXO spender projection for each UTXO in
96
+ /// `utxos`. Called when a lock's inputs are consumed (spend or
97
+ /// cancel) so that the public {locked} view returns
98
+ /// `(false, address(0))` for them immediately, even if some
99
+ /// future code path were to resurrect the locked-state entry.
100
+ function _clearLockDelegates(uint256[] memory utxos) internal {
101
+ for (uint256 i = 0; i < utxos.length; ++i) {
102
+ if (utxos[i] != 0) {
103
+ delete ZetoLockableStorage.layout().utxoDelegates[utxos[i]];
104
+ }
105
+ }
106
+ }
107
+
108
+ // ------------------------------------------------------------------
109
+ // ILockableCapability lifecycle
110
+ // ------------------------------------------------------------------
111
+
112
+ /// @inheritdoc IZetoLockableCapability
113
+ function computeLockId(
114
+ bytes calldata createArgs
115
+ ) external view override returns (bytes32) {
116
+ ZetoCreateLockArgs memory args = abi.decode(
117
+ createArgs,
118
+ (ZetoCreateLockArgs)
119
+ );
120
+ return _computeLockId(args.txId);
121
+ }
122
+
123
+ function _computeLockId(bytes32 txId) internal view returns (bytes32) {
124
+ return keccak256(abi.encode(address(this), msg.sender, txId));
125
+ }
126
+
127
+ /// @dev Domain-separation tag for spend-intent commitments.
128
+ /// Included in {_buildUnlockHash} so that a payload hashing to
129
+ /// a lock's {ZetoLockInfo.spendCommitment} cannot also hash to
130
+ /// its {ZetoLockInfo.cancelCommitment}, preventing a spender
131
+ /// from transposing a spend payload into {cancelLock} (or vice
132
+ /// versa) when one of the commitments is non-zero.
133
+ bytes32 private constant _SPEND_HASH_DOMAIN =
134
+ keccak256("Zeto.spendCommitment.v1");
135
+
136
+ /// @dev Domain-separation tag for cancel-intent commitments. See
137
+ /// {_SPEND_HASH_DOMAIN} for the rationale.
138
+ bytes32 private constant _CANCEL_HASH_DOMAIN =
139
+ keccak256("Zeto.cancelCommitment.v1");
140
+
141
+ /// @inheritdoc IZetoLockableCapability
142
+ function computeSpendHash(
143
+ uint256[] calldata lockedInputs,
144
+ uint256[] calldata lockedOutputs,
145
+ uint256[] calldata outputs,
146
+ bytes calldata data
147
+ ) external pure override returns (bytes32) {
148
+ return
149
+ _buildUnlockHash(
150
+ lockedInputs,
151
+ lockedOutputs,
152
+ outputs,
153
+ data,
154
+ _SPEND_HASH_DOMAIN
155
+ );
156
+ }
157
+
158
+ /// @inheritdoc IZetoLockableCapability
159
+ function computeCancelHash(
160
+ uint256[] calldata lockedInputs,
161
+ uint256[] calldata lockedOutputs,
162
+ uint256[] calldata outputs,
163
+ bytes calldata data
164
+ ) external pure override returns (bytes32) {
165
+ return
166
+ _buildUnlockHash(
167
+ lockedInputs,
168
+ lockedOutputs,
169
+ outputs,
170
+ data,
171
+ _CANCEL_HASH_DOMAIN
172
+ );
173
+ }
174
+
175
+ /**
176
+ * @dev Create a new lock by spending unlocked inputs and producing
177
+ * the locked content. See {ILockableCapability.createLock} for
178
+ * the generic semantics; the Zeto-specific payload is
179
+ * {ZetoCreateLockArgs}.
180
+ *
181
+ * Emits {LockCreated} (generic) and {ZetoLockCreated}.
182
+ */
183
+ function createLock(
184
+ bytes calldata createArgs,
185
+ bytes32 spendCommitment,
186
+ bytes32 cancelCommitment,
187
+ bytes calldata data
188
+ ) external override returns (bytes32) {
189
+ ZetoCreateLockArgs memory args = abi.decode(
190
+ createArgs,
191
+ (ZetoCreateLockArgs)
192
+ );
193
+
194
+ bytes32 lockId = _computeLockId(args.txId);
195
+ if (ZetoLockableStorage.layout().locks[lockId].owner != address(0)) {
196
+ revert DuplicateLock(lockId);
197
+ }
198
+ _useTxId(args.txId);
199
+
200
+ _doLockTransition(args);
201
+
202
+ ZetoLockableStorage.ZetoLockInfo storage lock = ZetoLockableStorage
203
+ .layout()
204
+ .locks[lockId];
205
+ lock.owner = msg.sender;
206
+ lock.spender = msg.sender;
207
+ lock.spendCommitment = spendCommitment;
208
+ lock.cancelCommitment = cancelCommitment;
209
+ lock.lockedInputs = args.lockedOutputs;
210
+
211
+ emit LockCreated(
212
+ lockId,
213
+ msg.sender,
214
+ msg.sender,
215
+ spendCommitment,
216
+ cancelCommitment,
217
+ data
218
+ );
219
+ emit ZetoLockCreated(
220
+ args.txId,
221
+ lockId,
222
+ msg.sender,
223
+ args.inputs,
224
+ args.outputs,
225
+ args.lockedOutputs,
226
+ args.proof,
227
+ data
228
+ );
229
+
230
+ return lockId;
231
+ }
232
+
233
+ /**
234
+ * @dev Update the spend/cancel commitments on an active,
235
+ * owner-controlled lock. Mirrors the previous prepareUnlock
236
+ * flow but with full-update semantics: both commitments are
237
+ * replaced atomically.
238
+ *
239
+ * Emits {LockUpdated} and {ZetoLockUpdated}.
240
+ */
241
+ function updateLock(
242
+ bytes32 lockId,
243
+ bytes calldata updateArgs,
244
+ bytes32 spendCommitment,
245
+ bytes32 cancelCommitment,
246
+ bytes calldata data
247
+ ) external override lockActive(lockId) onlyLockOwner(lockId) {
248
+ ZetoLockableStorage.ZetoLockInfo storage lock = ZetoLockableStorage
249
+ .layout()
250
+ .locks[lockId];
251
+ if (lock.spender != lock.owner) {
252
+ revert LockImmutable(lockId);
253
+ }
254
+
255
+ ZetoUpdateLockArgs memory args = abi.decode(
256
+ updateArgs,
257
+ (ZetoUpdateLockArgs)
258
+ );
259
+ _useTxId(args.txId);
260
+
261
+ lock.spendCommitment = spendCommitment;
262
+ lock.cancelCommitment = cancelCommitment;
263
+
264
+ emit LockUpdated(
265
+ lockId,
266
+ msg.sender,
267
+ spendCommitment,
268
+ cancelCommitment,
269
+ data
270
+ );
271
+ emit ZetoLockUpdated(args.txId, lockId, msg.sender, data);
272
+ }
273
+
274
+ /**
275
+ * @dev Delegate spending authority for the lock. The per-UTXO
276
+ * spender projection used by the public {locked} view is also
277
+ * moved here so that subsequent observers see the new spender
278
+ * for every locked input belonging to this lock.
279
+ *
280
+ * Emits {LockDelegated} and {ZetoLockDelegated}.
281
+ */
282
+ function delegateLock(
283
+ bytes32 lockId,
284
+ bytes calldata delegateArgs,
285
+ address newSpender,
286
+ bytes calldata data
287
+ ) external override lockActive(lockId) onlyLockSpender(lockId) {
288
+ ZetoDelegateLockArgs memory args = abi.decode(
289
+ delegateArgs,
290
+ (ZetoDelegateLockArgs)
291
+ );
292
+ _useTxId(args.txId);
293
+
294
+ ZetoLockableStorage.ZetoLockInfo storage lock = ZetoLockableStorage
295
+ .layout()
296
+ .locks[lockId];
297
+ address previousSpender = lock.spender;
298
+ lock.spender = newSpender;
299
+
300
+ _setLockDelegates(lock.lockedInputs, newSpender);
301
+
302
+ emit LockDelegated(lockId, previousSpender, newSpender, data);
303
+ emit ZetoLockDelegated(
304
+ args.txId,
305
+ lockId,
306
+ previousSpender,
307
+ newSpender,
308
+ data
309
+ );
310
+ }
311
+
312
+ /**
313
+ * @dev Spend the lock by executing the committed unlock operation.
314
+ * If `spendCommitment` is non-zero, the supplied spendArgs MUST
315
+ * hash to it.
316
+ *
317
+ * Emits {LockSpent} and {ZetoLockSpent}.
318
+ */
319
+ function spendLock(
320
+ bytes32 lockId,
321
+ bytes calldata spendArgs,
322
+ bytes calldata data
323
+ ) external virtual override lockActive(lockId) onlyLockSpender(lockId) {
324
+ ZetoSpendLockArgs memory args = abi.decode(
325
+ spendArgs,
326
+ (ZetoSpendLockArgs)
327
+ );
328
+
329
+ // Snapshot the locked content from storage. The caller is
330
+ // intentionally not allowed to substitute a different set of
331
+ // locked inputs.
332
+ uint256[] memory lockedInputs = ZetoLockableStorage
333
+ .layout()
334
+ .locks[lockId]
335
+ .lockedInputs;
336
+ bytes32 expectedHash = ZetoLockableStorage
337
+ .layout()
338
+ .locks[lockId]
339
+ .spendCommitment;
340
+ _consumeLock(
341
+ lockId,
342
+ lockedInputs,
343
+ expectedHash,
344
+ _SPEND_HASH_DOMAIN,
345
+ args
346
+ );
347
+
348
+ emit LockSpent(lockId, msg.sender, data);
349
+ emit ZetoLockSpent(
350
+ args.txId,
351
+ lockId,
352
+ msg.sender,
353
+ lockedInputs,
354
+ args.lockedOutputs,
355
+ args.outputs,
356
+ args.proof,
357
+ data
358
+ );
359
+ }
360
+
361
+ /**
362
+ * @dev Cancel the lock by executing the committed cancellation
363
+ * operation. If `cancelCommitment` is non-zero, the supplied
364
+ * cancelArgs MUST hash to it.
365
+ *
366
+ * Emits {LockCancelled} and {ZetoLockCancelled}.
367
+ */
368
+ function cancelLock(
369
+ bytes32 lockId,
370
+ bytes calldata cancelArgs,
371
+ bytes calldata data
372
+ ) external virtual override lockActive(lockId) onlyLockSpender(lockId) {
373
+ ZetoSpendLockArgs memory args = abi.decode(
374
+ cancelArgs,
375
+ (ZetoSpendLockArgs)
376
+ );
377
+
378
+ uint256[] memory lockedInputs = ZetoLockableStorage
379
+ .layout()
380
+ .locks[lockId]
381
+ .lockedInputs;
382
+ bytes32 expectedHash = ZetoLockableStorage
383
+ .layout()
384
+ .locks[lockId]
385
+ .cancelCommitment;
386
+ _consumeLock(
387
+ lockId,
388
+ lockedInputs,
389
+ expectedHash,
390
+ _CANCEL_HASH_DOMAIN,
391
+ args
392
+ );
393
+
394
+ emit LockCancelled(lockId, msg.sender, data);
395
+ emit ZetoLockCancelled(
396
+ args.txId,
397
+ lockId,
398
+ msg.sender,
399
+ lockedInputs,
400
+ args.lockedOutputs,
401
+ args.outputs,
402
+ args.proof,
403
+ data
404
+ );
405
+ }
406
+
407
+ // ------------------------------------------------------------------
408
+ // ILockableCapability views
409
+ // ------------------------------------------------------------------
410
+
411
+ function getLock(
412
+ bytes32 lockId
413
+ ) external view override lockActive(lockId) returns (LockInfo memory) {
414
+ ZetoLockableStorage.ZetoLockInfo storage lock = ZetoLockableStorage
415
+ .layout()
416
+ .locks[lockId];
417
+ return
418
+ LockInfo({
419
+ owner: lock.owner,
420
+ spender: lock.spender,
421
+ spendCommitment: lock.spendCommitment,
422
+ cancelCommitment: lock.cancelCommitment
423
+ });
424
+ }
425
+
426
+ function isLockActive(
427
+ bytes32 lockId
428
+ ) external view override returns (bool) {
429
+ return ZetoLockableStorage.layout().locks[lockId].owner != address(0);
430
+ }
431
+
432
+ function getLockContent(
433
+ bytes32 lockId
434
+ ) external view override lockActive(lockId) returns (bytes memory content) {
435
+ return
436
+ abi.encode(ZetoLockableStorage.layout().locks[lockId].lockedInputs);
437
+ }
438
+
439
+ /// @inheritdoc IZetoLockableCapability
440
+ function getLockedInputs(
441
+ bytes32 lockId
442
+ )
443
+ external
444
+ view
445
+ override
446
+ lockActive(lockId)
447
+ returns (uint256[] memory lockedInputs)
448
+ {
449
+ return ZetoLockableStorage.layout().locks[lockId].lockedInputs;
450
+ }
451
+
452
+ /**
453
+ * @dev Override of {ZetoCommon.locked} that fills in the per-UTXO
454
+ * spender from the {_utxoDelegates} projection. The storage
455
+ * layer only tells us "is this locked"; the spender lives at
456
+ * this layer because the lock model itself does.
457
+ *
458
+ * Returns `(false, address(0))` for any UTXO that is not
459
+ * currently locked-unspent, including UTXOs whose lock has
460
+ * been spent or cancelled (since both the storage layer's
461
+ * locked-set is cleared and `_utxoDelegates[X]` is wiped in
462
+ * the consume path).
463
+ */
464
+ function locked(uint256 utxo) public view override returns (bool, address) {
465
+ if (!ZetoCommonStorage.layout().utxoStorage.locked(utxo)) {
466
+ return (false, address(0));
467
+ }
468
+ return (true, ZetoLockableStorage.layout().utxoDelegates[utxo]);
469
+ }
470
+
471
+ // ------------------------------------------------------------------
472
+ // Internals
473
+ // ------------------------------------------------------------------
474
+
475
+ /**
476
+ * @dev Verify the ZK proof authorizing a {createLock} call and
477
+ * apply the resulting state transition. Implementations differ
478
+ * between fungible (padding to the next supported batch size)
479
+ * and non-fungible (fixed 1-in/1-out) tokens, so this hook is
480
+ * left abstract. They MUST:
481
+ *
482
+ * 1. {validateTransactionProposal}(args.inputs, args.outputs,
483
+ * args.lockedOutputs, args.proof, false).
484
+ * 2. {constructPublicInputs}(...) and
485
+ * {verifyProof}(...) for the unlocked-inputs flavour.
486
+ * 3. {processInputsAndOutputs}(args.inputs, args.outputs,
487
+ * false).
488
+ * 4. {processLockedOutputs}(args.lockedOutputs).
489
+ * 5. {_setLockDelegates}(args.lockedOutputs, msg.sender).
490
+ */
491
+ function _doLockTransition(ZetoCreateLockArgs memory args) internal virtual;
492
+
493
+ /**
494
+ * @dev Apply the ZK-verified state transition that consumes a
495
+ * lock's `lockedInputs`. See {_doLockTransition} for the
496
+ * rationale for keeping this abstract.
497
+ */
498
+ function _transferLocked(
499
+ bytes32 lockId,
500
+ uint256[] memory lockedInputs,
501
+ uint256[] memory lockedOutputs,
502
+ uint256[] memory outputs,
503
+ bytes memory proof,
504
+ bytes memory data
505
+ ) internal virtual;
506
+
507
+ /**
508
+ * @dev Consume an active lock.
509
+ *
510
+ * `lockedInputs` MUST be the lock's storage-pinned content
511
+ * (read by the caller from `_locks[lockId].lockedInputs`). The
512
+ * spender does not get to choose which UTXOs are consumed: that
513
+ * decision was made at {createLock} time. This is what makes
514
+ * "spending lock A consumes lock A's UTXOs" a hard contract
515
+ * invariant.
516
+ */
517
+ function _consumeLock(
518
+ bytes32 lockId,
519
+ uint256[] memory lockedInputs,
520
+ bytes32 expectedHash,
521
+ bytes32 hashDomain,
522
+ ZetoSpendLockArgs memory args
523
+ ) internal {
524
+ _useTxId(args.txId);
525
+
526
+ if (expectedHash != 0) {
527
+ bytes32 actualHash = _buildUnlockHash(
528
+ lockedInputs,
529
+ args.lockedOutputs,
530
+ args.outputs,
531
+ args.data,
532
+ hashDomain
533
+ );
534
+ if (actualHash != expectedHash) {
535
+ revert InvalidUnlockHash(expectedHash, actualHash);
536
+ }
537
+ }
538
+
539
+ // Tear down all lock-spender state BEFORE performing the state
540
+ // transition, so any reentrant lookup observes both
541
+ // `_locks[lockId]` and the per-UTXO `_utxoDelegates` projection
542
+ // as cleared.
543
+ _clearLockDelegates(lockedInputs);
544
+ delete ZetoLockableStorage.layout().locks[lockId];
545
+
546
+ _transferLocked(
547
+ lockId,
548
+ lockedInputs,
549
+ args.lockedOutputs,
550
+ args.outputs,
551
+ args.proof,
552
+ args.data
553
+ );
554
+ }
555
+
556
+ /// @dev Build the unlock hash committed to by
557
+ /// {ZetoLockInfo.spendCommitment} or
558
+ /// {ZetoLockInfo.cancelCommitment}. `domain` is a per-intent
559
+ /// separation tag (see {_SPEND_HASH_DOMAIN} /
560
+ /// {_CANCEL_HASH_DOMAIN}) so that the two commitments live in
561
+ /// disjoint hash spaces.
562
+ ///
563
+ /// Each variable-length component is hashed as
564
+ /// `abi.encode(...)` rather than `abi.encodePacked(...)` to
565
+ /// avoid the cross-array length-ambiguity that `encodePacked`
566
+ /// introduces for dynamic arguments (e.g. `[1, 2]` and `[12]`
567
+ /// collide under `encodePacked` but not under `encode`).
568
+ function _buildUnlockHash(
569
+ uint256[] memory lockedInputs,
570
+ uint256[] memory lockedOutputs,
571
+ uint256[] memory outputs,
572
+ bytes memory data,
573
+ bytes32 domain
574
+ ) internal pure returns (bytes32) {
575
+ return
576
+ keccak256(
577
+ abi.encode(
578
+ domain,
579
+ keccak256(abi.encode(lockedInputs)),
580
+ keccak256(abi.encode(lockedOutputs)),
581
+ keccak256(abi.encode(outputs)),
582
+ keccak256(data)
583
+ )
584
+ );
585
+ }
586
+ }
587
+
588
+ /// @dev ERC-7201 (`erc7201:zeto.storage.ZetoLockable`): lock lifecycle state for
589
+ /// this abstract contract.
590
+ library ZetoLockableStorage {
591
+ struct ZetoLockInfo {
592
+ address owner;
593
+ address spender;
594
+ bytes32 spendCommitment;
595
+ bytes32 cancelCommitment;
596
+ uint256[] lockedInputs;
597
+ }
598
+
599
+ struct Layout {
600
+ mapping(bytes32 => ZetoLockInfo) locks;
601
+ mapping(address => mapping(bytes32 => bool)) txIds;
602
+ mapping(uint256 => address) utxoDelegates;
603
+ }
604
+
605
+ bytes32 private constant STORAGE_LOCATION =
606
+ 0x6f84b5947db308f6274c3bdf3450b8e85913b258c3b2e7abbddf0986236a4900;
607
+
608
+ function layout() internal pure returns (Layout storage $) {
609
+ assembly {
610
+ $.slot := STORAGE_LOCATION
611
+ }
612
+ }
613
+ }