@openzeppelin/compact-contracts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -0
- package/access/AccessControl.compact +479 -0
- package/access/Ownable.compact +353 -0
- package/access/ShieldedAccessControl.compact +784 -0
- package/access/ZOwnablePK.compact +304 -0
- package/package.json +54 -0
- package/security/Initializable.compact +60 -0
- package/security/Pausable.compact +89 -0
- package/token/FungibleToken.compact +742 -0
- package/token/MultiToken.compact +728 -0
- package/token/NonFungibleToken.compact +954 -0
- package/utils/Utils.compact +98 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// OpenZeppelin Compact Contracts v0.1.0 (token/FungibleToken.compact)
|
|
3
|
+
|
|
4
|
+
pragma language_version >= 0.21.0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module FungibleToken
|
|
8
|
+
* @description An unshielded FungibleToken library.
|
|
9
|
+
*
|
|
10
|
+
* @notice One notable difference regarding this implementation and the EIP20 spec
|
|
11
|
+
* consists of the token size. Uint<128> is used as the token size because Uint<256>
|
|
12
|
+
* cannot be supported.
|
|
13
|
+
* This is due to encoding limits on the midnight circuit backend:
|
|
14
|
+
* https://github.com/midnightntwrk/compactc/issues/929
|
|
15
|
+
*
|
|
16
|
+
* @dev Canonicalization
|
|
17
|
+
* All `Either<Bytes<32>, ContractAddress>` values are canonicalized before use as map keys
|
|
18
|
+
* or in ledger writes. Canonicalization zeroes out the inactive branch of the Either,
|
|
19
|
+
* ensuring that two values with the same active branch always resolve to the same map key
|
|
20
|
+
* regardless of what data the inactive branch carries. Write paths are canonicalized in
|
|
21
|
+
* `_update` (for `_balances`) and `_approve` (for `_allowances`). Read paths are
|
|
22
|
+
* canonicalized in `balanceOf` and `allowance`. `_spendAllowance` canonicalizes
|
|
23
|
+
* independently to ensure consistent lookups before delegating to `_approve`.
|
|
24
|
+
*
|
|
25
|
+
* @notice At the moment Midnight does not support contract-to-contract (C2C) communication, but
|
|
26
|
+
* there are ongoing efforts to enable this in the future. Thus, the main circuits of this module
|
|
27
|
+
* (`transfer`, `transferFrom`, `_transfer`, `_mint`) reject `ContractAddress` recipients via an
|
|
28
|
+
* `isContract` guard, because a contract that receives tokens currently cannot move them back out.
|
|
29
|
+
* Developers who need to experiment with contract recipients can use the `_unsafe` variants, which
|
|
30
|
+
* skip the guard (the caller accepts that such tokens are stranded until C2C lands).
|
|
31
|
+
*
|
|
32
|
+
* @dev Migration plan (safe -> C2C-capable transfers)
|
|
33
|
+
*
|
|
34
|
+
* The recipient type is the final `Either<Bytes<32>, ContractAddress>` from day one, in both
|
|
35
|
+
* circuit signatures and ledger maps. This is deliberate. Midnight's upgrade mechanism, the
|
|
36
|
+
* Contract Maintenance Authority (CMA), can rotate a circuit's verifier key but cannot migrate
|
|
37
|
+
* ledger state (there is no state-rewrite primitive; see the `updatability` tutorial). Fixing the
|
|
38
|
+
* types now makes the future migration a pure verifier-key rotation with zero ledger-state change,
|
|
39
|
+
* which is the only kind of upgrade the CMA supports. Widening the types later would instead force
|
|
40
|
+
* a ledger-layout rewrite, which the CMA cannot perform.
|
|
41
|
+
*
|
|
42
|
+
* When C2C lands, an already-deployed token migrates without changing its address or ledger state.
|
|
43
|
+
* Below, `FN` stands for each guarded circuit (`transfer`, `transferFrom`, `_transfer`, `_mint`)
|
|
44
|
+
* and `_unsafeFN` for its unsafe counterpart:
|
|
45
|
+
*
|
|
46
|
+
* - Minor release: mark `_unsafeFN` deprecated (and emit a warning if possible); keep its
|
|
47
|
+
* implementation intact so existing callers continue to work.
|
|
48
|
+
* - Major release: ship `FN` with the `isContract` guard removed and drop `_unsafeFN`. Operators
|
|
49
|
+
* adopt the new logic by signing a CMA `MaintenanceUpdate` that rotates the affected verifier
|
|
50
|
+
* keys (`VerifierKeyRemove` + `VerifierKeyInsert` for each `FN`, and a `VerifierKeyRemove` for
|
|
51
|
+
* each `_unsafeFN`). The contract address and all ledger state are preserved. Because the CMA
|
|
52
|
+
* allows multiple active VK versions per operation, old and new `FN` keys can coexist during a
|
|
53
|
+
* transition window so in-flight proofs still verify. By this point, anyone using `_unsafeFN`
|
|
54
|
+
* should have migrated to the now C2C-capable `FN`.
|
|
55
|
+
*
|
|
56
|
+
* Due to the vast incompatibilities with the EIP20 spec, it is our
|
|
57
|
+
* opinion that this implementation should not be called ERC20 at this time
|
|
58
|
+
* as this would be both very confusing and misleading. This may change as more
|
|
59
|
+
* features become available. The list of missing features is as follows:
|
|
60
|
+
*
|
|
61
|
+
* - Full uint256 support.
|
|
62
|
+
* - Events.
|
|
63
|
+
* - Contract-to-contract calls.
|
|
64
|
+
*/
|
|
65
|
+
module FungibleToken {
|
|
66
|
+
import CompactStandardLibrary;
|
|
67
|
+
import "../security/Initializable" prefix Initializable_;
|
|
68
|
+
import "../utils/Utils" prefix Utils_;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @description Mapping from account addresses to their token balances.
|
|
72
|
+
* @type {Either<Bytes<32>, ContractAddress>} account - The account address.
|
|
73
|
+
* @type {Uint<128>} balance - The balance of the account.
|
|
74
|
+
* @type {Map<account, balance>}
|
|
75
|
+
* @type {Map<Either<Bytes<32>, ContractAddress>, Uint<128>>} _balances
|
|
76
|
+
*/
|
|
77
|
+
export ledger _balances: Map<Either<Bytes<32>, ContractAddress>, Uint<128>>;
|
|
78
|
+
/**
|
|
79
|
+
* @description Mapping from owner accounts to spender accounts and their allowances.
|
|
80
|
+
* @type {Either<Bytes<32>, ContractAddress>} account - The owner account address.
|
|
81
|
+
* @type {Either<Bytes<32>, ContractAddress>} spender - The spender account address.
|
|
82
|
+
* @type {Uint<128>} allowance - The amount allowed to be spent by the spender.
|
|
83
|
+
* @type {Map<account, Map<spender, allowance>>}
|
|
84
|
+
* @type {Map<Either<Bytes<32>, ContractAddress>, Map<Either<Bytes<32>, ContractAddress>, Uint<128>>>} _allowances
|
|
85
|
+
*/
|
|
86
|
+
export ledger _allowances: Map<Either<Bytes<32>, ContractAddress>,
|
|
87
|
+
Map<Either<Bytes<32>, ContractAddress>, Uint<128>>>;
|
|
88
|
+
|
|
89
|
+
export ledger _totalSupply: Uint<128>;
|
|
90
|
+
|
|
91
|
+
export sealed ledger _name: Opaque<"string">;
|
|
92
|
+
export sealed ledger _symbol: Opaque<"string">;
|
|
93
|
+
export sealed ledger _decimals: Uint<8>;
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @witness wit_FungibleTokenSK
|
|
98
|
+
* @description Returns the caller's secret key used in deriving the account identifier.
|
|
99
|
+
*
|
|
100
|
+
* The same key produces the same account identifier across all contracts. Users who
|
|
101
|
+
* desire cross-contract unlinkability should use different keys per contract.
|
|
102
|
+
*
|
|
103
|
+
* @returns {Bytes<32>} secretKey - A 32-byte cryptographically secure random value.
|
|
104
|
+
*/
|
|
105
|
+
witness wit_FungibleTokenSK(): Bytes<32>;
|
|
106
|
+
|
|
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
|
+
/**
|
|
121
|
+
* @description Initializes the contract by setting the name, symbol, and decimals.
|
|
122
|
+
* @dev This MUST be called in the implementing contract's constructor. Failure to do so
|
|
123
|
+
* can lead to an irreparable contract.
|
|
124
|
+
*
|
|
125
|
+
* @circuitInfo k=10, rows=71
|
|
126
|
+
*
|
|
127
|
+
* @param {Opaque<"string">} name_ - The name of the token.
|
|
128
|
+
* @param {Opaque<"string">} symbol_ - The symbol of the token.
|
|
129
|
+
* @param {Uint<8>} decimals_ - The number of decimals used to get the user representation.
|
|
130
|
+
* @return {[]} - Empty tuple.
|
|
131
|
+
*/
|
|
132
|
+
export circuit initialize(
|
|
133
|
+
name_: Opaque<"string">,
|
|
134
|
+
symbol_: Opaque<"string">,
|
|
135
|
+
decimals_: Uint<8>
|
|
136
|
+
): [] {
|
|
137
|
+
Initializable_initialize();
|
|
138
|
+
_name = disclose(name_);
|
|
139
|
+
_symbol = disclose(symbol_);
|
|
140
|
+
_decimals = disclose(decimals_);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @description Returns the token name.
|
|
145
|
+
*
|
|
146
|
+
* @circuitInfo k=6, rows=28
|
|
147
|
+
*
|
|
148
|
+
* Requirements:
|
|
149
|
+
*
|
|
150
|
+
* - Contract is initialized.
|
|
151
|
+
*
|
|
152
|
+
* @return {Opaque<"string">} - The token name.
|
|
153
|
+
*/
|
|
154
|
+
export circuit name(): Opaque<"string"> {
|
|
155
|
+
Initializable_assertInitialized();
|
|
156
|
+
return _name;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @description Returns the symbol of the token.
|
|
161
|
+
*
|
|
162
|
+
* @circuitInfo k=6, rows=28
|
|
163
|
+
*
|
|
164
|
+
* Requirements:
|
|
165
|
+
*
|
|
166
|
+
* - Contract is initialized.
|
|
167
|
+
*
|
|
168
|
+
* @return {Opaque<"string">} - The token name.
|
|
169
|
+
*/
|
|
170
|
+
export circuit symbol(): Opaque<"string"> {
|
|
171
|
+
Initializable_assertInitialized();
|
|
172
|
+
return _symbol;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @description Returns the number of decimals used to get its user representation.
|
|
177
|
+
*
|
|
178
|
+
* @circuitInfo k=6, rows=28
|
|
179
|
+
*
|
|
180
|
+
* Requirements:
|
|
181
|
+
*
|
|
182
|
+
* - Contract is initialized.
|
|
183
|
+
*
|
|
184
|
+
* @return {Uint<8>} - The account's token balance.
|
|
185
|
+
*/
|
|
186
|
+
export circuit decimals(): Uint<8> {
|
|
187
|
+
Initializable_assertInitialized();
|
|
188
|
+
return _decimals;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @description Returns the value of tokens in existence.
|
|
193
|
+
*
|
|
194
|
+
* @circuitInfo k=6, rows=28
|
|
195
|
+
*
|
|
196
|
+
* Requirements:
|
|
197
|
+
*
|
|
198
|
+
* - Contract is initialized.
|
|
199
|
+
*
|
|
200
|
+
* @return {Uint<128>} - The total supply of tokens.
|
|
201
|
+
*/
|
|
202
|
+
export circuit totalSupply(): Uint<128> {
|
|
203
|
+
Initializable_assertInitialized();
|
|
204
|
+
return _totalSupply;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @description Returns the value of tokens owned by `account`.
|
|
209
|
+
*
|
|
210
|
+
* @circuitInfo k=10, rows=673
|
|
211
|
+
*
|
|
212
|
+
* @dev Manually checks if `account` is a key in the map and returns 0 if it is not.
|
|
213
|
+
*
|
|
214
|
+
* Requirements:
|
|
215
|
+
*
|
|
216
|
+
* - Contract is initialized.
|
|
217
|
+
*
|
|
218
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - The account id or contract address to query.
|
|
219
|
+
* @return {Uint<128>} - The account's token balance.
|
|
220
|
+
*/
|
|
221
|
+
export circuit balanceOf(account: Either<Bytes<32>, ContractAddress>): Uint<128> {
|
|
222
|
+
Initializable_assertInitialized();
|
|
223
|
+
const canonAcct = Utils_canonicalize<Bytes<32>, ContractAddress>(account);
|
|
224
|
+
|
|
225
|
+
if (!_balances.member(disclose(canonAcct))) {
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return _balances.lookup(disclose(canonAcct));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @description Moves a `value` amount of tokens from the caller's account to `to`.
|
|
234
|
+
*
|
|
235
|
+
* @circuitInfo k=13, rows=3985
|
|
236
|
+
*
|
|
237
|
+
* @notice Transfers to contract addresses are currently disallowed until contract-to-contract
|
|
238
|
+
* interactions are supported in Compact. This restriction prevents assets from
|
|
239
|
+
* being inadvertently locked in contracts that cannot currently handle token receipt.
|
|
240
|
+
*
|
|
241
|
+
* Requirements:
|
|
242
|
+
*
|
|
243
|
+
* - Contract is initialized.
|
|
244
|
+
* - `to` is not a ContractAddress.
|
|
245
|
+
* - `to` is not the zero address.
|
|
246
|
+
* - The caller has a balance of at least `value`.
|
|
247
|
+
*
|
|
248
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The recipient of the transfer, either a user or a contract.
|
|
249
|
+
* @param {Uint<128>} value - The amount to transfer.
|
|
250
|
+
* @return {Boolean} - As per the IERC20 spec, this MUST return true.
|
|
251
|
+
*/
|
|
252
|
+
export circuit transfer(
|
|
253
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
254
|
+
value: Uint<128>
|
|
255
|
+
): Boolean {
|
|
256
|
+
Initializable_assertInitialized();
|
|
257
|
+
const isContractAddr = !to.is_left;
|
|
258
|
+
assert(!isContractAddr, "FungibleToken: unsafe transfer");
|
|
259
|
+
|
|
260
|
+
return _unsafeTransfer(to, value);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @description Unsafe variant of `transfer` which allows transfers to contract addresses.
|
|
265
|
+
*
|
|
266
|
+
* @circuitInfo k=13, rows=3982
|
|
267
|
+
*
|
|
268
|
+
* @warning Transfers to contract addresses are considered unsafe because contract-to-contract
|
|
269
|
+
* calls are not currently supported. Tokens sent to a contract address may become irretrievable.
|
|
270
|
+
* Once contract-to-contract calls are supported, this circuit may be deprecated.
|
|
271
|
+
*
|
|
272
|
+
* Requirements:
|
|
273
|
+
*
|
|
274
|
+
* - Contract is initialized.
|
|
275
|
+
* - `to` is not the zero address.
|
|
276
|
+
* - The caller has a balance of at least `value`.
|
|
277
|
+
*
|
|
278
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The recipient of the transfer, either a user or a contract.
|
|
279
|
+
* @param {Uint<128>} value - The amount to transfer.
|
|
280
|
+
* @return {Boolean} - As per the IERC20 spec, this MUST return true.
|
|
281
|
+
*/
|
|
282
|
+
export circuit _unsafeTransfer(
|
|
283
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
284
|
+
value: Uint<128>
|
|
285
|
+
): Boolean {
|
|
286
|
+
Initializable_assertInitialized();
|
|
287
|
+
const owner = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
288
|
+
_unsafeUncheckedTransfer(owner, to, value);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @description Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner`
|
|
294
|
+
* through `transferFrom`. This value changes when `approve` or `transferFrom` are called.
|
|
295
|
+
*
|
|
296
|
+
* @circuitInfo k=11, rows=1346
|
|
297
|
+
*
|
|
298
|
+
* @dev Manually checks if `owner` and `spender` are keys in the map and returns 0 if they are not.
|
|
299
|
+
*
|
|
300
|
+
* Requirements:
|
|
301
|
+
*
|
|
302
|
+
* - Contract is initialized.
|
|
303
|
+
*
|
|
304
|
+
* @param {Either<Bytes<32>, ContractAddress>} owner - The account id or contract address of approver.
|
|
305
|
+
* @param {Either<Bytes<32>, ContractAddress>} spender - The account id or contract address of spender.
|
|
306
|
+
* @return {Uint<128>} - The `spender`'s allowance over `owner`'s tokens.
|
|
307
|
+
*/
|
|
308
|
+
export circuit allowance(
|
|
309
|
+
owner: Either<Bytes<32>, ContractAddress>,
|
|
310
|
+
spender: Either<Bytes<32>, ContractAddress>
|
|
311
|
+
): Uint<128> {
|
|
312
|
+
Initializable_assertInitialized();
|
|
313
|
+
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
314
|
+
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
315
|
+
|
|
316
|
+
if (!_allowances.member(disclose(canonOwner)) || !_allowances.lookup(canonOwner).member(disclose(canonSpender))) {
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return _allowances.lookup(canonOwner).lookup(disclose(canonSpender));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @description Sets a `value` amount of tokens as allowance of `spender` over the caller's tokens.
|
|
325
|
+
*
|
|
326
|
+
* @circuitInfo k=13, rows=3072
|
|
327
|
+
*
|
|
328
|
+
* Requirements:
|
|
329
|
+
*
|
|
330
|
+
* - Contract is initialized.
|
|
331
|
+
* - `spender` is not the zero address.
|
|
332
|
+
*
|
|
333
|
+
* @param {Either<Bytes<32>, ContractAddress>} spender - The account id or ContractAddress that may spend on behalf of the caller.
|
|
334
|
+
* @param {Uint<128>} value - The amount of tokens the `spender` may spend.
|
|
335
|
+
* @return {Boolean} - Returns a boolean value indicating whether the operation succeeded.
|
|
336
|
+
*/
|
|
337
|
+
export circuit approve(spender: Either<Bytes<32>, ContractAddress>,
|
|
338
|
+
value: Uint<128>
|
|
339
|
+
): Boolean {
|
|
340
|
+
Initializable_assertInitialized();
|
|
341
|
+
|
|
342
|
+
const owner = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
343
|
+
_approve(owner, spender, value);
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @description Moves `value` tokens from `fromAddress` to `to` using the allowance mechanism.
|
|
349
|
+
* `value` is the deducted from the caller's allowance.
|
|
350
|
+
*
|
|
351
|
+
* @circuitInfo k=13, rows=4960
|
|
352
|
+
*
|
|
353
|
+
* @notice Transfers to contract addresses are currently disallowed until contract-to-contract
|
|
354
|
+
* interactions are supported in Compact. This restriction prevents assets from
|
|
355
|
+
* being inadvertently locked in contracts that cannot currently handle token receipt.
|
|
356
|
+
*
|
|
357
|
+
* Requirements:
|
|
358
|
+
*
|
|
359
|
+
* - Contract is initialized.
|
|
360
|
+
* - `fromAddress` is not the zero address.
|
|
361
|
+
* - `fromAddress` must have a balance of at least `value`.
|
|
362
|
+
* - `to` is not the zero address.
|
|
363
|
+
* - `to` is not a ContractAddress.
|
|
364
|
+
* - The caller has an allowance of `fromAddress`'s tokens of at least `value`.
|
|
365
|
+
*
|
|
366
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The current owner of the tokens for the transfer, either a user or a contract.
|
|
367
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The recipient of the transfer, either a user or a contract.
|
|
368
|
+
* @param {Uint<128>} value - The amount to transfer.
|
|
369
|
+
* @return {Boolean} - As per the IERC20 spec, this MUST return true.
|
|
370
|
+
*/
|
|
371
|
+
export circuit transferFrom(
|
|
372
|
+
fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
373
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
374
|
+
value: Uint<128>
|
|
375
|
+
): Boolean {
|
|
376
|
+
Initializable_assertInitialized();
|
|
377
|
+
const isContractAddr = !to.is_left;
|
|
378
|
+
assert(!isContractAddr, "FungibleToken: unsafe transfer");
|
|
379
|
+
return _unsafeTransferFrom(fromAddress, to, value);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @description Unsafe variant of `transferFrom` which allows transfers to contract addresses.
|
|
384
|
+
*
|
|
385
|
+
* @circuitInfo k=13, rows=4957
|
|
386
|
+
*
|
|
387
|
+
* @warning Transfers to contract addresses are considered unsafe because contract-to-contract
|
|
388
|
+
* calls are not currently supported. Tokens sent to a contract address may become irretrievable.
|
|
389
|
+
* Once contract-to-contract calls are supported, this circuit may be deprecated.
|
|
390
|
+
*
|
|
391
|
+
* Requirements:
|
|
392
|
+
*
|
|
393
|
+
* - Contract is initialized.
|
|
394
|
+
* - `fromAddress` is not the zero address.
|
|
395
|
+
* - `fromAddress` must have a balance of at least `value`.
|
|
396
|
+
* - `to` is not the zero address.
|
|
397
|
+
* - The caller has an allowance of `fromAddress`'s tokens of at least `value`.
|
|
398
|
+
*
|
|
399
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The current owner of the tokens for the transfer, either a user or a contract.
|
|
400
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The recipient of the transfer, either a user or a contract.
|
|
401
|
+
* @param {Uint<128>} value - The amount to transfer.
|
|
402
|
+
* @return {Boolean} - As per the IERC20 spec, this MUST return true.
|
|
403
|
+
*/
|
|
404
|
+
export circuit _unsafeTransferFrom(
|
|
405
|
+
fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
406
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
407
|
+
value: Uint<128>
|
|
408
|
+
): Boolean {
|
|
409
|
+
Initializable_assertInitialized();
|
|
410
|
+
|
|
411
|
+
const spender = left<Bytes<32>, ContractAddress>(_computeAccountId());
|
|
412
|
+
_spendAllowance(fromAddress, spender, value);
|
|
413
|
+
_unsafeUncheckedTransfer(fromAddress, to, value);
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @description Moves a `value` amount of tokens from `fromAddress` to `to`.
|
|
419
|
+
* This circuit is equivalent to {transfer}, and can be used to
|
|
420
|
+
* e.g. implement automatic token fees, slashing mechanisms, etc.
|
|
421
|
+
*
|
|
422
|
+
* @circuitInfo k=12, rows=2345
|
|
423
|
+
*
|
|
424
|
+
* @notice Transfers to contract addresses are currently disallowed until contract-to-contract
|
|
425
|
+
* interactions are supported in Compact. This restriction prevents assets from
|
|
426
|
+
* being inadvertently locked in contracts that cannot currently handle token receipt.
|
|
427
|
+
*
|
|
428
|
+
* Requirements:
|
|
429
|
+
*
|
|
430
|
+
* - Contract is initialized.
|
|
431
|
+
* - `fromAddress` is not be the zero address.
|
|
432
|
+
* - `fromAddress` must have at least a balance of `value`.
|
|
433
|
+
* - `to` must not be the zero address.
|
|
434
|
+
* - `to` must not be a ContractAddress.
|
|
435
|
+
*
|
|
436
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The owner of the tokens to transfer.
|
|
437
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The receipient of the transferred tokens.
|
|
438
|
+
* @param {Uint<128>} value - The amount of tokens to transfer.
|
|
439
|
+
* @return {[]} - Empty tuple.
|
|
440
|
+
*/
|
|
441
|
+
export circuit _transfer(
|
|
442
|
+
fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
443
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
444
|
+
value: Uint<128>
|
|
445
|
+
): [] {
|
|
446
|
+
Initializable_assertInitialized();
|
|
447
|
+
const isContractAddr = !to.is_left;
|
|
448
|
+
assert(!isContractAddr, "FungibleToken: unsafe transfer");
|
|
449
|
+
_unsafeUncheckedTransfer(fromAddress, to, value);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* @description Unsafe variant of `transferFrom` which allows transfers to contract addresses.
|
|
454
|
+
*
|
|
455
|
+
* @circuitInfo k=12, rows=2342
|
|
456
|
+
*
|
|
457
|
+
* @warning Transfers to contract addresses are considered unsafe because contract-to-contract
|
|
458
|
+
* calls are not currently supported. Tokens sent to a contract address may become irretrievable.
|
|
459
|
+
* Once contract-to-contract calls are supported, this circuit may be deprecated.
|
|
460
|
+
*
|
|
461
|
+
* Requirements:
|
|
462
|
+
*
|
|
463
|
+
* - Contract is initialized.
|
|
464
|
+
* - `fromAddress` is not the zero address.
|
|
465
|
+
* - `to` is not the zero address.
|
|
466
|
+
*
|
|
467
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The owner of the tokens to transfer.
|
|
468
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The receipient of the transferred tokens.
|
|
469
|
+
* @param {Uint<128>} value - The amount of tokens to transfer.
|
|
470
|
+
* @return {[]} - Empty tuple.
|
|
471
|
+
*/
|
|
472
|
+
export circuit _unsafeUncheckedTransfer(
|
|
473
|
+
fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
474
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
475
|
+
value: Uint<128>
|
|
476
|
+
): [] {
|
|
477
|
+
Initializable_assertInitialized();
|
|
478
|
+
assert(!_isTargetZero(fromAddress), "FungibleToken: invalid sender");
|
|
479
|
+
assert(!_isTargetZero(to), "FungibleToken: invalid receiver");
|
|
480
|
+
|
|
481
|
+
_update(fromAddress, to, value);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* @description Transfers a `value` amount of tokens from `fromAddress` to `to`, or alternatively mints (or burns) if `fromAddress`
|
|
486
|
+
* (or `to`) is the zero address.
|
|
487
|
+
* @dev Checks for a mint overflow in order to output a more readable error message.
|
|
488
|
+
*
|
|
489
|
+
* @circuitInfo k=11, rows=1305
|
|
490
|
+
*
|
|
491
|
+
* Requirements:
|
|
492
|
+
*
|
|
493
|
+
* - Contract is initialized.
|
|
494
|
+
*
|
|
495
|
+
* @param {Either<Bytes<32>, ContractAddress>} fromAddress - The original owner of the tokens moved (which is 0 if tokens are minted).
|
|
496
|
+
* @param {Either<Bytes<32>, ContractAddress>} to - The recipient of the tokens moved (which is 0 if tokens are burned).
|
|
497
|
+
* @param {Uint<128>} value - The amount of tokens moved from `fromAddress` to `to`.
|
|
498
|
+
* @return {[]} - Empty tuple.
|
|
499
|
+
*/
|
|
500
|
+
circuit _update(fromAddress: Either<Bytes<32>, ContractAddress>,
|
|
501
|
+
to: Either<Bytes<32>, ContractAddress>,
|
|
502
|
+
value: Uint<128>
|
|
503
|
+
): [] {
|
|
504
|
+
Initializable_assertInitialized();
|
|
505
|
+
const canonFrom = Utils_canonicalize<Bytes<32>, ContractAddress>(fromAddress);
|
|
506
|
+
const canonTo = Utils_canonicalize<Bytes<32>, ContractAddress>(to);
|
|
507
|
+
|
|
508
|
+
if (_isTargetZero(disclose(canonFrom))) {
|
|
509
|
+
// Mint
|
|
510
|
+
const MAX_UINT128 = 340282366920938463463374607431768211455;
|
|
511
|
+
assert(MAX_UINT128 - _totalSupply >= value, "FungibleToken: arithmetic overflow");
|
|
512
|
+
|
|
513
|
+
_totalSupply = disclose(_totalSupply + value as Uint<128>);
|
|
514
|
+
} else {
|
|
515
|
+
const fromBal = balanceOf(canonFrom);
|
|
516
|
+
assert(fromBal >= value, "FungibleToken: insufficient balance");
|
|
517
|
+
_balances.insert(disclose(canonFrom), disclose(fromBal - value as Uint<128>));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (_isTargetZero(disclose(canonTo))) {
|
|
521
|
+
// Burn
|
|
522
|
+
_totalSupply = disclose(_totalSupply - value as Uint<128>);
|
|
523
|
+
} else {
|
|
524
|
+
const toBal = balanceOf(canonTo);
|
|
525
|
+
_balances.insert(disclose(canonTo), disclose(toBal + value as Uint<128>));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* @description Creates a `value` amount of tokens and assigns them to `account`,
|
|
531
|
+
* by transferring it from the zero address. Relies on the `update` mechanism.
|
|
532
|
+
*
|
|
533
|
+
* @circuitInfo k=11, rows=1437
|
|
534
|
+
*
|
|
535
|
+
* @notice Transfers to contract addresses are currently disallowed until contract-to-contract
|
|
536
|
+
* interactions are supported in Compact. This restriction prevents assets from
|
|
537
|
+
* being inadvertently locked in contracts that cannot currently handle token receipt.
|
|
538
|
+
*
|
|
539
|
+
* Requirements:
|
|
540
|
+
*
|
|
541
|
+
* - Contract is initialized.
|
|
542
|
+
* - `account` is not a ContractAddress.
|
|
543
|
+
* - `account` is not the zero address.
|
|
544
|
+
*
|
|
545
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - The recipient of tokens minted.
|
|
546
|
+
* @param {Uint<128>} value - The amount of tokens minted.
|
|
547
|
+
* @return {[]} - Empty tuple.
|
|
548
|
+
*/
|
|
549
|
+
export circuit _mint(account: Either<Bytes<32>, ContractAddress>, value: Uint<128>): [] {
|
|
550
|
+
Initializable_assertInitialized();
|
|
551
|
+
const isContractAddr = !account.is_left;
|
|
552
|
+
assert(!isContractAddr, "FungibleToken: unsafe transfer");
|
|
553
|
+
_unsafeMint(account, value);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* @description Unsafe variant of `_mint` which allows transfers to contract addresses.
|
|
558
|
+
*
|
|
559
|
+
* @circuitInfo k=11, rows=1434
|
|
560
|
+
*
|
|
561
|
+
* @warning Transfers to contract addresses are considered unsafe because contract-to-contract
|
|
562
|
+
* calls are not currently supported. Tokens sent to a contract address may become irretrievable.
|
|
563
|
+
* Once contract-to-contract calls are supported, this circuit may be deprecated.
|
|
564
|
+
*
|
|
565
|
+
* Requirements:
|
|
566
|
+
*
|
|
567
|
+
* - Contract is initialized.
|
|
568
|
+
* - `account` is not the zero address.
|
|
569
|
+
*
|
|
570
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - The recipient of tokens minted.
|
|
571
|
+
* @param {Uint<128>} value - The amount of tokens minted.
|
|
572
|
+
* @return {[]} - Empty tuple.
|
|
573
|
+
*/
|
|
574
|
+
export circuit _unsafeMint(
|
|
575
|
+
account: Either<Bytes<32>, ContractAddress>,
|
|
576
|
+
value: Uint<128>
|
|
577
|
+
): [] {
|
|
578
|
+
Initializable_assertInitialized();
|
|
579
|
+
assert(!_isTargetZero(account), "FungibleToken: invalid receiver");
|
|
580
|
+
_update(ZERO(), account, value);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* @description Destroys a `value` amount of tokens from `account`, lowering the total supply.
|
|
585
|
+
* Relies on the `_update` mechanism.
|
|
586
|
+
*
|
|
587
|
+
* @circuitInfo k=11, rows=1377
|
|
588
|
+
*
|
|
589
|
+
* Requirements:
|
|
590
|
+
*
|
|
591
|
+
* - Contract is initialized.
|
|
592
|
+
* - `account` is not the zero address.
|
|
593
|
+
* - `account` must have at least a balance of `value`.
|
|
594
|
+
*
|
|
595
|
+
* @param {Either<Bytes<32>, ContractAddress>} account - The target owner of tokens to burn.
|
|
596
|
+
* @param {Uint<128>} value - The amount of tokens to burn.
|
|
597
|
+
* @return {[]} - Empty tuple.
|
|
598
|
+
*/
|
|
599
|
+
export circuit _burn(account: Either<Bytes<32>, ContractAddress>, value: Uint<128>): [] {
|
|
600
|
+
Initializable_assertInitialized();
|
|
601
|
+
assert(!_isTargetZero(account), "FungibleToken: invalid sender");
|
|
602
|
+
_update(account, ZERO(), value);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* @description Sets `value` as the allowance of `spender` over the `owner`'s tokens.
|
|
607
|
+
* This circuit is equivalent to `approve`, and can be used to
|
|
608
|
+
* e.g. set automatic allowances for certain subsystems, etc.
|
|
609
|
+
*
|
|
610
|
+
* @circuitInfo k=11, rows=1406
|
|
611
|
+
*
|
|
612
|
+
* Requirements:
|
|
613
|
+
*
|
|
614
|
+
* - Contract is initialized.
|
|
615
|
+
* - `owner` is not the zero address.
|
|
616
|
+
* - `spender` is not the zero address.
|
|
617
|
+
*
|
|
618
|
+
* @param {Either<Bytes<32>, ContractAddress>} owner - The owner of the tokens.
|
|
619
|
+
* @param {Either<Bytes<32>, ContractAddress>} spender - The spender of the tokens.
|
|
620
|
+
* @param {Uint<128>} value - The amount of tokens `spender` may spend on behalf of `owner`.
|
|
621
|
+
* @return {[]} - Empty tuple.
|
|
622
|
+
*/
|
|
623
|
+
export circuit _approve(
|
|
624
|
+
owner: Either<Bytes<32>, ContractAddress>,
|
|
625
|
+
spender: Either<Bytes<32>, ContractAddress>,
|
|
626
|
+
value: Uint<128>
|
|
627
|
+
): [] {
|
|
628
|
+
Initializable_assertInitialized();
|
|
629
|
+
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
630
|
+
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
631
|
+
|
|
632
|
+
assert(!_isTargetZero(canonOwner), "FungibleToken: invalid owner");
|
|
633
|
+
assert(!_isTargetZero(canonSpender), "FungibleToken: invalid spender");
|
|
634
|
+
|
|
635
|
+
if (!_allowances.member(disclose(canonOwner))) {
|
|
636
|
+
// If owner doesn't exist, create and insert a new sub-map directly
|
|
637
|
+
_allowances.insert(
|
|
638
|
+
disclose(canonOwner),
|
|
639
|
+
default<Map<Either<Bytes<32>, ContractAddress>, Uint<128>>>
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
_allowances.lookup(canonOwner).insert(disclose(canonSpender), disclose(value));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* @description Updates `owner`'s allowance for `spender` based on spent `value`.
|
|
647
|
+
* Does not update the allowance value in case of infinite allowance.
|
|
648
|
+
*
|
|
649
|
+
* @circuitInfo k=11, rows=1729
|
|
650
|
+
*
|
|
651
|
+
* Requirements:
|
|
652
|
+
*
|
|
653
|
+
* - Contract is initialized.
|
|
654
|
+
* - `spender` must have at least an allowance of `value` from `owner`.
|
|
655
|
+
*
|
|
656
|
+
* @param {Either<Bytes<32>, ContractAddress>} owner - The owner of the tokens.
|
|
657
|
+
* @param {Either<Bytes<32>, ContractAddress>} spender - The spender of the tokens.
|
|
658
|
+
* @param {Uint<128>} value - The amount of token allowance to spend.
|
|
659
|
+
* @return {[]} - Empty tuple.
|
|
660
|
+
*/
|
|
661
|
+
export circuit _spendAllowance(
|
|
662
|
+
owner: Either<Bytes<32>, ContractAddress>,
|
|
663
|
+
spender: Either<Bytes<32>, ContractAddress>,
|
|
664
|
+
value: Uint<128>
|
|
665
|
+
): [] {
|
|
666
|
+
Initializable_assertInitialized();
|
|
667
|
+
const canonOwner = Utils_canonicalize<Bytes<32>, ContractAddress>(owner);
|
|
668
|
+
const canonSpender = Utils_canonicalize<Bytes<32>, ContractAddress>(spender);
|
|
669
|
+
|
|
670
|
+
assert((_allowances.member(disclose(canonOwner)) &&
|
|
671
|
+
_allowances.lookup(canonOwner).member(disclose(canonSpender))),
|
|
672
|
+
"FungibleToken: insufficient allowance"
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
const currentAllowance = _allowances.lookup(canonOwner).lookup(disclose(canonSpender));
|
|
676
|
+
const MAX_UINT128 = 340282366920938463463374607431768211455;
|
|
677
|
+
if (currentAllowance < MAX_UINT128) {
|
|
678
|
+
assert(currentAllowance >= value, "FungibleToken: insufficient allowance");
|
|
679
|
+
_approve(canonOwner, canonSpender, currentAllowance - value as Uint<128>);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* @description Computes the caller's account identifier from the `wit_FungibleTokenSK` witness.
|
|
685
|
+
*
|
|
686
|
+
* ## ID Derivation
|
|
687
|
+
* `accountId = persistentHash(secretKey)`
|
|
688
|
+
*
|
|
689
|
+
* The result is a 32-byte commitment that uniquely identifies the caller.
|
|
690
|
+
*
|
|
691
|
+
* @returns {Bytes<32>} accountId - The computed account identifier.
|
|
692
|
+
*/
|
|
693
|
+
circuit _computeAccountId(): Bytes<32> {
|
|
694
|
+
return computeAccountId(wit_FungibleTokenSK());
|
|
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
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|