@openzeppelin/compact-contracts 0.3.0-alpha.1 → 0.3.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/access/AccessControl.compact +1 -1
  2. package/access/Ownable.compact +1 -1
  3. package/access/ShieldedAccessControl.compact +1 -1
  4. package/access/ZOwnablePK.compact +1 -1
  5. package/crypto/EcdhMask.compact +1 -1
  6. package/crypto/ElGamal.compact +44 -3
  7. package/multisig/ForwarderPrivate.compact +2 -2
  8. package/multisig/ForwarderShielded.compact +2 -2
  9. package/multisig/ForwarderUnshielded.compact +2 -2
  10. package/multisig/ProposalManager.compact +35 -1
  11. package/multisig/ShieldedTreasury.compact +34 -4
  12. package/multisig/ShieldedTreasuryStateless.compact +46 -17
  13. package/multisig/Signer.compact +45 -41
  14. package/multisig/UnshieldedTreasury.compact +44 -17
  15. package/multisig/presets/ShieldedMultiSig.compact +4 -4
  16. package/multisig/presets/ShieldedMultiSigV2.compact +2 -2
  17. package/multisig/presets/ShieldedMultiSigV3.compact +2 -2
  18. package/multisig/presets/forwarder/ForwarderPrivate.compact +1 -1
  19. package/multisig/presets/forwarder/ForwarderShielded.compact +1 -1
  20. package/multisig/presets/forwarder/ForwarderUnshielded.compact +1 -1
  21. package/package.json +3 -3
  22. package/security/Allowlist.compact +1 -1
  23. package/security/Blocklist.compact +1 -1
  24. package/security/Initializable.compact +1 -1
  25. package/security/Pausable.compact +1 -1
  26. package/token/ConfidentialFungibleToken.compact +289 -75
  27. package/token/FungibleToken.compact +1 -1
  28. package/token/MultiToken.compact +1 -1
  29. package/token/NativeShieldedToken.compact +60 -11
  30. package/token/NativeShieldedTokenCore.compact +41 -9
  31. package/token/NativeShieldedTokenFamily.compact +61 -11
  32. package/token/NonFungibleToken.compact +63 -19
  33. package/token/extensions/ConfidentialFungibleTokenPublicSupply.compact +1 -1
  34. package/token/extensions/NativeShieldedTokenDerivedNonce.compact +1 -1
  35. package/token/extensions/{NativeShieldedTokenFamilySupply.compact → NativeShieldedTokenFamilyPublicSupply.compact} +11 -11
  36. package/token/extensions/{NativeShieldedTokenSupply.compact → NativeShieldedTokenPublicSupply.compact} +11 -11
  37. package/token/extensions/{NativeShieldedTokenSupplyCore.compact → NativeShieldedTokenPublicSupplyCore.compact} +9 -9
  38. package/utils/Utils.compact +11 -1
  39. package/multisig/SignerManager.compact +0 -205
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/NativeShieldedToken.compact)
2
+ // OpenZeppelin Compact Contracts v0.3.0-rc.1 (token/NativeShieldedToken.compact)
3
3
 
4
4
  pragma language_version >= 0.23.0;
5
5
 
@@ -20,6 +20,10 @@ pragma language_version >= 0.23.0;
20
20
  * share one set of core state (a single init flag and one name/symbol/decimals).
21
21
  * Pick one flavor per contract.
22
22
  *
23
+ * @notice Recipients must be coin public keys: contract-addressed recipients
24
+ * are not supported at this layer. For a coin the contract holds itself, use
25
+ * `_mintToSelf`, which mints to `kernel.self()` and claims it in the same call.
26
+ *
23
27
  * @notice This module exposes composable building blocks for minting and
24
28
  * burning native shielded coins. It does NOT include access control: consuming
25
29
  * contracts compose that via the module/contract pattern (Ownable,
@@ -45,7 +49,7 @@ pragma language_version >= 0.23.0;
45
49
  * recipient-public.
46
50
  *
47
51
  * @notice Supply accounting is opt-in: this module tracks NO supply totals.
48
- * Compose the optional `NativeShieldedTokenSupply` extension only if you need an
52
+ * Compose the optional `NativeShieldedTokenPublicSupply` extension only if you need an
49
53
  * on-chain `totalSupply()`; see it for the privacy trade-off it introduces (it
50
54
  * makes contract-mediated burn amounts public).
51
55
  *
@@ -190,25 +194,65 @@ module NativeShieldedToken {
190
194
  * recipient; callers SHOULD deliver it out of band. Wallets cannot detect
191
195
  * contract-minted coins by scanning the chain.
192
196
  *
193
- * @circuitInfo k=14, rows=10843
197
+ * @circuitInfo k=14, rows=10473
194
198
  *
195
199
  * Requirements:
196
200
  *
197
201
  * - Contract is initialized.
198
202
  * - `recipient` is not zero.
199
203
  *
200
- * @param {Either<ZswapCoinPublicKey, ContractAddress>} recipient - The coin recipient.
204
+ * @param {ZswapCoinPublicKey} recipient - The coin recipient.
201
205
  * @param {Uint<64>} amount - Quantity to mint. Capped at `Uint<64>` by the protocol.
202
206
  * @param {Bytes<32>} nonce - Caller-supplied nonce. Must be unique for this
203
207
  * contract's color.
204
208
  * @return {ShieldedCoinInfo} - The newly created coin's info (nonce, color, value).
205
209
  */
206
210
  export circuit _mint(
207
- recipient: Either<ZswapCoinPublicKey, ContractAddress>,
211
+ recipient: ZswapCoinPublicKey,
212
+ amount: Uint<64>,
213
+ nonce: Bytes<32>
214
+ ): ShieldedCoinInfo {
215
+ return Core__mint(
216
+ _domain,
217
+ left<ZswapCoinPublicKey, ContractAddress>(recipient),
218
+ amount,
219
+ nonce
220
+ );
221
+ }
222
+
223
+ /**
224
+ * @description Mints `amount` to this contract itself, claiming the coin in
225
+ * the same call. Forwards to `NativeShieldedTokenCore._mint` with
226
+ * `right(kernel.self())`.
227
+ *
228
+ * @dev Nonce uniqueness is the caller's responsibility, as in `_mint`. Spend
229
+ * the coin later with `_burnFromSelf`, once it carries an `mt_index`.
230
+ *
231
+ * @notice The consumer SHOULD persist the returned coin info in its own
232
+ * ledger state: it is the contract's holding and is not otherwise
233
+ * recoverable.
234
+ *
235
+ * @circuitInfo k=14, rows=16070
236
+ *
237
+ * Requirements:
238
+ *
239
+ * - Contract is initialized.
240
+ *
241
+ * @param {Uint<64>} amount - Quantity to mint. Capped at `Uint<64>` by the protocol.
242
+ * @param {Bytes<32>} nonce - Caller-supplied nonce. Must be unique for this
243
+ * contract's color.
244
+ * @return {ShieldedCoinInfo} - The newly created coin's info (nonce, color, value).
245
+ */
246
+ export circuit _mintToSelf(
208
247
  amount: Uint<64>,
209
248
  nonce: Bytes<32>
210
249
  ): ShieldedCoinInfo {
211
- return Core__mint(_domain, recipient, amount, nonce);
250
+ return Core__mint(
251
+ _domain,
252
+ right<ZswapCoinPublicKey, ContractAddress>(kernel.self()),
253
+ amount,
254
+ nonce
255
+ );
212
256
  }
213
257
 
214
258
  /**
@@ -229,7 +273,7 @@ module NativeShieldedToken {
229
273
  * `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
230
274
  * contract-sent coins by scanning the chain.
231
275
  *
232
- * @circuitInfo k=16, rows=47656
276
+ * @circuitInfo k=16, rows=41492
233
277
  *
234
278
  * Requirements:
235
279
  *
@@ -241,17 +285,22 @@ module NativeShieldedToken {
241
285
  *
242
286
  * @param {ShieldedCoinInfo} coin - The coin to burn from.
243
287
  * @param {Uint<128>} amount - Value to destroy. Must be <= `coin.value`.
244
- * @param {Either<ZswapCoinPublicKey, ContractAddress>} refundTo - Where to
245
- * route the unspent portion (`coin.value - amount`).
288
+ * @param {ZswapCoinPublicKey} refundTo - Where to route the unspent
289
+ * portion (`coin.value - amount`).
246
290
  * @return {Maybe<ShieldedCoinInfo>} - The refund coin created for `refundTo`,
247
291
  * or `none` if the coin was burned in full.
248
292
  */
249
293
  export circuit _burn(
250
294
  coin: ShieldedCoinInfo,
251
295
  amount: Uint<128>,
252
- refundTo: Either<ZswapCoinPublicKey, ContractAddress>
296
+ refundTo: ZswapCoinPublicKey
253
297
  ): Maybe<ShieldedCoinInfo> {
254
- return Core__burn(_domain, coin, amount, refundTo);
298
+ return Core__burn(
299
+ _domain,
300
+ coin,
301
+ amount,
302
+ left<ZswapCoinPublicKey, ContractAddress>(refundTo)
303
+ );
255
304
  }
256
305
 
257
306
  /**
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/NativeShieldedTokenCore.compact)
2
+ // OpenZeppelin Compact Contracts v0.3.0-rc.1 (token/NativeShieldedTokenCore.compact)
3
3
 
4
4
  pragma language_version >= 0.23.0;
5
5
 
@@ -44,6 +44,18 @@ pragma language_version >= 0.23.0;
44
44
  * compose `NativeShieldedTokenDerivedNonce` and pass its `_deriveNonce()`
45
45
  * output, at the cost of making those mints recipient-public.
46
46
  *
47
+ * @notice A recipient may be any coin public key, or this contract's own
48
+ * address. Any other contract address is rejected.
49
+ *
50
+ * @dev Recipient arm
51
+ * The ledger accepts a contract-addressed commitment only if the recipient
52
+ * contract's own call claims it as a shielded receive in the same transaction,
53
+ * and no primitive claims a receive on another contract's behalf. So the
54
+ * address arm is accepted only for `kernel.self()`, where `_mint` / `_burn`
55
+ * claim the coin themselves in the same call. The flavors do not expose the
56
+ * arm at all: they take a `ZswapCoinPublicKey` and offer `_mintToSelf` for
57
+ * contract-held coins.
58
+ *
47
59
  * @dev Burn variants
48
60
  * `_burn` destroys value from a coin provided within the current transaction
49
61
  * (e.g. the caller's wallet pays the contract): it receives the coin and spends
@@ -212,21 +224,26 @@ module NativeShieldedTokenCore {
212
224
  *
213
225
  * @dev The caller is fully responsible for nonce uniqueness. Reusing a
214
226
  * nonce for the same (domain, value, recipient) produces a duplicate
215
- * commitment, which the protocol rejects.
227
+ * commitment, which the protocol rejects. A `right(kernel.self())` recipient
228
+ * is claimed via `receiveShielded` in this same call, so the consumer SHOULD
229
+ * persist the returned coin info: it is the contract's holding and is not
230
+ * otherwise recoverable.
216
231
  *
217
232
  * @notice The returned coin info is the only copy available to the
218
233
  * recipient; callers SHOULD deliver it out of band. Wallets cannot detect
219
234
  * contract-minted coins by scanning the chain.
220
235
  *
221
- * @circuitInfo k=14, rows=11119
236
+ * @circuitInfo k=15, rows=17017
222
237
  *
223
238
  * Requirements:
224
239
  *
225
240
  * - Contract is initialized.
226
241
  * - `recipient` is not zero.
242
+ * - `recipient`, if a contract address, is this contract's own address.
227
243
  *
228
244
  * @param {Bytes<32>} domain - 32-byte token discriminator.
229
- * @param {Either<ZswapCoinPublicKey, ContractAddress>} recipient - The coin recipient.
245
+ * @param {Either<ZswapCoinPublicKey, ContractAddress>} recipient - The coin
246
+ * recipient: any coin public key, or this contract's own address.
230
247
  * @param {Uint<64>} amount - Quantity to mint. Capped at `Uint<64>` by the protocol.
231
248
  * @param {Bytes<32>} nonce - Caller-supplied nonce. Must be unique per (domain, contract).
232
249
  * @return {ShieldedCoinInfo} - The newly created coin's info (nonce, color, value).
@@ -238,9 +255,16 @@ module NativeShieldedTokenCore {
238
255
  nonce: Bytes<32>
239
256
  ): ShieldedCoinInfo {
240
257
  assertInitialized();
258
+ const self = kernel.self();
241
259
  assert(!Utils_isKeyOrAddressZero(recipient), "NativeShieldedToken: invalid recipient");
260
+ assert(recipient.is_left || recipient.right == self, "NativeShieldedToken: recipient contract must be self");
242
261
 
243
- return mintShieldedToken(disclose(domain), disclose(amount), disclose(nonce), disclose(recipient));
262
+ const coin = mintShieldedToken(disclose(domain), disclose(amount), disclose(nonce), disclose(recipient));
263
+ if (disclose(!recipient.is_left)) {
264
+ receiveShielded(coin);
265
+ }
266
+
267
+ return coin;
244
268
  }
245
269
 
246
270
  /**
@@ -255,13 +279,14 @@ module NativeShieldedTokenCore {
255
279
  * @dev The coin is received by the contract and spent in the same
256
280
  * transaction, so the spend goes through `sendImmediateShielded` (the
257
281
  * transient path). For coins the contract already holds, use
258
- * `_burnFromSelf` instead.
282
+ * `_burnFromSelf` instead. A `right(kernel.self())` refund is claimed via
283
+ * `receiveShielded` in this same call.
259
284
  *
260
285
  * @notice The returned refund coin info is the only copy available to
261
286
  * `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
262
287
  * contract-sent coins by scanning the chain.
263
288
  *
264
- * @circuitInfo k=16, rows=47932
289
+ * @circuitInfo k=16, rows=53832
265
290
  *
266
291
  * Requirements:
267
292
  *
@@ -270,12 +295,14 @@ module NativeShieldedTokenCore {
270
295
  * - `amount` is less than or equal to `coin.value`.
271
296
  * - `refundTo` is not zero (the zero key is the burn address; a zero
272
297
  * `refundTo` would silently burn the change as well).
298
+ * - `refundTo`, if a contract address, is this contract's own address.
273
299
  *
274
300
  * @param {Bytes<32>} domain - 32-byte token discriminator.
275
301
  * @param {ShieldedCoinInfo} coin - The coin to burn from.
276
302
  * @param {Uint<128>} amount - Value to destroy. Must be <= `coin.value`.
277
303
  * @param {Either<ZswapCoinPublicKey, ContractAddress>} refundTo - Where to
278
- * route the unspent portion (`coin.value - amount`).
304
+ * route the unspent portion (`coin.value - amount`): any coin public key, or
305
+ * this contract's own address.
279
306
  * @return {Maybe<ShieldedCoinInfo>} - The refund coin created for `refundTo`,
280
307
  * or `none` if the coin was burned in full.
281
308
  */
@@ -286,9 +313,11 @@ module NativeShieldedTokenCore {
286
313
  refundTo: Either<ZswapCoinPublicKey, ContractAddress>
287
314
  ): Maybe<ShieldedCoinInfo> {
288
315
  assertInitialized();
289
- assert(coin.color == tokenType(disclose(domain), kernel.self()), "NativeShieldedToken: wrong token");
316
+ const self = kernel.self();
317
+ assert(coin.color == tokenType(disclose(domain), self), "NativeShieldedToken: wrong token");
290
318
  assert(coin.value >= amount, "NativeShieldedToken: insufficient coin value");
291
319
  assert(!Utils_isKeyOrAddressZero(refundTo), "NativeShieldedToken: invalid refund target");
320
+ assert(refundTo.is_left || refundTo.right == self, "NativeShieldedToken: refund contract must be self");
292
321
 
293
322
  receiveShielded(disclose(coin));
294
323
  const sendRes = sendImmediateShielded(disclose(coin), shieldedBurnAddress(), disclose(amount));
@@ -299,6 +328,9 @@ module NativeShieldedTokenCore {
299
328
  disclose(refundTo),
300
329
  disclose(sendRes.change.value.value)
301
330
  );
331
+ if (disclose(!refundTo.is_left)) {
332
+ receiveShielded(refundRes.sent);
333
+ }
302
334
  return some<ShieldedCoinInfo>(refundRes.sent);
303
335
  }
304
336
  return none<ShieldedCoinInfo>();
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/NativeShieldedTokenFamily.compact)
2
+ // OpenZeppelin Compact Contracts v0.3.0-rc.1 (token/NativeShieldedTokenFamily.compact)
3
3
 
4
4
  pragma language_version >= 0.23.0;
5
5
 
@@ -26,6 +26,10 @@ pragma language_version >= 0.23.0;
26
26
  * set of core state (a single init flag and one name/symbol/decimals).
27
27
  * Pick one flavor per contract.
28
28
  *
29
+ * @notice Recipients must be coin public keys: contract-addressed recipients
30
+ * are not supported at this layer. For a coin the contract holds itself, use
31
+ * `_mintToSelf`, which mints to `kernel.self()` and claims it in the same call.
32
+ *
29
33
  * @notice This module exposes composable building blocks for minting and
30
34
  * burning native shielded coins. It does NOT include access control: consuming
31
35
  * contracts compose that via the module/contract pattern (Ownable,
@@ -53,7 +57,7 @@ pragma language_version >= 0.23.0;
53
57
  * output, at the cost of making those mints recipient-public.
54
58
  *
55
59
  * @notice Supply accounting is opt-in: this module tracks NO supply totals.
56
- * Compose the optional `NativeShieldedTokenFamilySupply` extension only if you
60
+ * Compose the optional `NativeShieldedTokenFamilyPublicSupply` extension only if you
57
61
  * need an on-chain `totalSupply(domain)`; see it for the privacy trade-off it
58
62
  * introduces (it makes contract-mediated burn amounts public).
59
63
  *
@@ -186,7 +190,7 @@ module NativeShieldedTokenFamily {
186
190
  * recipient; callers SHOULD deliver it out of band. Wallets cannot detect
187
191
  * contract-minted coins by scanning the chain.
188
192
  *
189
- * @circuitInfo k=14, rows=11119
193
+ * @circuitInfo k=14, rows=10750
190
194
  *
191
195
  * Requirements:
192
196
  *
@@ -195,18 +199,59 @@ module NativeShieldedTokenFamily {
195
199
  *
196
200
  * @param {Bytes<32>} domain - 32-byte token discriminator. Same domain + same
197
201
  * contract address produces the same coin color (i.e. same token type).
198
- * @param {Either<ZswapCoinPublicKey, ContractAddress>} recipient - The coin recipient.
202
+ * @param {ZswapCoinPublicKey} recipient - The coin recipient.
199
203
  * @param {Uint<64>} amount - Quantity to mint. Capped at `Uint<64>` by the protocol.
200
204
  * @param {Bytes<32>} nonce - Caller-supplied nonce. Must be unique per (domain, contract).
201
205
  * @return {ShieldedCoinInfo} - The newly created coin's info (nonce, color, value).
202
206
  */
203
207
  export circuit _mint(
204
208
  domain: Bytes<32>,
205
- recipient: Either<ZswapCoinPublicKey, ContractAddress>,
209
+ recipient: ZswapCoinPublicKey,
210
+ amount: Uint<64>,
211
+ nonce: Bytes<32>
212
+ ): ShieldedCoinInfo {
213
+ return Core__mint(
214
+ domain,
215
+ left<ZswapCoinPublicKey, ContractAddress>(recipient),
216
+ amount,
217
+ nonce
218
+ );
219
+ }
220
+
221
+ /**
222
+ * @description Mints `amount` of the token identified by `domain` to this
223
+ * contract itself, claiming the coin in the same call. Forwards to
224
+ * `NativeShieldedTokenCore._mint` with `right(kernel.self())`.
225
+ *
226
+ * @dev Nonce uniqueness is the caller's responsibility, as in `_mint`. Spend
227
+ * the coin later with `_burnFromSelf`, once it carries an `mt_index`.
228
+ *
229
+ * @notice The consumer SHOULD persist the returned coin info in its own
230
+ * ledger state: it is the contract's holding and is not otherwise
231
+ * recoverable.
232
+ *
233
+ * @circuitInfo k=14, rows=16346
234
+ *
235
+ * Requirements:
236
+ *
237
+ * - Contract is initialized.
238
+ *
239
+ * @param {Bytes<32>} domain - 32-byte token discriminator.
240
+ * @param {Uint<64>} amount - Quantity to mint. Capped at `Uint<64>` by the protocol.
241
+ * @param {Bytes<32>} nonce - Caller-supplied nonce. Must be unique per (domain, contract).
242
+ * @return {ShieldedCoinInfo} - The newly created coin's info (nonce, color, value).
243
+ */
244
+ export circuit _mintToSelf(
245
+ domain: Bytes<32>,
206
246
  amount: Uint<64>,
207
247
  nonce: Bytes<32>
208
248
  ): ShieldedCoinInfo {
209
- return Core__mint(domain, recipient, amount, nonce);
249
+ return Core__mint(
250
+ domain,
251
+ right<ZswapCoinPublicKey, ContractAddress>(kernel.self()),
252
+ amount,
253
+ nonce
254
+ );
210
255
  }
211
256
 
212
257
  /**
@@ -227,7 +272,7 @@ module NativeShieldedTokenFamily {
227
272
  * `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
228
273
  * contract-sent coins by scanning the chain.
229
274
  *
230
- * @circuitInfo k=16, rows=47932
275
+ * @circuitInfo k=16, rows=41768
231
276
  *
232
277
  * Requirements:
233
278
  *
@@ -240,8 +285,8 @@ module NativeShieldedTokenFamily {
240
285
  * @param {Bytes<32>} domain - 32-byte token discriminator.
241
286
  * @param {ShieldedCoinInfo} coin - The coin to burn from.
242
287
  * @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`).
288
+ * @param {ZswapCoinPublicKey} refundTo - Where to route the unspent
289
+ * portion (`coin.value - amount`).
245
290
  * @return {Maybe<ShieldedCoinInfo>} - The refund coin created for `refundTo`,
246
291
  * or `none` if the coin was burned in full.
247
292
  */
@@ -249,9 +294,14 @@ module NativeShieldedTokenFamily {
249
294
  domain: Bytes<32>,
250
295
  coin: ShieldedCoinInfo,
251
296
  amount: Uint<128>,
252
- refundTo: Either<ZswapCoinPublicKey, ContractAddress>
297
+ refundTo: ZswapCoinPublicKey
253
298
  ): Maybe<ShieldedCoinInfo> {
254
- return Core__burn(domain, coin, amount, refundTo);
299
+ return Core__burn(
300
+ domain,
301
+ coin,
302
+ amount,
303
+ left<ZswapCoinPublicKey, ContractAddress>(refundTo)
304
+ );
255
305
  }
256
306
 
257
307
  /**
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/NonFungibleToken.compact)
2
+ // OpenZeppelin Compact Contracts v0.3.0-rc.1 (token/NonFungibleToken.compact)
3
3
 
4
4
  pragma language_version >= 0.23.0;
5
5
 
@@ -58,7 +58,7 @@ pragma language_version >= 0.23.0;
58
58
  * or in ledger writes. Canonicalization zeroes out the inactive branch of the Either,
59
59
  * ensuring that two values with the same active branch always resolve to the same map key
60
60
  * regardless of what data the inactive branch carries. Write paths are canonicalized in
61
- * `_update` (for `_owners` and `_balances`), `_approve` (for `_tokenApprovals`), and
61
+ * `_update` (for `_owners` and `_balances`), `_unsafeApprove` (for `_tokenApprovals`), and
62
62
  * `_setApprovalForAll` (for `_operatorApprovals`). Read paths are canonicalized in
63
63
  * `balanceOf`, `isApprovedForAll`, and `_isAuthorized`.
64
64
  *
@@ -338,7 +338,7 @@ module NonFungibleToken {
338
338
  * In the case of an external (non-contract) caller, the caller's identity is derived from the `wit_NonFungibleTokenSK`
339
339
  * witness as `persistentHash(secretKey)`.
340
340
  *
341
- * @circuitInfo k=13, rows=3468
341
+ * @circuitInfo k=13, rows=3415
342
342
  *
343
343
  * Requirements:
344
344
  *
@@ -383,7 +383,7 @@ module NonFungibleToken {
383
383
  * In the case of an external (non-contract) caller, the caller's identity is derived from the `wit_NonFungibleTokenSK`
384
384
  * witness as `persistentHash(secretKey)`.
385
385
  *
386
- * @circuitInfo k=13, rows=2936
386
+ * @circuitInfo k=13, rows=2947
387
387
  *
388
388
  * Requirements:
389
389
  *
@@ -439,7 +439,7 @@ module NonFungibleToken {
439
439
  * In the case of an external (non-contract) caller, the caller's identity is derived from the `wit_NonFungibleTokenSK`
440
440
  * witness as `persistentHash(secretKey)`.
441
441
  *
442
- * @circuitInfo k=13, rows=4800
442
+ * @circuitInfo k=13, rows=4739
443
443
  *
444
444
  * Requirements:
445
445
  *
@@ -477,7 +477,7 @@ module NonFungibleToken {
477
477
  * are not currently supported. Tokens sent to a contract address may become irretrievable.
478
478
  * Once contract-to-contract calls are supported, this circuit may be deprecated.
479
479
  *
480
- * @circuitInfo k=13, rows=4797
480
+ * @circuitInfo k=13, rows=4736
481
481
  *
482
482
  * Requirements:
483
483
  *
@@ -499,11 +499,14 @@ module NonFungibleToken {
499
499
  ): [] {
500
500
  assertInitialized();
501
501
  assert(!Utils_isTargetZero(to), "NonFungibleToken: invalid receiver");
502
- // Setting an "auth" argument enables the `_isAuthorized` check which verifies that the token exists
503
- // (fromAddress != 0). Therefore, it is not needed to verify that the return value is not 0 here.
504
502
  const auth = left<Bytes<32>, ContractAddress>(_computeAccountId());
505
503
  const previousOwner = _update(to, tokenId, auth);
506
504
 
505
+ // The `_isAuthorized` check inside `_update` already implies the token exists, but that
506
+ // holds only while approvals are never recorded for unminted tokens. Assert it directly
507
+ // so a misused `_unsafeApprove` cannot turn this circuit into an unauthorized mint.
508
+ assert(!Utils_isTargetZero(previousOwner), "NonFungibleToken: nonexistent token");
509
+
507
510
  const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
508
511
  assert(previousOwner == canonFrom, "NonFungibleToken: incorrect owner");
509
512
  }
@@ -643,8 +646,8 @@ module NonFungibleToken {
643
646
 
644
647
  // Execute the update
645
648
  if (!Utils_isTargetZero(disclose(fromAddress))) {
646
- // Clear approval. No need to re-authorize
647
- _approve(Utils_zeroAccount(), tokenId, Utils_zeroAccount());
649
+ // Clear approval. No need to re-authorize or re-read the owner
650
+ _unsafeApprove(Utils_zeroAccount(), tokenId, Utils_zeroAccount(), false);
648
651
  const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
649
652
  const newBalance = _balances.lookup(disclose(canonFrom)) - 1 as Uint<128>;
650
653
  _balances.insert(disclose(canonFrom), disclose(newBalance));
@@ -666,7 +669,7 @@ module NonFungibleToken {
666
669
  /**
667
670
  * @description Mints `tokenId` and transfers it to `to`.
668
671
  *
669
- * @circuitInfo k=11, rows=1473
672
+ * @circuitInfo k=11, rows=1411
670
673
  *
671
674
  * Requirements:
672
675
  *
@@ -690,7 +693,7 @@ module NonFungibleToken {
690
693
  /**
691
694
  * @description Mints `tokenId` and transfers it to `to`. It does NOT check if the recipient is a ContractAddress.
692
695
  *
693
- * @circuitInfo k=11, rows=1470
696
+ * @circuitInfo k=11, rows=1408
694
697
  *
695
698
  * Requirements:
696
699
  *
@@ -723,7 +726,7 @@ module NonFungibleToken {
723
726
  * The approval is cleared when the token is burned.
724
727
  * This circuit does not check if the sender is authorized to operate on the token.
725
728
  *
726
- * @circuitInfo k=10, rows=509
729
+ * @circuitInfo k=10, rows=512
727
730
  *
728
731
  * Requirements:
729
732
  *
@@ -748,7 +751,7 @@ module NonFungibleToken {
748
751
  * interactions are supported in Compact. This restriction prevents assets from being inadvertently
749
752
  * locked in contracts that cannot currently handle token receipt.
750
753
  *
751
- * @circuitInfo k=12, rows=2067
754
+ * @circuitInfo k=11, rows=2005
752
755
  *
753
756
  * Requirements:
754
757
  *
@@ -779,7 +782,7 @@ module NonFungibleToken {
779
782
  * As opposed to {_unsafeTransferFrom}, this imposes no restrictions on the caller's identity.
780
783
  * It does NOT check if the recipient is a ContractAddress.
781
784
  *
782
- * @circuitInfo k=12, rows=2064
785
+ * @circuitInfo k=11, rows=2002
783
786
  *
784
787
  * Requirements:
785
788
  *
@@ -815,11 +818,12 @@ module NonFungibleToken {
815
818
  /**
816
819
  * @description Approve `to` to operate on `tokenId`.
817
820
  *
818
- * @circuitInfo k=11, rows=1810
821
+ * @circuitInfo k=11, rows=1757
819
822
  *
820
823
  * Requirements:
821
824
  *
822
825
  * - The contract is initialized.
826
+ * - `tokenId` must exist.
823
827
  * - If `auth` is non 0, then this function will check that `auth` is either the owner of the token,
824
828
  * or approved to operate on the token (by the owner).
825
829
  *
@@ -833,6 +837,43 @@ module NonFungibleToken {
833
837
  tokenId: Uint<128>,
834
838
  auth: Either<Bytes<32>, ContractAddress>
835
839
  ): [] {
840
+ _unsafeApprove(to, tokenId, auth, true);
841
+ }
842
+
843
+ /**
844
+ * @description Approve `to` to operate on `tokenId`, requiring the token to exist
845
+ * only when `isExistenceRequired` is true.
846
+ *
847
+ * @circuitInfo k=11, rows=1870
848
+ *
849
+ * @warning Passing false skips the check that `tokenId` was minted. Both
850
+ * `_unsafeTransferFrom` and `_update` read that invariant back, so a planted approval
851
+ * can mint a token nobody authorized or outlive a later mint. Pass false only where
852
+ * the owner has already been read, as `_update` does.
853
+ *
854
+ * @dev Counterpart to Solidity's `_approve(to, tokenId, auth, bool emitEvent)`, where
855
+ * the flag also suppresses the `Approval` event. Compact has no events, so it carries
856
+ * the existence check alone.
857
+ *
858
+ * Requirements:
859
+ *
860
+ * - The contract is initialized.
861
+ * - `tokenId` must exist, unless `isExistenceRequired` is false and `auth` is zero.
862
+ * - If `auth` is non 0, then this function will check that `auth` is either the owner of the token,
863
+ * or approved to operate on the token (by the owner).
864
+ *
865
+ * @param {Either<Bytes<32>, ContractAddress>} to - The target account to approve
866
+ * @param {Uint<128>} tokenId - The token to approve
867
+ * @param {Either<Bytes<32>, ContractAddress>} auth - An account authorized to operate on all tokens held by the owner of the token
868
+ * @param {Boolean} isExistenceRequired - Whether `tokenId` must exist
869
+ * @return {[]} - Empty tuple.
870
+ */
871
+ export circuit _unsafeApprove(
872
+ to: Either<Bytes<32>, ContractAddress>,
873
+ tokenId: Uint<128>,
874
+ auth: Either<Bytes<32>, ContractAddress>,
875
+ isExistenceRequired: Boolean
876
+ ): [] {
836
877
  assertInitialized();
837
878
  // Canonicalize + normalize `to`
838
879
  const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
@@ -841,11 +882,14 @@ module NonFungibleToken {
841
882
  // Canonicalize auth
842
883
  const canonAuth = Utils_canonicalize<Bytes<32>, ContractAddress>(auth);
843
884
 
844
- if (!Utils_isTargetZero(disclose(canonAuth))) {
885
+ // Avoid reading the owner unless necessary
886
+ if (disclose(isExistenceRequired) || !Utils_isTargetZero(disclose(canonAuth))) {
845
887
  const owner = _requireOwned(tokenId);
846
888
 
847
889
  // We do not use _isAuthorized because single-token approvals should not be able to call approve
848
- assert((owner == disclose(canonAuth) || isApprovedForAll(owner, canonAuth)),
890
+ assert((Utils_isTargetZero(disclose(canonAuth)) ||
891
+ owner == disclose(canonAuth) ||
892
+ isApprovedForAll(owner, canonAuth)),
849
893
  "NonFungibleToken: invalid approver"
850
894
  );
851
895
  }
@@ -856,7 +900,7 @@ module NonFungibleToken {
856
900
  /**
857
901
  * @description Approve `operator` to operate on all of `owner` tokens.
858
902
  *
859
- * @circuitInfo k=11, rows=1261
903
+ * @circuitInfo k=11, rows=1282
860
904
  *
861
905
  * Requirements:
862
906
  *
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/extensions/ConfidentialFungibleTokenPublicSupply.compact)
2
+ // OpenZeppelin Compact Contracts v0.3.0-rc.1 (token/extensions/ConfidentialFungibleTokenPublicSupply.compact)
3
3
 
4
4
  pragma language_version >= 0.23.0;
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // OpenZeppelin Compact Contracts v0.3.0-alpha.1 (token/extensions/NativeShieldedTokenDerivedNonce.compact)
2
+ // OpenZeppelin Compact Contracts v0.3.0-rc.1 (token/extensions/NativeShieldedTokenDerivedNonce.compact)
3
3
 
4
4
  pragma language_version >= 0.23.0;
5
5