@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,101 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (security/Allowlist.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module Allowlist
|
|
8
|
+
* @description A minimal, reusable permit-list keyed by an abstract `Bytes<32>`
|
|
9
|
+
* account identifier. It answers a single question: is this account a member?
|
|
10
|
+
*
|
|
11
|
+
* This module is a pure membership MECHANISM, not a policy. It holds no notion
|
|
12
|
+
* of "when" the list is enforced and no access control. The mutators (`_allow`,
|
|
13
|
+
* `_disallow`) are deliberately UNGATED composable primitives: a composing
|
|
14
|
+
* contract gates them behind its access-control module (the same way a token
|
|
15
|
+
* gates `_mint` / `_burn`), and decides when to call `assertAllowed` (for
|
|
16
|
+
* example, only while an "eligibility required" flag is set). Keeping the
|
|
17
|
+
* enforce-or-not decision in the composing layer leaves this module general
|
|
18
|
+
* enough to back KYC allowlists, permissioned mints, membership gates, and the
|
|
19
|
+
* like.
|
|
20
|
+
*
|
|
21
|
+
* @notice This is the PUBLIC (transparent) allowlist: membership status is
|
|
22
|
+
* publicly readable and the approved set is publicly enumerable. That is the
|
|
23
|
+
* right posture for transparent or public-graph assets. A graph-private asset
|
|
24
|
+
* should instead use a shielded allowlist (a public allowlist would re-expose
|
|
25
|
+
* the candidate set and bound the anonymity set).
|
|
26
|
+
*/
|
|
27
|
+
module Allowlist {
|
|
28
|
+
import CompactStandardLibrary;
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// State
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/** @description Approved accounts. Absent means not allowed. */
|
|
35
|
+
export ledger _allowed: Set<Bytes<32>>;
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Views
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @description Returns whether `account` is currently allowed.
|
|
43
|
+
*
|
|
44
|
+
* @circuitInfo k=9, rows=305
|
|
45
|
+
*
|
|
46
|
+
* @param {Bytes<32>} account - The account to query.
|
|
47
|
+
* @return {Boolean} - True if `account` is a member of the allowlist.
|
|
48
|
+
*/
|
|
49
|
+
export circuit isAllowed(account: Bytes<32>): Boolean {
|
|
50
|
+
return _allowed.member(disclose(account));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Check (the eligibility seam)
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @description Asserts that `account` is allowed. The composing contract
|
|
59
|
+
* decides when this gate applies.
|
|
60
|
+
*
|
|
61
|
+
* @circuitInfo k=9, rows=305
|
|
62
|
+
*
|
|
63
|
+
* Requirements:
|
|
64
|
+
*
|
|
65
|
+
* - `account` is a member of the allowlist.
|
|
66
|
+
*
|
|
67
|
+
* @param {Bytes<32>} account - The account that must be allowed.
|
|
68
|
+
* @return {[]} - Empty tuple.
|
|
69
|
+
*/
|
|
70
|
+
export circuit assertAllowed(account: Bytes<32>): [] {
|
|
71
|
+
assert(isAllowed(account), "Allowlist: account not allowed");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Mutators (UNGATED - the composing contract is responsible for gating these)
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @description Adds `account` to the allowlist.
|
|
80
|
+
*
|
|
81
|
+
* @circuitInfo k=9, rows=303
|
|
82
|
+
*
|
|
83
|
+
* @param {Bytes<32>} account - The account to allow.
|
|
84
|
+
* @return {[]} - Empty tuple.
|
|
85
|
+
*/
|
|
86
|
+
export circuit _allow(account: Bytes<32>): [] {
|
|
87
|
+
_allowed.insert(disclose(account));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @description Removes `account` from the allowlist.
|
|
92
|
+
*
|
|
93
|
+
* @circuitInfo k=9, rows=303
|
|
94
|
+
*
|
|
95
|
+
* @param {Bytes<32>} account - The account to disallow.
|
|
96
|
+
* @return {[]} - Empty tuple.
|
|
97
|
+
*/
|
|
98
|
+
export circuit _disallow(account: Bytes<32>): [] {
|
|
99
|
+
_allowed.remove(disclose(account));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (security/Blocklist.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module Blocklist
|
|
8
|
+
* @description A minimal, reusable deny-list keyed by an abstract `Bytes<32>`
|
|
9
|
+
* account identifier. It answers a single question: is this account blocked?
|
|
10
|
+
*
|
|
11
|
+
* This module is a pure membership MECHANISM, not a policy. It holds no notion
|
|
12
|
+
* of "when" the list is enforced and no access control. The mutators (`_block`,
|
|
13
|
+
* `_unblock`) are deliberately UNGATED composable primitives: a composing
|
|
14
|
+
* contract gates them behind its access-control module (the same way a token
|
|
15
|
+
* gates `_mint` / `_burn`), and decides when to call `assertNotBlocked`. Keeping
|
|
16
|
+
* the enforce-or-not decision in the composing layer leaves this module general
|
|
17
|
+
* enough to back sanctions lists, account freezes, abuse denials, and the like.
|
|
18
|
+
*
|
|
19
|
+
* @notice This is the PUBLIC (transparent) blocklist: membership status is
|
|
20
|
+
* publicly readable and the blocked set is publicly enumerable. That is the
|
|
21
|
+
* right posture for transparent or public-graph assets. A graph-private asset
|
|
22
|
+
* should instead use a shielded blocklist (a public blocklist would re-expose
|
|
23
|
+
* the candidate set and bound the anonymity set).
|
|
24
|
+
*/
|
|
25
|
+
module Blocklist {
|
|
26
|
+
import CompactStandardLibrary;
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// State
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/** @description Blocked accounts. Absent means not blocked. */
|
|
33
|
+
export ledger _blocked: Set<Bytes<32>>;
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Views
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @description Returns whether `account` is currently blocked.
|
|
41
|
+
*
|
|
42
|
+
* @circuitInfo k=9, rows=305
|
|
43
|
+
*
|
|
44
|
+
* @param {Bytes<32>} account - The account to query.
|
|
45
|
+
* @return {Boolean} - True if `account` is a member of the blocklist.
|
|
46
|
+
*/
|
|
47
|
+
export circuit isBlocked(account: Bytes<32>): Boolean {
|
|
48
|
+
return _blocked.member(disclose(account));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Check (the eligibility seam)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @description Asserts that `account` is not blocked. The composing contract
|
|
57
|
+
* decides when this gate applies.
|
|
58
|
+
*
|
|
59
|
+
* @circuitInfo k=9, rows=308
|
|
60
|
+
*
|
|
61
|
+
* Requirements:
|
|
62
|
+
*
|
|
63
|
+
* - `account` is not a member of the blocklist.
|
|
64
|
+
*
|
|
65
|
+
* @param {Bytes<32>} account - The account that must not be blocked.
|
|
66
|
+
* @return {[]} - Empty tuple.
|
|
67
|
+
*/
|
|
68
|
+
export circuit assertNotBlocked(account: Bytes<32>): [] {
|
|
69
|
+
assert(!isBlocked(account), "Blocklist: account blocked");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Mutators (UNGATED - the composing contract is responsible for gating these)
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @description Adds `account` to the blocklist.
|
|
78
|
+
*
|
|
79
|
+
* @circuitInfo k=9, rows=303
|
|
80
|
+
*
|
|
81
|
+
* @param {Bytes<32>} account - The account to block.
|
|
82
|
+
* @return {[]} - Empty tuple.
|
|
83
|
+
*/
|
|
84
|
+
export circuit _block(account: Bytes<32>): [] {
|
|
85
|
+
_blocked.insert(disclose(account));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @description Removes `account` from the blocklist.
|
|
90
|
+
*
|
|
91
|
+
* @circuitInfo k=9, rows=303
|
|
92
|
+
*
|
|
93
|
+
* @param {Bytes<32>} account - The account to unblock.
|
|
94
|
+
* @return {[]} - Empty tuple.
|
|
95
|
+
*/
|
|
96
|
+
export circuit _unblock(account: Bytes<32>): [] {
|
|
97
|
+
_blocked.remove(disclose(account));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (security/Initializable.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module Initializable
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (security/Pausable.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module Pausable
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// OpenZeppelin Compact Contracts v0.
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (token/FungibleToken.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module FungibleToken
|
|
@@ -111,19 +111,6 @@ module FungibleToken {
|
|
|
111
111
|
*/
|
|
112
112
|
witness wit_FungibleTokenSK(): Bytes<32>;
|
|
113
113
|
|
|
114
|
-
/**
|
|
115
|
-
* @description Returns a canonical zero Either value for Bytes<32> and ContractAddress.
|
|
116
|
-
* This circuit returns the left variant (Bytes<32>) to avoid misleading contract-to-contract
|
|
117
|
-
* error messages.
|
|
118
|
-
*
|
|
119
|
-
* @return {Either<Bytes<32>, ContractAddress>} - The zero value.
|
|
120
|
-
*/
|
|
121
|
-
export pure circuit ZERO(): Either<Bytes<32>, ContractAddress> {
|
|
122
|
-
return Either<Bytes<32>, ContractAddress> {
|
|
123
|
-
is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
114
|
/**
|
|
128
115
|
* @description Initializes the contract by setting the name, symbol, and decimals.
|
|
129
116
|
* @dev This MUST be called in the implementing contract's constructor. Failure to do so
|
|
@@ -509,8 +496,8 @@ module FungibleToken {
|
|
|
509
496
|
value: Uint<128>
|
|
510
497
|
): [] {
|
|
511
498
|
assertInitialized();
|
|
512
|
-
assert(!
|
|
513
|
-
assert(!
|
|
499
|
+
assert(!Utils_isTargetZero(fromAddress), "FungibleToken: invalid sender");
|
|
500
|
+
assert(!Utils_isTargetZero(to), "FungibleToken: invalid receiver");
|
|
514
501
|
|
|
515
502
|
_update(fromAddress, to, value);
|
|
516
503
|
}
|
|
@@ -539,7 +526,7 @@ module FungibleToken {
|
|
|
539
526
|
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
540
527
|
const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
|
|
541
528
|
|
|
542
|
-
if (
|
|
529
|
+
if (Utils_isTargetZero(disclose(canonFrom))) {
|
|
543
530
|
// Mint
|
|
544
531
|
const MAX_UINT128 = 340282366920938463463374607431768211455;
|
|
545
532
|
assert(MAX_UINT128 - _totalSupply >= value, "FungibleToken: arithmetic overflow");
|
|
@@ -551,7 +538,7 @@ module FungibleToken {
|
|
|
551
538
|
_balances.insert(disclose(canonFrom), disclose(fromBal - value as Uint<128>));
|
|
552
539
|
}
|
|
553
540
|
|
|
554
|
-
if (
|
|
541
|
+
if (Utils_isTargetZero(disclose(canonTo))) {
|
|
555
542
|
// Burn
|
|
556
543
|
_totalSupply = disclose(_totalSupply - value as Uint<128>);
|
|
557
544
|
} else {
|
|
@@ -610,8 +597,8 @@ module FungibleToken {
|
|
|
610
597
|
value: Uint<128>
|
|
611
598
|
): [] {
|
|
612
599
|
assertInitialized();
|
|
613
|
-
assert(!
|
|
614
|
-
_update(
|
|
600
|
+
assert(!Utils_isTargetZero(account), "FungibleToken: invalid receiver");
|
|
601
|
+
_update(Utils_zeroAccount(), account, value);
|
|
615
602
|
}
|
|
616
603
|
|
|
617
604
|
/**
|
|
@@ -632,8 +619,8 @@ module FungibleToken {
|
|
|
632
619
|
*/
|
|
633
620
|
export circuit _burn(account: Either<Bytes<32>, ContractAddress>, value: Uint<128>): [] {
|
|
634
621
|
assertInitialized();
|
|
635
|
-
assert(!
|
|
636
|
-
_update(account,
|
|
622
|
+
assert(!Utils_isTargetZero(account), "FungibleToken: invalid sender");
|
|
623
|
+
_update(account, Utils_zeroAccount(), value);
|
|
637
624
|
}
|
|
638
625
|
|
|
639
626
|
/**
|
|
@@ -663,8 +650,8 @@ module FungibleToken {
|
|
|
663
650
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
664
651
|
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
665
652
|
|
|
666
|
-
assert(!
|
|
667
|
-
assert(!
|
|
653
|
+
assert(!Utils_isTargetZero(canonOwner), "FungibleToken: invalid owner");
|
|
654
|
+
assert(!Utils_isTargetZero(canonSpender), "FungibleToken: invalid spender");
|
|
668
655
|
|
|
669
656
|
if (!_allowances.member(disclose(canonOwner))) {
|
|
670
657
|
// If owner doesn't exist, create and insert a new sub-map directly
|
|
@@ -701,16 +688,17 @@ module FungibleToken {
|
|
|
701
688
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
702
689
|
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
703
690
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
691
|
+
// A missing allowance entry is treated as a zero allowance, mirroring
|
|
692
|
+
// `allowance`, and a zero-value spend is a no-op. Guarding on `value`
|
|
693
|
+
// avoids reverting (outside an assertion) on a missing entry and avoids
|
|
694
|
+
// writing a spurious zero entry for a zero-value spend.
|
|
695
|
+
if (disclose(value > 0)) {
|
|
696
|
+
const currentAllowance = allowance(canonOwner, canonSpender);
|
|
697
|
+
const MAX_UINT128 = 340282366920938463463374607431768211455;
|
|
698
|
+
if (currentAllowance < MAX_UINT128) {
|
|
699
|
+
assert(currentAllowance >= value, "FungibleToken: insufficient allowance");
|
|
700
|
+
_approve(canonOwner, canonSpender, currentAllowance - value as Uint<128>);
|
|
701
|
+
}
|
|
714
702
|
}
|
|
715
703
|
}
|
|
716
704
|
|
|
@@ -725,52 +713,6 @@ module FungibleToken {
|
|
|
725
713
|
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
726
714
|
*/
|
|
727
715
|
circuit _computeAccountId(): Bytes<32> {
|
|
728
|
-
return
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
733
|
-
* their identity commitment before submitting a token operation.
|
|
734
|
-
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
735
|
-
* given the same inputs.
|
|
736
|
-
*
|
|
737
|
-
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
738
|
-
* permanently compromise the security of this system:
|
|
739
|
-
*
|
|
740
|
-
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
741
|
-
* application logs, analytics pipelines, or any observable side-channel.
|
|
742
|
-
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
743
|
-
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
744
|
-
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
745
|
-
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
746
|
-
* - **Treat key loss as identity loss** — a lost key cannot be recovered.
|
|
747
|
-
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
748
|
-
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
749
|
-
* the key to a malicious observer.
|
|
750
|
-
*
|
|
751
|
-
* ## ID Derivation
|
|
752
|
-
* `accountId = persistentHash(secretKey)`
|
|
753
|
-
*
|
|
754
|
-
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
755
|
-
*
|
|
756
|
-
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
757
|
-
*/
|
|
758
|
-
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
759
|
-
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
* @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
|
|
764
|
-
* holds the zero value.
|
|
765
|
-
*
|
|
766
|
-
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
|
|
767
|
-
* @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
|
|
768
|
-
*/
|
|
769
|
-
circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
|
|
770
|
-
if (target.is_left) {
|
|
771
|
-
return target.left == default<Bytes<32>>;
|
|
772
|
-
} else {
|
|
773
|
-
return target.right == default<ContractAddress>;
|
|
774
|
-
}
|
|
716
|
+
return Utils_computeAccountId(wit_FungibleTokenSK());
|
|
775
717
|
}
|
|
776
718
|
}
|
package/token/MultiToken.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 (token/MultiToken.compact)
|
|
3
3
|
|
|
4
|
-
pragma language_version >= 0.
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @module MultiToken
|
|
@@ -173,25 +173,6 @@ module MultiToken {
|
|
|
173
173
|
*/
|
|
174
174
|
witness wit_MultiTokenSK(): Bytes<32>;
|
|
175
175
|
|
|
176
|
-
/**
|
|
177
|
-
* @description Returns a canonical zero Either value (left variant with zero Bytes<32>).
|
|
178
|
-
* Used as the zero value for mint/burn operations in `_update`, where a zero
|
|
179
|
-
* `fromAddress` signals a mint and a zero `to` signals a burn.
|
|
180
|
-
*
|
|
181
|
-
* The left variant is chosen so that `_isTargetZero` checks against `default<Bytes<32>>`,
|
|
182
|
-
* which is consistent with how `_update` dispatches on the zero check. A right variant
|
|
183
|
-
* with `default<ContractAddress>` would also pass `_isTargetZero`, but using the left
|
|
184
|
-
* variant avoids triggering contract address guards in circuits like `_mint` that check
|
|
185
|
-
* `!to.is_left` before delegating to `_unsafeMint`.
|
|
186
|
-
*
|
|
187
|
-
* @return {Either<Bytes<32>, ContractAddress>} - The zero value.
|
|
188
|
-
*/
|
|
189
|
-
export pure circuit ZERO(): Either<Bytes<32>, ContractAddress> {
|
|
190
|
-
return Either<Bytes<32>, ContractAddress> {
|
|
191
|
-
is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
176
|
/**
|
|
196
177
|
* @description Initializes the contract by setting the base URI for all tokens.
|
|
197
178
|
*
|
|
@@ -441,14 +422,20 @@ module MultiToken {
|
|
|
441
422
|
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
442
423
|
const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
|
|
443
424
|
|
|
444
|
-
if (!
|
|
425
|
+
if (!Utils_isTargetZero(disclose(canonFrom))) {
|
|
445
426
|
const fromBalance = balanceOf(canonFrom, id);
|
|
446
427
|
assert(fromBalance >= value, "MultiToken: insufficient balance");
|
|
447
|
-
|
|
448
|
-
|
|
428
|
+
// Skip the write when `value` is zero. A zero-value update leaves the
|
|
429
|
+
// balance unchanged, so there is nothing to write, and guarding here
|
|
430
|
+
// avoids `_balances.lookup(id)` reverting outside an assertion when `id`
|
|
431
|
+
// has never been initialized.
|
|
432
|
+
if (disclose(value > 0)) {
|
|
433
|
+
const newBalance = fromBalance - value;
|
|
434
|
+
_balances.lookup(id).insert(disclose(canonFrom), disclose(newBalance));
|
|
435
|
+
}
|
|
449
436
|
}
|
|
450
437
|
|
|
451
|
-
if (!
|
|
438
|
+
if (!Utils_isTargetZero(disclose(canonTo)) && disclose(value > 0)) {
|
|
452
439
|
if (!_balances.member(disclose(id))) {
|
|
453
440
|
_balances.insert(
|
|
454
441
|
disclose(id),
|
|
@@ -540,8 +527,8 @@ module MultiToken {
|
|
|
540
527
|
): [] {
|
|
541
528
|
assertInitialized();
|
|
542
529
|
|
|
543
|
-
assert(!
|
|
544
|
-
assert(!
|
|
530
|
+
assert(!Utils_isTargetZero(fromAddress), "MultiToken: invalid sender");
|
|
531
|
+
assert(!Utils_isTargetZero(to), "MultiToken: invalid receiver");
|
|
545
532
|
_update(fromAddress, to, id, value);
|
|
546
533
|
}
|
|
547
534
|
|
|
@@ -627,8 +614,8 @@ module MultiToken {
|
|
|
627
614
|
value: Uint<128>
|
|
628
615
|
): [] {
|
|
629
616
|
assertInitialized();
|
|
630
|
-
assert(!
|
|
631
|
-
_update(
|
|
617
|
+
assert(!Utils_isTargetZero(to), "MultiToken: invalid receiver");
|
|
618
|
+
_update(Utils_zeroAccount(), to, id, value);
|
|
632
619
|
}
|
|
633
620
|
|
|
634
621
|
/**
|
|
@@ -652,8 +639,8 @@ module MultiToken {
|
|
|
652
639
|
value: Uint<128>
|
|
653
640
|
): [] {
|
|
654
641
|
assertInitialized();
|
|
655
|
-
assert(!
|
|
656
|
-
_update(fromAddress,
|
|
642
|
+
assert(!Utils_isTargetZero(fromAddress), "MultiToken: invalid sender");
|
|
643
|
+
_update(fromAddress, Utils_zeroAccount(), id, value);
|
|
657
644
|
}
|
|
658
645
|
|
|
659
646
|
/**
|
|
@@ -684,10 +671,10 @@ module MultiToken {
|
|
|
684
671
|
): [] {
|
|
685
672
|
assertInitialized();
|
|
686
673
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
687
|
-
assert(!
|
|
674
|
+
assert(!Utils_isTargetZero(canonOwner), "MultiToken: invalid owner");
|
|
688
675
|
|
|
689
676
|
const canonOp = Utils_canonicalize<Bytes<32>, ContractAddress>(operator);
|
|
690
|
-
assert(!
|
|
677
|
+
assert(!Utils_isTargetZero(canonOp), "MultiToken: invalid operator");
|
|
691
678
|
|
|
692
679
|
if (!_operatorApprovals.member(disclose(canonOwner))) {
|
|
693
680
|
_operatorApprovals.insert(
|
|
@@ -710,52 +697,6 @@ module MultiToken {
|
|
|
710
697
|
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
711
698
|
*/
|
|
712
699
|
circuit _computeAccountId(): Bytes<32> {
|
|
713
|
-
return
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
718
|
-
* their identity commitment before submitting it in a token operation.
|
|
719
|
-
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
720
|
-
* given the same inputs.
|
|
721
|
-
*
|
|
722
|
-
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
723
|
-
* permanently compromise the security of this system:
|
|
724
|
-
*
|
|
725
|
-
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
726
|
-
* application logs, analytics pipelines, or any observable side-channel.
|
|
727
|
-
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
728
|
-
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
729
|
-
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
730
|
-
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
731
|
-
* - **Treat key loss as identity loss** — a lost key cannot be recovered.
|
|
732
|
-
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
733
|
-
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
734
|
-
* the key to a malicious observer.
|
|
735
|
-
*
|
|
736
|
-
* ## ID Derivation
|
|
737
|
-
* `accountId = persistentHash(secretKey)`
|
|
738
|
-
*
|
|
739
|
-
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
740
|
-
*
|
|
741
|
-
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
742
|
-
*/
|
|
743
|
-
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
744
|
-
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
|
|
749
|
-
* holds the zero value.
|
|
750
|
-
*
|
|
751
|
-
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
|
|
752
|
-
* @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
|
|
753
|
-
*/
|
|
754
|
-
circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
|
|
755
|
-
if (target.is_left) {
|
|
756
|
-
return target.left == default<Bytes<32>>;
|
|
757
|
-
} else {
|
|
758
|
-
return target.right == default<ContractAddress>;
|
|
759
|
-
}
|
|
700
|
+
return Utils_computeAccountId(wit_MultiTokenSK());
|
|
760
701
|
}
|
|
761
702
|
}
|