@lfdecentralizedtrust/zeto-contracts 0.2.1 → 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.
- package/README.md +70 -162
- package/contracts/factory.sol +30 -34
- package/contracts/factory_upgradeable.sol +160 -0
- package/contracts/lib/common/util.sol +40 -0
- package/contracts/lib/interfaces/ILockableCapability.sol +318 -0
- package/contracts/lib/interfaces/{izeto.sol → IZeto.sol} +13 -1
- package/contracts/lib/interfaces/{izeto_initializable.sol → IZetoInitializable.sol} +14 -12
- package/contracts/lib/interfaces/{izeto_kyc.sol → IZetoKyc.sol} +3 -3
- package/contracts/lib/interfaces/IZetoLockableCapability.sol +237 -0
- package/contracts/lib/interfaces/IZetoStorage.sol +106 -0
- package/contracts/lib/interfaces/{izeto_lockable.sol → IZetoVerifier.sol} +8 -21
- package/contracts/lib/registry.sol +77 -27
- package/contracts/lib/storage/base.sol +210 -0
- package/contracts/lib/storage/nullifier.sol +166 -0
- package/contracts/lib/zeto_common.sol +277 -28
- package/contracts/lib/zeto_fungible.sol +451 -33
- package/contracts/lib/zeto_fungible_base.sol +69 -0
- package/contracts/lib/zeto_fungible_burn.sol +83 -53
- package/contracts/lib/zeto_fungible_burn_nullifier.sol +115 -72
- package/contracts/lib/zeto_fungible_nullifier.sol +167 -0
- package/contracts/lib/zeto_lockable.sol +613 -0
- package/contracts/lib/zeto_non_fungible.sol +236 -0
- package/contracts/lib/zeto_non_fungible_base.sol +61 -0
- package/contracts/lib/zeto_non_fungible_nullifier.sol +61 -0
- package/contracts/test/escrow1.sol +90 -34
- package/contracts/test/escrow2.sol +64 -29
- package/contracts/test/qurrency.sol +10 -2
- package/contracts/test/smt.sol +8 -3
- package/contracts/test/tendecimals.sol +14 -12
- package/contracts/verifiers/impl/anon.sol +189 -0
- package/contracts/verifiers/impl/anon_batch.sol +301 -0
- package/contracts/verifiers/impl/anon_enc.sol +266 -0
- package/contracts/verifiers/impl/anon_enc_batch.sol +602 -0
- package/contracts/verifiers/impl/anon_enc_nullifier.sol +287 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_batch.sol +679 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_kyc.sol +294 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_kyc_batch.sol +686 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation.sol +413 -0
- package/contracts/verifiers/impl/anon_enc_nullifier_non_repudiation_batch.sol +1141 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transfer.sol +217 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked.sol +196 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transferLocked_batch.sol +308 -0
- package/contracts/verifiers/impl/anon_nullifier_kyc_transfer_batch.sol +385 -0
- package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer.sol +497 -0
- package/contracts/verifiers/impl/anon_nullifier_qurrency_transfer_batch.sol +1001 -0
- package/contracts/verifiers/{verifier_anon_nullifier_transferLocked.sol → impl/anon_nullifier_transfer.sol} +12 -19
- package/contracts/verifiers/{verifier_anon_nullifier_transferLocked_batch.sol → impl/anon_nullifier_transfer_batch.sol} +44 -51
- package/contracts/verifiers/impl/burn.sol +182 -0
- package/contracts/verifiers/impl/burn_batch.sol +238 -0
- package/contracts/verifiers/impl/burn_nullifier.sol +203 -0
- package/contracts/verifiers/impl/burn_nullifier_batch.sol +315 -0
- package/contracts/verifiers/impl/deposit.sol +182 -0
- package/contracts/verifiers/impl/deposit_kyc.sol +189 -0
- package/contracts/verifiers/impl/nf_anon.sol +175 -0
- package/contracts/verifiers/{verifier_nf_anon_nullifier_transferLocked.sol → impl/nf_anon_nullifier_transfer.sol} +6 -13
- package/contracts/verifiers/impl/withdraw.sol +189 -0
- package/contracts/verifiers/impl/withdraw_batch.sol +245 -0
- package/contracts/verifiers/impl/withdraw_nullifier.sol +210 -0
- package/contracts/verifiers/impl/withdraw_nullifier_batch.sol +322 -0
- package/contracts/verifiers/verifier_anon.sol +31 -186
- package/contracts/verifiers/verifier_anon_batch.sol +31 -298
- package/contracts/verifiers/verifier_anon_enc.sol +31 -263
- package/contracts/verifiers/verifier_anon_enc_batch.sol +31 -599
- package/contracts/verifiers/verifier_anon_enc_nullifier.sol +31 -284
- package/contracts/verifiers/verifier_anon_enc_nullifier_batch.sol +33 -676
- package/contracts/verifiers/verifier_anon_enc_nullifier_kyc.sol +31 -291
- package/contracts/verifiers/verifier_anon_enc_nullifier_kyc_batch.sol +33 -683
- package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation.sol +33 -410
- package/contracts/verifiers/verifier_anon_enc_nullifier_non_repudiation_batch.sol +33 -1138
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer.sol +33 -214
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked.sol +33 -221
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transferLocked_batch.sol +33 -389
- package/contracts/verifiers/verifier_anon_nullifier_kyc_transfer_batch.sol +33 -382
- package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer.sol +33 -221
- package/contracts/verifiers/verifier_anon_nullifier_qurrency_transfer_batch.sol +33 -389
- package/contracts/verifiers/verifier_anon_nullifier_transfer.sol +33 -207
- package/contracts/verifiers/verifier_anon_nullifier_transfer_batch.sol +33 -375
- package/contracts/verifiers/verifier_burn.sol +31 -179
- package/contracts/verifiers/verifier_burn_batch.sol +31 -235
- package/contracts/verifiers/verifier_burn_nullifier.sol +31 -200
- package/contracts/verifiers/verifier_burn_nullifier_batch.sol +31 -312
- package/contracts/verifiers/verifier_deposit.sol +31 -179
- package/contracts/verifiers/verifier_deposit_kyc.sol +34 -0
- package/contracts/verifiers/verifier_nf_anon.sol +31 -172
- package/contracts/verifiers/verifier_nf_anon_nullifier_transfer.sol +33 -179
- package/contracts/verifiers/verifier_withdraw.sol +31 -186
- package/contracts/verifiers/verifier_withdraw_batch.sol +31 -242
- package/contracts/verifiers/verifier_withdraw_nullifier.sol +31 -207
- package/contracts/verifiers/verifier_withdraw_nullifier_batch.sol +33 -319
- package/contracts/zeto_anon.sol +77 -231
- package/contracts/zeto_anon_burnable.sol +56 -12
- package/contracts/zeto_anon_enc.sol +93 -190
- package/contracts/zeto_anon_enc_nullifier.sol +249 -195
- package/contracts/zeto_anon_enc_nullifier_kyc.sol +51 -232
- package/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +231 -238
- package/contracts/zeto_anon_nullifier.sol +164 -298
- package/contracts/zeto_anon_nullifier_burnable.sol +68 -18
- package/contracts/zeto_anon_nullifier_kyc.sol +40 -345
- package/contracts/zeto_anon_nullifier_qurrency.sol +218 -360
- package/contracts/zeto_nf_anon.sol +55 -130
- package/contracts/zeto_nf_anon_nullifier.sol +90 -152
- package/hardhat.config.ts +9 -3
- package/ignition/modules/lib/deps.ts +9 -6
- package/ignition/modules/zeto_anon_burnable.ts +8 -7
- package/ignition/modules/zeto_anon_enc_nullifier.ts +33 -1
- package/ignition/modules/zeto_anon_enc_nullifier_kyc.ts +2 -2
- package/ignition/modules/zeto_anon_enc_nullifier_non_repudiation.ts +2 -1
- package/ignition/modules/zeto_anon_nullifier.ts +30 -5
- package/ignition/modules/zeto_anon_nullifier_burnable.ts +52 -17
- package/ignition/modules/zeto_anon_nullifier_kyc.ts +27 -12
- package/ignition/modules/zeto_anon_nullifier_qurrency.ts +2 -1
- package/ignition/modules/zeto_nf_anon_nullifier.ts +16 -12
- package/package.json +4 -2
- package/scripts/deploy_cloneable.ts +19 -10
- package/scripts/deploy_upgradeable.ts +9 -5
- package/scripts/tokens/Zeto_Anon.ts +3 -3
- package/scripts/tokens/Zeto_AnonBurnable.ts +1 -1
- package/scripts/tokens/Zeto_AnonEnc.ts +12 -3
- package/scripts/tokens/Zeto_AnonEncNullifier.ts +7 -3
- package/scripts/tokens/Zeto_AnonEncNullifierKyc.ts +1 -1
- package/scripts/tokens/Zeto_AnonEncNullifierNonRepudiation.ts +3 -1
- package/scripts/tokens/Zeto_AnonNullifier.ts +3 -1
- package/scripts/tokens/Zeto_AnonNullifierBurnable.ts +3 -1
- package/scripts/tokens/Zeto_AnonNullifierKyc.ts +1 -1
- package/scripts/tokens/Zeto_AnonNullifierQurrency.ts +3 -1
- package/scripts/tokens/Zeto_NfAnon.ts +2 -2
- package/scripts/tokens/Zeto_NfAnonNullifier.ts +3 -2
- package/test/factory.ts +46 -73
- package/test/factory_upgradeable.ts +311 -0
- package/test/lib/anon_nullifier_helpers.ts +89 -0
- package/test/lib/anon_zeto_helpers.ts +76 -0
- package/test/lib/deploy.ts +3 -2
- package/test/lib/utils.ts +74 -35
- package/test/test/escrow1.ts +185 -58
- package/test/test/escrow2.ts +200 -107
- package/test/test/qurrency.ts +13 -33
- package/test/usdc-shielding.ts +39 -26
- package/test/utils.ts +144 -21
- package/test/zeto_anon.ts +956 -465
- package/test/zeto_anon_enc.ts +881 -142
- package/test/zeto_anon_enc_nullifier.ts +847 -38
- package/test/zeto_anon_enc_nullifier_kyc.ts +120 -181
- package/test/zeto_anon_enc_nullifier_non_repudiation.ts +77 -46
- package/test/zeto_anon_nullifier.ts +1212 -953
- package/test/zeto_anon_nullifier_kyc.ts +674 -655
- package/test/zeto_anon_nullifier_qurrency.ts +452 -306
- package/test/zeto_nf_anon.ts +737 -137
- package/test/zeto_nf_anon_nullifier.ts +876 -246
- package/contracts/lib/zeto_base.sol +0 -293
- package/contracts/lib/zeto_fungible_withdraw.sol +0 -132
- package/contracts/lib/zeto_fungible_withdraw_nullifier.sol +0 -144
- package/contracts/lib/zeto_nullifier.sol +0 -334
- package/contracts/zkDvP.sol_ +0 -273
- package/test/zkDvP.ts_ +0 -455
- /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
|
+
}
|