@openzeppelin/compact-contracts 0.1.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 +15 -16
- package/access/AccessControl.compact +3 -35
- package/access/Ownable.compact +47 -60
- package/access/ShieldedAccessControl.compact +43 -10
- package/access/ZOwnablePK.compact +45 -10
- 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 +12 -6
- package/security/Allowlist.compact +101 -0
- package/security/Blocklist.compact +99 -0
- package/security/Initializable.compact +18 -2
- package/security/Pausable.compact +2 -2
- package/token/FungibleToken.compact +79 -103
- package/token/MultiToken.compact +67 -93
- package/token/NativeShieldedToken.compact +285 -0
- package/token/NativeShieldedTokenCore.compact +343 -0
- package/token/NativeShieldedTokenFamily.compact +287 -0
- package/token/NonFungibleToken.compact +85 -110
- 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,11 +1,27 @@
|
|
|
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
|
|
8
8
|
* @description Initializable provides a simple mechanism that mimics the functionality of a constructor.
|
|
9
|
+
*
|
|
10
|
+
* @notice ⚠️ TECHNICAL WARNING: only use this module at the top contract layer, and
|
|
11
|
+
* import it into at most one module per contract.
|
|
12
|
+
*
|
|
13
|
+
* The Compact compiler deduplicates a shared transitive dependency's ledger state
|
|
14
|
+
* inconsistently depending on directory layout
|
|
15
|
+
* (https://github.com/LFDT-Minokawa/compact/issues/270, reproduced on compiler
|
|
16
|
+
* 0.29.0–0.31.0). When two modules in the SAME directory both import this module,
|
|
17
|
+
* their `_isInitialized` flags collapse into a SINGLE shared ledger slot: calling
|
|
18
|
+
* `initialize()` on one marks the other as initialized, and the other can never be
|
|
19
|
+
* initialized independently. Modules in different directories get separate slots.
|
|
20
|
+
* The result is unpredictable and a security hazard for composed contracts.
|
|
21
|
+
*
|
|
22
|
+
* For this reason the OpenZeppelin modules no longer use this module: each tracks
|
|
23
|
+
* its own `_isInitialized` ledger flag inline. This module is kept for direct,
|
|
24
|
+
* single-import use and can be reintroduced across modules once #270 is resolved.
|
|
9
25
|
*/
|
|
10
26
|
module Initializable {
|
|
11
27
|
import CompactStandardLibrary;
|
|
@@ -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
|
|
@@ -64,9 +64,15 @@ pragma language_version >= 0.21.0;
|
|
|
64
64
|
*/
|
|
65
65
|
module FungibleToken {
|
|
66
66
|
import CompactStandardLibrary;
|
|
67
|
-
import "../security/Initializable" prefix Initializable_;
|
|
68
67
|
import "../utils/Utils" prefix Utils_;
|
|
69
68
|
|
|
69
|
+
/**
|
|
70
|
+
* @description Initialization flag, tracked per-module to avoid the compiler's
|
|
71
|
+
* shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
|
|
72
|
+
* Initializable module for rationale.
|
|
73
|
+
*/
|
|
74
|
+
export ledger _isInitialized: Boolean;
|
|
75
|
+
|
|
70
76
|
/**
|
|
71
77
|
* @description Mapping from account addresses to their token balances.
|
|
72
78
|
* @type {Either<Bytes<32>, ContractAddress>} account - The account address.
|
|
@@ -75,6 +81,7 @@ module FungibleToken {
|
|
|
75
81
|
* @type {Map<Either<Bytes<32>, ContractAddress>, Uint<128>>} _balances
|
|
76
82
|
*/
|
|
77
83
|
export ledger _balances: Map<Either<Bytes<32>, ContractAddress>, Uint<128>>;
|
|
84
|
+
|
|
78
85
|
/**
|
|
79
86
|
* @description Mapping from owner accounts to spender accounts and their allowances.
|
|
80
87
|
* @type {Either<Bytes<32>, ContractAddress>} account - The owner account address.
|
|
@@ -104,19 +111,6 @@ module FungibleToken {
|
|
|
104
111
|
*/
|
|
105
112
|
witness wit_FungibleTokenSK(): Bytes<32>;
|
|
106
113
|
|
|
107
|
-
/**
|
|
108
|
-
* @description Returns a canonical zero Either value for Bytes<32> and ContractAddress.
|
|
109
|
-
* This circuit returns the left variant (Bytes<32>) to avoid misleading contract-to-contract
|
|
110
|
-
* error messages.
|
|
111
|
-
*
|
|
112
|
-
* @return {Either<Bytes<32>, ContractAddress>} - The zero value.
|
|
113
|
-
*/
|
|
114
|
-
export pure circuit ZERO(): Either<Bytes<32>, ContractAddress> {
|
|
115
|
-
return Either<Bytes<32>, ContractAddress> {
|
|
116
|
-
is_left: true, left: default<Bytes<32>>, right: default<ContractAddress>
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
114
|
/**
|
|
121
115
|
* @description Initializes the contract by setting the name, symbol, and decimals.
|
|
122
116
|
* @dev This MUST be called in the implementing contract's constructor. Failure to do so
|
|
@@ -134,12 +128,39 @@ module FungibleToken {
|
|
|
134
128
|
symbol_: Opaque<"string">,
|
|
135
129
|
decimals_: Uint<8>
|
|
136
130
|
): [] {
|
|
137
|
-
|
|
131
|
+
assertNotInitialized();
|
|
132
|
+
_isInitialized = true;
|
|
138
133
|
_name = disclose(name_);
|
|
139
134
|
_symbol = disclose(symbol_);
|
|
140
135
|
_decimals = disclose(decimals_);
|
|
141
136
|
}
|
|
142
137
|
|
|
138
|
+
/**
|
|
139
|
+
* @description Asserts that the contract has been initialized, throwing an error if not.
|
|
140
|
+
*
|
|
141
|
+
* Requirements:
|
|
142
|
+
*
|
|
143
|
+
* - Contract must be initialized.
|
|
144
|
+
*
|
|
145
|
+
* @return {[]} - Empty tuple.
|
|
146
|
+
*/
|
|
147
|
+
circuit assertInitialized(): [] {
|
|
148
|
+
assert(_isInitialized, "FungibleToken: contract not initialized");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @description Asserts that the contract has not been initialized, throwing an error if it has.
|
|
153
|
+
*
|
|
154
|
+
* Requirements:
|
|
155
|
+
*
|
|
156
|
+
* - Contract must not be initialized.
|
|
157
|
+
*
|
|
158
|
+
* @return {[]} - Empty tuple.
|
|
159
|
+
*/
|
|
160
|
+
circuit assertNotInitialized(): [] {
|
|
161
|
+
assert(!_isInitialized, "FungibleToken: contract already initialized");
|
|
162
|
+
}
|
|
163
|
+
|
|
143
164
|
/**
|
|
144
165
|
* @description Returns the token name.
|
|
145
166
|
*
|
|
@@ -152,7 +173,7 @@ module FungibleToken {
|
|
|
152
173
|
* @return {Opaque<"string">} - The token name.
|
|
153
174
|
*/
|
|
154
175
|
export circuit name(): Opaque<"string"> {
|
|
155
|
-
|
|
176
|
+
assertInitialized();
|
|
156
177
|
return _name;
|
|
157
178
|
}
|
|
158
179
|
|
|
@@ -168,7 +189,7 @@ module FungibleToken {
|
|
|
168
189
|
* @return {Opaque<"string">} - The token name.
|
|
169
190
|
*/
|
|
170
191
|
export circuit symbol(): Opaque<"string"> {
|
|
171
|
-
|
|
192
|
+
assertInitialized();
|
|
172
193
|
return _symbol;
|
|
173
194
|
}
|
|
174
195
|
|
|
@@ -184,7 +205,7 @@ module FungibleToken {
|
|
|
184
205
|
* @return {Uint<8>} - The account's token balance.
|
|
185
206
|
*/
|
|
186
207
|
export circuit decimals(): Uint<8> {
|
|
187
|
-
|
|
208
|
+
assertInitialized();
|
|
188
209
|
return _decimals;
|
|
189
210
|
}
|
|
190
211
|
|
|
@@ -200,7 +221,7 @@ module FungibleToken {
|
|
|
200
221
|
* @return {Uint<128>} - The total supply of tokens.
|
|
201
222
|
*/
|
|
202
223
|
export circuit totalSupply(): Uint<128> {
|
|
203
|
-
|
|
224
|
+
assertInitialized();
|
|
204
225
|
return _totalSupply;
|
|
205
226
|
}
|
|
206
227
|
|
|
@@ -219,7 +240,7 @@ module FungibleToken {
|
|
|
219
240
|
* @return {Uint<128>} - The account's token balance.
|
|
220
241
|
*/
|
|
221
242
|
export circuit balanceOf(account: Either<Bytes<32>, ContractAddress>): Uint<128> {
|
|
222
|
-
|
|
243
|
+
assertInitialized();
|
|
223
244
|
const canonAcct = Utils_canonicalize<Bytes<32>, ContractAddress>(account);
|
|
224
245
|
|
|
225
246
|
if (!_balances.member(disclose(canonAcct))) {
|
|
@@ -253,7 +274,7 @@ module FungibleToken {
|
|
|
253
274
|
to: Either<Bytes<32>, ContractAddress>,
|
|
254
275
|
value: Uint<128>
|
|
255
276
|
): Boolean {
|
|
256
|
-
|
|
277
|
+
assertInitialized();
|
|
257
278
|
const isContractAddr = !to.is_left;
|
|
258
279
|
assert(!isContractAddr, "FungibleToken: unsafe transfer");
|
|
259
280
|
|
|
@@ -283,7 +304,7 @@ module FungibleToken {
|
|
|
283
304
|
to: Either<Bytes<32>, ContractAddress>,
|
|
284
305
|
value: Uint<128>
|
|
285
306
|
): Boolean {
|
|
286
|
-
|
|
307
|
+
assertInitialized();
|
|
287
308
|
const owner = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
288
309
|
_unsafeUncheckedTransfer(owner, to, value);
|
|
289
310
|
return true;
|
|
@@ -309,7 +330,7 @@ module FungibleToken {
|
|
|
309
330
|
owner: Either<Bytes<32>, ContractAddress>,
|
|
310
331
|
spender: Either<Bytes<32>, ContractAddress>
|
|
311
332
|
): Uint<128> {
|
|
312
|
-
|
|
333
|
+
assertInitialized();
|
|
313
334
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
314
335
|
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
315
336
|
|
|
@@ -337,7 +358,7 @@ module FungibleToken {
|
|
|
337
358
|
export circuit approve(spender: Either<Bytes<32>, ContractAddress>,
|
|
338
359
|
value: Uint<128>
|
|
339
360
|
): Boolean {
|
|
340
|
-
|
|
361
|
+
assertInitialized();
|
|
341
362
|
|
|
342
363
|
const owner = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
343
364
|
_approve(owner, spender, value);
|
|
@@ -373,7 +394,7 @@ module FungibleToken {
|
|
|
373
394
|
to: Either<Bytes<32>, ContractAddress>,
|
|
374
395
|
value: Uint<128>
|
|
375
396
|
): Boolean {
|
|
376
|
-
|
|
397
|
+
assertInitialized();
|
|
377
398
|
const isContractAddr = !to.is_left;
|
|
378
399
|
assert(!isContractAddr, "FungibleToken: unsafe transfer");
|
|
379
400
|
return _unsafeTransferFrom(fromAddress, to, value);
|
|
@@ -406,7 +427,7 @@ module FungibleToken {
|
|
|
406
427
|
to: Either<Bytes<32>, ContractAddress>,
|
|
407
428
|
value: Uint<128>
|
|
408
429
|
): Boolean {
|
|
409
|
-
|
|
430
|
+
assertInitialized();
|
|
410
431
|
|
|
411
432
|
const spender = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
412
433
|
_spendAllowance(fromAddress, spender, value);
|
|
@@ -443,7 +464,7 @@ module FungibleToken {
|
|
|
443
464
|
to: Either<Bytes<32>, ContractAddress>,
|
|
444
465
|
value: Uint<128>
|
|
445
466
|
): [] {
|
|
446
|
-
|
|
467
|
+
assertInitialized();
|
|
447
468
|
const isContractAddr = !to.is_left;
|
|
448
469
|
assert(!isContractAddr, "FungibleToken: unsafe transfer");
|
|
449
470
|
_unsafeUncheckedTransfer(fromAddress, to, value);
|
|
@@ -474,9 +495,9 @@ module FungibleToken {
|
|
|
474
495
|
to: Either<Bytes<32>, ContractAddress>,
|
|
475
496
|
value: Uint<128>
|
|
476
497
|
): [] {
|
|
477
|
-
|
|
478
|
-
assert(!
|
|
479
|
-
assert(!
|
|
498
|
+
assertInitialized();
|
|
499
|
+
assert(!Utils_isTargetZero(fromAddress), "FungibleToken: invalid sender");
|
|
500
|
+
assert(!Utils_isTargetZero(to), "FungibleToken: invalid receiver");
|
|
480
501
|
|
|
481
502
|
_update(fromAddress, to, value);
|
|
482
503
|
}
|
|
@@ -501,11 +522,11 @@ module FungibleToken {
|
|
|
501
522
|
to: Either<Bytes<32>, ContractAddress>,
|
|
502
523
|
value: Uint<128>
|
|
503
524
|
): [] {
|
|
504
|
-
|
|
525
|
+
assertInitialized();
|
|
505
526
|
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
506
527
|
const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
|
|
507
528
|
|
|
508
|
-
if (
|
|
529
|
+
if (Utils_isTargetZero(disclose(canonFrom))) {
|
|
509
530
|
// Mint
|
|
510
531
|
const MAX_UINT128 = 340282366920938463463374607431768211455;
|
|
511
532
|
assert(MAX_UINT128 - _totalSupply >= value, "FungibleToken: arithmetic overflow");
|
|
@@ -517,7 +538,7 @@ module FungibleToken {
|
|
|
517
538
|
_balances.insert(disclose(canonFrom), disclose(fromBal - value as Uint<128>));
|
|
518
539
|
}
|
|
519
540
|
|
|
520
|
-
if (
|
|
541
|
+
if (Utils_isTargetZero(disclose(canonTo))) {
|
|
521
542
|
// Burn
|
|
522
543
|
_totalSupply = disclose(_totalSupply - value as Uint<128>);
|
|
523
544
|
} else {
|
|
@@ -547,7 +568,7 @@ module FungibleToken {
|
|
|
547
568
|
* @return {[]} - Empty tuple.
|
|
548
569
|
*/
|
|
549
570
|
export circuit _mint(account: Either<Bytes<32>, ContractAddress>, value: Uint<128>): [] {
|
|
550
|
-
|
|
571
|
+
assertInitialized();
|
|
551
572
|
const isContractAddr = !account.is_left;
|
|
552
573
|
assert(!isContractAddr, "FungibleToken: unsafe transfer");
|
|
553
574
|
_unsafeMint(account, value);
|
|
@@ -575,9 +596,9 @@ module FungibleToken {
|
|
|
575
596
|
account: Either<Bytes<32>, ContractAddress>,
|
|
576
597
|
value: Uint<128>
|
|
577
598
|
): [] {
|
|
578
|
-
|
|
579
|
-
assert(!
|
|
580
|
-
_update(
|
|
599
|
+
assertInitialized();
|
|
600
|
+
assert(!Utils_isTargetZero(account), "FungibleToken: invalid receiver");
|
|
601
|
+
_update(Utils_zeroAccount(), account, value);
|
|
581
602
|
}
|
|
582
603
|
|
|
583
604
|
/**
|
|
@@ -597,9 +618,9 @@ module FungibleToken {
|
|
|
597
618
|
* @return {[]} - Empty tuple.
|
|
598
619
|
*/
|
|
599
620
|
export circuit _burn(account: Either<Bytes<32>, ContractAddress>, value: Uint<128>): [] {
|
|
600
|
-
|
|
601
|
-
assert(!
|
|
602
|
-
_update(account,
|
|
621
|
+
assertInitialized();
|
|
622
|
+
assert(!Utils_isTargetZero(account), "FungibleToken: invalid sender");
|
|
623
|
+
_update(account, Utils_zeroAccount(), value);
|
|
603
624
|
}
|
|
604
625
|
|
|
605
626
|
/**
|
|
@@ -625,12 +646,12 @@ module FungibleToken {
|
|
|
625
646
|
spender: Either<Bytes<32>, ContractAddress>,
|
|
626
647
|
value: Uint<128>
|
|
627
648
|
): [] {
|
|
628
|
-
|
|
649
|
+
assertInitialized();
|
|
629
650
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
630
651
|
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
631
652
|
|
|
632
|
-
assert(!
|
|
633
|
-
assert(!
|
|
653
|
+
assert(!Utils_isTargetZero(canonOwner), "FungibleToken: invalid owner");
|
|
654
|
+
assert(!Utils_isTargetZero(canonSpender), "FungibleToken: invalid spender");
|
|
634
655
|
|
|
635
656
|
if (!_allowances.member(disclose(canonOwner))) {
|
|
636
657
|
// If owner doesn't exist, create and insert a new sub-map directly
|
|
@@ -663,20 +684,21 @@ module FungibleToken {
|
|
|
663
684
|
spender: Either<Bytes<32>, ContractAddress>,
|
|
664
685
|
value: Uint<128>
|
|
665
686
|
): [] {
|
|
666
|
-
|
|
687
|
+
assertInitialized();
|
|
667
688
|
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
668
689
|
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
669
690
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
+
}
|
|
680
702
|
}
|
|
681
703
|
}
|
|
682
704
|
|
|
@@ -691,52 +713,6 @@ module FungibleToken {
|
|
|
691
713
|
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
692
714
|
*/
|
|
693
715
|
circuit _computeAccountId(): Bytes<32> {
|
|
694
|
-
return
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* @description Computes an account identifier without on-chain state, allowing a user to derive
|
|
699
|
-
* their identity commitment before submitting a token operation.
|
|
700
|
-
* This is the off-chain counterpart to {_computeAccountId} and produces an identical result
|
|
701
|
-
* given the same inputs.
|
|
702
|
-
*
|
|
703
|
-
* @warning OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
|
|
704
|
-
* permanently compromise the security of this system:
|
|
705
|
-
*
|
|
706
|
-
* - **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
|
|
707
|
-
* application logs, analytics pipelines, or any observable side-channel.
|
|
708
|
-
* - **Store offline or in secure enclaves** — hardware security modules (HSMs),
|
|
709
|
-
* air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
|
|
710
|
-
* - **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
|
|
711
|
-
* or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
|
|
712
|
-
* - **Treat key loss as identity loss** — a lost key cannot be recovered.
|
|
713
|
-
* - **Avoid calling this circuit in untrusted environments** — executing this in an
|
|
714
|
-
* unverified browser extension, compromised runtime, or shared machine may expose
|
|
715
|
-
* the key to a malicious observer.
|
|
716
|
-
*
|
|
717
|
-
* ## ID Derivation
|
|
718
|
-
* `accountId = persistentHash(secretKey)`
|
|
719
|
-
*
|
|
720
|
-
* @param {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
721
|
-
*
|
|
722
|
-
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
723
|
-
*/
|
|
724
|
-
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
|
|
725
|
-
return persistentHash<Vector<1, Bytes<32>>>([secretKey]);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* @description Returns `true` if `target`'s active branch (as indicated by `is_left`)
|
|
730
|
-
* holds the zero value.
|
|
731
|
-
*
|
|
732
|
-
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
|
|
733
|
-
* @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
|
|
734
|
-
*/
|
|
735
|
-
circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
|
|
736
|
-
if (target.is_left) {
|
|
737
|
-
return target.left == default<Bytes<32>>;
|
|
738
|
-
} else {
|
|
739
|
-
return target.right == default<ContractAddress>;
|
|
740
|
-
}
|
|
716
|
+
return Utils_computeAccountId(wit_FungibleTokenSK());
|
|
741
717
|
}
|
|
742
718
|
}
|