@openzeppelin/compact-contracts 0.2.0 → 0.3.0-alpha
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 +3 -3
- package/access/AccessControl.compact +3 -35
- package/access/Ownable.compact +5 -51
- package/access/ShieldedAccessControl.compact +2 -2
- package/access/ZOwnablePK.compact +2 -2
- package/crypto/ElGamal.compact +473 -0
- package/multisig/ForwarderPrivate.compact +229 -0
- package/multisig/ForwarderShielded.compact +149 -0
- package/multisig/ForwarderUnshielded.compact +152 -0
- package/multisig/ProposalManager.compact +328 -0
- package/multisig/ShieldedTreasury.compact +201 -0
- package/multisig/ShieldedTreasuryStateless.compact +76 -0
- package/multisig/Signer.compact +312 -0
- package/multisig/SignerManager.compact +205 -0
- package/multisig/UnshieldedTreasury.compact +114 -0
- package/multisig/presets/ShieldedMultiSig.compact +236 -0
- package/multisig/presets/ShieldedMultiSigV2.compact +280 -0
- package/multisig/presets/ShieldedMultiSigV3.compact +368 -0
- package/multisig/presets/forwarder/ForwarderPrivate.compact +125 -0
- package/multisig/presets/forwarder/ForwarderShielded.compact +62 -0
- package/multisig/presets/forwarder/ForwarderUnshielded.compact +64 -0
- package/package.json +9 -5
- package/security/Allowlist.compact +101 -0
- package/security/Blocklist.compact +99 -0
- package/security/Initializable.compact +2 -2
- package/security/Pausable.compact +2 -2
- package/token/FungibleToken.compact +24 -82
- package/token/MultiToken.compact +21 -80
- package/token/NativeShieldedToken.compact +285 -0
- package/token/NativeShieldedTokenCore.compact +343 -0
- package/token/NativeShieldedTokenFamily.compact +287 -0
- package/token/NonFungibleToken.compact +25 -83
- package/utils/Utils.compact +86 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (token/NativeShieldedTokenFamily.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NativeShieldedTokenFamily
|
|
8
|
+
* @description A module for issuing a FAMILY of native shielded (Zswap)
|
|
9
|
+
* token types from one contract, discriminated by a per-call domain.
|
|
10
|
+
*
|
|
11
|
+
* This flavor exists because of Midnight's composition model: there is no
|
|
12
|
+
* contract-to-contract interaction and no way for a contract to deploy
|
|
13
|
+
* another, so the Ethereum pattern of one token contract per asset (stamped
|
|
14
|
+
* out by a factory) is unavailable. A protocol that issues many related
|
|
15
|
+
* tokens — e.g. a DEX minting one liquidity-share token per pair — must do
|
|
16
|
+
* so from a single contract, which the protocol's color derivation
|
|
17
|
+
* (`tokenType(domain, contractAddress)`) natively supports. For a
|
|
18
|
+
* single-token issuer, use `NativeShieldedToken` instead.
|
|
19
|
+
*
|
|
20
|
+
* This module is a thin wrapper over `NativeShieldedTokenCore`: it forwards the
|
|
21
|
+
* caller's per-call `domain` to every core op, so the mint/burn logic and
|
|
22
|
+
* metadata live in the shared core, not here.
|
|
23
|
+
*
|
|
24
|
+
* @notice A contract MUST NOT import both `NativeShieldedTokenFamily` and
|
|
25
|
+
* `NativeShieldedToken`. Both wrap `NativeShieldedTokenCore`, so they share one
|
|
26
|
+
* set of core state (a single init flag and one name/symbol/decimals).
|
|
27
|
+
* Pick one flavor per contract.
|
|
28
|
+
*
|
|
29
|
+
* @notice This module exposes composable building blocks for minting and
|
|
30
|
+
* burning native shielded coins. It does NOT include access control: consuming
|
|
31
|
+
* contracts compose that via the module/contract pattern (Ownable,
|
|
32
|
+
* AccessControl, etc.) and SHOULD gate the mint and burn circuits, which are
|
|
33
|
+
* unrestricted at the module level.
|
|
34
|
+
*
|
|
35
|
+
* @dev The `domain` parameter is passed explicitly per-call; the coin's `color`
|
|
36
|
+
* is `tokenType(domain, kernel.self())`, so the domain discriminates the token
|
|
37
|
+
* types within this contract (e.g. one LP share token per pair) and only this
|
|
38
|
+
* contract can mint coins of its colors. Metadata is contract-wide FAMILY
|
|
39
|
+
* metadata, following the Uniswap-V2 LP precedent: every token type shares one
|
|
40
|
+
* brand, and per-type identity belongs in the consumer's own state (e.g. a pair
|
|
41
|
+
* registry mapping domain -> underlying tokens), which is strictly more
|
|
42
|
+
* informative than a stored string. `decimals` applies family-wide; issuers
|
|
43
|
+
* with heterogeneous decimals should add their own per-domain handling. Mint
|
|
44
|
+
* amounts are `Uint<64>`, burn amounts `Uint<128>` — an asymmetry imposed by
|
|
45
|
+
* the protocol primitives, not a module choice. The mint/burn mechanics and the
|
|
46
|
+
* `_burn` vs `_burnFromSelf` spend paths live in `NativeShieldedTokenCore`.
|
|
47
|
+
*
|
|
48
|
+
* @notice With a secret, cryptographically random `_mint` nonce the mint is
|
|
49
|
+
* recipient-private; the caller is responsible for nonce uniqueness (a reused
|
|
50
|
+
* nonce for the same (domain, value, recipient) produces a duplicate
|
|
51
|
+
* commitment, which the ledger rejects). For mints that need no caller-managed
|
|
52
|
+
* nonce, compose `NativeShieldedTokenDerivedNonce` and pass its `_deriveNonce()`
|
|
53
|
+
* output, at the cost of making those mints recipient-public.
|
|
54
|
+
*
|
|
55
|
+
* @notice Supply accounting is opt-in: this module tracks NO supply totals.
|
|
56
|
+
* Compose the optional `NativeShieldedTokenFamilySupply` extension only if you
|
|
57
|
+
* need an on-chain `totalSupply(domain)`; see it for the privacy trade-off it
|
|
58
|
+
* introduces (it makes contract-mediated burn amounts public).
|
|
59
|
+
*
|
|
60
|
+
* @notice Out of scope (phase two, pending contract-to-contract support):
|
|
61
|
+
* `balanceOf`, `allowance`, transfer mediation, batch operations, and operator
|
|
62
|
+
* approvals are not representable for native shielded UTXOs — once a user holds
|
|
63
|
+
* a coin, the contract cannot observe or restrict its movement.
|
|
64
|
+
*/
|
|
65
|
+
module NativeShieldedTokenFamily {
|
|
66
|
+
import CompactStandardLibrary;
|
|
67
|
+
import "./NativeShieldedTokenCore" prefix Core_;
|
|
68
|
+
|
|
69
|
+
// Circuits use the `Core_` prefix above. The named import/re-export below is
|
|
70
|
+
// only for the core state variables, so the implementing contract's ledger
|
|
71
|
+
// keys read as `_name` etc. rather than `NativeShieldedTokenCore__name`. The
|
|
72
|
+
// circuit names are intentionally NOT imported unprefixed, to avoid potential clashes.
|
|
73
|
+
import { _name, _symbol, _decimals, _isInitialized } from "./NativeShieldedTokenCore";
|
|
74
|
+
export { _name, _symbol, _decimals, _isInitialized };
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @description Initializes the module's family metadata.
|
|
78
|
+
* @dev This MUST be called in the implementing contract's constructor, and it
|
|
79
|
+
* MUST be the only token operation performed there. Failure to call it can
|
|
80
|
+
* lead to an irreparable contract; perform mint, burn, and `tokenColor` only
|
|
81
|
+
* in post-deployment circuits, never in the constructor.
|
|
82
|
+
*
|
|
83
|
+
* @param {Opaque<"string">} name_ - The family name shared by all token types.
|
|
84
|
+
* @param {Opaque<"string">} symbol_ - The family symbol shared by all token types.
|
|
85
|
+
* @param {Uint<8>} decimals_ - The family-wide number of decimals used to
|
|
86
|
+
* get the user representation.
|
|
87
|
+
* @return {[]} - Empty tuple.
|
|
88
|
+
*/
|
|
89
|
+
export circuit initialize(
|
|
90
|
+
name_: Opaque<"string">,
|
|
91
|
+
symbol_: Opaque<"string">,
|
|
92
|
+
decimals_: Uint<8>
|
|
93
|
+
): [] {
|
|
94
|
+
Core_initialize(name_, symbol_, decimals_);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @description Returns whether the contract has been initialized.
|
|
99
|
+
*
|
|
100
|
+
* @circuitInfo k=6, rows=26
|
|
101
|
+
*
|
|
102
|
+
* @return {Boolean} - True once `initialize` has run.
|
|
103
|
+
*/
|
|
104
|
+
export circuit isInitialized(): Boolean {
|
|
105
|
+
return Core_isInitialized();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @description Returns the family name shared by all token types of this
|
|
110
|
+
* contract. Per-type identity belongs in the consumer's own state; see the
|
|
111
|
+
* module notes.
|
|
112
|
+
*
|
|
113
|
+
* @circuitInfo k=6, rows=28
|
|
114
|
+
*
|
|
115
|
+
* Requirements:
|
|
116
|
+
*
|
|
117
|
+
* - Contract is initialized.
|
|
118
|
+
*
|
|
119
|
+
* @return {Opaque<"string">} - The family name.
|
|
120
|
+
*/
|
|
121
|
+
export circuit name(): Opaque<"string"> {
|
|
122
|
+
return Core_name();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @description Returns the family symbol shared by all token types of this
|
|
127
|
+
* contract.
|
|
128
|
+
*
|
|
129
|
+
* @circuitInfo k=6, rows=28
|
|
130
|
+
*
|
|
131
|
+
* Requirements:
|
|
132
|
+
*
|
|
133
|
+
* - Contract is initialized.
|
|
134
|
+
*
|
|
135
|
+
* @return {Opaque<"string">} - The family symbol.
|
|
136
|
+
*/
|
|
137
|
+
export circuit symbol(): Opaque<"string"> {
|
|
138
|
+
return Core_symbol();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @description Returns the family-wide number of decimals used to get the
|
|
143
|
+
* user representation.
|
|
144
|
+
*
|
|
145
|
+
* @circuitInfo k=6, rows=28
|
|
146
|
+
*
|
|
147
|
+
* Requirements:
|
|
148
|
+
*
|
|
149
|
+
* - Contract is initialized.
|
|
150
|
+
*
|
|
151
|
+
* @return {Uint<8>} - The decimals value.
|
|
152
|
+
*/
|
|
153
|
+
export circuit decimals(): Uint<8> {
|
|
154
|
+
return Core_decimals();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @description Returns the coin color (token type) for `domain` under this
|
|
159
|
+
* contract's address, so integrators do not have to re-derive it by hand.
|
|
160
|
+
*
|
|
161
|
+
* @circuitInfo k=13, rows=4248
|
|
162
|
+
*
|
|
163
|
+
* Requirements:
|
|
164
|
+
*
|
|
165
|
+
* - Contract is initialized.
|
|
166
|
+
*
|
|
167
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
168
|
+
* @return {Bytes<32>} - The coin color: `tokenType(domain, kernel.self())`.
|
|
169
|
+
*/
|
|
170
|
+
export circuit tokenColor(domain: Bytes<32>): Bytes<32> {
|
|
171
|
+
return Core_tokenColor(domain);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @description Mints `amount` of the token identified by `domain` to
|
|
176
|
+
* `recipient`, using a caller-supplied nonce.
|
|
177
|
+
*
|
|
178
|
+
* @dev The caller is fully responsible for nonce uniqueness. Reusing a
|
|
179
|
+
* nonce for the same (domain, value, recipient) produces a duplicate
|
|
180
|
+
* commitment, which the protocol rejects. With a secret random nonce this
|
|
181
|
+
* is the recipient-private mint. For mints that require no caller-managed
|
|
182
|
+
* nonce, compose the `NativeShieldedTokenDerivedNonce` extension and pass
|
|
183
|
+
* its `_deriveNonce()` output.
|
|
184
|
+
*
|
|
185
|
+
* @notice The returned coin info is the only copy available to the
|
|
186
|
+
* recipient; callers SHOULD deliver it out of band. Wallets cannot detect
|
|
187
|
+
* contract-minted coins by scanning the chain.
|
|
188
|
+
*
|
|
189
|
+
* @circuitInfo k=14, rows=11119
|
|
190
|
+
*
|
|
191
|
+
* Requirements:
|
|
192
|
+
*
|
|
193
|
+
* - Contract is initialized.
|
|
194
|
+
* - `recipient` is not zero.
|
|
195
|
+
*
|
|
196
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator. Same domain + same
|
|
197
|
+
* contract address produces the same coin color (i.e. same token type).
|
|
198
|
+
* @param {Either<ZswapCoinPublicKey, ContractAddress>} recipient - The coin recipient.
|
|
199
|
+
* @param {Uint<64>} amount - Quantity to mint. Capped at `Uint<64>` by the protocol.
|
|
200
|
+
* @param {Bytes<32>} nonce - Caller-supplied nonce. Must be unique per (domain, contract).
|
|
201
|
+
* @return {ShieldedCoinInfo} - The newly created coin's info (nonce, color, value).
|
|
202
|
+
*/
|
|
203
|
+
export circuit _mint(
|
|
204
|
+
domain: Bytes<32>,
|
|
205
|
+
recipient: Either<ZswapCoinPublicKey, ContractAddress>,
|
|
206
|
+
amount: Uint<64>,
|
|
207
|
+
nonce: Bytes<32>
|
|
208
|
+
): ShieldedCoinInfo {
|
|
209
|
+
return Core__mint(domain, recipient, amount, nonce);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @description Burns `amount` from `coin`, a coin provided within the
|
|
214
|
+
* current transaction (e.g. paid in by the caller's wallet), and routes the
|
|
215
|
+
* remaining change to `refundTo`.
|
|
216
|
+
*
|
|
217
|
+
* To destroy `coin` in full, pass `coin.value` as `amount`; the change
|
|
218
|
+
* branch will not fire and `none` is returned. The `refundTo` value is
|
|
219
|
+
* inert in that case but must still be supplied and non-zero.
|
|
220
|
+
*
|
|
221
|
+
* @dev The coin is received by the contract and spent in the same
|
|
222
|
+
* transaction, so the spend goes through `sendImmediateShielded` (the
|
|
223
|
+
* transient path). For coins the contract already holds, use
|
|
224
|
+
* `_burnFromSelf` instead.
|
|
225
|
+
*
|
|
226
|
+
* @notice The returned refund coin info is the only copy available to
|
|
227
|
+
* `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
|
|
228
|
+
* contract-sent coins by scanning the chain.
|
|
229
|
+
*
|
|
230
|
+
* @circuitInfo k=16, rows=47932
|
|
231
|
+
*
|
|
232
|
+
* Requirements:
|
|
233
|
+
*
|
|
234
|
+
* - Contract is initialized.
|
|
235
|
+
* - `coin.color` is this contract's token type for `domain`.
|
|
236
|
+
* - `amount` is less than or equal to `coin.value`.
|
|
237
|
+
* - `refundTo` is not zero (the zero key is the burn address; a zero
|
|
238
|
+
* `refundTo` would silently burn the change as well).
|
|
239
|
+
*
|
|
240
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
241
|
+
* @param {ShieldedCoinInfo} coin - The coin to burn from.
|
|
242
|
+
* @param {Uint<128>} amount - Value to destroy. Must be <= `coin.value`.
|
|
243
|
+
* @param {Either<ZswapCoinPublicKey, ContractAddress>} refundTo - Where to
|
|
244
|
+
* route the unspent portion (`coin.value - amount`).
|
|
245
|
+
* @return {Maybe<ShieldedCoinInfo>} - The refund coin created for `refundTo`,
|
|
246
|
+
* or `none` if the coin was burned in full.
|
|
247
|
+
*/
|
|
248
|
+
export circuit _burn(
|
|
249
|
+
domain: Bytes<32>,
|
|
250
|
+
coin: ShieldedCoinInfo,
|
|
251
|
+
amount: Uint<128>,
|
|
252
|
+
refundTo: Either<ZswapCoinPublicKey, ContractAddress>
|
|
253
|
+
): Maybe<ShieldedCoinInfo> {
|
|
254
|
+
return Core__burn(domain, coin, amount, refundTo);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @description Burns `amount` from `coin`, a coin this contract already
|
|
259
|
+
* holds (a Merkle-tree entry with a valid `mt_index`). Any change is
|
|
260
|
+
* auto-received by the contract and returned.
|
|
261
|
+
*
|
|
262
|
+
* @dev The consumer SHOULD persist the returned change coin info in its own
|
|
263
|
+
* ledger state: the change replaces `coin` as the contract's holding, and
|
|
264
|
+
* its info is not otherwise recoverable.
|
|
265
|
+
*
|
|
266
|
+
* @circuitInfo k=15, rows=23583
|
|
267
|
+
*
|
|
268
|
+
* Requirements:
|
|
269
|
+
*
|
|
270
|
+
* - Contract is initialized.
|
|
271
|
+
* - `coin.color` is this contract's token type for `domain`.
|
|
272
|
+
* - `amount` is less than or equal to `coin.value`.
|
|
273
|
+
*
|
|
274
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
275
|
+
* @param {QualifiedShieldedCoinInfo} coin - The contract-held coin to burn from.
|
|
276
|
+
* @param {Uint<128>} amount - Value to destroy. Must be <= `coin.value`.
|
|
277
|
+
* @return {Maybe<ShieldedCoinInfo>} - The change coin retained by the
|
|
278
|
+
* contract, or `none` if the coin was burned in full.
|
|
279
|
+
*/
|
|
280
|
+
export circuit _burnFromSelf(
|
|
281
|
+
domain: Bytes<32>,
|
|
282
|
+
coin: QualifiedShieldedCoinInfo,
|
|
283
|
+
amount: Uint<128>
|
|
284
|
+
): Maybe<ShieldedCoinInfo> {
|
|
285
|
+
return Core__burnFromSelf(domain, coin, amount);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (token/NonFungibleToken.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module NonFungibleToken
|
|
@@ -154,18 +154,6 @@ module NonFungibleToken {
|
|
|
154
154
|
*/
|
|
155
155
|
witness wit_NonFungibleTokenSK(): Bytes<32>;
|
|
156
156
|
|
|
157
|
-
/**
|
|
158
|
-
* @description Returns a canonical zero Either value (left variant with zero Bytes<32>).
|
|
159
|
-
* Used as the zero value for mint/burn operations and cleared approvals.
|
|
160
|
-
*
|
|
161
|
-
* @return {Either<Bytes<32>, ContractAddress>} - The zero value.
|
|
162
|
-
*/
|
|
163
|
-
export pure circuit ZERO(): Either<Bytes<32>, ContractAddress> {
|
|
164
|
-
return Either<Bytes<32>, ContractAddress> {
|
|
165
|
-
is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
157
|
/**
|
|
170
158
|
* @description Initializes the contract by setting the name and symbol.
|
|
171
159
|
*
|
|
@@ -510,7 +498,7 @@ module NonFungibleToken {
|
|
|
510
498
|
tokenId: Uint<128>
|
|
511
499
|
): [] {
|
|
512
500
|
assertInitialized();
|
|
513
|
-
assert(!
|
|
501
|
+
assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
514
502
|
// Setting an "auth" argument enables the `_isAuthorized` check which verifies that the token exists
|
|
515
503
|
// (fromAddress != 0). Therefore, it is not needed to verify that the return value is not 0 here.
|
|
516
504
|
const auth = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
@@ -535,7 +523,7 @@ module NonFungibleToken {
|
|
|
535
523
|
export circuit _ownerOf(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
536
524
|
assertInitialized();
|
|
537
525
|
if (!_owners.member(disclose(tokenId))) {
|
|
538
|
-
return
|
|
526
|
+
return Utils_zeroAccount();
|
|
539
527
|
}
|
|
540
528
|
|
|
541
529
|
return _owners.lookup(disclose(tokenId));
|
|
@@ -556,7 +544,7 @@ module NonFungibleToken {
|
|
|
556
544
|
export circuit _getApproved(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
557
545
|
assertInitialized();
|
|
558
546
|
if (!_tokenApprovals.member(disclose(tokenId))) {
|
|
559
|
-
return
|
|
547
|
+
return Utils_zeroAccount();
|
|
560
548
|
}
|
|
561
549
|
return _tokenApprovals.lookup(disclose(tokenId));
|
|
562
550
|
}
|
|
@@ -588,7 +576,7 @@ module NonFungibleToken {
|
|
|
588
576
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
589
577
|
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
590
578
|
|
|
591
|
-
return (!
|
|
579
|
+
return (!Utils_isTargetZero(disclose(canonSpender)) &&
|
|
592
580
|
(disclose(canonOwner) == disclose(canonSpender) ||
|
|
593
581
|
isApprovedForAll(canonOwner, canonSpender) ||
|
|
594
582
|
_getApproved(tokenId) == disclose(canonSpender)));
|
|
@@ -619,7 +607,7 @@ module NonFungibleToken {
|
|
|
619
607
|
): [] {
|
|
620
608
|
assertInitialized();
|
|
621
609
|
if (!_isAuthorized(owner, spender, tokenId)) {
|
|
622
|
-
assert(!
|
|
610
|
+
assert(!Utils_isTargetZero(owner), "NonFungibleToken: nonexistent token");
|
|
623
611
|
assert(false, "NonFungibleToken: insufficient approval");
|
|
624
612
|
}
|
|
625
613
|
}
|
|
@@ -649,20 +637,20 @@ module NonFungibleToken {
|
|
|
649
637
|
const fromAddress = _ownerOf(tokenId);
|
|
650
638
|
|
|
651
639
|
// Perform (optional) operator check
|
|
652
|
-
if (!
|
|
640
|
+
if (!Utils_isTargetZero(disclose(canonAuth))) {
|
|
653
641
|
_checkAuthorized(fromAddress, canonAuth, tokenId);
|
|
654
642
|
}
|
|
655
643
|
|
|
656
644
|
// Execute the update
|
|
657
|
-
if (!
|
|
645
|
+
if (!Utils_isTargetZero(disclose(fromAddress))) {
|
|
658
646
|
// Clear approval. No need to re-authorize
|
|
659
|
-
_approve(
|
|
647
|
+
_approve(Utils_zeroAccount(), tokenId, Utils_zeroAccount());
|
|
660
648
|
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
661
649
|
const newBalance = _balances.lookup(disclose(canonFrom)) - 1 as Uint<128>;
|
|
662
650
|
_balances.insert(disclose(canonFrom), disclose(newBalance));
|
|
663
651
|
}
|
|
664
652
|
|
|
665
|
-
if (!
|
|
653
|
+
if (!Utils_isTargetZero(disclose(canonTo))) {
|
|
666
654
|
if (!_balances.member(disclose(canonTo))) {
|
|
667
655
|
_balances.insert(disclose(canonTo), 0);
|
|
668
656
|
}
|
|
@@ -723,11 +711,11 @@ module NonFungibleToken {
|
|
|
723
711
|
tokenId: Uint<128>
|
|
724
712
|
): [] {
|
|
725
713
|
assertInitialized();
|
|
726
|
-
assert(!
|
|
714
|
+
assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
727
715
|
|
|
728
|
-
const previousOwner = _update(to, tokenId,
|
|
716
|
+
const previousOwner = _update(to, tokenId, Utils_zeroAccount());
|
|
729
717
|
|
|
730
|
-
assert(
|
|
718
|
+
assert(Utils_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
|
|
731
719
|
}
|
|
732
720
|
|
|
733
721
|
/**
|
|
@@ -747,8 +735,8 @@ module NonFungibleToken {
|
|
|
747
735
|
*/
|
|
748
736
|
export circuit _burn(tokenId: Uint<128>): [] {
|
|
749
737
|
assertInitialized();
|
|
750
|
-
const previousOwner = _update(
|
|
751
|
-
assert(!
|
|
738
|
+
const previousOwner = _update(Utils_zeroAccount(), tokenId, Utils_zeroAccount());
|
|
739
|
+
assert(!Utils_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
|
|
752
740
|
_tokenURIs.remove(disclose(tokenId));
|
|
753
741
|
}
|
|
754
742
|
|
|
@@ -814,11 +802,11 @@ module NonFungibleToken {
|
|
|
814
802
|
tokenId: Uint<128>
|
|
815
803
|
): [] {
|
|
816
804
|
assertInitialized();
|
|
817
|
-
assert(!
|
|
805
|
+
assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
818
806
|
|
|
819
|
-
const previousOwner = _update(to, tokenId,
|
|
807
|
+
const previousOwner = _update(to, tokenId, Utils_zeroAccount());
|
|
820
808
|
|
|
821
|
-
assert(!
|
|
809
|
+
assert(!Utils_isTargetZero(previousOwner), "NonFungibleToken: nonexistent token");
|
|
822
810
|
|
|
823
811
|
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
824
812
|
assert(previousOwner == canonFrom, "NonFungibleToken: incorrect owner");
|
|
@@ -848,12 +836,12 @@ module NonFungibleToken {
|
|
|
848
836
|
assertInitialized();
|
|
849
837
|
// Canonicalize + normalize `to`
|
|
850
838
|
const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
|
|
851
|
-
const normalizedTo =
|
|
839
|
+
const normalizedTo = Utils_isTargetZero(canonTo) ? Utils_zeroAccount() : canonTo;
|
|
852
840
|
|
|
853
841
|
// Canonicalize auth
|
|
854
842
|
const canonAuth = Utils_canonicalize<Bytes<32>, ContractAddress>(auth);
|
|
855
843
|
|
|
856
|
-
if (!
|
|
844
|
+
if (!Utils_isTargetZero(disclose(canonAuth))) {
|
|
857
845
|
const owner = _requireOwned(tokenId);
|
|
858
846
|
|
|
859
847
|
// We do not use _isAuthorized because single-token approvals should not be able to call approve
|
|
@@ -888,10 +876,10 @@ module NonFungibleToken {
|
|
|
888
876
|
): [] {
|
|
889
877
|
assertInitialized();
|
|
890
878
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
891
|
-
assert(!
|
|
879
|
+
assert(!Utils_isTargetZero(canonOwner), "NonFungibleToken: invalid owner");
|
|
892
880
|
|
|
893
881
|
const canonOp = Utils_canonicalize<Bytes<32>, ContractAddress>(operator);
|
|
894
|
-
assert(!
|
|
882
|
+
assert(!Utils_isTargetZero(canonOp), "NonFungibleToken: invalid operator");
|
|
895
883
|
|
|
896
884
|
if (!_operatorApprovals.member(disclose(canonOwner))) {
|
|
897
885
|
_operatorApprovals.insert(
|
|
@@ -921,7 +909,7 @@ module NonFungibleToken {
|
|
|
921
909
|
assertInitialized();
|
|
922
910
|
const owner = _ownerOf(tokenId);
|
|
923
911
|
|
|
924
|
-
assert(!
|
|
912
|
+
assert(!Utils_isTargetZero(owner), "NonFungibleToken: nonexistent token");
|
|
925
913
|
return owner;
|
|
926
914
|
}
|
|
927
915
|
|
|
@@ -936,52 +924,6 @@ module NonFungibleToken {
|
|
|
936
924
|
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
937
925
|
*/
|
|
938
926
|
circuit _computeAccountId(): Bytes<32> {
|
|
939
|
-
return
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
944
|
-
* their identity commitment before submitting it in a token operation.
|
|
945
|
-
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
946
|
-
* given the same inputs.
|
|
947
|
-
*
|
|
948
|
-
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
949
|
-
* permanently compromise the security of this system:
|
|
950
|
-
*
|
|
951
|
-
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
952
|
-
* application logs, analytics pipelines, or any observable side-channel.
|
|
953
|
-
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
954
|
-
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
955
|
-
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
956
|
-
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
957
|
-
* - **Treat key loss as identity loss** — a lost key cannot be recovered.
|
|
958
|
-
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
959
|
-
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
960
|
-
* the key to a malicious observer.
|
|
961
|
-
*
|
|
962
|
-
* ## ID Derivation
|
|
963
|
-
* `accountId = persistentHash(secretKey)`
|
|
964
|
-
*
|
|
965
|
-
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
966
|
-
*
|
|
967
|
-
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
968
|
-
*/
|
|
969
|
-
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
970
|
-
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
/**
|
|
974
|
-
* @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
|
|
975
|
-
* holds the zero value.
|
|
976
|
-
*
|
|
977
|
-
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
|
|
978
|
-
* @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
|
|
979
|
-
*/
|
|
980
|
-
circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
|
|
981
|
-
if (target.is_left) {
|
|
982
|
-
return target.left == default<Bytes<32>>;
|
|
983
|
-
} else {
|
|
984
|
-
return target.right == default<ContractAddress>;
|
|
985
|
-
}
|
|
927
|
+
return Utils_computeAccountId(wit_NonFungibleTokenSK());
|
|
986
928
|
}
|
|
987
929
|
}
|
package/utils/Utils.compact
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (utils/Utils.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module Utils.
|
|
@@ -95,4 +95,88 @@ module Utils {
|
|
|
95
95
|
? Either<T1, T2>{ is_left: true, left: value.left, right: default<T2> }
|
|
96
96
|
: Either<T1, T2>{ is_left: false, left: default<T1>, right: value.right };
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @description Returns the canonical zero `Either<Bytes<32>, ContractAddress>` value: the
|
|
101
|
+
* left variant (`Bytes<32>`) set to zero. Used as the sentinel for mint/burn operations
|
|
102
|
+
* (a zero `from` signals a mint, a zero `to` signals a burn) and for cleared approvals.
|
|
103
|
+
*
|
|
104
|
+
* The left variant is chosen so that `isTargetZero` checks against `default<Bytes<32>>`,
|
|
105
|
+
* and to avoid triggering contract-address guards (e.g. circuits that check `!to.is_left`
|
|
106
|
+
* before delegating to an `_unsafe` variant). A right variant with `default<ContractAddress>`
|
|
107
|
+
* would also pass `isTargetZero`, but the left variant keeps guards and error messages consistent.
|
|
108
|
+
*
|
|
109
|
+
* @return {Either<Bytes<32>, ContractAddress>} - The canonical zero value.
|
|
110
|
+
*/
|
|
111
|
+
export pure circuit zeroAccount(): Either<Bytes<32>, ContractAddress> {
|
|
112
|
+
return Either<Bytes<32>, ContractAddress> {
|
|
113
|
+
is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
|
|
119
|
+
* holds the zero value.
|
|
120
|
+
*
|
|
121
|
+
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
|
|
122
|
+
* @return {Boolean} - `true` if the active branch is zero, `false` otherwise.
|
|
123
|
+
*/
|
|
124
|
+
export pure circuit isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
|
|
125
|
+
if (target.is_left) {
|
|
126
|
+
return target.left == default<Bytes<32>>;
|
|
127
|
+
} else {
|
|
128
|
+
return target.right == default<ContractAddress>;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
134
|
+
* their identity commitment before submitting an operation.
|
|
135
|
+
*
|
|
136
|
+
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
137
|
+
* permanently compromise the security of this system:
|
|
138
|
+
*
|
|
139
|
+
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
140
|
+
* application logs, analytics pipelines, or any observable side-channel.
|
|
141
|
+
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
142
|
+
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
143
|
+
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
144
|
+
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
145
|
+
* - **Treat key loss as identity loss** — a lost key cannot be recovered.
|
|
146
|
+
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
147
|
+
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
148
|
+
* the key to a malicious observer.
|
|
149
|
+
*
|
|
150
|
+
* ## ID Derivation
|
|
151
|
+
* `accountId = persistentHash(secretKey)`
|
|
152
|
+
*
|
|
153
|
+
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
154
|
+
*
|
|
155
|
+
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
156
|
+
*/
|
|
157
|
+
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
158
|
+
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @description Returns the current contract's address as an
|
|
163
|
+
* `Either<ZswapCoinPublicKey, ContractAddress>` for use as a
|
|
164
|
+
* recipient in shielded send operations (deposits and receiving change).
|
|
165
|
+
*
|
|
166
|
+
* @returns {Either<ZswapCoinPublicKey, ContractAddress>} The contract's address as a recipient.
|
|
167
|
+
*/
|
|
168
|
+
export circuit selfAsRecipient(): Either<ZswapCoinPublicKey, ContractAddress> {
|
|
169
|
+
return right<ZswapCoinPublicKey, ContractAddress>(kernel.self());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @description The maximum value representable by a `Uint<128>`.
|
|
174
|
+
*
|
|
175
|
+
* @notice TODO: Remove once a math module providing this constant is available.
|
|
176
|
+
*
|
|
177
|
+
* @returns {Uint<128>} `2^128 - 1`.
|
|
178
|
+
*/
|
|
179
|
+
export pure circuit UINT128_MAX(): Uint<128> {
|
|
180
|
+
return 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
|
|
181
|
+
}
|
|
98
182
|
}
|