@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,343 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (token/NativeShieldedTokenCore.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NativeShieldedTokenCore
|
|
8
|
+
* @description Shared, domain-parameterized core of the native shielded
|
|
9
|
+
* (Zswap) token standard. It owns the initialization flag, the contract-wide
|
|
10
|
+
* metadata, and the mint/burn building blocks; every token operation takes the
|
|
11
|
+
* `domain` separator as a parameter.
|
|
12
|
+
*
|
|
13
|
+
* This module is NOT meant to be composed directly. It is the de-duplicated
|
|
14
|
+
* base behind the two user-facing flavors:
|
|
15
|
+
*
|
|
16
|
+
* - `NativeShieldedToken` — single token type; stores one `sealed _domain` and
|
|
17
|
+
* forwards it to every core op.
|
|
18
|
+
* - `NativeShieldedTokenFamily` — many token types; forwards the caller's
|
|
19
|
+
* per-call `domain` to every core op.
|
|
20
|
+
*
|
|
21
|
+
* @dev The flavors are thin wrappers; all of the bug-prone coin logic lives
|
|
22
|
+
* here so a fix or change happens once. Assertion messages use the generic
|
|
23
|
+
* `NativeShieldedToken:` prefix so they read sensibly from either flavor. The
|
|
24
|
+
* coin's `color` is `tokenType(domain, kernel.self())`, computed at call time
|
|
25
|
+
* (never in the constructor), so only this contract can mint coins of its
|
|
26
|
+
* colors. Metadata follows the FungibleToken (ERC-20) convention: contract-wide
|
|
27
|
+
* `name`, `symbol`, and `decimals`, fixed at construction. Mint amounts are
|
|
28
|
+
* `Uint<64>` and burn amounts `Uint<128>`; that asymmetry is imposed by the
|
|
29
|
+
* protocol primitives (`mintShieldedToken` caps at 64, `sendShielded` accepts
|
|
30
|
+
* 128), not a choice the module makes.
|
|
31
|
+
*
|
|
32
|
+
* @notice Constructor usage: an implementing contract's constructor MUST call
|
|
33
|
+
* only `initialize` (via the wrapping flavor). Do NOT perform any other token
|
|
34
|
+
* operation (`_mint`, `_burn`, `_burnFromSelf`, `tokenColor`) during
|
|
35
|
+
* construction. `assertInitialized` gates on the init flag but does not stop a
|
|
36
|
+
* constructor from calling `initialize` and then a token op in the same scope;
|
|
37
|
+
* those operations belong in post-deployment circuits, not construction.
|
|
38
|
+
*
|
|
39
|
+
* @notice Minting and recipient privacy: `_mint` takes a caller-supplied nonce,
|
|
40
|
+
* mirroring `mintShieldedToken` one-to-one, and the caller is responsible for
|
|
41
|
+
* nonce uniqueness (reusing a nonce for the same (value, recipient) produces a
|
|
42
|
+
* duplicate commitment, which the ledger rejects). With a secret, random nonce
|
|
43
|
+
* the mint is recipient-private; for mints that need no caller-managed nonce,
|
|
44
|
+
* compose `NativeShieldedTokenDerivedNonce` and pass its `_deriveNonce()`
|
|
45
|
+
* output, at the cost of making those mints recipient-public.
|
|
46
|
+
*
|
|
47
|
+
* @dev Burn variants
|
|
48
|
+
* `_burn` destroys value from a coin provided within the current transaction
|
|
49
|
+
* (e.g. the caller's wallet pays the contract): it receives the coin and spends
|
|
50
|
+
* via `sendImmediateShielded`, the required path for coins created in the same
|
|
51
|
+
* transaction. `_burnFromSelf` destroys value from a coin the contract already
|
|
52
|
+
* holds (a Merkle-tree entry with a valid `mt_index`): it spends via
|
|
53
|
+
* `sendShielded`, and any change is auto-received by the contract and returned
|
|
54
|
+
* for the consumer to persist.
|
|
55
|
+
*
|
|
56
|
+
* @notice Contract-initiated sends do not create coin ciphertexts, so recipient
|
|
57
|
+
* wallets cannot detect coins minted or refunded to them by scanning the chain.
|
|
58
|
+
* The returned `ShieldedCoinInfo` values are the only copies of those coins'
|
|
59
|
+
* info: DApps SHOULD capture and deliver them out of band; dropping them strands
|
|
60
|
+
* value irrecoverably.
|
|
61
|
+
*/
|
|
62
|
+
module NativeShieldedTokenCore {
|
|
63
|
+
import CompactStandardLibrary;
|
|
64
|
+
import "../utils/Utils" prefix Utils_;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @description Initialization flag, tracked per-module to avoid the compiler's
|
|
68
|
+
* shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
|
|
69
|
+
* Initializable module for rationale.
|
|
70
|
+
*/
|
|
71
|
+
export ledger _isInitialized: Boolean;
|
|
72
|
+
|
|
73
|
+
export sealed ledger _name: Opaque<"string">;
|
|
74
|
+
export sealed ledger _symbol: Opaque<"string">;
|
|
75
|
+
export sealed ledger _decimals: Uint<8>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @description Initializes the contract-wide metadata.
|
|
79
|
+
* @dev This MUST be called in the implementing contract's constructor (via
|
|
80
|
+
* the wrapping flavor's `initialize`). Failure to do so can lead to an
|
|
81
|
+
* irreparable contract.
|
|
82
|
+
*
|
|
83
|
+
* @param {Opaque<"string">} name_ - The name of the token.
|
|
84
|
+
* @param {Opaque<"string">} symbol_ - The symbol of the token.
|
|
85
|
+
* @param {Uint<8>} decimals_ - The number of decimals used to get the user representation.
|
|
86
|
+
* @return {[]} - Empty tuple.
|
|
87
|
+
*/
|
|
88
|
+
export circuit initialize(
|
|
89
|
+
name_: Opaque<"string">,
|
|
90
|
+
symbol_: Opaque<"string">,
|
|
91
|
+
decimals_: Uint<8>
|
|
92
|
+
): [] {
|
|
93
|
+
assertNotInitialized();
|
|
94
|
+
_isInitialized = true;
|
|
95
|
+
_name = disclose(name_);
|
|
96
|
+
_symbol = disclose(symbol_);
|
|
97
|
+
_decimals = disclose(decimals_);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @description Asserts that the contract has been initialized, throwing an
|
|
102
|
+
* error if not.
|
|
103
|
+
*
|
|
104
|
+
* Requirements:
|
|
105
|
+
*
|
|
106
|
+
* - Contract must be initialized.
|
|
107
|
+
*
|
|
108
|
+
* @return {[]} - Empty tuple.
|
|
109
|
+
*/
|
|
110
|
+
export circuit assertInitialized(): [] {
|
|
111
|
+
assert(_isInitialized, "NativeShieldedToken: contract not initialized");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @description Asserts that the contract has not been initialized, throwing
|
|
116
|
+
* an error if it has.
|
|
117
|
+
*
|
|
118
|
+
* Requirements:
|
|
119
|
+
*
|
|
120
|
+
* - Contract must not be initialized.
|
|
121
|
+
*
|
|
122
|
+
* @return {[]} - Empty tuple.
|
|
123
|
+
*/
|
|
124
|
+
export circuit assertNotInitialized(): [] {
|
|
125
|
+
assert(!_isInitialized, "NativeShieldedToken: contract already initialized");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @description Returns whether the contract has been initialized.
|
|
130
|
+
* @dev Does not gate on init (it reports it), so it is safe to call before
|
|
131
|
+
* `initialize`.
|
|
132
|
+
*
|
|
133
|
+
* @circuitInfo k=6, rows=26
|
|
134
|
+
*
|
|
135
|
+
* @return {Boolean} - True once `initialize` has run.
|
|
136
|
+
*/
|
|
137
|
+
export circuit isInitialized(): Boolean {
|
|
138
|
+
return _isInitialized;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @description Returns the token name.
|
|
143
|
+
*
|
|
144
|
+
* @circuitInfo k=6, rows=28
|
|
145
|
+
*
|
|
146
|
+
* Requirements:
|
|
147
|
+
*
|
|
148
|
+
* - Contract is initialized.
|
|
149
|
+
*
|
|
150
|
+
* @return {Opaque<"string">} - The token name.
|
|
151
|
+
*/
|
|
152
|
+
export circuit name(): Opaque<"string"> {
|
|
153
|
+
assertInitialized();
|
|
154
|
+
return _name;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @description Returns the symbol of the token.
|
|
159
|
+
*
|
|
160
|
+
* @circuitInfo k=6, rows=28
|
|
161
|
+
*
|
|
162
|
+
* Requirements:
|
|
163
|
+
*
|
|
164
|
+
* - Contract is initialized.
|
|
165
|
+
*
|
|
166
|
+
* @return {Opaque<"string">} - The token symbol.
|
|
167
|
+
*/
|
|
168
|
+
export circuit symbol(): Opaque<"string"> {
|
|
169
|
+
assertInitialized();
|
|
170
|
+
return _symbol;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @description Returns the number of decimals used to get its user representation.
|
|
175
|
+
* @dev This is a display convention only. The protocol stores and operates
|
|
176
|
+
* on integer values without reference to decimals.
|
|
177
|
+
*
|
|
178
|
+
* @circuitInfo k=6, rows=28
|
|
179
|
+
*
|
|
180
|
+
* Requirements:
|
|
181
|
+
*
|
|
182
|
+
* - Contract is initialized.
|
|
183
|
+
*
|
|
184
|
+
* @return {Uint<8>} - The decimals value.
|
|
185
|
+
*/
|
|
186
|
+
export circuit decimals(): Uint<8> {
|
|
187
|
+
assertInitialized();
|
|
188
|
+
return _decimals;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @description Returns the coin color for `domain` under this contract's
|
|
193
|
+
* address: `tokenType(domain, kernel.self())`, computed at call time.
|
|
194
|
+
*
|
|
195
|
+
* @circuitInfo k=13, rows=4248
|
|
196
|
+
*
|
|
197
|
+
* Requirements:
|
|
198
|
+
*
|
|
199
|
+
* - Contract is initialized.
|
|
200
|
+
*
|
|
201
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
202
|
+
* @return {Bytes<32>} - The coin color.
|
|
203
|
+
*/
|
|
204
|
+
export circuit tokenColor(domain: Bytes<32>): Bytes<32> {
|
|
205
|
+
assertInitialized();
|
|
206
|
+
return tokenType(disclose(domain), kernel.self());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @description Mints `amount` of the token identified by `domain` to
|
|
211
|
+
* `recipient`, using a caller-supplied nonce.
|
|
212
|
+
*
|
|
213
|
+
* @dev The caller is fully responsible for nonce uniqueness. Reusing a
|
|
214
|
+
* nonce for the same (domain, value, recipient) produces a duplicate
|
|
215
|
+
* commitment, which the protocol rejects.
|
|
216
|
+
*
|
|
217
|
+
* @notice The returned coin info is the only copy available to the
|
|
218
|
+
* recipient; callers SHOULD deliver it out of band. Wallets cannot detect
|
|
219
|
+
* contract-minted coins by scanning the chain.
|
|
220
|
+
*
|
|
221
|
+
* @circuitInfo k=14, rows=11119
|
|
222
|
+
*
|
|
223
|
+
* Requirements:
|
|
224
|
+
*
|
|
225
|
+
* - Contract is initialized.
|
|
226
|
+
* - `recipient` is not zero.
|
|
227
|
+
*
|
|
228
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
229
|
+
* @param {Either<ZswapCoinPublicKey, ContractAddress>} recipient - The coin recipient.
|
|
230
|
+
* @param {Uint<64>} amount - Quantity to mint. Capped at `Uint<64>` by the protocol.
|
|
231
|
+
* @param {Bytes<32>} nonce - Caller-supplied nonce. Must be unique per (domain, contract).
|
|
232
|
+
* @return {ShieldedCoinInfo} - The newly created coin's info (nonce, color, value).
|
|
233
|
+
*/
|
|
234
|
+
export circuit _mint(
|
|
235
|
+
domain: Bytes<32>,
|
|
236
|
+
recipient: Either<ZswapCoinPublicKey, ContractAddress>,
|
|
237
|
+
amount: Uint<64>,
|
|
238
|
+
nonce: Bytes<32>
|
|
239
|
+
): ShieldedCoinInfo {
|
|
240
|
+
assertInitialized();
|
|
241
|
+
assert(!Utils_isKeyOrAddressZero(recipient), "NativeShieldedToken: invalid recipient");
|
|
242
|
+
|
|
243
|
+
return mintShieldedToken(disclose(domain), disclose(amount), disclose(nonce), disclose(recipient));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @description Burns `amount` from `coin`, a coin provided within the
|
|
248
|
+
* current transaction (e.g. paid in by the caller's wallet), and routes the
|
|
249
|
+
* remaining change to `refundTo`.
|
|
250
|
+
*
|
|
251
|
+
* To destroy `coin` in full, pass `coin.value` as `amount`; the change
|
|
252
|
+
* branch will not fire and `none` is returned. The `refundTo` value is
|
|
253
|
+
* inert in that case but must still be supplied and non-zero.
|
|
254
|
+
*
|
|
255
|
+
* @dev The coin is received by the contract and spent in the same
|
|
256
|
+
* transaction, so the spend goes through `sendImmediateShielded` (the
|
|
257
|
+
* transient path). For coins the contract already holds, use
|
|
258
|
+
* `_burnFromSelf` instead.
|
|
259
|
+
*
|
|
260
|
+
* @notice The returned refund coin info is the only copy available to
|
|
261
|
+
* `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
|
|
262
|
+
* contract-sent coins by scanning the chain.
|
|
263
|
+
*
|
|
264
|
+
* @circuitInfo k=16, rows=47932
|
|
265
|
+
*
|
|
266
|
+
* Requirements:
|
|
267
|
+
*
|
|
268
|
+
* - Contract is initialized.
|
|
269
|
+
* - `coin.color` is this contract's token type for `domain`.
|
|
270
|
+
* - `amount` is less than or equal to `coin.value`.
|
|
271
|
+
* - `refundTo` is not zero (the zero key is the burn address; a zero
|
|
272
|
+
* `refundTo` would silently burn the change as well).
|
|
273
|
+
*
|
|
274
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
275
|
+
* @param {ShieldedCoinInfo} coin - The coin to burn from.
|
|
276
|
+
* @param {Uint<128>} amount - Value to destroy. Must be <= `coin.value`.
|
|
277
|
+
* @param {Either<ZswapCoinPublicKey, ContractAddress>} refundTo - Where to
|
|
278
|
+
* route the unspent portion (`coin.value - amount`).
|
|
279
|
+
* @return {Maybe<ShieldedCoinInfo>} - The refund coin created for `refundTo`,
|
|
280
|
+
* or `none` if the coin was burned in full.
|
|
281
|
+
*/
|
|
282
|
+
export circuit _burn(
|
|
283
|
+
domain: Bytes<32>,
|
|
284
|
+
coin: ShieldedCoinInfo,
|
|
285
|
+
amount: Uint<128>,
|
|
286
|
+
refundTo: Either<ZswapCoinPublicKey, ContractAddress>
|
|
287
|
+
): Maybe<ShieldedCoinInfo> {
|
|
288
|
+
assertInitialized();
|
|
289
|
+
assert(coin.color == tokenType(disclose(domain), kernel.self()), "NativeShieldedToken: wrong token");
|
|
290
|
+
assert(coin.value >= amount, "NativeShieldedToken: insufficient coin value");
|
|
291
|
+
assert(!Utils_isKeyOrAddressZero(refundTo), "NativeShieldedToken: invalid refund target");
|
|
292
|
+
|
|
293
|
+
receiveShielded(disclose(coin));
|
|
294
|
+
const sendRes = sendImmediateShielded(disclose(coin), shieldedBurnAddress(), disclose(amount));
|
|
295
|
+
|
|
296
|
+
if (disclose(sendRes.change.is_some)) {
|
|
297
|
+
const refundRes = sendImmediateShielded(
|
|
298
|
+
disclose(sendRes.change.value),
|
|
299
|
+
disclose(refundTo),
|
|
300
|
+
disclose(sendRes.change.value.value)
|
|
301
|
+
);
|
|
302
|
+
return some<ShieldedCoinInfo>(refundRes.sent);
|
|
303
|
+
}
|
|
304
|
+
return none<ShieldedCoinInfo>();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @description Burns `amount` from `coin`, a coin this contract already
|
|
309
|
+
* holds (a Merkle-tree entry with a valid `mt_index`). Any change is
|
|
310
|
+
* auto-received by the contract and returned.
|
|
311
|
+
*
|
|
312
|
+
* @dev The consumer SHOULD persist the returned change coin info in its own
|
|
313
|
+
* ledger state: the change replaces `coin` as the contract's holding, and
|
|
314
|
+
* its info is not otherwise recoverable.
|
|
315
|
+
*
|
|
316
|
+
* @circuitInfo k=15, rows=23583
|
|
317
|
+
*
|
|
318
|
+
* Requirements:
|
|
319
|
+
*
|
|
320
|
+
* - Contract is initialized.
|
|
321
|
+
* - `coin.color` is this contract's token type for `domain`.
|
|
322
|
+
* - `amount` is less than or equal to `coin.value`.
|
|
323
|
+
*
|
|
324
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
325
|
+
* @param {QualifiedShieldedCoinInfo} coin - The contract-held coin to burn from.
|
|
326
|
+
* @param {Uint<128>} amount - Value to destroy. Must be <= `coin.value`.
|
|
327
|
+
* @return {Maybe<ShieldedCoinInfo>} - The change coin retained by the
|
|
328
|
+
* contract, or `none` if the coin was burned in full.
|
|
329
|
+
*/
|
|
330
|
+
export circuit _burnFromSelf(
|
|
331
|
+
domain: Bytes<32>,
|
|
332
|
+
coin: QualifiedShieldedCoinInfo,
|
|
333
|
+
amount: Uint<128>
|
|
334
|
+
): Maybe<ShieldedCoinInfo> {
|
|
335
|
+
assertInitialized();
|
|
336
|
+
assert(coin.color == tokenType(disclose(domain), kernel.self()), "NativeShieldedToken: wrong token");
|
|
337
|
+
assert(coin.value >= amount, "NativeShieldedToken: insufficient coin value");
|
|
338
|
+
|
|
339
|
+
const sendRes = sendShielded(disclose(coin), shieldedBurnAddress(), disclose(amount));
|
|
340
|
+
|
|
341
|
+
return disclose(sendRes.change);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.3.0-alpha (token/NativeShieldedTokenFamily.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.23.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module NativeShieldedTokenFamily
|
|
8
|
+
* @description A module for issuing a FAMILY of native shielded (Zswap)
|
|
9
|
+
* token types from one contract, discriminated by a per-call domain.
|
|
10
|
+
*
|
|
11
|
+
* This flavor exists because of Midnight's composition model: there is no
|
|
12
|
+
* contract-to-contract interaction and no way for a contract to deploy
|
|
13
|
+
* another, so the Ethereum pattern of one token contract per asset (stamped
|
|
14
|
+
* out by a factory) is unavailable. A protocol that issues many related
|
|
15
|
+
* tokens — e.g. a DEX minting one liquidity-share token per pair — must do
|
|
16
|
+
* so from a single contract, which the protocol's color derivation
|
|
17
|
+
* (`tokenType(domain, contractAddress)`) natively supports. For a
|
|
18
|
+
* single-token issuer, use `NativeShieldedToken` instead.
|
|
19
|
+
*
|
|
20
|
+
* This module is a thin wrapper over `NativeShieldedTokenCore`: it forwards the
|
|
21
|
+
* caller's per-call `domain` to every core op, so the mint/burn logic and
|
|
22
|
+
* metadata live in the shared core, not here.
|
|
23
|
+
*
|
|
24
|
+
* @notice A contract MUST NOT import both `NativeShieldedTokenFamily` and
|
|
25
|
+
* `NativeShieldedToken`. Both wrap `NativeShieldedTokenCore`, so they share one
|
|
26
|
+
* set of core state (a single init flag and one name/symbol/decimals).
|
|
27
|
+
* Pick one flavor per contract.
|
|
28
|
+
*
|
|
29
|
+
* @notice This module exposes composable building blocks for minting and
|
|
30
|
+
* burning native shielded coins. It does NOT include access control: consuming
|
|
31
|
+
* contracts compose that via the module/contract pattern (Ownable,
|
|
32
|
+
* AccessControl, etc.) and SHOULD gate the mint and burn circuits, which are
|
|
33
|
+
* unrestricted at the module level.
|
|
34
|
+
*
|
|
35
|
+
* @dev The `domain` parameter is passed explicitly per-call; the coin's `color`
|
|
36
|
+
* is `tokenType(domain, kernel.self())`, so the domain discriminates the token
|
|
37
|
+
* types within this contract (e.g. one LP share token per pair) and only this
|
|
38
|
+
* contract can mint coins of its colors. Metadata is contract-wide FAMILY
|
|
39
|
+
* metadata, following the Uniswap-V2 LP precedent: every token type shares one
|
|
40
|
+
* brand, and per-type identity belongs in the consumer's own state (e.g. a pair
|
|
41
|
+
* registry mapping domain -> underlying tokens), which is strictly more
|
|
42
|
+
* informative than a stored string. `decimals` applies family-wide; issuers
|
|
43
|
+
* with heterogeneous decimals should add their own per-domain handling. Mint
|
|
44
|
+
* amounts are `Uint<64>`, burn amounts `Uint<128>` — an asymmetry imposed by
|
|
45
|
+
* the protocol primitives, not a module choice. The mint/burn mechanics and the
|
|
46
|
+
* `_burn` vs `_burnFromSelf` spend paths live in `NativeShieldedTokenCore`.
|
|
47
|
+
*
|
|
48
|
+
* @notice With a secret, cryptographically random `_mint` nonce the mint is
|
|
49
|
+
* recipient-private; the caller is responsible for nonce uniqueness (a reused
|
|
50
|
+
* nonce for the same (domain, value, recipient) produces a duplicate
|
|
51
|
+
* commitment, which the ledger rejects). For mints that need no caller-managed
|
|
52
|
+
* nonce, compose `NativeShieldedTokenDerivedNonce` and pass its `_deriveNonce()`
|
|
53
|
+
* output, at the cost of making those mints recipient-public.
|
|
54
|
+
*
|
|
55
|
+
* @notice Supply accounting is opt-in: this module tracks NO supply totals.
|
|
56
|
+
* Compose the optional `NativeShieldedTokenFamilySupply` extension only if you
|
|
57
|
+
* need an on-chain `totalSupply(domain)`; see it for the privacy trade-off it
|
|
58
|
+
* introduces (it makes contract-mediated burn amounts public).
|
|
59
|
+
*
|
|
60
|
+
* @notice Out of scope (phase two, pending contract-to-contract support):
|
|
61
|
+
* `balanceOf`, `allowance`, transfer mediation, batch operations, and operator
|
|
62
|
+
* approvals are not representable for native shielded UTXOs — once a user holds
|
|
63
|
+
* a coin, the contract cannot observe or restrict its movement.
|
|
64
|
+
*/
|
|
65
|
+
module NativeShieldedTokenFamily {
|
|
66
|
+
import CompactStandardLibrary;
|
|
67
|
+
import "./NativeShieldedTokenCore" prefix Core_;
|
|
68
|
+
|
|
69
|
+
// Circuits use the `Core_` prefix above. The named import/re-export below is
|
|
70
|
+
// only for the core state variables, so the implementing contract's ledger
|
|
71
|
+
// keys read as `_name` etc. rather than `NativeShieldedTokenCore__name`. The
|
|
72
|
+
// circuit names are intentionally NOT imported unprefixed, to avoid potential clashes.
|
|
73
|
+
import { _name, _symbol, _decimals, _isInitialized } from "./NativeShieldedTokenCore";
|
|
74
|
+
export { _name, _symbol, _decimals, _isInitialized };
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @description Initializes the module's family metadata.
|
|
78
|
+
* @dev This MUST be called in the implementing contract's constructor, and it
|
|
79
|
+
* MUST be the only token operation performed there. Failure to call it can
|
|
80
|
+
* lead to an irreparable contract; perform mint, burn, and `tokenColor` only
|
|
81
|
+
* in post-deployment circuits, never in the constructor.
|
|
82
|
+
*
|
|
83
|
+
* @param {Opaque<"string">} name_ - The family name shared by all token types.
|
|
84
|
+
* @param {Opaque<"string">} symbol_ - The family symbol shared by all token types.
|
|
85
|
+
* @param {Uint<8>} decimals_ - The family-wide number of decimals used to
|
|
86
|
+
* get the user representation.
|
|
87
|
+
* @return {[]} - Empty tuple.
|
|
88
|
+
*/
|
|
89
|
+
export circuit initialize(
|
|
90
|
+
name_: Opaque<"string">,
|
|
91
|
+
symbol_: Opaque<"string">,
|
|
92
|
+
decimals_: Uint<8>
|
|
93
|
+
): [] {
|
|
94
|
+
Core_initialize(name_, symbol_, decimals_);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @description Returns whether the contract has been initialized.
|
|
99
|
+
*
|
|
100
|
+
* @circuitInfo k=6, rows=26
|
|
101
|
+
*
|
|
102
|
+
* @return {Boolean} - True once `initialize` has run.
|
|
103
|
+
*/
|
|
104
|
+
export circuit isInitialized(): Boolean {
|
|
105
|
+
return Core_isInitialized();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @description Returns the family name shared by all token types of this
|
|
110
|
+
* contract. Per-type identity belongs in the consumer's own state; see the
|
|
111
|
+
* module notes.
|
|
112
|
+
*
|
|
113
|
+
* @circuitInfo k=6, rows=28
|
|
114
|
+
*
|
|
115
|
+
* Requirements:
|
|
116
|
+
*
|
|
117
|
+
* - Contract is initialized.
|
|
118
|
+
*
|
|
119
|
+
* @return {Opaque<"string">} - The family name.
|
|
120
|
+
*/
|
|
121
|
+
export circuit name(): Opaque<"string"> {
|
|
122
|
+
return Core_name();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @description Returns the family symbol shared by all token types of this
|
|
127
|
+
* contract.
|
|
128
|
+
*
|
|
129
|
+
* @circuitInfo k=6, rows=28
|
|
130
|
+
*
|
|
131
|
+
* Requirements:
|
|
132
|
+
*
|
|
133
|
+
* - Contract is initialized.
|
|
134
|
+
*
|
|
135
|
+
* @return {Opaque<"string">} - The family symbol.
|
|
136
|
+
*/
|
|
137
|
+
export circuit symbol(): Opaque<"string"> {
|
|
138
|
+
return Core_symbol();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @description Returns the family-wide number of decimals used to get the
|
|
143
|
+
* user representation.
|
|
144
|
+
*
|
|
145
|
+
* @circuitInfo k=6, rows=28
|
|
146
|
+
*
|
|
147
|
+
* Requirements:
|
|
148
|
+
*
|
|
149
|
+
* - Contract is initialized.
|
|
150
|
+
*
|
|
151
|
+
* @return {Uint<8>} - The decimals value.
|
|
152
|
+
*/
|
|
153
|
+
export circuit decimals(): Uint<8> {
|
|
154
|
+
return Core_decimals();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @description Returns the coin color (token type) for `domain` under this
|
|
159
|
+
* contract's address, so integrators do not have to re-derive it by hand.
|
|
160
|
+
*
|
|
161
|
+
* @circuitInfo k=13, rows=4248
|
|
162
|
+
*
|
|
163
|
+
* Requirements:
|
|
164
|
+
*
|
|
165
|
+
* - Contract is initialized.
|
|
166
|
+
*
|
|
167
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
168
|
+
* @return {Bytes<32>} - The coin color: `tokenType(domain, kernel.self())`.
|
|
169
|
+
*/
|
|
170
|
+
export circuit tokenColor(domain: Bytes<32>): Bytes<32> {
|
|
171
|
+
return Core_tokenColor(domain);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @description Mints `amount` of the token identified by `domain` to
|
|
176
|
+
* `recipient`, using a caller-supplied nonce.
|
|
177
|
+
*
|
|
178
|
+
* @dev The caller is fully responsible for nonce uniqueness. Reusing a
|
|
179
|
+
* nonce for the same (domain, value, recipient) produces a duplicate
|
|
180
|
+
* commitment, which the protocol rejects. With a secret random nonce this
|
|
181
|
+
* is the recipient-private mint. For mints that require no caller-managed
|
|
182
|
+
* nonce, compose the `NativeShieldedTokenDerivedNonce` extension and pass
|
|
183
|
+
* its `_deriveNonce()` output.
|
|
184
|
+
*
|
|
185
|
+
* @notice The returned coin info is the only copy available to the
|
|
186
|
+
* recipient; callers SHOULD deliver it out of band. Wallets cannot detect
|
|
187
|
+
* contract-minted coins by scanning the chain.
|
|
188
|
+
*
|
|
189
|
+
* @circuitInfo k=14, rows=11119
|
|
190
|
+
*
|
|
191
|
+
* Requirements:
|
|
192
|
+
*
|
|
193
|
+
* - Contract is initialized.
|
|
194
|
+
* - `recipient` is not zero.
|
|
195
|
+
*
|
|
196
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator. Same domain + same
|
|
197
|
+
* contract address produces the same coin color (i.e. same token type).
|
|
198
|
+
* @param {Either<ZswapCoinPublicKey, ContractAddress>} recipient - The coin recipient.
|
|
199
|
+
* @param {Uint<64>} amount - Quantity to mint. Capped at `Uint<64>` by the protocol.
|
|
200
|
+
* @param {Bytes<32>} nonce - Caller-supplied nonce. Must be unique per (domain, contract).
|
|
201
|
+
* @return {ShieldedCoinInfo} - The newly created coin's info (nonce, color, value).
|
|
202
|
+
*/
|
|
203
|
+
export circuit _mint(
|
|
204
|
+
domain: Bytes<32>,
|
|
205
|
+
recipient: Either<ZswapCoinPublicKey, ContractAddress>,
|
|
206
|
+
amount: Uint<64>,
|
|
207
|
+
nonce: Bytes<32>
|
|
208
|
+
): ShieldedCoinInfo {
|
|
209
|
+
return Core__mint(domain, recipient, amount, nonce);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @description Burns `amount` from `coin`, a coin provided within the
|
|
214
|
+
* current transaction (e.g. paid in by the caller's wallet), and routes the
|
|
215
|
+
* remaining change to `refundTo`.
|
|
216
|
+
*
|
|
217
|
+
* To destroy `coin` in full, pass `coin.value` as `amount`; the change
|
|
218
|
+
* branch will not fire and `none` is returned. The `refundTo` value is
|
|
219
|
+
* inert in that case but must still be supplied and non-zero.
|
|
220
|
+
*
|
|
221
|
+
* @dev The coin is received by the contract and spent in the same
|
|
222
|
+
* transaction, so the spend goes through `sendImmediateShielded` (the
|
|
223
|
+
* transient path). For coins the contract already holds, use
|
|
224
|
+
* `_burnFromSelf` instead.
|
|
225
|
+
*
|
|
226
|
+
* @notice The returned refund coin info is the only copy available to
|
|
227
|
+
* `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
|
|
228
|
+
* contract-sent coins by scanning the chain.
|
|
229
|
+
*
|
|
230
|
+
* @circuitInfo k=16, rows=47932
|
|
231
|
+
*
|
|
232
|
+
* Requirements:
|
|
233
|
+
*
|
|
234
|
+
* - Contract is initialized.
|
|
235
|
+
* - `coin.color` is this contract's token type for `domain`.
|
|
236
|
+
* - `amount` is less than or equal to `coin.value`.
|
|
237
|
+
* - `refundTo` is not zero (the zero key is the burn address; a zero
|
|
238
|
+
* `refundTo` would silently burn the change as well).
|
|
239
|
+
*
|
|
240
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
241
|
+
* @param {ShieldedCoinInfo} coin - The coin to burn from.
|
|
242
|
+
* @param {Uint<128>} amount - Value to destroy. Must be <= `coin.value`.
|
|
243
|
+
* @param {Either<ZswapCoinPublicKey, ContractAddress>} refundTo - Where to
|
|
244
|
+
* route the unspent portion (`coin.value - amount`).
|
|
245
|
+
* @return {Maybe<ShieldedCoinInfo>} - The refund coin created for `refundTo`,
|
|
246
|
+
* or `none` if the coin was burned in full.
|
|
247
|
+
*/
|
|
248
|
+
export circuit _burn(
|
|
249
|
+
domain: Bytes<32>,
|
|
250
|
+
coin: ShieldedCoinInfo,
|
|
251
|
+
amount: Uint<128>,
|
|
252
|
+
refundTo: Either<ZswapCoinPublicKey, ContractAddress>
|
|
253
|
+
): Maybe<ShieldedCoinInfo> {
|
|
254
|
+
return Core__burn(domain, coin, amount, refundTo);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @description Burns `amount` from `coin`, a coin this contract already
|
|
259
|
+
* holds (a Merkle-tree entry with a valid `mt_index`). Any change is
|
|
260
|
+
* auto-received by the contract and returned.
|
|
261
|
+
*
|
|
262
|
+
* @dev The consumer SHOULD persist the returned change coin info in its own
|
|
263
|
+
* ledger state: the change replaces `coin` as the contract's holding, and
|
|
264
|
+
* its info is not otherwise recoverable.
|
|
265
|
+
*
|
|
266
|
+
* @circuitInfo k=15, rows=23583
|
|
267
|
+
*
|
|
268
|
+
* Requirements:
|
|
269
|
+
*
|
|
270
|
+
* - Contract is initialized.
|
|
271
|
+
* - `coin.color` is this contract's token type for `domain`.
|
|
272
|
+
* - `amount` is less than or equal to `coin.value`.
|
|
273
|
+
*
|
|
274
|
+
* @param {Bytes<32>} domain - 32-byte token discriminator.
|
|
275
|
+
* @param {QualifiedShieldedCoinInfo} coin - The contract-held coin to burn from.
|
|
276
|
+
* @param {Uint<128>} amount - Value to destroy. Must be <= `coin.value`.
|
|
277
|
+
* @return {Maybe<ShieldedCoinInfo>} - The change coin retained by the
|
|
278
|
+
* contract, or `none` if the coin was burned in full.
|
|
279
|
+
*/
|
|
280
|
+
export circuit _burnFromSelf(
|
|
281
|
+
domain: Bytes<32>,
|
|
282
|
+
coin: QualifiedShieldedCoinInfo,
|
|
283
|
+
amount: Uint<128>
|
|
284
|
+
): Maybe<ShieldedCoinInfo> {
|
|
285
|
+
return Core__burnFromSelf(domain, coin, amount);
|
|
286
|
+
}
|
|
287
|
+
}
|