@openzeppelin/compact-contracts 0.1.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 +278 -0
- package/access/AccessControl.compact +479 -0
- package/access/Ownable.compact +353 -0
- package/access/ShieldedAccessControl.compact +784 -0
- package/access/ZOwnablePK.compact +304 -0
- package/package.json +54 -0
- package/security/Initializable.compact +60 -0
- package/security/Pausable.compact +89 -0
- package/token/FungibleToken.compact +742 -0
- package/token/MultiToken.compact +728 -0
- package/token/NonFungibleToken.compact +954 -0
- package/utils/Utils.compact +98 -0
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.1.0 (token/NonFungibleToken.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.21.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NonFungibleToken
|
|
8
|
+
* @description An unshielded Non-Fungible Token library.
|
|
9
|
+
*
|
|
10
|
+
* Authorization is based on a witness-derived identity scheme. Each caller proves knowledge
|
|
11
|
+
* of a secret key by injecting it via the `wit_NonFungibleTokenSK` witness. The module computes
|
|
12
|
+
* an account identifier as `persistentHash(secretKey)` which is a commitment that hides the key
|
|
13
|
+
* while providing a stable, pseudonymous on-chain identity.
|
|
14
|
+
*
|
|
15
|
+
* Because the account identifier is `H(secretKey)` with no per-deployment salt or domain
|
|
16
|
+
* separator, the same secret key produces the same identity across all contracts. This is
|
|
17
|
+
* intentional. It provides a linkable pseudonymous identity analogous to Solidity's
|
|
18
|
+
* `msg.sender`. Users who desire cross-contract unlinkability can use different secret keys
|
|
19
|
+
* per contract at the wallet layer.
|
|
20
|
+
*
|
|
21
|
+
* @notice One notable difference regarding this implementation and the EIP721 spec
|
|
22
|
+
* consists of the token size. Uint<128> is used as the token size because Uint<256>
|
|
23
|
+
* cannot be supported.
|
|
24
|
+
* This is due to encoding limits on the midnight circuit backend:
|
|
25
|
+
* https://github.com/midnightntwrk/compactc/issues/929
|
|
26
|
+
*
|
|
27
|
+
* @notice At the moment Midnight does not support contract-to-contract (C2C) communication, but
|
|
28
|
+
* there are ongoing efforts to enable this in the future. Thus, the main circuits of this module
|
|
29
|
+
* (`transfer`, `transferFrom`, `_transfer`, `_mint`) reject `ContractAddress` recipients via an
|
|
30
|
+
* `isContract` guard, because a contract that receives a token currently cannot move it back out.
|
|
31
|
+
* Developers who need to experiment with contract recipients can use the `_unsafe` variants, which
|
|
32
|
+
* skip the guard (the caller accepts that such tokens are stranded until C2C lands).
|
|
33
|
+
*
|
|
34
|
+
* @dev Migration plan (safe -> C2C-capable `transfer`)
|
|
35
|
+
*
|
|
36
|
+
* The recipient type is the final `Either<Bytes<32>, ContractAddress>` from day one, in both
|
|
37
|
+
* circuit signatures and ledger maps. This is deliberate. Midnight's upgrade mechanism, the
|
|
38
|
+
* Contract Maintenance Authority (CMA), can rotate a circuit's verifier key but cannot migrate
|
|
39
|
+
* ledger state (there is no state-rewrite primitive; see the `updatability` tutorial). Fixing the
|
|
40
|
+
* types now makes the future migration a pure verifier-key rotation with zero ledger-state change,
|
|
41
|
+
* which is the only kind of upgrade the CMA supports. Widening the types later would instead force
|
|
42
|
+
* a ledger-layout rewrite, which the CMA cannot perform.
|
|
43
|
+
*
|
|
44
|
+
* When C2C lands, an already-deployed token migrates without changing its address or ledger state:
|
|
45
|
+
*
|
|
46
|
+
* - Minor release: mark `_unsafeTransfer` deprecated (and emit a warning if possible); keep its
|
|
47
|
+
* implementation intact so existing callers continue to work.
|
|
48
|
+
* - Major release: ship `transfer` with the `isContract` guard removed and drop `_unsafeTransfer`.
|
|
49
|
+
* Operators adopt the new logic by signing a CMA `MaintenanceUpdate` that rotates the affected
|
|
50
|
+
* verifier keys (`VerifierKeyRemove` + `VerifierKeyInsert` for `transfer`, and a `VerifierKeyRemove`
|
|
51
|
+
* for `_unsafeTransfer`). The contract address and all ledger state are preserved. Because the CMA
|
|
52
|
+
* allows multiple active VK versions per operation, the old and new `transfer` keys can coexist
|
|
53
|
+
* during a transition window so in-flight proofs still verify. By this point, anyone using
|
|
54
|
+
* `_unsafeTransfer` should have migrated to the now C2C-capable `transfer`.
|
|
55
|
+
*
|
|
56
|
+
* @dev Canonicalization
|
|
57
|
+
* All `Either<Bytes<32>, ContractAddress>` values are canonicalized before use as map keys
|
|
58
|
+
* or in ledger writes. Canonicalization zeroes out the inactive branch of the Either,
|
|
59
|
+
* ensuring that two values with the same active branch always resolve to the same map key
|
|
60
|
+
* regardless of what data the inactive branch carries. Write paths are canonicalized in
|
|
61
|
+
* `_update` (for `_owners` and `_balances`), `_approve` (for `_tokenApprovals`), and
|
|
62
|
+
* `_setApprovalForAll` (for `_operatorApprovals`). Read paths are canonicalized in
|
|
63
|
+
* `balanceOf`, `isApprovedForAll`, and `_isAuthorized`.
|
|
64
|
+
*
|
|
65
|
+
* @dev Security Considerations:
|
|
66
|
+
* - The `secretKey` must be kept private. Loss of the key prevents token holders
|
|
67
|
+
* from proving ownership or authorization. Key exposure allows impersonation.
|
|
68
|
+
* - It is strongly recommended to use cryptographically secure random values for the secret key
|
|
69
|
+
* (e.g., `crypto.getRandomValues()`). Weak or predictable keys can be brute-forced.
|
|
70
|
+
* - The `secretKey` is provided via the `wit_NonFungibleTokenSK` witness. As with all witnesses,
|
|
71
|
+
* the contract must not assume that the witness implementation matches the developer's code.
|
|
72
|
+
* Any DApp may provide any implementation. The ZK proof system constrains what values
|
|
73
|
+
* can produce valid proofs.
|
|
74
|
+
*
|
|
75
|
+
* @notice Missing Features and Improvements:
|
|
76
|
+
*
|
|
77
|
+
* - Uint256 token IDs
|
|
78
|
+
* - Transfer/Approval events
|
|
79
|
+
* - safeTransfer functions
|
|
80
|
+
* - _baseURI() support
|
|
81
|
+
* - An ERC165-like interface
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
module NonFungibleToken {
|
|
85
|
+
import CompactStandardLibrary;
|
|
86
|
+
import "../security/Initializable" prefix Initializable_;
|
|
87
|
+
import "../utils/Utils" prefix Utils_;
|
|
88
|
+
|
|
89
|
+
/// Public state
|
|
90
|
+
export sealed ledger _name: Opaque<"string">;
|
|
91
|
+
export sealed ledger _symbol: Opaque<"string">;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @description Mapping from token IDs to their owner addresses.
|
|
95
|
+
* @type {Uint<128>} tokenId - The unique identifier for a token.
|
|
96
|
+
* @type {Either<Bytes<32>, ContractAddress>} owner - The owner address (account ID or contract).
|
|
97
|
+
* @type {Map<tokenId, owner>}
|
|
98
|
+
* @type {Map<Uint<128>, Either<Bytes<32>, ContractAddress>>} _owners
|
|
99
|
+
*/
|
|
100
|
+
export ledger _owners: Map<Uint<128>, Either<Bytes<32>, ContractAddress>>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @description Mapping from account addresses to their token balances.
|
|
104
|
+
* @type {Either<Bytes<32>, ContractAddress>} owner - The owner address.
|
|
105
|
+
* @type {Uint<128>} balance - The balance of the owner.
|
|
106
|
+
* @type {Map<owner, balance>}
|
|
107
|
+
* @type {Map<Either<Bytes<32>, ContractAddress>, Uint<128>>} _balances
|
|
108
|
+
*/
|
|
109
|
+
export ledger _balances: Map<Either<Bytes<32>, ContractAddress>, Uint<128>>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @description Mapping from token IDs to approved addresses.
|
|
113
|
+
* @type {Uint<128>} tokenId - The unique identifier for a token.
|
|
114
|
+
* @type {Either<Bytes<32>, ContractAddress>} approved - The approved address (account ID or contract).
|
|
115
|
+
* @type {Map<tokenId, approved>}
|
|
116
|
+
* @type {Map<Uint<128>, Either<Bytes<32>, ContractAddress>>} _tokenApprovals
|
|
117
|
+
*/
|
|
118
|
+
export ledger _tokenApprovals: Map<Uint<128>, Either<Bytes<32>, ContractAddress>>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @description Mapping from owner addresses to operator approvals.
|
|
122
|
+
* @type {Either<Bytes<32>, ContractAddress>} owner - The owner address.
|
|
123
|
+
* @type {Either<Bytes<32>, ContractAddress>} operator - The operator address.
|
|
124
|
+
* @type {Boolean} approved - Whether the operator is approved.
|
|
125
|
+
* @type {Map<owner, Map<operator, approved>>}
|
|
126
|
+
* @type {Map<Either<Bytes<32>, ContractAddress>, Map<Either<Bytes<32>, ContractAddress>, Boolean>>} _operatorApprovals
|
|
127
|
+
*/
|
|
128
|
+
export ledger _operatorApprovals: Map<Either<Bytes<32>, ContractAddress>,
|
|
129
|
+
Map<Either<Bytes<32>, ContractAddress>, Boolean>>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @description Mapping from token IDs to their metadata URIs.
|
|
133
|
+
* @type {Uint<128>} tokenId - The unique identifier for a token.
|
|
134
|
+
* @type {Opaque<"string">} uri - The metadata URI for the token.
|
|
135
|
+
* @type {Map<tokenId, uri>}
|
|
136
|
+
* @type {Map<Uint<128>, Opaque<"string">>} _tokenURIs
|
|
137
|
+
*/
|
|
138
|
+
export ledger _tokenURIs: Map<Uint<128>, Opaque<"string">>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @witness wit_NonFungibleTokenSK
|
|
142
|
+
* @description Returns the caller's secret key used in deriving the account identifier.
|
|
143
|
+
*
|
|
144
|
+
* The same key produces the same account identifier across all contracts. Users who
|
|
145
|
+
* desire cross-contract unlinkability should use different keys per contract.
|
|
146
|
+
*
|
|
147
|
+
* @returns {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
148
|
+
*/
|
|
149
|
+
witness wit_NonFungibleTokenSK(): Bytes<32>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @description Returns a canonical zero Either value (left variant with zero Bytes<32>).
|
|
153
|
+
* Used as the zero value for mint/burn operations and cleared approvals.
|
|
154
|
+
*
|
|
155
|
+
* @return {Either<Bytes<32>, ContractAddress>} - The zero value.
|
|
156
|
+
*/
|
|
157
|
+
export pure circuit ZERO(): Either<Bytes<32>, ContractAddress> {
|
|
158
|
+
return Either<Bytes<32>, ContractAddress> {
|
|
159
|
+
is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @description Initializes the contract by setting the name and symbol.
|
|
165
|
+
*
|
|
166
|
+
* This MUST be called in the implementing contract's constructor.
|
|
167
|
+
* Failure to do so can lead to an irreparable contract.
|
|
168
|
+
*
|
|
169
|
+
* @circuitInfo k=6, rows=52
|
|
170
|
+
*
|
|
171
|
+
* Requirements:
|
|
172
|
+
*
|
|
173
|
+
* - Contract is not initialized.
|
|
174
|
+
*
|
|
175
|
+
* @param {Opaque<"string">} name_ - The name of the token.
|
|
176
|
+
* @param {Opaque<"string">} symbol_ - The symbol of the token.
|
|
177
|
+
* @return {[]} - Empty tuple.
|
|
178
|
+
*/
|
|
179
|
+
export circuit initialize(name_: Opaque<"string">, symbol_: Opaque<"string">): [] {
|
|
180
|
+
Initializable_initialize();
|
|
181
|
+
_name = disclose(name_);
|
|
182
|
+
_symbol = disclose(symbol_);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @description Returns the number of tokens in `owner`'s account.
|
|
187
|
+
*
|
|
188
|
+
* @circuitInfo k=10, rows=673
|
|
189
|
+
*
|
|
190
|
+
* Requirements:
|
|
191
|
+
*
|
|
192
|
+
* - The contract is initialized.
|
|
193
|
+
*
|
|
194
|
+
* @param {Either<Bytes<32>, ContractAddress>} owner - The account to query.
|
|
195
|
+
* @return {Uint<128>} - The number of tokens in `owner`'s account.
|
|
196
|
+
*/
|
|
197
|
+
export circuit balanceOf(owner: Either<Bytes<32>, ContractAddress>): Uint<128> {
|
|
198
|
+
Initializable_assertInitialized();
|
|
199
|
+
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
200
|
+
|
|
201
|
+
if (!_balances.member(disclose(canonOwner))) {
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return _balances.lookup(disclose(canonOwner));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @description Returns the owner of the `tokenId` token.
|
|
210
|
+
*
|
|
211
|
+
* @circuitInfo k=9, rows=333
|
|
212
|
+
*
|
|
213
|
+
* Requirements:
|
|
214
|
+
*
|
|
215
|
+
* - The contract is initialized.
|
|
216
|
+
* - The `tokenId` must exist.
|
|
217
|
+
*
|
|
218
|
+
* @param {Uint<128>} tokenId - The identifier for a token.
|
|
219
|
+
* @return {Either<Bytes<32>, ContractAddress>} - The account that owns the token.
|
|
220
|
+
*/
|
|
221
|
+
export circuit ownerOf(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
222
|
+
Initializable_assertInitialized();
|
|
223
|
+
return _requireOwned(tokenId);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @description Returns the token name.
|
|
228
|
+
*
|
|
229
|
+
* @circuitInfo k=6, rows=28
|
|
230
|
+
*
|
|
231
|
+
* Requirements:
|
|
232
|
+
*
|
|
233
|
+
* - The contract is initialized.
|
|
234
|
+
*
|
|
235
|
+
* @return {Opaque<"string">} - The token name.
|
|
236
|
+
*/
|
|
237
|
+
export circuit name(): Opaque<"string"> {
|
|
238
|
+
Initializable_assertInitialized();
|
|
239
|
+
return _name;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @description Returns the symbol of the token.
|
|
244
|
+
*
|
|
245
|
+
* @circuitInfo k=6, rows=28
|
|
246
|
+
*
|
|
247
|
+
* Requirements:
|
|
248
|
+
*
|
|
249
|
+
* - The contract is initialized.
|
|
250
|
+
*
|
|
251
|
+
* @return {Opaque<"string">} - The token symbol.
|
|
252
|
+
*/
|
|
253
|
+
export circuit symbol(): Opaque<"string"> {
|
|
254
|
+
Initializable_assertInitialized();
|
|
255
|
+
return _symbol;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @description Returns the token URI for the given `tokenId`. Returns the empty
|
|
260
|
+
* string if a tokenURI does not exist.
|
|
261
|
+
*
|
|
262
|
+
* @circuitInfo k=9, rows=326
|
|
263
|
+
*
|
|
264
|
+
* Requirements:
|
|
265
|
+
*
|
|
266
|
+
* - The contract is initialized.
|
|
267
|
+
* - The `tokenId` must exist.
|
|
268
|
+
*
|
|
269
|
+
* @notice Native strings and string operations aren't supported within the Compact language,
|
|
270
|
+
* e.g. concatenating a base URI + token ID is not possible like in other NFT implementations.
|
|
271
|
+
* Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs.
|
|
272
|
+
* It's up to the implementation to decide on how to handle this.
|
|
273
|
+
*
|
|
274
|
+
* @param {Uint<128>} tokenId - The identifier for a token.
|
|
275
|
+
* @return {Opaque<"string">} - the token id's URI.
|
|
276
|
+
*/
|
|
277
|
+
export circuit tokenURI(tokenId: Uint<128>): Opaque<"string"> {
|
|
278
|
+
Initializable_assertInitialized();
|
|
279
|
+
_requireOwned(tokenId);
|
|
280
|
+
|
|
281
|
+
if (!_tokenURIs.member(disclose(tokenId))) {
|
|
282
|
+
return Utils_emptyString();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return _tokenURIs.lookup(disclose(tokenId));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @description Sets the URI as `tokenURI` for the given `tokenId`.
|
|
290
|
+
*
|
|
291
|
+
* @circuitInfo k=9, rows=289
|
|
292
|
+
*
|
|
293
|
+
* Requirements:
|
|
294
|
+
*
|
|
295
|
+
* - The contract is initialized.
|
|
296
|
+
* - The `tokenId` must exist.
|
|
297
|
+
*
|
|
298
|
+
* @notice The URI for a given NFT is usually set when the NFT is minted.
|
|
299
|
+
*
|
|
300
|
+
* @param {Uint<128>} tokenId - The identifier of the token.
|
|
301
|
+
* @param {Opaque<"string">} tokenURI - The URI of `tokenId`.
|
|
302
|
+
* @return {[]} - Empty tuple.
|
|
303
|
+
*/
|
|
304
|
+
export circuit _setTokenURI(tokenId: Uint<128>, tokenURI: Opaque<"string">): [] {
|
|
305
|
+
Initializable_assertInitialized();
|
|
306
|
+
_requireOwned(tokenId);
|
|
307
|
+
|
|
308
|
+
return _tokenURIs.insert(disclose(tokenId), disclose(tokenURI));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @description Gives permission to `to` to transfer `tokenId` token to another account.
|
|
313
|
+
* The approval is cleared when the token is transferred.
|
|
314
|
+
*
|
|
315
|
+
* Only a single account can be approved at a time, so approving the zero value clears previous approvals.
|
|
316
|
+
*
|
|
317
|
+
* In the case of an external (non-contract) caller, the caller's identity is derived from the `wit_NonFungibleTokenSK`
|
|
318
|
+
* witness as `persistentHash(secretKey)`.
|
|
319
|
+
*
|
|
320
|
+
* @circuitInfo k=13, rows=3468
|
|
321
|
+
*
|
|
322
|
+
* Requirements:
|
|
323
|
+
*
|
|
324
|
+
* - The contract is initialized.
|
|
325
|
+
* - The caller must either own the token or be an approved operator.
|
|
326
|
+
* - `tokenId` must exist.
|
|
327
|
+
*
|
|
328
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The account receiving the approval
|
|
329
|
+
* @param {Uint<128>} tokenId - The token `to` may be permitted to transfer
|
|
330
|
+
* @return {[]} - Empty tuple.
|
|
331
|
+
*/
|
|
332
|
+
export circuit approve(to: Either<Bytes<32>, ContractAddress>, tokenId: Uint<128>): [] {
|
|
333
|
+
Initializable_assertInitialized();
|
|
334
|
+
const auth = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
335
|
+
_approve(to, tokenId, auth);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @description Returns the account approved for `tokenId` token.
|
|
340
|
+
*
|
|
341
|
+
* @circuitInfo k=9, rows=422
|
|
342
|
+
*
|
|
343
|
+
* Requirements:
|
|
344
|
+
*
|
|
345
|
+
* - The contract is initialized.
|
|
346
|
+
* - `tokenId` must exist.
|
|
347
|
+
*
|
|
348
|
+
* @param {Uint<128>} tokenId - The token an account may be approved to manage
|
|
349
|
+
* @return {Either<Bytes<32>, ContractAddress>} - The account approved to manage the token
|
|
350
|
+
*/
|
|
351
|
+
export circuit getApproved(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
352
|
+
Initializable_assertInitialized();
|
|
353
|
+
_requireOwned(tokenId);
|
|
354
|
+
|
|
355
|
+
return _getApproved(tokenId);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @description Approve or remove `operator` as an operator for the caller.
|
|
360
|
+
* Operators can call {transferFrom} for any token owned by the caller.
|
|
361
|
+
*
|
|
362
|
+
* In the case of an external (non-contract) caller, the caller's identity is derived from the `wit_NonFungibleTokenSK`
|
|
363
|
+
* witness as `persistentHash(secretKey)`.
|
|
364
|
+
*
|
|
365
|
+
* @circuitInfo k=13, rows=2936
|
|
366
|
+
*
|
|
367
|
+
* Requirements:
|
|
368
|
+
*
|
|
369
|
+
* - The contract is initialized.
|
|
370
|
+
* - The `operator` cannot be zero.
|
|
371
|
+
*
|
|
372
|
+
* @param {Either<Bytes<32>, ContractAddress>} operator - An operator to manage the caller's tokens
|
|
373
|
+
* @param {Boolean} approved - A boolean determining if `operator` may manage all tokens of the caller
|
|
374
|
+
* @return {[]} - Empty tuple.
|
|
375
|
+
*/
|
|
376
|
+
export circuit setApprovalForAll(
|
|
377
|
+
operator: Either<Bytes<32>, ContractAddress>,
|
|
378
|
+
approved: Boolean
|
|
379
|
+
): [] {
|
|
380
|
+
Initializable_assertInitialized();
|
|
381
|
+
const owner = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
382
|
+
_setApprovalForAll(owner, operator, approved);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @description Returns if the `operator` is allowed to manage all of the assets of `owner`.
|
|
387
|
+
*
|
|
388
|
+
* @circuitInfo k=11, rows=1343
|
|
389
|
+
*
|
|
390
|
+
* Requirements:
|
|
391
|
+
*
|
|
392
|
+
* - The contract is initialized.
|
|
393
|
+
*
|
|
394
|
+
* @param {Either<Bytes<32>, ContractAddress>} owner - The owner of a token
|
|
395
|
+
* @param {Either<Bytes<32>, ContractAddress>} operator - An account that may operate on `owner`'s tokens
|
|
396
|
+
* @return {Boolean} - A boolean determining if `operator` is allowed to manage all of the tokens of `owner`
|
|
397
|
+
*/
|
|
398
|
+
export circuit isApprovedForAll(
|
|
399
|
+
owner: Either<Bytes<32>, ContractAddress>,
|
|
400
|
+
operator: Either<Bytes<32>, ContractAddress>
|
|
401
|
+
): Boolean {
|
|
402
|
+
Initializable_assertInitialized();
|
|
403
|
+
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
404
|
+
const canonOp = Utils_canonicalize<Bytes<32>, ContractAddress>(operator);
|
|
405
|
+
|
|
406
|
+
return _operatorApprovals.member(disclose(canonOwner)) &&
|
|
407
|
+
_operatorApprovals.lookup(canonOwner).member(disclose(canonOp)) &&
|
|
408
|
+
_operatorApprovals.lookup(canonOwner).lookup(disclose(canonOp));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* @description Transfers `tokenId` token from `fromAddress` to `to`.
|
|
413
|
+
*
|
|
414
|
+
* @notice Transfers to contract addresses are currently disallowed until contract-to-contract interactions
|
|
415
|
+
* are supported in Compact. This restriction prevents assets from being inadvertently locked in contracts that cannot
|
|
416
|
+
* currently handle token receipt.
|
|
417
|
+
*
|
|
418
|
+
* In the case of an external (non-contract) caller, the caller's identity is derived from the `wit_NonFungibleTokenSK`
|
|
419
|
+
* witness as `persistentHash(secretKey)`.
|
|
420
|
+
*
|
|
421
|
+
* @circuitInfo k=13, rows=4800
|
|
422
|
+
*
|
|
423
|
+
* Requirements:
|
|
424
|
+
*
|
|
425
|
+
* - The contract is initialized.
|
|
426
|
+
* - `fromAddress` is not zero.
|
|
427
|
+
* - `to` is not zero.
|
|
428
|
+
* - `to` is not a ContractAddress.
|
|
429
|
+
* - `tokenId` token must be owned by `fromAddress`.
|
|
430
|
+
* - If the caller is not `fromAddress`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
|
|
431
|
+
*
|
|
432
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The source account from which the token is being transferred
|
|
433
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The target account to transfer token to
|
|
434
|
+
* @param {Uint<128>} tokenId - The token being transferred
|
|
435
|
+
* @return {[]} - Empty tuple.
|
|
436
|
+
*/
|
|
437
|
+
export circuit transferFrom(
|
|
438
|
+
fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
439
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
440
|
+
tokenId: Uint<128>
|
|
441
|
+
): [] {
|
|
442
|
+
Initializable_assertInitialized();
|
|
443
|
+
const isContractAddr = !to.is_left;
|
|
444
|
+
assert(!isContractAddr, "NonFungibleToken: unsafe transfer");
|
|
445
|
+
|
|
446
|
+
_unsafeTransferFrom(fromAddress, to, tokenId);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* @description Transfers `tokenId` token from `fromAddress` to `to`. It does NOT check if the recipient is a ContractAddress.
|
|
451
|
+
*
|
|
452
|
+
* In the case of an external (non-contract) caller, the caller's identity is derived from the `wit_NonFungibleTokenSK`
|
|
453
|
+
* witness as `persistentHash(secretKey)`.
|
|
454
|
+
*
|
|
455
|
+
* @warning Transfers to contract addresses are considered unsafe because contract-to-contract calls
|
|
456
|
+
* are not currently supported. Tokens sent to a contract address may become irretrievable.
|
|
457
|
+
* Once contract-to-contract calls are supported, this circuit may be deprecated.
|
|
458
|
+
*
|
|
459
|
+
* @circuitInfo k=13, rows=4797
|
|
460
|
+
*
|
|
461
|
+
* Requirements:
|
|
462
|
+
*
|
|
463
|
+
* - The contract is initialized.
|
|
464
|
+
* - `fromAddress` is not zero.
|
|
465
|
+
* - `to` is not zero.
|
|
466
|
+
* - `tokenId` token must be owned by `fromAddress`.
|
|
467
|
+
* - If the caller is not `fromAddress`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
|
|
468
|
+
*
|
|
469
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The source account from which the token is being transferred
|
|
470
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The target account to transfer token to
|
|
471
|
+
* @param {Uint<128>} tokenId - The token being transferred
|
|
472
|
+
* @return {[]} - Empty tuple.
|
|
473
|
+
*/
|
|
474
|
+
export circuit _unsafeTransferFrom(
|
|
475
|
+
fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
476
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
477
|
+
tokenId: Uint<128>
|
|
478
|
+
): [] {
|
|
479
|
+
Initializable_assertInitialized();
|
|
480
|
+
assert(!_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
481
|
+
// Setting an "auth" argument enables the `_isAuthorized` check which verifies that the token exists
|
|
482
|
+
// (fromAddress != 0). Therefore, it is not needed to verify that the return value is not 0 here.
|
|
483
|
+
const auth = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
484
|
+
const previousOwner = _update(to, tokenId, auth);
|
|
485
|
+
|
|
486
|
+
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
487
|
+
assert(previousOwner == canonFrom, "NonFungibleToken: incorrect owner");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* @description Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist.
|
|
492
|
+
*
|
|
493
|
+
* @circuitInfo k=9, rows=308
|
|
494
|
+
*
|
|
495
|
+
* Requirements:
|
|
496
|
+
*
|
|
497
|
+
* - The contract is initialized.
|
|
498
|
+
*
|
|
499
|
+
* @param {Uint<128>} tokenId - The target token of the owner query
|
|
500
|
+
* @return {Either<Bytes<32>, ContractAddress>} - The owner of the token
|
|
501
|
+
*/
|
|
502
|
+
export circuit _ownerOf(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
503
|
+
Initializable_assertInitialized();
|
|
504
|
+
if (!_owners.member(disclose(tokenId))) {
|
|
505
|
+
return ZERO();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return _owners.lookup(disclose(tokenId));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* @description Returns the approved address for `tokenId`. Returns the zero value if `tokenId` is not minted.
|
|
513
|
+
*
|
|
514
|
+
* @circuitInfo k=9, rows=308
|
|
515
|
+
*
|
|
516
|
+
* Requirements:
|
|
517
|
+
*
|
|
518
|
+
* - The contract is initialized.
|
|
519
|
+
*
|
|
520
|
+
* @param {Uint<128>} tokenId - The token to query
|
|
521
|
+
* @return {Either<Bytes<32>, ContractAddress>} - An account approved to spend `tokenId`
|
|
522
|
+
*/
|
|
523
|
+
export circuit _getApproved(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
524
|
+
Initializable_assertInitialized();
|
|
525
|
+
if (!_tokenApprovals.member(disclose(tokenId))) {
|
|
526
|
+
return ZERO();
|
|
527
|
+
}
|
|
528
|
+
return _tokenApprovals.lookup(disclose(tokenId));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* @description Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
|
|
533
|
+
* particular (ignoring whether it is owned by `owner`).
|
|
534
|
+
*
|
|
535
|
+
* @circuitInfo k=11, rows=1798
|
|
536
|
+
*
|
|
537
|
+
* Requirements:
|
|
538
|
+
*
|
|
539
|
+
* - The contract is initialized.
|
|
540
|
+
*
|
|
541
|
+
* @warning This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
|
|
542
|
+
* assumption.
|
|
543
|
+
*
|
|
544
|
+
* @param {Either<Bytes<32>, ContractAddress>} owner - Owner of the token
|
|
545
|
+
* @param {Either<Bytes<32>, ContractAddress>} spender - Account that wishes to spend `tokenId`
|
|
546
|
+
* @param {Uint<128>} tokenId - Token to spend
|
|
547
|
+
* @return {Boolean} - A boolean determining if `spender` may manage `tokenId`
|
|
548
|
+
*/
|
|
549
|
+
export circuit _isAuthorized(
|
|
550
|
+
owner: Either<Bytes<32>, ContractAddress>,
|
|
551
|
+
spender: Either<Bytes<32>, ContractAddress>,
|
|
552
|
+
tokenId: Uint<128>
|
|
553
|
+
): Boolean {
|
|
554
|
+
Initializable_assertInitialized();
|
|
555
|
+
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
556
|
+
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
557
|
+
|
|
558
|
+
return (!_isTargetZero(disclose(canonSpender)) &&
|
|
559
|
+
(disclose(canonOwner) == disclose(canonSpender) ||
|
|
560
|
+
isApprovedForAll(canonOwner, canonSpender) ||
|
|
561
|
+
_getApproved(tokenId) == disclose(canonSpender)));
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* @description Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
|
|
566
|
+
*
|
|
567
|
+
* @circuitInfo k=11, rows=1804
|
|
568
|
+
*
|
|
569
|
+
* Requirements:
|
|
570
|
+
*
|
|
571
|
+
* - The contract is initialized.
|
|
572
|
+
* - `spender` has approval from `owner` for `tokenId` OR `spender` has approval to manage all of `owner`'s assets.
|
|
573
|
+
*
|
|
574
|
+
* @warning This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
|
|
575
|
+
* assumption.
|
|
576
|
+
*
|
|
577
|
+
* @param {Either<Bytes<32>, ContractAddress>} owner - Owner of the token
|
|
578
|
+
* @param {Either<Bytes<32>, ContractAddress>} spender - Account operating on `tokenId`
|
|
579
|
+
* @param {Uint<128>} tokenId - The token to spend
|
|
580
|
+
* @return {[]} - Empty tuple.
|
|
581
|
+
*/
|
|
582
|
+
export circuit _checkAuthorized(
|
|
583
|
+
owner: Either<Bytes<32>, ContractAddress>,
|
|
584
|
+
spender: Either<Bytes<32>, ContractAddress>,
|
|
585
|
+
tokenId: Uint<128>
|
|
586
|
+
): [] {
|
|
587
|
+
Initializable_assertInitialized();
|
|
588
|
+
if (!_isAuthorized(owner, spender, tokenId)) {
|
|
589
|
+
assert(!_isTargetZero(owner), "NonFungibleToken: nonexistent token");
|
|
590
|
+
assert(false, "NonFungibleToken: insufficient approval");
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* @description Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner
|
|
596
|
+
* (or `to`) is zero. Returns the owner of the `tokenId` before the update.
|
|
597
|
+
*
|
|
598
|
+
* Requirements:
|
|
599
|
+
*
|
|
600
|
+
* - The contract is initialized.
|
|
601
|
+
* - If `auth` is non 0, then this function will check that `auth` is either the owner of the token,
|
|
602
|
+
* or approved to operate on the token (by the owner).
|
|
603
|
+
*
|
|
604
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The intended recipient of the token transfer
|
|
605
|
+
* @param {Uint<128>} tokenId - The token being transferred
|
|
606
|
+
* @param {Either<Bytes<32>, ContractAddress>} auth - An account authorized to transfer the token
|
|
607
|
+
* @return {Either<Bytes<32>, ContractAddress>} - Owner of the token before it was transferred
|
|
608
|
+
*/
|
|
609
|
+
circuit _update(to: Either<Bytes<32>, ContractAddress>,
|
|
610
|
+
tokenId: Uint<128>,
|
|
611
|
+
auth: Either<Bytes<32>, ContractAddress>
|
|
612
|
+
): Either<Bytes<32>, ContractAddress> {
|
|
613
|
+
Initializable_assertInitialized();
|
|
614
|
+
const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
|
|
615
|
+
const canonAuth = Utils_canonicalize<Bytes<32>, ContractAddress>(auth);
|
|
616
|
+
const fromAddress = _ownerOf(tokenId);
|
|
617
|
+
|
|
618
|
+
// Perform (optional) operator check
|
|
619
|
+
if (!_isTargetZero(disclose(canonAuth))) {
|
|
620
|
+
_checkAuthorized(fromAddress, canonAuth, tokenId);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Execute the update
|
|
624
|
+
if (!_isTargetZero(disclose(fromAddress))) {
|
|
625
|
+
// Clear approval. No need to re-authorize
|
|
626
|
+
_approve(ZERO(), tokenId, ZERO());
|
|
627
|
+
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
628
|
+
const newBalance = _balances.lookup(disclose(canonFrom)) - 1 as Uint<128>;
|
|
629
|
+
_balances.insert(disclose(canonFrom), disclose(newBalance));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (!_isTargetZero(disclose(canonTo))) {
|
|
633
|
+
if (!_balances.member(disclose(canonTo))) {
|
|
634
|
+
_balances.insert(disclose(canonTo), 0);
|
|
635
|
+
}
|
|
636
|
+
const newBalance = _balances.lookup(disclose(canonTo)) + 1 as Uint<128>;
|
|
637
|
+
_balances.insert(disclose(canonTo), disclose(newBalance));
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
_owners.insert(disclose(tokenId), disclose(canonTo));
|
|
641
|
+
|
|
642
|
+
return fromAddress;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* @description Mints `tokenId` and transfers it to `to`.
|
|
647
|
+
*
|
|
648
|
+
* @circuitInfo k=11, rows=1473
|
|
649
|
+
*
|
|
650
|
+
* Requirements:
|
|
651
|
+
*
|
|
652
|
+
* - The contract is initialized.
|
|
653
|
+
* - `tokenId` must not exist.
|
|
654
|
+
* - `to` is not zero.
|
|
655
|
+
* - `to` is not a ContractAddress.
|
|
656
|
+
*
|
|
657
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The account receiving `tokenId`
|
|
658
|
+
* @param {Uint<128>} tokenId - The token to transfer
|
|
659
|
+
* @return {[]} - Empty tuple.
|
|
660
|
+
*/
|
|
661
|
+
export circuit _mint(to: Either<Bytes<32>, ContractAddress>, tokenId: Uint<128>): [] {
|
|
662
|
+
Initializable_assertInitialized();
|
|
663
|
+
const isContractAddr = !to.is_left;
|
|
664
|
+
assert(!isContractAddr, "NonFungibleToken: unsafe transfer");
|
|
665
|
+
|
|
666
|
+
_unsafeMint(to, tokenId);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* @description Mints `tokenId` and transfers it to `to`. It does NOT check if the recipient is a ContractAddress.
|
|
671
|
+
*
|
|
672
|
+
* @circuitInfo k=11, rows=1470
|
|
673
|
+
*
|
|
674
|
+
* Requirements:
|
|
675
|
+
*
|
|
676
|
+
* - The contract is initialized.
|
|
677
|
+
* - `tokenId` must not exist.
|
|
678
|
+
* - `to` is not zero.
|
|
679
|
+
*
|
|
680
|
+
* @warning Transfers to contract addresses are considered unsafe because contract-to-contract
|
|
681
|
+
* calls are not currently supported. Tokens sent to a contract address may become irretrievable.
|
|
682
|
+
* Once contract-to-contract calls are supported, this circuit may be deprecated.
|
|
683
|
+
*
|
|
684
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The account receiving `tokenId`
|
|
685
|
+
* @param {Uint<128>} tokenId - The token to transfer
|
|
686
|
+
* @return {[]} - Empty tuple.
|
|
687
|
+
*/
|
|
688
|
+
export circuit _unsafeMint(
|
|
689
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
690
|
+
tokenId: Uint<128>
|
|
691
|
+
): [] {
|
|
692
|
+
Initializable_assertInitialized();
|
|
693
|
+
assert(!_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
694
|
+
|
|
695
|
+
const previousOwner = _update(to, tokenId, ZERO());
|
|
696
|
+
|
|
697
|
+
assert(_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* @description Destroys `tokenId`.
|
|
702
|
+
* The approval is cleared when the token is burned.
|
|
703
|
+
* This circuit does not check if the sender is authorized to operate on the token.
|
|
704
|
+
*
|
|
705
|
+
* @circuitInfo k=10, rows=509
|
|
706
|
+
*
|
|
707
|
+
* Requirements:
|
|
708
|
+
*
|
|
709
|
+
* - The contract is initialized.
|
|
710
|
+
* - `tokenId` must exist.
|
|
711
|
+
*
|
|
712
|
+
* @param {Uint<128>} tokenId - The token to burn
|
|
713
|
+
* @return {[]} - Empty tuple.
|
|
714
|
+
*/
|
|
715
|
+
export circuit _burn(tokenId: Uint<128>): [] {
|
|
716
|
+
Initializable_assertInitialized();
|
|
717
|
+
const previousOwner = _update(ZERO(), tokenId, ZERO());
|
|
718
|
+
assert(!_isTargetZero(previousOwner), "NonFungibleToken: invalid sender");
|
|
719
|
+
_tokenURIs.remove(disclose(tokenId));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* @description Transfers `tokenId` from `fromAddress` to `to`.
|
|
724
|
+
* As opposed to {transferFrom}, this imposes no restrictions on the caller's identity.
|
|
725
|
+
*
|
|
726
|
+
* @notice Transfers to contract addresses are currently disallowed until contract-to-contract
|
|
727
|
+
* interactions are supported in Compact. This restriction prevents assets from being inadvertently
|
|
728
|
+
* locked in contracts that cannot currently handle token receipt.
|
|
729
|
+
*
|
|
730
|
+
* @circuitInfo k=12, rows=2067
|
|
731
|
+
*
|
|
732
|
+
* Requirements:
|
|
733
|
+
*
|
|
734
|
+
* - The contract is initialized.
|
|
735
|
+
* - `to` is not zero.
|
|
736
|
+
* - `to` is not a ContractAddress.
|
|
737
|
+
* - `tokenId` token must be owned by `fromAddress`.
|
|
738
|
+
*
|
|
739
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The source account of the token transfer
|
|
740
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The target account of the token transfer
|
|
741
|
+
* @param {Uint<128>} tokenId - The token to transfer
|
|
742
|
+
* @return {[]} - Empty tuple.
|
|
743
|
+
*/
|
|
744
|
+
export circuit _transfer(
|
|
745
|
+
fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
746
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
747
|
+
tokenId: Uint<128>
|
|
748
|
+
): [] {
|
|
749
|
+
Initializable_assertInitialized();
|
|
750
|
+
const isContractAddr = !to.is_left;
|
|
751
|
+
assert(!isContractAddr, "NonFungibleToken: unsafe transfer");
|
|
752
|
+
|
|
753
|
+
_unsafeTransfer(fromAddress, to, tokenId);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* @description Transfers `tokenId` from `fromAddress` to `to`.
|
|
758
|
+
* As opposed to {_unsafeTransferFrom}, this imposes no restrictions on the caller's identity.
|
|
759
|
+
* It does NOT check if the recipient is a ContractAddress.
|
|
760
|
+
*
|
|
761
|
+
* @circuitInfo k=12, rows=2064
|
|
762
|
+
*
|
|
763
|
+
* Requirements:
|
|
764
|
+
*
|
|
765
|
+
* - The contract is initialized.
|
|
766
|
+
* - `to` is not zero.
|
|
767
|
+
* - `tokenId` token must be owned by `fromAddress`.
|
|
768
|
+
*
|
|
769
|
+
* @warning Transfers to contract addresses are considered unsafe because contract-to-contract
|
|
770
|
+
* calls are not currently supported. Tokens sent to a contract address may become irretrievable.
|
|
771
|
+
* Once contract-to-contract calls are supported, this circuit may be deprecated.
|
|
772
|
+
*
|
|
773
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The source account of the token transfer
|
|
774
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The target account of the token transfer
|
|
775
|
+
* @param {Uint<128>} tokenId - The token to transfer
|
|
776
|
+
* @return {[]} - Empty tuple.
|
|
777
|
+
*/
|
|
778
|
+
export circuit _unsafeTransfer(
|
|
779
|
+
fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
780
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
781
|
+
tokenId: Uint<128>
|
|
782
|
+
): [] {
|
|
783
|
+
Initializable_assertInitialized();
|
|
784
|
+
assert(!_isTargetZero(to), "NonFungibleToken: invalid receiver");
|
|
785
|
+
|
|
786
|
+
const previousOwner = _update(to, tokenId, ZERO());
|
|
787
|
+
|
|
788
|
+
assert(!_isTargetZero(previousOwner), "NonFungibleToken: nonexistent token");
|
|
789
|
+
|
|
790
|
+
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
791
|
+
assert(previousOwner == canonFrom, "NonFungibleToken: incorrect owner");
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* @description Approve `to` to operate on `tokenId`.
|
|
796
|
+
*
|
|
797
|
+
* @circuitInfo k=11, rows=1810
|
|
798
|
+
*
|
|
799
|
+
* Requirements:
|
|
800
|
+
*
|
|
801
|
+
* - The contract is initialized.
|
|
802
|
+
* - If `auth` is non 0, then this function will check that `auth` is either the owner of the token,
|
|
803
|
+
* or approved to operate on the token (by the owner).
|
|
804
|
+
*
|
|
805
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The target account to approve
|
|
806
|
+
* @param {Uint<128>} tokenId - The token to approve
|
|
807
|
+
* @param {Either<Bytes<32>, ContractAddress>} auth - An account authorized to operate on all tokens held by the owner of the token
|
|
808
|
+
* @return {[]} - Empty tuple.
|
|
809
|
+
*/
|
|
810
|
+
export circuit _approve(
|
|
811
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
812
|
+
tokenId: Uint<128>,
|
|
813
|
+
auth: Either<Bytes<32>, ContractAddress>
|
|
814
|
+
): [] {
|
|
815
|
+
Initializable_assertInitialized();
|
|
816
|
+
// Canonicalize + normalize `to`
|
|
817
|
+
const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
|
|
818
|
+
const normalizedTo = _isTargetZero(canonTo) ? ZERO() : canonTo;
|
|
819
|
+
|
|
820
|
+
// Canonicalize auth
|
|
821
|
+
const canonAuth = Utils_canonicalize<Bytes<32>, ContractAddress>(auth);
|
|
822
|
+
|
|
823
|
+
if (!_isTargetZero(disclose(canonAuth))) {
|
|
824
|
+
const owner = _requireOwned(tokenId);
|
|
825
|
+
|
|
826
|
+
// We do not use _isAuthorized because single-token approvals should not be able to call approve
|
|
827
|
+
assert((owner == disclose(canonAuth) || isApprovedForAll(owner, canonAuth)),
|
|
828
|
+
"NonFungibleToken: invalid approver"
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
_tokenApprovals.insert(disclose(tokenId), disclose(normalizedTo));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* @description Approve `operator` to operate on all of `owner` tokens.
|
|
837
|
+
*
|
|
838
|
+
* @circuitInfo k=11, rows=1261
|
|
839
|
+
*
|
|
840
|
+
* Requirements:
|
|
841
|
+
*
|
|
842
|
+
* - The contract is initialized.
|
|
843
|
+
* - `owner` is not zero.
|
|
844
|
+
* - `operator` is not zero.
|
|
845
|
+
*
|
|
846
|
+
* @param {Either<Bytes<32>, ContractAddress>} owner - Owner of a token
|
|
847
|
+
* @param {Either<Bytes<32>, ContractAddress>} operator - The account to approve
|
|
848
|
+
* @param {Boolean} approved - A boolean determining if `operator` may operate on all of `owner` tokens
|
|
849
|
+
* @return {[]} - Empty tuple.
|
|
850
|
+
*/
|
|
851
|
+
export circuit _setApprovalForAll(
|
|
852
|
+
owner: Either<Bytes<32>, ContractAddress>,
|
|
853
|
+
operator: Either<Bytes<32>, ContractAddress>,
|
|
854
|
+
approved: Boolean
|
|
855
|
+
): [] {
|
|
856
|
+
Initializable_assertInitialized();
|
|
857
|
+
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
858
|
+
assert(!_isTargetZero(canonOwner), "NonFungibleToken: invalid owner");
|
|
859
|
+
|
|
860
|
+
const canonOp = Utils_canonicalize<Bytes<32>, ContractAddress>(operator);
|
|
861
|
+
assert(!_isTargetZero(canonOp), "NonFungibleToken: invalid operator");
|
|
862
|
+
|
|
863
|
+
if (!_operatorApprovals.member(disclose(canonOwner))) {
|
|
864
|
+
_operatorApprovals.insert(
|
|
865
|
+
disclose(canonOwner),
|
|
866
|
+
default<Map<Either<Bytes<32>, ContractAddress>, Boolean>>
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
_operatorApprovals.lookup(canonOwner).insert(disclose(canonOp), disclose(approved));
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* @description Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
|
|
875
|
+
* Returns the owner.
|
|
876
|
+
*
|
|
877
|
+
* @circuitInfo k=9, rows=331
|
|
878
|
+
*
|
|
879
|
+
* Requirements:
|
|
880
|
+
*
|
|
881
|
+
* - The contract is initialized.
|
|
882
|
+
* - `tokenId` must exist.
|
|
883
|
+
*
|
|
884
|
+
* @param {Uint<128>} tokenId - The token that should be owned
|
|
885
|
+
* @return {Either<Bytes<32>, ContractAddress>} - The owner of `tokenId`
|
|
886
|
+
*/
|
|
887
|
+
export circuit _requireOwned(tokenId: Uint<128>): Either<Bytes<32>, ContractAddress> {
|
|
888
|
+
Initializable_assertInitialized();
|
|
889
|
+
const owner = _ownerOf(tokenId);
|
|
890
|
+
|
|
891
|
+
assert(!_isTargetZero(owner), "NonFungibleToken: nonexistent token");
|
|
892
|
+
return owner;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* @description Computes the caller's account identifier from the `wit_NonFungibleTokenSK` witness.
|
|
897
|
+
*
|
|
898
|
+
* ## ID Derivation
|
|
899
|
+
* `accountId = persistentHash(secretKey)`
|
|
900
|
+
*
|
|
901
|
+
* The result is a 32-byte commitment that uniquely identifies the caller.
|
|
902
|
+
*
|
|
903
|
+
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
904
|
+
*/
|
|
905
|
+
circuit _computeAccountId(): Bytes<32> {
|
|
906
|
+
return computeAccountId(wit_NonFungibleTokenSK());
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
911
|
+
* their identity commitment before submitting it in a token operation.
|
|
912
|
+
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
913
|
+
* given the same inputs.
|
|
914
|
+
*
|
|
915
|
+
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
916
|
+
* permanently compromise the security of this system:
|
|
917
|
+
*
|
|
918
|
+
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
919
|
+
* application logs, analytics pipelines, or any observable side-channel.
|
|
920
|
+
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
921
|
+
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
922
|
+
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
923
|
+
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
924
|
+
* - **Treat key loss as identity loss** — a lost key cannot be recovered.
|
|
925
|
+
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
926
|
+
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
927
|
+
* the key to a malicious observer.
|
|
928
|
+
*
|
|
929
|
+
* ## ID Derivation
|
|
930
|
+
* `accountId = persistentHash(secretKey)`
|
|
931
|
+
*
|
|
932
|
+
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
933
|
+
*
|
|
934
|
+
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
935
|
+
*/
|
|
936
|
+
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
937
|
+
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
|
|
942
|
+
* holds the zero value.
|
|
943
|
+
*
|
|
944
|
+
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
|
|
945
|
+
* @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
|
|
946
|
+
*/
|
|
947
|
+
circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
|
|
948
|
+
if (target.is_left) {
|
|
949
|
+
return target.left == default<Bytes<32>>;
|
|
950
|
+
} else {
|
|
951
|
+
return target.right == default<ContractAddress>;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|