@lfdecentralizedtrust/zeto-contracts 0.5.0 → 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 (50) hide show
  1. package/README.md +6 -0
  2. package/config/eip170.ts +30 -0
  3. package/contracts/lib/interfaces/IZetoLockHooks.sol +42 -0
  4. package/contracts/lib/zeto_fungible.sol +9 -17
  5. package/contracts/lib/zeto_lockable.sol +85 -479
  6. package/contracts/lib/zeto_lockable_lib.sol +460 -0
  7. package/contracts/lib/zeto_lockable_storage.sol +43 -0
  8. package/contracts/lib/zeto_non_fungible.sol +8 -16
  9. package/hardhat.config.ts +11 -2
  10. package/ignition/modules/lib/deps.ts +5 -0
  11. package/ignition/modules/test/tendecimals.ts +7 -1
  12. package/ignition/modules/zeto_anon.ts +3 -0
  13. package/ignition/modules/zeto_anon_burnable.ts +3 -0
  14. package/ignition/modules/zeto_anon_enc.ts +3 -0
  15. package/ignition/modules/zeto_anon_enc_nullifier.ts +3 -0
  16. package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +3 -0
  17. package/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +3 -0
  18. package/ignition/modules/zeto_anon_nullifier.ts +3 -0
  19. package/ignition/modules/zeto_anon_nullifier_burnable.ts +3 -0
  20. package/ignition/modules/zeto_anon_nullifier_kyc.ts +3 -0
  21. package/ignition/modules/zeto_anon_nullifier_qurrency.ts +3 -0
  22. package/ignition/modules/zeto_nf_anon.ts +3 -1
  23. package/ignition/modules/zeto_nf_anon_nullifier.ts +3 -2
  24. package/package.json +4 -2
  25. package/scripts/lib/zeto_libraries.ts +47 -0
  26. package/scripts/tokens/Zeto_Anon.ts +3 -0
  27. package/scripts/tokens/Zeto_AnonBurnable.ts +3 -0
  28. package/scripts/tokens/Zeto_AnonEnc.ts +3 -0
  29. package/scripts/tokens/Zeto_AnonEncNullifier.ts +6 -5
  30. package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +6 -5
  31. package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +6 -5
  32. package/scripts/tokens/Zeto_AnonNullifier.ts +6 -5
  33. package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +6 -5
  34. package/scripts/tokens/Zeto_AnonNullifierKyc.ts +6 -5
  35. package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +6 -7
  36. package/scripts/tokens/Zeto_NfAnon.ts +3 -1
  37. package/scripts/tokens/Zeto_NfAnonNullifier.ts +6 -6
  38. package/test/factory.ts +10 -1
  39. package/test/lib/deploy.ts +2 -0
  40. package/test/lib/eip170.ts +23 -0
  41. package/test/usdc-shielding.ts +0 -1
  42. package/test/zeto_anon_enc.ts +0 -1
  43. package/test/zeto_anon_enc_nullifier.ts +3 -1
  44. package/test/zeto_anon_enc_nullifier_kyc.ts +3 -1
  45. package/test/zeto_anon_enc_nullifier_non_repudiation.ts +3 -1
  46. package/test/zeto_anon_nullifier.ts +0 -1
  47. package/test/zeto_anon_nullifier_kyc.ts +3 -1
  48. package/test/zeto_anon_nullifier_qurrency.ts +3 -1
  49. package/test/zeto_nf_anon.ts +0 -1
  50. package/test/zeto_nf_anon_nullifier.ts +0 -1
@@ -16,128 +16,37 @@
16
16
  pragma solidity ^0.8.27;
17
17
 
18
18
  import {IZetoLockableCapability} from "./interfaces/IZetoLockableCapability.sol";
19
- import {Commonlib} from "./common/common.sol";
20
- import {ZetoCommon, ZetoCommonStorage} from "./zeto_common.sol";
19
+ import {IZetoLockHooks} from "./interfaces/IZetoLockHooks.sol";
20
+ import {ILockableCapability} from "./interfaces/ILockableCapability.sol";
21
+ import {ZetoCommon} from "./zeto_common.sol";
22
+ import {ZetoLockableLib} from "./zeto_lockable_lib.sol";
21
23
 
22
24
  /// @title ZetoLockable
23
25
  /// @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);
26
+ /// @dev Thin embedding layer for {IZetoLockableCapability}. Shared lock
27
+ /// lifecycle logic lives in the linked external {ZetoLockableLib};
28
+ /// this contract forwards interface entry points to the library and
29
+ /// exposes {IZetoLockHooks} callbacks for circuit-specific work in
30
+ /// descendants ({ZetoFungible}, {ZetoNonFungible}).
31
+ abstract contract ZetoLockable is
32
+ ZetoCommon,
33
+ IZetoLockableCapability,
34
+ IZetoLockHooks
35
+ {
36
+ modifier onlySelf() {
37
+ if (msg.sender != address(this)) {
38
+ revert NotSelf(msg.sender);
62
39
  }
63
40
  _;
64
41
  }
65
42
 
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
43
  /// @inheritdoc IZetoLockableCapability
113
44
  function computeLockId(
114
45
  bytes calldata createArgs
115
46
  ) 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));
47
+ return ZetoLockableLib.computeLockId(createArgs);
125
48
  }
126
49
 
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
50
  /// @inheritdoc IZetoLockableCapability
142
51
  function computeSpendHash(
143
52
  uint256[] calldata lockedInputs,
@@ -146,12 +55,11 @@ abstract contract ZetoLockable is ZetoCommon, IZetoLockableCapability {
146
55
  bytes calldata data
147
56
  ) external pure override returns (bytes32) {
148
57
  return
149
- _buildUnlockHash(
58
+ ZetoLockableLib.computeSpendHash(
150
59
  lockedInputs,
151
60
  lockedOutputs,
152
61
  outputs,
153
- data,
154
- _SPEND_HASH_DOMAIN
62
+ data
155
63
  );
156
64
  }
157
65
 
@@ -163,451 +71,149 @@ abstract contract ZetoLockable is ZetoCommon, IZetoLockableCapability {
163
71
  bytes calldata data
164
72
  ) external pure override returns (bytes32) {
165
73
  return
166
- _buildUnlockHash(
74
+ ZetoLockableLib.computeCancelHash(
167
75
  lockedInputs,
168
76
  lockedOutputs,
169
77
  outputs,
170
- data,
171
- _CANCEL_HASH_DOMAIN
78
+ data
172
79
  );
173
80
  }
174
81
 
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
- */
82
+ /// @inheritdoc ILockableCapability
183
83
  function createLock(
184
84
  bytes calldata createArgs,
185
85
  bytes32 spendCommitment,
186
86
  bytes32 cancelCommitment,
187
87
  bytes calldata data
188
88
  ) 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;
89
+ return
90
+ ZetoLockableLib.createLock(
91
+ createArgs,
92
+ spendCommitment,
93
+ cancelCommitment,
94
+ data
95
+ );
231
96
  }
232
97
 
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
- */
98
+ /// @inheritdoc ILockableCapability
241
99
  function updateLock(
242
100
  bytes32 lockId,
243
101
  bytes calldata updateArgs,
244
102
  bytes32 spendCommitment,
245
103
  bytes32 cancelCommitment,
246
104
  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(
105
+ ) external override {
106
+ ZetoLockableLib.updateLock(
265
107
  lockId,
266
- msg.sender,
108
+ updateArgs,
267
109
  spendCommitment,
268
110
  cancelCommitment,
269
111
  data
270
112
  );
271
- emit ZetoLockUpdated(args.txId, lockId, msg.sender, data);
272
113
  }
273
114
 
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
- */
115
+ /// @inheritdoc ILockableCapability
282
116
  function delegateLock(
283
117
  bytes32 lockId,
284
118
  bytes calldata delegateArgs,
285
119
  address newSpender,
286
120
  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,
121
+ ) external override {
122
+ ZetoLockableLib.delegateLock(
305
123
  lockId,
306
- previousSpender,
124
+ delegateArgs,
307
125
  newSpender,
308
126
  data
309
127
  );
310
128
  }
311
129
 
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
- */
130
+ /// @inheritdoc ILockableCapability
319
131
  function spendLock(
320
132
  bytes32 lockId,
321
133
  bytes calldata spendArgs,
322
134
  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
- );
135
+ ) external override {
136
+ ZetoLockableLib.spendLock(lockId, spendArgs, data);
359
137
  }
360
138
 
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
- */
139
+ /// @inheritdoc ILockableCapability
368
140
  function cancelLock(
369
141
  bytes32 lockId,
370
142
  bytes calldata cancelArgs,
371
143
  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
- );
144
+ ) external override {
145
+ ZetoLockableLib.cancelLock(lockId, cancelArgs, data);
405
146
  }
406
147
 
407
- // ------------------------------------------------------------------
408
- // ILockableCapability views
409
- // ------------------------------------------------------------------
410
-
148
+ /// @inheritdoc ILockableCapability
411
149
  function getLock(
412
150
  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
- });
151
+ ) external view override returns (LockInfo memory) {
152
+ return ZetoLockableLib.getLock(lockId);
424
153
  }
425
154
 
155
+ /// @inheritdoc ILockableCapability
426
156
  function isLockActive(
427
157
  bytes32 lockId
428
158
  ) external view override returns (bool) {
429
- return ZetoLockableStorage.layout().locks[lockId].owner != address(0);
159
+ return ZetoLockableLib.isLockActive(lockId);
430
160
  }
431
161
 
162
+ /// @inheritdoc ILockableCapability
432
163
  function getLockContent(
433
164
  bytes32 lockId
434
- ) external view override lockActive(lockId) returns (bytes memory content) {
435
- return
436
- abi.encode(ZetoLockableStorage.layout().locks[lockId].lockedInputs);
165
+ ) external view override returns (bytes memory content) {
166
+ return ZetoLockableLib.getLockContent(lockId);
437
167
  }
438
168
 
439
169
  /// @inheritdoc IZetoLockableCapability
440
170
  function getLockedInputs(
441
171
  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;
172
+ ) external view override returns (uint256[] memory lockedInputs) {
173
+ return ZetoLockableLib.getLockedInputs(lockId);
450
174
  }
451
175
 
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
- */
176
+ /// @inheritdoc ZetoCommon
464
177
  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]);
178
+ return ZetoLockableLib.locked(utxo);
469
179
  }
470
180
 
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;
181
+ /// @inheritdoc IZetoLockHooks
182
+ function zetoLockDoLockTransition(
183
+ ZetoCreateLockArgs calldata args
184
+ ) external override onlySelf {
185
+ _doLockTransition(args);
186
+ }
506
187
 
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(
188
+ /// @inheritdoc IZetoLockHooks
189
+ function zetoLockTransferLocked(
518
190
  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
-
191
+ uint256[] calldata lockedInputs,
192
+ uint256[] calldata lockedOutputs,
193
+ uint256[] calldata outputs,
194
+ bytes calldata proof,
195
+ bytes calldata data
196
+ ) external override onlySelf {
546
197
  _transferLocked(
547
198
  lockId,
548
199
  lockedInputs,
549
- args.lockedOutputs,
550
- args.outputs,
551
- args.proof,
552
- args.data
200
+ lockedOutputs,
201
+ outputs,
202
+ proof,
203
+ data
553
204
  );
554
205
  }
555
206
 
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;
207
+ function _doLockTransition(
208
+ ZetoCreateLockArgs calldata args
209
+ ) internal virtual;
607
210
 
608
- function layout() internal pure returns (Layout storage $) {
609
- assembly {
610
- $.slot := STORAGE_LOCATION
611
- }
612
- }
211
+ function _transferLocked(
212
+ bytes32 lockId,
213
+ uint256[] calldata lockedInputs,
214
+ uint256[] calldata lockedOutputs,
215
+ uint256[] calldata outputs,
216
+ bytes calldata proof,
217
+ bytes calldata data
218
+ ) internal virtual;
613
219
  }