// SPDX-License-Identifier: MIT // MyFun Contracts v0.1.4 (token/FungibleToken.compact) pragma language_version >= 0.16.0; /** * @module FungibleToken * @description An unshielded FungibleToken library. * * @notice One notable difference regarding this implementation and the EIP20 spec * consists of the token size. Uint<128> is used as the token size because Uint<256> * cannot be supported. * This is due to encoding limits on the midnight circuit backend: * https://github.com/midnightntwrk/compactc/issues/929 * * @notice At the moment Midnight does not support contract-to-contract communication, but * there are ongoing efforts to enable this in the future. Thus, the main circuits of this module * restrict developers from sending tokens to contracts; however, we provide developers * the ability to experiment with sending tokens to contracts using the `_unsafe` * transfer methods. Once contract-to-contract communication is available we will follow the * deprecation plan outlined below: * * Initial Minor Version Change: * * - Mark _unsafeFN as deprecated and emit a warning if possible. * - Keep its implementation intact so existing callers continue to work. * * Later Major Version Change: * * - Drop _unsafeFN and remove `isContract` guard from `FN`. * - By this point, anyone using _unsafeFN should have migrated to the now C2C-capable `FN`. * * Due to the vast incompatibilities with the EIP20 spec, it is our * opinion that this implementation should not be called ERC20 at this time * as this would be both very confusing and misleading. This may change as more * features become available. The list of missing features is as follows: * * - Full uint256 support. * - Events. * - Contract-to-contract calls. */ module FungibleToken { import CompactStandardLibrary; import "../security/Initializable" prefix Initializable_; import "../utils/Utils" prefix Utils_; /** * @description Mapping from account addresses to their token balances. * @type {Either} account - The account address. * @type {Uint<128>} balance - The balance of the account. * @type {Map} * @type {Map, Uint<128>>} _balances */ export ledger _balances: Map, Uint<128>>; /** * @description Mapping from owner accounts to spender accounts and their allowances. * @type {Either} account - The owner account address. * @type {Either} spender - The spender account address. * @type {Uint<128>} allowance - The amount allowed to be spent by the spender. * @type {Map>} * @type {Map, Map, Uint<128>>>} _allowances */ export ledger _allowances: Map, Map, Uint<128>>>; export ledger _totalSupply: Uint<128>; export sealed ledger _name: Opaque<"string">; export sealed ledger _symbol: Opaque<"string">; export sealed ledger _decimals: Uint<8>; /** * @description Initializes the contract by setting the name, symbol, and decimals. * @dev This MUST be called in the implementing contract's constructor. Failure to do so * can lead to an irreparable contract. * * @circuitInfo k=10, rows=71 * * @param {Opaque<"string">} name_ - The name of the token. * @param {Opaque<"string">} symbol_ - The symbol of the token. * @param {Uint<8>} decimals_ - The number of decimals used to get the user representation. * @return {[]} - Empty tuple. */ export circuit initialize( name_: Opaque<"string">, symbol_: Opaque<"string">, decimals_:Uint<8> ): [] { Initializable_initialize(); _name = disclose(name_); _symbol = disclose(symbol_); _decimals = disclose(decimals_); } /** * @description Returns the token name. * * @circuitInfo k=10, rows=37 * * Requirements: * * - Contract is initialized. * * @return {Opaque<"string">} - The token name. */ export circuit name(): Opaque<"string"> { Initializable_assertInitialized(); return _name; } /** * @description Returns the symbol of the token. * * @circuitInfo k=10, rows=37 * * Requirements: * * - Contract is initialized. * * @return {Opaque<"string">} - The token name. */ export circuit symbol(): Opaque<"string"> { Initializable_assertInitialized(); return _symbol; } /** * @description Returns the number of decimals used to get its user representation. * * @circuitInfo k=10, rows=36 * * Requirements: * * - Contract is initialized. * * @return {Uint<8>} - The account's token balance. */ export circuit decimals(): Uint<8> { Initializable_assertInitialized(); return _decimals; } /** * @description Returns the value of tokens in existence. * * @circuitInfo k=10, rows=36 * * Requirements: * * - Contract is initialized. * * @return {Uint<128>} - The total supply of tokens. */ export circuit totalSupply(): Uint<128> { Initializable_assertInitialized(); return _totalSupply; } /** * @description Returns the value of tokens owned by `account`. * * @circuitInfo k=10, rows=310 * * @dev Manually checks if `account` is a key in the map and returns 0 if it is not. * * Requirements: * * - Contract is initialized. * * @param {Either} account - The public key or contract address to query. * @return {Uint<128>} - The account's token balance. */ export circuit balanceOf(account: Either): Uint<128> { Initializable_assertInitialized(); if (!_balances.member(disclose(account))) { return 0; } return _balances.lookup(disclose(account)); } /** * @description Moves a `value` amount of tokens from the caller's account to `to`. * * @circuitInfo k=11, rows=1173 * * @notice Transfers to contract addresses are currently disallowed until contract-to-contract * interactions are supported in Compact. This restriction prevents assets from * being inadvertently locked in contracts that cannot currently handle token receipt. * * Requirements: * * - Contract is initialized. * - `to` is not a ContractAddress. * - `to` is not the zero address. * - The caller has a balance of at least `value`. * * @param {Either} to - The recipient of the transfer, either a user or a contract. * @param {Uint<128>} value - The amount to transfer. * @return {Boolean} - As per the IERC20 spec, this MUST return true. */ export circuit transfer(to: Either, value: Uint<128>): Boolean { Initializable_assertInitialized(); assert(!Utils_isContractAddress(to), "FungibleToken: Unsafe Transfer"); return _unsafeTransfer(to, value); } /** * @description Unsafe variant of `transfer` which allows transfers to contract addresses. * * @circuitInfo k=11, rows=1170 * * @warning Transfers to contract addresses are considered unsafe because contract-to-contract * calls are not currently supported. Tokens sent to a contract address may become irretrievable. * Once contract-to-contract calls are supported, this circuit may be deprecated. * * Requirements: * * - Contract is initialized. * - `to` is not the zero address. * - The caller has a balance of at least `value`. * * @param {Either} to - The recipient of the transfer, either a user or a contract. * @param {Uint<128>} value - The amount to transfer. * @return {Boolean} - As per the IERC20 spec, this MUST return true. */ export circuit _unsafeTransfer(to: Either, value: Uint<128>): Boolean { Initializable_assertInitialized(); const owner = left(ownPublicKey()); _unsafeUncheckedTransfer(owner, to, value); return true; } /** * @description Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` * through `transferFrom`. This value changes when `approve` or `transferFrom` are called. * * @circuitInfo k=10, rows=624 * * @dev Manually checks if `owner` and `spender` are keys in the map and returns 0 if they are not. * * Requirements: * * - Contract is initialized. * * @param {Either} owner - The public key or contract address of approver. * @param {Either} spender - The public key or contract address of spender. * @return {Uint<128>} - The `spender`'s allowance over `owner`'s tokens. */ export circuit allowance( owner: Either, spender: Either ): Uint<128> { Initializable_assertInitialized(); if (!_allowances.member(disclose(owner)) || !_allowances.lookup(owner).member(disclose(spender))) { return 0; } return _allowances.lookup(owner).lookup(disclose(spender)); } /** * @description Sets a `value` amount of tokens as allowance of `spender` over the caller's tokens. * * @circuitInfo k=10, rows=452 * * Requirements: * * - Contract is initialized. * - `spender` is not the zero address. * * @param {Either} spender - The Zswap key or ContractAddress that may spend on behalf of the caller. * @param {Uint<128>} value - The amount of tokens the `spender` may spend. * @return {Boolean} - Returns a boolean value indicating whether the operation succeeded. */ export circuit approve(spender: Either, value: Uint<128>): Boolean { Initializable_assertInitialized(); const owner = left(ownPublicKey()); _approve(owner, spender, value); return true; } /** * @description Moves `value` tokens from `from` to `to` using the allowance mechanism. * `value` is the deducted from the caller's allowance. * * @circuitInfo k=11, rows=1821 * * @notice Transfers to contract addresses are currently disallowed until contract-to-contract * interactions are supported in Compact. This restriction prevents assets from * being inadvertently locked in contracts that cannot currently handle token receipt. * * Requirements: * * - Contract is initialized. * - `from` is not the zero address. * - `from` must have a balance of at least `value`. * - `to` is not the zero address. * - `to` is not a ContractAddress. * - The caller has an allowance of `from`'s tokens of at least `value`. * * @param {Either} from - The current owner of the tokens for the transfer, either a user or a contract. * @param {Either} to - The recipient of the transfer, either a user or a contract. * @param {Uint<128>} value - The amount to transfer. * @return {Boolean} - As per the IERC20 spec, this MUST return true. */ export circuit transferFrom( from: Either, to: Either, value: Uint<128> ): Boolean { Initializable_assertInitialized(); assert(!Utils_isContractAddress(to), "FungibleToken: Unsafe Transfer"); return _unsafeTransferFrom(from, to, value); } /** * @description Unsafe variant of `transferFrom` which allows transfers to contract addresses. * * @circuitInfo k=11, rows=1818 * * @warning Transfers to contract addresses are considered unsafe because contract-to-contract * calls are not currently supported. Tokens sent to a contract address may become irretrievable. * Once contract-to-contract calls are supported, this circuit may be deprecated. * * Requirements: * * - Contract is initialized. * - `from` is not the zero address. * - `from` must have a balance of at least `value`. * - `to` is not the zero address. * - The caller has an allowance of `from`'s tokens of at least `value`. * * @param {Either} from - The current owner of the tokens for the transfer, either a user or a contract. * @param {Either} to - The recipient of the transfer, either a user or a contract. * @param {Uint<128>} value - The amount to transfer. * @return {Boolean} - As per the IERC20 spec, this MUST return true. */ export circuit _unsafeTransferFrom( from: Either, to: Either, value: Uint<128> ): Boolean { Initializable_assertInitialized(); const spender = left(ownPublicKey()); _spendAllowance(from, spender, value); _unsafeUncheckedTransfer(from, to, value); return true; } /** * @description Moves a `value` amount of tokens from `from` to `to`. * This circuit is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * @circuitInfo k=11, rows=1312 * * @notice Transfers to contract addresses are currently disallowed until contract-to-contract * interactions are supported in Compact. This restriction prevents assets from * being inadvertently locked in contracts that cannot currently handle token receipt. * * Requirements: * * - Contract is initialized. * - `from` is not be the zero address. * - `from` must have at least a balance of `value`. * - `to` must not be the zero address. * - `to` must not be a ContractAddress. * * @param {Either} from - The owner of the tokens to transfer. * @param {Either} to - The receipient of the transferred tokens. * @param {Uint<128>} value - The amount of tokens to transfer. * @return {[]} - Empty tuple. */ export circuit _transfer( from: Either, to: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); assert(!Utils_isContractAddress(to), "FungibleToken: Unsafe Transfer"); _unsafeUncheckedTransfer(from, to, value); } /** * @description Unsafe variant of `transferFrom` which allows transfers to contract addresses. * * @circuitInfo k=11, rows=1309 * * @warning Transfers to contract addresses are considered unsafe because contract-to-contract * calls are not currently supported. Tokens sent to a contract address may become irretrievable. * Once contract-to-contract calls are supported, this circuit may be deprecated. * * Requirements: * * - Contract is initialized. * - `from` is not the zero address. * - `to` is not the zero address. * * @param {Either} from - The owner of the tokens to transfer. * @param {Either} to - The receipient of the transferred tokens. * @param {Uint<128>} value - The amount of tokens to transfer. * @return {[]} - Empty tuple. */ export circuit _unsafeUncheckedTransfer( from: Either, to: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); assert(!Utils_isKeyOrAddressZero(from), "FungibleToken: invalid sender"); assert(!Utils_isKeyOrAddressZero(to), "FungibleToken: invalid receiver"); _update(from, to, value); } /** * @description Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` * (or `to`) is the zero address. * @dev Checks for a mint overflow in order to output a more readable error message. * * @circuitInfo k=11, rows=1305 * * Requirements: * * - Contract is initialized. * * @param {Either} from - The original owner of the tokens moved (which is 0 if tokens are minted). * @param {Either} to - The recipient of the tokens moved (which is 0 if tokens are burned). * @param {Uint<128>} value - The amount of tokens moved from `from` to `to`. * @return {[]} - Empty tuple. */ circuit _update( from: Either, to: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); if (Utils_isKeyOrAddressZero(disclose(from))) { // Mint const MAX_UINT128 = 340282366920938463463374607431768211455; assert(MAX_UINT128 - _totalSupply >= value, "FungibleToken: arithmetic overflow"); _totalSupply = disclose(_totalSupply + value as Uint<128>); } else { const fromBal = balanceOf(from); assert(fromBal >= value, "FungibleToken: insufficient balance"); _balances.insert(disclose(from), disclose(fromBal - value as Uint<128>)); } if (Utils_isKeyOrAddressZero(disclose(to))) { // Burn _totalSupply = disclose(_totalSupply - value as Uint<128>); } else { const toBal = balanceOf(to); _balances.insert(disclose(to), disclose(toBal + value as Uint<128>)); } } /** * @description Creates a `value` amount of tokens and assigns them to `account`, * by transferring it from the zero address. Relies on the `update` mechanism. * * @circuitInfo k=10, rows=752 * * @notice Transfers to contract addresses are currently disallowed until contract-to-contract * interactions are supported in Compact. This restriction prevents assets from * being inadvertently locked in contracts that cannot currently handle token receipt. * * Requirements: * * - Contract is initialized. * - `to` is not a ContractAddress. * - `account` is not the zero address. * * @param {Either} account - The recipient of tokens minted. * @param {Uint<128>} value - The amount of tokens minted. * @return {[]} - Empty tuple. */ export circuit _mint( account: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); assert(!Utils_isContractAddress(account), "FungibleToken: Unsafe Transfer"); _unsafeMint(account, value); } /** * @description Unsafe variant of `_mint` which allows transfers to contract addresses. * * @circuitInfo k=10, rows=749 * * @warning Transfers to contract addresses are considered unsafe because contract-to-contract * calls are not currently supported. Tokens sent to a contract address may become irretrievable. * Once contract-to-contract calls are supported, this circuit may be deprecated. * * Requirements: * * - Contract is initialized. * - `account` is not the zero address. * * @param {Either} account - The recipient of tokens minted. * @param {Uint<128>} value - The amount of tokens minted. * @return {[]} - Empty tuple. */ export circuit _unsafeMint( account: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); assert(!Utils_isKeyOrAddressZero(account), "FungibleToken: invalid receiver"); _update(burnAddress(), account, value); } /** * @description Destroys a `value` amount of tokens from `account`, lowering the total supply. * Relies on the `_update` mechanism. * * @circuitInfo k=10, rows=773 * * Requirements: * * - Contract is initialized. * - `account` is not the zero address. * - `account` must have at least a balance of `value`. * * @param {Either} account - The target owner of tokens to burn. * @param {Uint<128>} value - The amount of tokens to burn. * @return {[]} - Empty tuple. */ export circuit _burn( account: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); assert(!Utils_isKeyOrAddressZero(account), "FungibleToken: invalid sender"); _update(account, burnAddress(), value); } /** * @description Sets `value` as the allowance of `spender` over the `owner`'s tokens. * This circuit is equivalent to `approve`, and can be used to * * @circuitInfo k=10, rows=583 * * e.g. set automatic allowances for certain subsystems, etc. * * Requirements: * * - Contract is initialized. * - `owner` is not the zero address. * - `spender` is not the zero address. * * @param {Either} owner - The owner of the tokens. * @param {Either} spender - The spender of the tokens. * @param {Uint<128>} value - The amount of tokens `spender` may spend on behalf of `owner`. * @return {[]} - Empty tuple. */ export circuit _approve( owner: Either, spender: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); assert(!Utils_isKeyOrAddressZero(owner), "FungibleToken: invalid owner"); assert(!Utils_isKeyOrAddressZero(spender), "FungibleToken: invalid spender"); if (!_allowances.member(disclose(owner))) { // If owner doesn't exist, create and insert a new sub-map directly _allowances.insert(disclose(owner), default, Uint<128>>>); } _allowances.lookup(owner).insert(disclose(spender), disclose(value)); } /** * @description Updates `owner`'s allowance for `spender` based on spent `value`. * Does not update the allowance value in case of infinite allowance. * * @circuitInfo k=10, rows=931 * * Requirements: * * - Contract is initialized. * - `spender` must have at least an allowance of `value` from `owner`. * * @param {Either} owner - The owner of the tokens. * @param {Either} spender - The spender of the tokens. * @param {Uint<128>} value - The amount of token allowance to spend. * @return {[]} - Empty tuple. */ export circuit _spendAllowance( owner: Either, spender: Either, value: Uint<128> ): [] { Initializable_assertInitialized(); assert((_allowances.member(disclose(owner)) && _allowances.lookup(owner).member(disclose(spender))), "FungibleToken: insufficient allowance"); const currentAllowance = _allowances.lookup(owner).lookup(disclose(spender)); const MAX_UINT128 = 340282366920938463463374607431768211455; if (currentAllowance < MAX_UINT128) { assert(currentAllowance >= value, "FungibleToken: insufficient allowance"); _approve(owner, spender, currentAllowance - value as Uint<128>); } } }