ETH Price: $2,087.20 (+0.74%)

Transaction Decoder

Block:
16055349 at Nov-26-2022 04:27:47 PM +UTC
Transaction Fee:
0.00106036288465836 ETH $2.21
Gas Used:
108,070 Gas / 9.811815348 Gwei

Emitted Events:

282 PremiaStakingProxy.0xc9695243a805adb74c91f28311176c65b417e842d5699893cef56d18bfa48cba( 0xc9695243a805adb74c91f28311176c65b417e842d5699893cef56d18bfa48cba, 0x000000000000000000000000159162a97cbf388600ecb72a0597062510fe19ea, 000000000000000000000000000000000000000000000000000000000029e700 )
283 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000f1bb87563a122211d40d393ebf1c633c330377f9, 0x000000000000000000000000159162a97cbf388600ecb72a0597062510fe19ea, 000000000000000000000000000000000000000000000000000000000029e700 )

Account State Difference:

  Address   Before After State Difference Code
0x159162A9...510FE19EA
0.008401312423661646 Eth
Nonce: 19
0.007340949539003286 Eth
Nonce: 20
0.00106036288465836
0xA0b86991...E3606eB48
(Flashbots: Builder)
1.184214304462669913 Eth1.184267258762669913 Eth0.0000529543
0xF1bB8756...c330377F9

Execution Trace

PremiaStakingProxy.CALL( )
  • VxPremia.DELEGATECALL( )
    • FiatTokenProxy.a9059cbb( )
      • FiatTokenV2_1.transfer( to=0x159162A97CBF388600Ecb72A0597062510FE19EA, value=2746112 ) => ( True )
        File 1 of 4: PremiaStakingProxy
        // SPDX-License-Identifier: BUSL-1.1
        // For further clarification please see https://license.premia.legal
        pragma solidity ^0.8.0;
        import {ProxyUpgradeableOwnable} from "../ProxyUpgradeableOwnable.sol";
        import {ERC20MetadataStorage} from "@solidstate/contracts/token/ERC20/metadata/ERC20MetadataStorage.sol";
        import {PremiaStakingStorage} from "./PremiaStakingStorage.sol";
        contract PremiaStakingProxy is ProxyUpgradeableOwnable {
            using ERC20MetadataStorage for ERC20MetadataStorage.Layout;
            constructor(address implementation)
                ProxyUpgradeableOwnable(implementation)
            {
                ERC20MetadataStorage.Layout storage l = ERC20MetadataStorage.layout();
                l.setName("Staked Premia");
                l.setSymbol("xPREMIA");
                l.setDecimals(18);
                PremiaStakingStorage.layout().withdrawalDelay = 10 days;
            }
        }
        // SPDX-License-Identifier: BUSL-1.1
        // For further clarification please see https://license.premia.legal
        pragma solidity ^0.8.0;
        import {Proxy} from "@solidstate/contracts/proxy/Proxy.sol";
        import {SafeOwnable, OwnableStorage} from "@solidstate/contracts/access/SafeOwnable.sol";
        import {ProxyUpgradeableOwnableStorage} from "./ProxyUpgradeableOwnableStorage.sol";
        contract ProxyUpgradeableOwnable is Proxy, SafeOwnable {
            using ProxyUpgradeableOwnableStorage for ProxyUpgradeableOwnableStorage.Layout;
            using OwnableStorage for OwnableStorage.Layout;
            constructor(address implementation) {
                OwnableStorage.layout().setOwner(msg.sender);
                ProxyUpgradeableOwnableStorage.layout().implementation = implementation;
            }
            receive() external payable {}
            /**
             * @inheritdoc Proxy
             */
            function _getImplementation() internal view override returns (address) {
                return ProxyUpgradeableOwnableStorage.layout().implementation;
            }
            /**
             * @notice get address of implementation contract
             * @return implementation address
             */
            function getImplementation() external view returns (address) {
                return _getImplementation();
            }
            /**
             * @notice set address of implementation contract
             * @param implementation address of the new implementation
             */
            function setImplementation(address implementation) external onlyOwner {
                ProxyUpgradeableOwnableStorage.layout().implementation = implementation;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        library ERC20MetadataStorage {
            struct Layout {
                string name;
                string symbol;
                uint8 decimals;
            }
            bytes32 internal constant STORAGE_SLOT =
                keccak256('solidstate.contracts.storage.ERC20Metadata');
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
            function setName(Layout storage l, string memory name) internal {
                l.name = name;
            }
            function setSymbol(Layout storage l, string memory symbol) internal {
                l.symbol = symbol;
            }
            function setDecimals(Layout storage l, uint8 decimals) internal {
                l.decimals = decimals;
            }
        }
        // SPDX-License-Identifier: BUSL-1.1
        // For further clarification please see https://license.premia.legal
        pragma solidity ^0.8.0;
        library PremiaStakingStorage {
            bytes32 internal constant STORAGE_SLOT =
                keccak256("premia.contracts.staking.PremiaStaking");
            struct Withdrawal {
                uint256 amount; // Premia amount
                uint256 startDate; // Will unlock at startDate + withdrawalDelay
            }
            struct Layout {
                uint256 pendingWithdrawal;
                uint256 withdrawalDelay;
                mapping(address => Withdrawal) withdrawals;
            }
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import { AddressUtils } from '../utils/AddressUtils.sol';
        /**
         * @title Base proxy contract
         */
        abstract contract Proxy {
            using AddressUtils for address;
            /**
             * @notice delegate all calls to implementation contract
             * @dev reverts if implementation address contains no code, for compatibility with metamorphic contracts
             * @dev memory location in use by assembly may be unsafe in other contexts
             */
            fallback() external payable virtual {
                address implementation = _getImplementation();
                require(
                    implementation.isContract(),
                    'Proxy: implementation must be contract'
                );
                assembly {
                    calldatacopy(0, 0, calldatasize())
                    let result := delegatecall(
                        gas(),
                        implementation,
                        0,
                        calldatasize(),
                        0,
                        0
                    )
                    returndatacopy(0, 0, returndatasize())
                    switch result
                    case 0 {
                        revert(0, returndatasize())
                    }
                    default {
                        return(0, returndatasize())
                    }
                }
            }
            /**
             * @notice get logic implementation address
             * @return implementation address
             */
            function _getImplementation() internal virtual returns (address);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import { Ownable, OwnableStorage } from './Ownable.sol';
        import { SafeOwnableInternal } from './SafeOwnableInternal.sol';
        import { SafeOwnableStorage } from './SafeOwnableStorage.sol';
        /**
         * @title Ownership access control based on ERC173 with ownership transfer safety check
         */
        abstract contract SafeOwnable is Ownable, SafeOwnableInternal {
            using OwnableStorage for OwnableStorage.Layout;
            using SafeOwnableStorage for SafeOwnableStorage.Layout;
            function nomineeOwner() public view virtual returns (address) {
                return SafeOwnableStorage.layout().nomineeOwner;
            }
            /**
             * @inheritdoc Ownable
             * @dev ownership transfer must be accepted by beneficiary before transfer is complete
             */
            function transferOwnership(address account)
                public
                virtual
                override
                onlyOwner
            {
                SafeOwnableStorage.layout().setNomineeOwner(account);
            }
            /**
             * @notice accept transfer of contract ownership
             */
            function acceptOwnership() public virtual onlyNomineeOwner {
                OwnableStorage.Layout storage l = OwnableStorage.layout();
                emit OwnershipTransferred(l.owner, msg.sender);
                l.setOwner(msg.sender);
                SafeOwnableStorage.layout().setNomineeOwner(address(0));
            }
        }
        // SPDX-License-Identifier: BUSL-1.1
        // For further clarification please see https://license.premia.legal
        pragma solidity ^0.8.0;
        library ProxyUpgradeableOwnableStorage {
            bytes32 internal constant STORAGE_SLOT =
                keccak256("premia.contracts.storage.ProxyUpgradeableOwnable");
            struct Layout {
                address implementation;
            }
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        library AddressUtils {
            function toString(address account) internal pure returns (string memory) {
                bytes32 value = bytes32(uint256(uint160(account)));
                bytes memory alphabet = '0123456789abcdef';
                bytes memory chars = new bytes(42);
                chars[0] = '0';
                chars[1] = 'x';
                for (uint256 i = 0; i < 20; i++) {
                    chars[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)];
                    chars[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];
                }
                return string(chars);
            }
            function isContract(address account) internal view returns (bool) {
                uint256 size;
                assembly {
                    size := extcodesize(account)
                }
                return size > 0;
            }
            function sendValue(address payable account, uint256 amount) internal {
                (bool success, ) = account.call{ value: amount }('');
                require(success, 'AddressUtils: failed to send value');
            }
            function functionCall(address target, bytes memory data)
                internal
                returns (bytes memory)
            {
                return
                    functionCall(target, data, 'AddressUtils: failed low-level call');
            }
            function functionCall(
                address target,
                bytes memory data,
                string memory error
            ) internal returns (bytes memory) {
                return _functionCallWithValue(target, data, 0, error);
            }
            function functionCallWithValue(
                address target,
                bytes memory data,
                uint256 value
            ) internal returns (bytes memory) {
                return
                    functionCallWithValue(
                        target,
                        data,
                        value,
                        'AddressUtils: failed low-level call with value'
                    );
            }
            function functionCallWithValue(
                address target,
                bytes memory data,
                uint256 value,
                string memory error
            ) internal returns (bytes memory) {
                require(
                    address(this).balance >= value,
                    'AddressUtils: insufficient balance for call'
                );
                return _functionCallWithValue(target, data, value, error);
            }
            function _functionCallWithValue(
                address target,
                bytes memory data,
                uint256 value,
                string memory error
            ) private returns (bytes memory) {
                require(
                    isContract(target),
                    'AddressUtils: function call to non-contract'
                );
                (bool success, bytes memory returnData) = target.call{ value: value }(
                    data
                );
                if (success) {
                    return returnData;
                } else if (returnData.length > 0) {
                    assembly {
                        let returnData_size := mload(returnData)
                        revert(add(32, returnData), returnData_size)
                    }
                } else {
                    revert(error);
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import { IERC173 } from './IERC173.sol';
        import { OwnableInternal } from './OwnableInternal.sol';
        import { OwnableStorage } from './OwnableStorage.sol';
        /**
         * @title Ownership access control based on ERC173
         */
        abstract contract Ownable is IERC173, OwnableInternal {
            using OwnableStorage for OwnableStorage.Layout;
            /**
             * @inheritdoc IERC173
             */
            function owner() public view virtual override returns (address) {
                return OwnableStorage.layout().owner;
            }
            /**
             * @inheritdoc IERC173
             */
            function transferOwnership(address account)
                public
                virtual
                override
                onlyOwner
            {
                OwnableStorage.layout().setOwner(account);
                emit OwnershipTransferred(msg.sender, account);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import { SafeOwnableStorage } from './SafeOwnableStorage.sol';
        abstract contract SafeOwnableInternal {
            using SafeOwnableStorage for SafeOwnableStorage.Layout;
            modifier onlyNomineeOwner() {
                require(
                    msg.sender == SafeOwnableStorage.layout().nomineeOwner,
                    'SafeOwnable: sender must be nominee owner'
                );
                _;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        library SafeOwnableStorage {
            struct Layout {
                address nomineeOwner;
            }
            bytes32 internal constant STORAGE_SLOT =
                keccak256('solidstate.contracts.storage.SafeOwnable');
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
            function setNomineeOwner(Layout storage l, address nomineeOwner) internal {
                l.nomineeOwner = nomineeOwner;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        /**
         * @title Contract ownership standard interface
         * @dev see https://eips.ethereum.org/EIPS/eip-173
         */
        interface IERC173 {
            event OwnershipTransferred(
                address indexed previousOwner,
                address indexed newOwner
            );
            /**
             * @notice get the ERC173 contract owner
             * @return conract owner
             */
            function owner() external view returns (address);
            /**
             * @notice transfer contract ownership to new account
             * @param account address of new owner
             */
            function transferOwnership(address account) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import { OwnableStorage } from './OwnableStorage.sol';
        abstract contract OwnableInternal {
            using OwnableStorage for OwnableStorage.Layout;
            modifier onlyOwner() {
                require(
                    msg.sender == OwnableStorage.layout().owner,
                    'Ownable: sender must be owner'
                );
                _;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        library OwnableStorage {
            struct Layout {
                address owner;
            }
            bytes32 internal constant STORAGE_SLOT =
                keccak256('solidstate.contracts.storage.Ownable');
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
            function setOwner(Layout storage l, address owner) internal {
                l.owner = owner;
            }
        }
        

        File 2 of 4: FiatTokenProxy
        pragma solidity ^0.4.24;
        
        // File: zos-lib/contracts/upgradeability/Proxy.sol
        
        /**
         * @title Proxy
         * @dev Implements delegation of calls to other contracts, with proper
         * forwarding of return values and bubbling of failures.
         * It defines a fallback function that delegates all calls to the address
         * returned by the abstract _implementation() internal function.
         */
        contract Proxy {
          /**
           * @dev Fallback function.
           * Implemented entirely in `_fallback`.
           */
          function () payable external {
            _fallback();
          }
        
          /**
           * @return The Address of the implementation.
           */
          function _implementation() internal view returns (address);
        
          /**
           * @dev Delegates execution to an implementation contract.
           * This is a low level function that doesn't return to its internal call site.
           * It will return to the external caller whatever the implementation returns.
           * @param implementation Address to delegate.
           */
          function _delegate(address implementation) internal {
            assembly {
              // Copy msg.data. We take full control of memory in this inline assembly
              // block because it will not return to Solidity code. We overwrite the
              // Solidity scratch pad at memory position 0.
              calldatacopy(0, 0, calldatasize)
        
              // Call the implementation.
              // out and outsize are 0 because we don't know the size yet.
              let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
        
              // Copy the returned data.
              returndatacopy(0, 0, returndatasize)
        
              switch result
              // delegatecall returns 0 on error.
              case 0 { revert(0, returndatasize) }
              default { return(0, returndatasize) }
            }
          }
        
          /**
           * @dev Function that is run as the first thing in the fallback function.
           * Can be redefined in derived contracts to add functionality.
           * Redefinitions must call super._willFallback().
           */
          function _willFallback() internal {
          }
        
          /**
           * @dev fallback implementation.
           * Extracted to enable manual triggering.
           */
          function _fallback() internal {
            _willFallback();
            _delegate(_implementation());
          }
        }
        
        // File: openzeppelin-solidity/contracts/AddressUtils.sol
        
        /**
         * Utility library of inline functions on addresses
         */
        library AddressUtils {
        
          /**
           * Returns whether the target address is a contract
           * @dev This function will return false if invoked during the constructor of a contract,
           * as the code is not actually created until after the constructor finishes.
           * @param addr address to check
           * @return whether the target address is a contract
           */
          function isContract(address addr) internal view returns (bool) {
            uint256 size;
            // XXX Currently there is no better way to check if there is a contract in an address
            // than to check the size of the code at that address.
            // See https://ethereum.stackexchange.com/a/14016/36603
            // for more details about how this works.
            // TODO Check this again before the Serenity release, because all addresses will be
            // contracts then.
            // solium-disable-next-line security/no-inline-assembly
            assembly { size := extcodesize(addr) }
            return size > 0;
          }
        
        }
        
        // File: zos-lib/contracts/upgradeability/UpgradeabilityProxy.sol
        
        /**
         * @title UpgradeabilityProxy
         * @dev This contract implements a proxy that allows to change the
         * implementation address to which it will delegate.
         * Such a change is called an implementation upgrade.
         */
        contract UpgradeabilityProxy is Proxy {
          /**
           * @dev Emitted when the implementation is upgraded.
           * @param implementation Address of the new implementation.
           */
          event Upgraded(address implementation);
        
          /**
           * @dev Storage slot with the address of the current implementation.
           * This is the keccak-256 hash of "org.zeppelinos.proxy.implementation", and is
           * validated in the constructor.
           */
          bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3;
        
          /**
           * @dev Contract constructor.
           * @param _implementation Address of the initial implementation.
           */
          constructor(address _implementation) public {
            assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation"));
        
            _setImplementation(_implementation);
          }
        
          /**
           * @dev Returns the current implementation.
           * @return Address of the current implementation
           */
          function _implementation() internal view returns (address impl) {
            bytes32 slot = IMPLEMENTATION_SLOT;
            assembly {
              impl := sload(slot)
            }
          }
        
          /**
           * @dev Upgrades the proxy to a new implementation.
           * @param newImplementation Address of the new implementation.
           */
          function _upgradeTo(address newImplementation) internal {
            _setImplementation(newImplementation);
            emit Upgraded(newImplementation);
          }
        
          /**
           * @dev Sets the implementation address of the proxy.
           * @param newImplementation Address of the new implementation.
           */
          function _setImplementation(address newImplementation) private {
            require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
        
            bytes32 slot = IMPLEMENTATION_SLOT;
        
            assembly {
              sstore(slot, newImplementation)
            }
          }
        }
        
        // File: zos-lib/contracts/upgradeability/AdminUpgradeabilityProxy.sol
        
        /**
         * @title AdminUpgradeabilityProxy
         * @dev This contract combines an upgradeability proxy with an authorization
         * mechanism for administrative tasks.
         * All external functions in this contract must be guarded by the
         * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
         * feature proposal that would enable this to be done automatically.
         */
        contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
          /**
           * @dev Emitted when the administration has been transferred.
           * @param previousAdmin Address of the previous admin.
           * @param newAdmin Address of the new admin.
           */
          event AdminChanged(address previousAdmin, address newAdmin);
        
          /**
           * @dev Storage slot with the admin of the contract.
           * This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is
           * validated in the constructor.
           */
          bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b;
        
          /**
           * @dev Modifier to check whether the `msg.sender` is the admin.
           * If it is, it will run the function. Otherwise, it will delegate the call
           * to the implementation.
           */
          modifier ifAdmin() {
            if (msg.sender == _admin()) {
              _;
            } else {
              _fallback();
            }
          }
        
          /**
           * Contract constructor.
           * It sets the `msg.sender` as the proxy administrator.
           * @param _implementation address of the initial implementation.
           */
          constructor(address _implementation) UpgradeabilityProxy(_implementation) public {
            assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin"));
        
            _setAdmin(msg.sender);
          }
        
          /**
           * @return The address of the proxy admin.
           */
          function admin() external view ifAdmin returns (address) {
            return _admin();
          }
        
          /**
           * @return The address of the implementation.
           */
          function implementation() external view ifAdmin returns (address) {
            return _implementation();
          }
        
          /**
           * @dev Changes the admin of the proxy.
           * Only the current admin can call this function.
           * @param newAdmin Address to transfer proxy administration to.
           */
          function changeAdmin(address newAdmin) external ifAdmin {
            require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
            emit AdminChanged(_admin(), newAdmin);
            _setAdmin(newAdmin);
          }
        
          /**
           * @dev Upgrade the backing implementation of the proxy.
           * Only the admin can call this function.
           * @param newImplementation Address of the new implementation.
           */
          function upgradeTo(address newImplementation) external ifAdmin {
            _upgradeTo(newImplementation);
          }
        
          /**
           * @dev Upgrade the backing implementation of the proxy and call a function
           * on the new implementation.
           * This is useful to initialize the proxied contract.
           * @param newImplementation Address of the new implementation.
           * @param data Data to send as msg.data in the low level call.
           * It should include the signature and the parameters of the function to be
           * called, as described in
           * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.
           */
          function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin {
            _upgradeTo(newImplementation);
            require(address(this).call.value(msg.value)(data));
          }
        
          /**
           * @return The admin slot.
           */
          function _admin() internal view returns (address adm) {
            bytes32 slot = ADMIN_SLOT;
            assembly {
              adm := sload(slot)
            }
          }
        
          /**
           * @dev Sets the address of the proxy admin.
           * @param newAdmin Address of the new proxy admin.
           */
          function _setAdmin(address newAdmin) internal {
            bytes32 slot = ADMIN_SLOT;
        
            assembly {
              sstore(slot, newAdmin)
            }
          }
        
          /**
           * @dev Only fall back when the sender is not the admin.
           */
          function _willFallback() internal {
            require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
            super._willFallback();
          }
        }
        
        // File: contracts/FiatTokenProxy.sol
        
        /**
        * Copyright CENTRE SECZ 2018
        *
        * Permission is hereby granted, free of charge, to any person obtaining a copy 
        * of this software and associated documentation files (the "Software"), to deal 
        * in the Software without restriction, including without limitation the rights 
        * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
        * copies of the Software, and to permit persons to whom the Software is furnished to 
        * do so, subject to the following conditions:
        *
        * The above copyright notice and this permission notice shall be included in all 
        * copies or substantial portions of the Software.
        *
        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
        * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
        * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
        * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
        * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
        * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
        */
        
        pragma solidity ^0.4.24;
        
        
        /**
         * @title FiatTokenProxy
         * @dev This contract proxies FiatToken calls and enables FiatToken upgrades
        */ 
        contract FiatTokenProxy is AdminUpgradeabilityProxy {
            constructor(address _implementation) public AdminUpgradeabilityProxy(_implementation) {
            }
        }

        File 3 of 4: VxPremia
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC173Internal } from '../../interfaces/IERC173Internal.sol';
        interface IOwnableInternal is IERC173Internal {
            error Ownable__NotOwner();
            error Ownable__NotTransitiveOwner();
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC173 } from '../../interfaces/IERC173.sol';
        import { AddressUtils } from '../../utils/AddressUtils.sol';
        import { IOwnableInternal } from './IOwnableInternal.sol';
        import { OwnableStorage } from './OwnableStorage.sol';
        abstract contract OwnableInternal is IOwnableInternal {
            using AddressUtils for address;
            using OwnableStorage for OwnableStorage.Layout;
            modifier onlyOwner() {
                if (msg.sender != _owner()) revert Ownable__NotOwner();
                _;
            }
            modifier onlyTransitiveOwner() {
                if (msg.sender != _transitiveOwner())
                    revert Ownable__NotTransitiveOwner();
                _;
            }
            function _owner() internal view virtual returns (address) {
                return OwnableStorage.layout().owner;
            }
            function _transitiveOwner() internal view virtual returns (address) {
                address owner = _owner();
                while (owner.isContract()) {
                    try IERC173(owner).owner() returns (address transitiveOwner) {
                        owner = transitiveOwner;
                    } catch {
                        return owner;
                    }
                }
                return owner;
            }
            function _transferOwnership(address account) internal virtual {
                OwnableStorage.layout().setOwner(account);
                emit OwnershipTransferred(msg.sender, account);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        library OwnableStorage {
            struct Layout {
                address owner;
            }
            bytes32 internal constant STORAGE_SLOT =
                keccak256('solidstate.contracts.storage.Ownable');
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
            function setOwner(Layout storage l, address owner) internal {
                l.owner = owner;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        /**
         * @title Elliptic Curve Digital Signature Algorithm (ECDSA) operations
         * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
         */
        library ECDSA {
            error ECDSA__InvalidS();
            error ECDSA__InvalidSignature();
            error ECDSA__InvalidSignatureLength();
            error ECDSA__InvalidV();
            /**
             * @notice recover signer of hashed message from signature
             * @param hash hashed data payload
             * @param signature signed data payload
             * @return recovered message signer
             */
            function recover(bytes32 hash, bytes memory signature)
                internal
                pure
                returns (address)
            {
                if (signature.length != 65) revert ECDSA__InvalidSignatureLength();
                bytes32 r;
                bytes32 s;
                uint8 v;
                assembly {
                    r := mload(add(signature, 0x20))
                    s := mload(add(signature, 0x40))
                    v := byte(0, mload(add(signature, 0x60)))
                }
                return recover(hash, v, r, s);
            }
            /**
             * @notice recover signer of hashed message from signature v, r, and s values
             * @param hash hashed data payload
             * @param v signature "v" value
             * @param r signature "r" value
             * @param s signature "s" value
             * @return recovered message signer
             */
            function recover(
                bytes32 hash,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal pure returns (address) {
                // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
                // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                //
                // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                // these malleable signatures as well.
                if (
                    uint256(s) >
                    0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
                ) revert ECDSA__InvalidS();
                if (v != 27 && v != 28) revert ECDSA__InvalidV();
                // If the signature is valid (and not malleable), return the signer address
                address signer = ecrecover(hash, v, r, s);
                if (signer == address(0)) revert ECDSA__InvalidSignature();
                return signer;
            }
            /**
             * @notice generate an "Ethereum Signed Message" in the format returned by the eth_sign JSON-RPC method
             * @param hash hashed data payload
             * @return signed message hash
             */
            function toEthSignedMessageHash(bytes32 hash)
                internal
                pure
                returns (bytes32)
            {
                return
                    keccak256(
                        abi.encodePacked('\\x19Ethereum Signed Message:\
        32', hash)
                    );
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        /**
         * @title ERC165 interface registration interface
         * @dev see https://eips.ethereum.org/EIPS/eip-165
         */
        interface IERC165 {
            /**
             * @notice query whether contract has registered support for given interface
             * @param interfaceId interface id
             * @return bool whether interface is supported
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC173Internal } from './IERC173Internal.sol';
        /**
         * @title Contract ownership standard interface
         * @dev see https://eips.ethereum.org/EIPS/eip-173
         */
        interface IERC173 is IERC173Internal {
            /**
             * @notice get the ERC173 contract owner
             * @return conrtact owner
             */
            function owner() external view returns (address);
            /**
             * @notice transfer contract ownership to new account
             * @param account address of new owner
             */
            function transferOwnership(address account) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        /**
         * @title Partial ERC173 interface needed by internal functions
         */
        interface IERC173Internal {
            event OwnershipTransferred(
                address indexed previousOwner,
                address indexed newOwner
            );
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20Internal } from './IERC20Internal.sol';
        /**
         * @title ERC20 interface
         * @dev see https://github.com/ethereum/EIPs/issues/20
         */
        interface IERC20 is IERC20Internal {
            /**
             * @notice query the total minted token supply
             * @return token supply
             */
            function totalSupply() external view returns (uint256);
            /**
             * @notice query the token balance of given account
             * @param account address to query
             * @return token balance
             */
            function balanceOf(address account) external view returns (uint256);
            /**
             * @notice query the allowance granted from given holder to given spender
             * @param holder approver of allowance
             * @param spender recipient of allowance
             * @return token allowance
             */
            function allowance(address holder, address spender)
                external
                view
                returns (uint256);
            /**
             * @notice grant approval to spender to spend tokens
             * @dev prefer ERC20Extended functions to avoid transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)
             * @param spender recipient of allowance
             * @param amount quantity of tokens approved for spending
             * @return success status (always true; otherwise function should revert)
             */
            function approve(address spender, uint256 amount) external returns (bool);
            /**
             * @notice transfer tokens to given recipient
             * @param recipient beneficiary of token transfer
             * @param amount quantity of tokens to transfer
             * @return success status (always true; otherwise function should revert)
             */
            function transfer(address recipient, uint256 amount)
                external
                returns (bool);
            /**
             * @notice transfer tokens to given recipient on behalf of given holder
             * @param holder holder of tokens prior to transfer
             * @param recipient beneficiary of token transfer
             * @param amount quantity of tokens to transfer
             * @return success status (always true; otherwise function should revert)
             */
            function transferFrom(
                address holder,
                address recipient,
                uint256 amount
            ) external returns (bool);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        /**
         * @title Partial ERC20 interface needed by internal functions
         */
        interface IERC20Internal {
            event Transfer(address indexed from, address indexed to, uint256 value);
            event Approval(
                address indexed owner,
                address indexed spender,
                uint256 value
            );
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC165 } from '../interfaces/IERC165.sol';
        import { ERC165Storage } from './ERC165Storage.sol';
        /**
         * @title ERC165 implementation
         */
        abstract contract ERC165 is IERC165 {
            using ERC165Storage for ERC165Storage.Layout;
            /**
             * @inheritdoc IERC165
             */
            function supportsInterface(bytes4 interfaceId) public view returns (bool) {
                return ERC165Storage.layout().isSupportedInterface(interfaceId);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        library ERC165Storage {
            error ERC165Storage__InvalidInterfaceId();
            struct Layout {
                mapping(bytes4 => bool) supportedInterfaces;
            }
            bytes32 internal constant STORAGE_SLOT =
                keccak256('solidstate.contracts.storage.ERC165');
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
            function isSupportedInterface(Layout storage l, bytes4 interfaceId)
                internal
                view
                returns (bool)
            {
                return l.supportedInterfaces[interfaceId];
            }
            function setSupportedInterface(
                Layout storage l,
                bytes4 interfaceId,
                bool status
            ) internal {
                if (interfaceId == 0xffffffff)
                    revert ERC165Storage__InvalidInterfaceId();
                l.supportedInterfaces[interfaceId] = status;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20 } from '../../../interfaces/IERC20.sol';
        import { IERC20Base } from './IERC20Base.sol';
        import { ERC20BaseInternal } from './ERC20BaseInternal.sol';
        import { ERC20BaseStorage } from './ERC20BaseStorage.sol';
        /**
         * @title Base ERC20 implementation, excluding optional extensions
         */
        abstract contract ERC20Base is IERC20Base, ERC20BaseInternal {
            /**
             * @inheritdoc IERC20
             */
            function totalSupply() public view virtual returns (uint256) {
                return _totalSupply();
            }
            /**
             * @inheritdoc IERC20
             */
            function balanceOf(address account) public view virtual returns (uint256) {
                return _balanceOf(account);
            }
            /**
             * @inheritdoc IERC20
             */
            function allowance(address holder, address spender)
                public
                view
                virtual
                returns (uint256)
            {
                return _allowance(holder, spender);
            }
            /**
             * @inheritdoc IERC20
             */
            function approve(address spender, uint256 amount)
                public
                virtual
                returns (bool)
            {
                return _approve(msg.sender, spender, amount);
            }
            /**
             * @inheritdoc IERC20
             */
            function transfer(address recipient, uint256 amount)
                public
                virtual
                returns (bool)
            {
                return _transfer(msg.sender, recipient, amount);
            }
            /**
             * @inheritdoc IERC20
             */
            function transferFrom(
                address holder,
                address recipient,
                uint256 amount
            ) public virtual returns (bool) {
                return _transferFrom(holder, recipient, amount);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20BaseInternal } from './IERC20BaseInternal.sol';
        import { ERC20BaseStorage } from './ERC20BaseStorage.sol';
        /**
         * @title Base ERC20 internal functions, excluding optional extensions
         */
        abstract contract ERC20BaseInternal is IERC20BaseInternal {
            /**
             * @notice query the total minted token supply
             * @return token supply
             */
            function _totalSupply() internal view virtual returns (uint256) {
                return ERC20BaseStorage.layout().totalSupply;
            }
            /**
             * @notice query the token balance of given account
             * @param account address to query
             * @return token balance
             */
            function _balanceOf(address account)
                internal
                view
                virtual
                returns (uint256)
            {
                return ERC20BaseStorage.layout().balances[account];
            }
            /**
             * @notice query the allowance granted from given holder to given spender
             * @param holder approver of allowance
             * @param spender recipient of allowance
             * @return token allowance
             */
            function _allowance(address holder, address spender)
                internal
                view
                virtual
                returns (uint256)
            {
                return ERC20BaseStorage.layout().allowances[holder][spender];
            }
            /**
             * @notice enable spender to spend tokens on behalf of holder
             * @param holder address on whose behalf tokens may be spent
             * @param spender recipient of allowance
             * @param amount quantity of tokens approved for spending
             * @return success status (always true; otherwise function should revert)
             */
            function _approve(
                address holder,
                address spender,
                uint256 amount
            ) internal virtual returns (bool) {
                if (holder == address(0)) revert ERC20Base__ApproveFromZeroAddress();
                if (spender == address(0)) revert ERC20Base__ApproveToZeroAddress();
                ERC20BaseStorage.layout().allowances[holder][spender] = amount;
                emit Approval(holder, spender, amount);
                return true;
            }
            /**
             * @notice decrease spend amount granted by holder to spender
             * @param holder address on whose behalf tokens may be spent
             * @param spender address whose allowance to decrease
             * @param amount quantity by which to decrease allowance
             */
            function _decreaseAllowance(
                address holder,
                address spender,
                uint256 amount
            ) internal {
                uint256 allowance = _allowance(holder, spender);
                if (amount > allowance) revert ERC20Base__InsufficientAllowance();
                unchecked {
                    _approve(holder, spender, allowance - amount);
                }
            }
            /**
             * @notice mint tokens for given account
             * @param account recipient of minted tokens
             * @param amount quantity of tokens minted
             */
            function _mint(address account, uint256 amount) internal virtual {
                if (account == address(0)) revert ERC20Base__MintToZeroAddress();
                _beforeTokenTransfer(address(0), account, amount);
                ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
                l.totalSupply += amount;
                l.balances[account] += amount;
                emit Transfer(address(0), account, amount);
            }
            /**
             * @notice burn tokens held by given account
             * @param account holder of burned tokens
             * @param amount quantity of tokens burned
             */
            function _burn(address account, uint256 amount) internal virtual {
                if (account == address(0)) revert ERC20Base__BurnFromZeroAddress();
                _beforeTokenTransfer(account, address(0), amount);
                ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
                uint256 balance = l.balances[account];
                if (amount > balance) revert ERC20Base__BurnExceedsBalance();
                unchecked {
                    l.balances[account] = balance - amount;
                }
                l.totalSupply -= amount;
                emit Transfer(account, address(0), amount);
            }
            /**
             * @notice transfer tokens from holder to recipient
             * @param holder owner of tokens to be transferred
             * @param recipient beneficiary of transfer
             * @param amount quantity of tokens transferred
             * @return success status (always true; otherwise function should revert)
             */
            function _transfer(
                address holder,
                address recipient,
                uint256 amount
            ) internal virtual returns (bool) {
                if (holder == address(0)) revert ERC20Base__TransferFromZeroAddress();
                if (recipient == address(0)) revert ERC20Base__TransferToZeroAddress();
                _beforeTokenTransfer(holder, recipient, amount);
                ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
                uint256 holderBalance = l.balances[holder];
                if (amount > holderBalance) revert ERC20Base__TransferExceedsBalance();
                unchecked {
                    l.balances[holder] = holderBalance - amount;
                }
                l.balances[recipient] += amount;
                emit Transfer(holder, recipient, amount);
                return true;
            }
            /**
             * @notice transfer tokens to given recipient on behalf of given holder
             * @param holder holder of tokens prior to transfer
             * @param recipient beneficiary of token transfer
             * @param amount quantity of tokens to transfer
             * @return success status (always true; otherwise function should revert)
             */
            function _transferFrom(
                address holder,
                address recipient,
                uint256 amount
            ) internal virtual returns (bool) {
                _decreaseAllowance(holder, msg.sender, amount);
                _transfer(holder, recipient, amount);
                return true;
            }
            /**
             * @notice ERC20 hook, called before all transfers including mint and burn
             * @dev function should be overridden and new implementation must call super
             * @param from sender of tokens
             * @param to receiver of tokens
             * @param amount quantity of tokens transferred
             */
            function _beforeTokenTransfer(
                address from,
                address to,
                uint256 amount
            ) internal virtual {}
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        library ERC20BaseStorage {
            struct Layout {
                mapping(address => uint256) balances;
                mapping(address => mapping(address => uint256)) allowances;
                uint256 totalSupply;
            }
            bytes32 internal constant STORAGE_SLOT =
                keccak256('solidstate.contracts.storage.ERC20Base');
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20 } from '../../../interfaces/IERC20.sol';
        import { IERC20BaseInternal } from './IERC20BaseInternal.sol';
        /**
         * @title ERC20 base interface
         */
        interface IERC20Base is IERC20BaseInternal, IERC20 {
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20Internal } from '../../../interfaces/IERC20Internal.sol';
        /**
         * @title ERC20 base interface
         */
        interface IERC20BaseInternal is IERC20Internal {
            error ERC20Base__ApproveFromZeroAddress();
            error ERC20Base__ApproveToZeroAddress();
            error ERC20Base__BurnExceedsBalance();
            error ERC20Base__BurnFromZeroAddress();
            error ERC20Base__InsufficientAllowance();
            error ERC20Base__MintToZeroAddress();
            error ERC20Base__TransferExceedsBalance();
            error ERC20Base__TransferFromZeroAddress();
            error ERC20Base__TransferToZeroAddress();
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20Extended } from './IERC20Extended.sol';
        import { ERC20ExtendedInternal } from './ERC20ExtendedInternal.sol';
        /**
         * @title ERC20 safe approval extensions
         * @dev mitigations for transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)
         */
        abstract contract ERC20Extended is IERC20Extended, ERC20ExtendedInternal {
            /**
             * @inheritdoc IERC20Extended
             */
            function increaseAllowance(address spender, uint256 amount)
                external
                returns (bool)
            {
                return _increaseAllowance(spender, amount);
            }
            /**
             * @inheritdoc IERC20Extended
             */
            function decreaseAllowance(address spender, uint256 amount)
                external
                returns (bool)
            {
                return _decreaseAllowance(spender, amount);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { ERC20BaseInternal, ERC20BaseStorage } from '../base/ERC20Base.sol';
        import { IERC20ExtendedInternal } from './IERC20ExtendedInternal.sol';
        /**
         * @title ERC20 safe approval extensions
         * @dev mitigations for transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)
         */
        abstract contract ERC20ExtendedInternal is
            ERC20BaseInternal,
            IERC20ExtendedInternal
        {
            /**
             * @notice increase spend amount granted to spender
             * @param spender address whose allowance to increase
             * @param amount quantity by which to increase allowance
             * @return success status (always true; otherwise function will revert)
             */
            function _increaseAllowance(address spender, uint256 amount)
                internal
                virtual
                returns (bool)
            {
                uint256 allowance = _allowance(msg.sender, spender);
                unchecked {
                    if (allowance > allowance + amount)
                        revert ERC20Extended__ExcessiveAllowance();
                    return _approve(msg.sender, spender, allowance + amount);
                }
            }
            /**
             * @notice decrease spend amount granted to spender
             * @param spender address whose allowance to decrease
             * @param amount quantity by which to decrease allowance
             * @return success status (always true; otherwise function will revert)
             */
            function _decreaseAllowance(address spender, uint256 amount)
                internal
                virtual
                returns (bool)
            {
                _decreaseAllowance(msg.sender, spender, amount);
                return true;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20ExtendedInternal } from './IERC20ExtendedInternal.sol';
        /**
         * @title ERC20 extended interface
         */
        interface IERC20Extended is IERC20ExtendedInternal {
            /**
             * @notice increase spend amount granted to spender
             * @param spender address whose allowance to increase
             * @param amount quantity by which to increase allowance
             * @return success status (always true; otherwise function will revert)
             */
            function increaseAllowance(address spender, uint256 amount)
                external
                returns (bool);
            /**
             * @notice decrease spend amount granted to spender
             * @param spender address whose allowance to decrease
             * @param amount quantity by which to decrease allowance
             * @return success status (always true; otherwise function will revert)
             */
            function decreaseAllowance(address spender, uint256 amount)
                external
                returns (bool);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20BaseInternal } from '../base/IERC20BaseInternal.sol';
        /**
         * @title ERC20 extended internal interface
         */
        interface IERC20ExtendedInternal is IERC20BaseInternal {
            error ERC20Extended__ExcessiveAllowance();
            error ERC20Extended__InsufficientAllowance();
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20Base } from './base/IERC20Base.sol';
        import { IERC20Extended } from './extended/IERC20Extended.sol';
        import { IERC20Metadata } from './metadata/IERC20Metadata.sol';
        import { IERC20Permit } from './permit/IERC20Permit.sol';
        interface ISolidStateERC20 is
            IERC20Base,
            IERC20Extended,
            IERC20Metadata,
            IERC20Permit
        {}
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20Metadata } from './IERC20Metadata.sol';
        import { ERC20MetadataInternal } from './ERC20MetadataInternal.sol';
        /**
         * @title ERC20 metadata extensions
         */
        abstract contract ERC20Metadata is IERC20Metadata, ERC20MetadataInternal {
            /**
             * @inheritdoc IERC20Metadata
             */
            function name() external view returns (string memory) {
                return _name();
            }
            /**
             * @inheritdoc IERC20Metadata
             */
            function symbol() external view returns (string memory) {
                return _symbol();
            }
            /**
             * @inheritdoc IERC20Metadata
             */
            function decimals() external view returns (uint8) {
                return _decimals();
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20MetadataInternal } from './IERC20MetadataInternal.sol';
        import { ERC20MetadataStorage } from './ERC20MetadataStorage.sol';
        /**
         * @title ERC20Metadata internal functions
         */
        abstract contract ERC20MetadataInternal is IERC20MetadataInternal {
            /**
             * @notice return token name
             * @return token name
             */
            function _name() internal view virtual returns (string memory) {
                return ERC20MetadataStorage.layout().name;
            }
            /**
             * @notice return token symbol
             * @return token symbol
             */
            function _symbol() internal view virtual returns (string memory) {
                return ERC20MetadataStorage.layout().symbol;
            }
            /**
             * @notice return token decimals, generally used only for display purposes
             * @return token decimals
             */
            function _decimals() internal view virtual returns (uint8) {
                return ERC20MetadataStorage.layout().decimals;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        library ERC20MetadataStorage {
            struct Layout {
                string name;
                string symbol;
                uint8 decimals;
            }
            bytes32 internal constant STORAGE_SLOT =
                keccak256('solidstate.contracts.storage.ERC20Metadata');
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
            function setName(Layout storage l, string memory name) internal {
                l.name = name;
            }
            function setSymbol(Layout storage l, string memory symbol) internal {
                l.symbol = symbol;
            }
            function setDecimals(Layout storage l, uint8 decimals) internal {
                l.decimals = decimals;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20MetadataInternal } from './IERC20MetadataInternal.sol';
        /**
         * @title ERC20 metadata interface
         */
        interface IERC20Metadata is IERC20MetadataInternal {
            /**
             * @notice return token name
             * @return token name
             */
            function name() external view returns (string memory);
            /**
             * @notice return token symbol
             * @return token symbol
             */
            function symbol() external view returns (string memory);
            /**
             * @notice return token decimals, generally used only for display purposes
             * @return token decimals
             */
            function decimals() external view returns (uint8);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        /**
         * @title ERC20 metadata internal interface
         */
        interface IERC20MetadataInternal {
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { ERC20Base } from '../base/ERC20Base.sol';
        import { ERC20Metadata } from '../metadata/ERC20Metadata.sol';
        import { ERC20PermitInternal } from './ERC20PermitInternal.sol';
        import { ERC20PermitStorage } from './ERC20PermitStorage.sol';
        import { IERC2612 } from './IERC2612.sol';
        import { IERC20Permit } from './IERC20Permit.sol';
        /**
         * @title ERC20 extension with support for ERC2612 permits
         * @dev derived from https://github.com/soliditylabs/ERC20-Permit (MIT license)
         */
        abstract contract ERC20Permit is IERC20Permit, ERC20PermitInternal {
            /**
             * @inheritdoc IERC2612
             */
            function DOMAIN_SEPARATOR()
                external
                view
                returns (bytes32 domainSeparator)
            {
                return _DOMAIN_SEPARATOR();
            }
            /**
             * @inheritdoc IERC2612
             */
            function nonces(address owner) public view returns (uint256) {
                return _nonces(owner);
            }
            /**
             * @inheritdoc IERC2612
             */
            function permit(
                address owner,
                address spender,
                uint256 amount,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) public virtual {
                _permit(owner, spender, amount, deadline, v, r, s);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { ECDSA } from '../../../cryptography/ECDSA.sol';
        import { ERC20BaseInternal } from '../base/ERC20BaseInternal.sol';
        import { ERC20MetadataInternal } from '../metadata/ERC20MetadataInternal.sol';
        import { ERC20PermitStorage } from './ERC20PermitStorage.sol';
        import { IERC20PermitInternal } from './IERC20PermitInternal.sol';
        /**
         * @title ERC20 extension with support for ERC2612 permits
         * @dev derived from https://github.com/soliditylabs/ERC20-Permit (MIT license)
         */
        abstract contract ERC20PermitInternal is
            ERC20BaseInternal,
            ERC20MetadataInternal,
            IERC20PermitInternal
        {
            using ECDSA for bytes32;
            /**
             * @notice return the EIP-712 domain separator unique to contract and chain
             * @return domainSeparator domain separator
             */
            function _DOMAIN_SEPARATOR()
                internal
                view
                returns (bytes32 domainSeparator)
            {
                domainSeparator = ERC20PermitStorage.layout().domainSeparators[
                    _chainId()
                ];
                if (domainSeparator == 0x00) {
                    domainSeparator = _calculateDomainSeparator();
                }
            }
            /**
             * @notice get the current ERC2612 nonce for the given address
             * @return current nonce
             */
            function _nonces(address owner) internal view returns (uint256) {
                return ERC20PermitStorage.layout().nonces[owner];
            }
            /**
             * @notice calculate unique EIP-712 domain separator
             * @return domainSeparator domain separator
             */
            function _calculateDomainSeparator()
                internal
                view
                returns (bytes32 domainSeparator)
            {
                // no need for assembly, running very rarely
                domainSeparator = keccak256(
                    abi.encode(
                        keccak256(
                            'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
                        ),
                        keccak256(bytes(_name())), // ERC-20 Name
                        keccak256(bytes('1')), // Version
                        _chainId(),
                        address(this)
                    )
                );
            }
            /**
             * @notice get the current chain ID
             * @return chainId chain ID
             */
            function _chainId() private view returns (uint256 chainId) {
                assembly {
                    chainId := chainid()
                }
            }
            /**
             * @notice approve spender to transfer tokens held by owner via signature
             * @dev this function may be vulnerable to approval replay attacks
             * @param owner holder of tokens and signer of permit
             * @param spender beneficiary of approval
             * @param amount quantity of tokens to approve
             * @param v secp256k1 'v' value
             * @param r secp256k1 'r' value
             * @param s secp256k1 's' value
             * @dev If https://eips.ethereum.org/EIPS/eip-1344[ChainID] ever changes, the
             * EIP712 Domain Separator is automatically recalculated.
             */
            function _permit(
                address owner,
                address spender,
                uint256 amount,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal virtual {
                if (block.timestamp > deadline) revert ERC20Permit__ExpiredDeadline();
                // Assembly for more efficiently computing:
                // bytes32 hashStruct = keccak256(
                //   abi.encode(
                //     _PERMIT_TYPEHASH,
                //     owner,
                //     spender,
                //     amount,
                //     _nonces[owner].current(),
                //     deadline
                //   )
                // );
                ERC20PermitStorage.Layout storage l = ERC20PermitStorage.layout();
                bytes32 hashStruct;
                uint256 nonce = l.nonces[owner];
                assembly {
                    // Load free memory pointer
                    let pointer := mload(64)
                    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
                    mstore(
                        pointer,
                        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9
                    )
                    mstore(add(pointer, 32), owner)
                    mstore(add(pointer, 64), spender)
                    mstore(add(pointer, 96), amount)
                    mstore(add(pointer, 128), nonce)
                    mstore(add(pointer, 160), deadline)
                    hashStruct := keccak256(pointer, 192)
                }
                bytes32 domainSeparator = l.domainSeparators[_chainId()];
                if (domainSeparator == 0x00) {
                    domainSeparator = _calculateDomainSeparator();
                    l.domainSeparators[_chainId()] = domainSeparator;
                }
                // Assembly for more efficient computing:
                // bytes32 hash = keccak256(
                //   abi.encodePacked(uint16(0x1901), domainSeparator, hashStruct)
                // );
                bytes32 hash;
                assembly {
                    // Load free memory pointer
                    let pointer := mload(64)
                    mstore(
                        pointer,
                        0x1901000000000000000000000000000000000000000000000000000000000000
                    ) // EIP191 header
                    mstore(add(pointer, 2), domainSeparator) // EIP712 domain hash
                    mstore(add(pointer, 34), hashStruct) // Hash of struct
                    hash := keccak256(pointer, 66)
                }
                address signer = hash.recover(v, r, s);
                if (signer != owner) revert ERC20Permit__InvalidSignature();
                l.nonces[owner]++;
                _approve(owner, spender, amount);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        library ERC20PermitStorage {
            struct Layout {
                mapping(address => uint256) nonces;
                // Mapping of ChainID to domain separators. This is a very gas efficient way
                // to not recalculate the domain separator on every call, while still
                // automatically detecting ChainID changes.
                mapping(uint256 => bytes32) domainSeparators;
            }
            bytes32 internal constant STORAGE_SLOT =
                keccak256('solidstate.contracts.storage.ERC20Permit');
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20Metadata } from '../metadata/IERC20Metadata.sol';
        import { IERC2612 } from './IERC2612.sol';
        import { IERC20PermitInternal } from './IERC20PermitInternal.sol';
        // TODO: note that IERC20Metadata is needed for eth-permit library
        interface IERC20Permit is IERC20PermitInternal, IERC2612 {
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC2612Internal } from './IERC2612Internal.sol';
        interface IERC20PermitInternal is IERC2612Internal {
            error ERC20Permit__ExpiredDeadline();
            error ERC20Permit__InvalidSignature();
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC2612Internal } from './IERC2612Internal.sol';
        /**
         * @title ERC2612 interface
         * @dev see https://eips.ethereum.org/EIPS/eip-2612.
         */
        interface IERC2612 is IERC2612Internal {
            /**
             * @notice return the EIP-712 domain separator unique to contract and chain
             * @return domainSeparator domain separator
             */
            function DOMAIN_SEPARATOR() external view returns (bytes32 domainSeparator);
            /**
             * @notice get the current ERC2612 nonce for the given address
             * @return current nonce
             */
            function nonces(address owner) external view returns (uint256);
            /**
             * @notice approve spender to transfer tokens held by owner via signature
             * @dev this function may be vulnerable to approval replay attacks
             * @param owner holder of tokens and signer of permit
             * @param spender beneficiary of approval
             * @param amount quantity of tokens to approve
             * @param v secp256k1 'v' value
             * @param r secp256k1 'r' value
             * @param s secp256k1 's' value
             */
            function permit(
                address owner,
                address spender,
                uint256 amount,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        interface IERC2612Internal {}
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { ISolidStateERC20 } from './ISolidStateERC20.sol';
        import { ERC20Base } from './base/ERC20Base.sol';
        import { ERC20Extended } from './extended/ERC20Extended.sol';
        import { ERC20Metadata } from './metadata/ERC20Metadata.sol';
        import { ERC20Permit } from './permit/ERC20Permit.sol';
        /**
         * @title SolidState ERC20 implementation, including recommended extensions
         */
        abstract contract SolidStateERC20 is
            ISolidStateERC20,
            ERC20Base,
            ERC20Extended,
            ERC20Metadata,
            ERC20Permit
        {
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { UintUtils } from './UintUtils.sol';
        library AddressUtils {
            using UintUtils for uint256;
            error AddressUtils__InsufficientBalance();
            error AddressUtils__NotContract();
            error AddressUtils__SendValueFailed();
            function toString(address account) internal pure returns (string memory) {
                return uint256(uint160(account)).toHexString(20);
            }
            function isContract(address account) internal view returns (bool) {
                uint256 size;
                assembly {
                    size := extcodesize(account)
                }
                return size > 0;
            }
            function sendValue(address payable account, uint256 amount) internal {
                (bool success, ) = account.call{ value: amount }('');
                if (!success) revert AddressUtils__SendValueFailed();
            }
            function functionCall(address target, bytes memory data)
                internal
                returns (bytes memory)
            {
                return
                    functionCall(target, data, 'AddressUtils: failed low-level call');
            }
            function functionCall(
                address target,
                bytes memory data,
                string memory error
            ) internal returns (bytes memory) {
                return _functionCallWithValue(target, data, 0, error);
            }
            function functionCallWithValue(
                address target,
                bytes memory data,
                uint256 value
            ) internal returns (bytes memory) {
                return
                    functionCallWithValue(
                        target,
                        data,
                        value,
                        'AddressUtils: failed low-level call with value'
                    );
            }
            function functionCallWithValue(
                address target,
                bytes memory data,
                uint256 value,
                string memory error
            ) internal returns (bytes memory) {
                if (value > address(this).balance)
                    revert AddressUtils__InsufficientBalance();
                return _functionCallWithValue(target, data, value, error);
            }
            function _functionCallWithValue(
                address target,
                bytes memory data,
                uint256 value,
                string memory error
            ) private returns (bytes memory) {
                if (!isContract(target)) revert AddressUtils__NotContract();
                (bool success, bytes memory returnData) = target.call{ value: value }(
                    data
                );
                if (success) {
                    return returnData;
                } else if (returnData.length > 0) {
                    assembly {
                        let returnData_size := mload(returnData)
                        revert(add(32, returnData), returnData_size)
                    }
                } else {
                    revert(error);
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        library Math {
            /**
             * @notice calculate the absolute value of a number
             * @param a number whose absoluve value to calculate
             * @return absolute value
             */
            function abs(int256 a) internal pure returns (uint256) {
                return uint256(a < 0 ? -a : a);
            }
            /**
             * @notice select the greater of two numbers
             * @param a first number
             * @param b second number
             * @return greater number
             */
            function max(uint256 a, uint256 b) internal pure returns (uint256) {
                return a > b ? a : b;
            }
            /**
             * @notice select the lesser of two numbers
             * @param a first number
             * @param b second number
             * @return lesser number
             */
            function min(uint256 a, uint256 b) internal pure returns (uint256) {
                return a > b ? b : a;
            }
            /**
             * @notice calculate the average of two numbers, rounded down
             * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
             * @param a first number
             * @param b second number
             * @return mean value
             */
            function average(uint256 a, uint256 b) internal pure returns (uint256) {
                unchecked {
                    return (a & b) + ((a ^ b) >> 1);
                }
            }
            /**
             * @notice estimate square root of number
             * @dev uses Babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
             * @param x input number
             * @return y square root
             */
            function sqrt(uint256 x) internal pure returns (uint256 y) {
                uint256 z = (x + 1) >> 1;
                y = x;
                while (z < y) {
                    y = z;
                    z = (x / z + z) >> 1;
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        import { IERC20 } from '../interfaces/IERC20.sol';
        import { AddressUtils } from './AddressUtils.sol';
        /**
         * @title Safe ERC20 interaction library
         * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)
         */
        library SafeERC20 {
            using AddressUtils for address;
            error SafeERC20__ApproveFromNonZeroToNonZero();
            error SafeERC20__DecreaseAllowanceBelowZero();
            error SafeERC20__OperationFailed();
            function safeTransfer(
                IERC20 token,
                address to,
                uint256 value
            ) internal {
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(token.transfer.selector, to, value)
                );
            }
            function safeTransferFrom(
                IERC20 token,
                address from,
                address to,
                uint256 value
            ) internal {
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
                );
            }
            /**
             * @dev safeApprove (like approve) should only be called when setting an initial allowance or when resetting it to zero; otherwise prefer safeIncreaseAllowance and safeDecreaseAllowance
             */
            function safeApprove(
                IERC20 token,
                address spender,
                uint256 value
            ) internal {
                if ((value != 0) && (token.allowance(address(this), spender) != 0))
                    revert SafeERC20__ApproveFromNonZeroToNonZero();
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(token.approve.selector, spender, value)
                );
            }
            function safeIncreaseAllowance(
                IERC20 token,
                address spender,
                uint256 value
            ) internal {
                uint256 newAllowance = token.allowance(address(this), spender) + value;
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(
                        token.approve.selector,
                        spender,
                        newAllowance
                    )
                );
            }
            function safeDecreaseAllowance(
                IERC20 token,
                address spender,
                uint256 value
            ) internal {
                unchecked {
                    uint256 oldAllowance = token.allowance(address(this), spender);
                    if (oldAllowance < value)
                        revert SafeERC20__DecreaseAllowanceBelowZero();
                    uint256 newAllowance = oldAllowance - value;
                    _callOptionalReturn(
                        token,
                        abi.encodeWithSelector(
                            token.approve.selector,
                            spender,
                            newAllowance
                        )
                    );
                }
            }
            /**
             * @notice send transaction data and check validity of return value, if present
             * @param token ERC20 token interface
             * @param data transaction data
             */
            function _callOptionalReturn(IERC20 token, bytes memory data) private {
                bytes memory returndata = address(token).functionCall(
                    data,
                    'SafeERC20: low-level call failed'
                );
                if (returndata.length > 0) {
                    if (!abi.decode(returndata, (bool)))
                        revert SafeERC20__OperationFailed();
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.8;
        /**
         * @title utility functions for uint256 operations
         * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)
         */
        library UintUtils {
            error UintUtils__InsufficientHexLength();
            bytes16 private constant HEX_SYMBOLS = '0123456789abcdef';
            function add(uint256 a, int256 b) internal pure returns (uint256) {
                return b < 0 ? sub(a, -b) : a + uint256(b);
            }
            function sub(uint256 a, int256 b) internal pure returns (uint256) {
                return b < 0 ? add(a, -b) : a - uint256(b);
            }
            function toString(uint256 value) internal pure returns (string memory) {
                if (value == 0) {
                    return '0';
                }
                uint256 temp = value;
                uint256 digits;
                while (temp != 0) {
                    digits++;
                    temp /= 10;
                }
                bytes memory buffer = new bytes(digits);
                while (value != 0) {
                    digits -= 1;
                    buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                    value /= 10;
                }
                return string(buffer);
            }
            function toHexString(uint256 value) internal pure returns (string memory) {
                if (value == 0) {
                    return '0x00';
                }
                uint256 length = 0;
                for (uint256 temp = value; temp != 0; temp >>= 8) {
                    unchecked {
                        length++;
                    }
                }
                return toHexString(value, length);
            }
            function toHexString(uint256 value, uint256 length)
                internal
                pure
                returns (string memory)
            {
                bytes memory buffer = new bytes(2 * length + 2);
                buffer[0] = '0';
                buffer[1] = 'x';
                unchecked {
                    for (uint256 i = 2 * length + 1; i > 1; --i) {
                        buffer[i] = HEX_SYMBOLS[value & 0xf];
                        value >>= 4;
                    }
                }
                if (value != 0) revert UintUtils__InsufficientHexLength();
                return string(buffer);
            }
        }
        // SPDX-License-Identifier: BSD-4-Clause
        /*
         * ABDK Math 64.64 Smart Contract Library.  Copyright © 2019 by ABDK Consulting.
         * Author: Mikhail Vladimirov <mikhail.vladimirov@gmail.com>
         */
        pragma solidity ^0.8.0;
        /**
         * Smart contract library of mathematical functions operating with signed
         * 64.64-bit fixed point numbers.  Signed 64.64-bit fixed point number is
         * basically a simple fraction whose numerator is signed 128-bit integer and
         * denominator is 2^64.  As long as denominator is always the same, there is no
         * need to store it, thus in Solidity signed 64.64-bit fixed point numbers are
         * represented by int128 type holding only the numerator.
         */
        library ABDKMath64x64 {
          /*
           * Minimum value signed 64.64-bit fixed point number may have. 
           */
          int128 private constant MIN_64x64 = -0x80000000000000000000000000000000;
          /*
           * Maximum value signed 64.64-bit fixed point number may have. 
           */
          int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
          /**
           * Convert signed 256-bit integer number into signed 64.64-bit fixed point
           * number.  Revert on overflow.
           *
           * @param x signed 256-bit integer number
           * @return signed 64.64-bit fixed point number
           */
          function fromInt (int256 x) internal pure returns (int128) {
            unchecked {
              require (x >= -0x8000000000000000 && x <= 0x7FFFFFFFFFFFFFFF);
              return int128 (x << 64);
            }
          }
          /**
           * Convert signed 64.64 fixed point number into signed 64-bit integer number
           * rounding down.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64-bit integer number
           */
          function toInt (int128 x) internal pure returns (int64) {
            unchecked {
              return int64 (x >> 64);
            }
          }
          /**
           * Convert unsigned 256-bit integer number into signed 64.64-bit fixed point
           * number.  Revert on overflow.
           *
           * @param x unsigned 256-bit integer number
           * @return signed 64.64-bit fixed point number
           */
          function fromUInt (uint256 x) internal pure returns (int128) {
            unchecked {
              require (x <= 0x7FFFFFFFFFFFFFFF);
              return int128 (int256 (x << 64));
            }
          }
          /**
           * Convert signed 64.64 fixed point number into unsigned 64-bit integer
           * number rounding down.  Revert on underflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @return unsigned 64-bit integer number
           */
          function toUInt (int128 x) internal pure returns (uint64) {
            unchecked {
              require (x >= 0);
              return uint64 (uint128 (x >> 64));
            }
          }
          /**
           * Convert signed 128.128 fixed point number into signed 64.64-bit fixed point
           * number rounding down.  Revert on overflow.
           *
           * @param x signed 128.128-bin fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function from128x128 (int256 x) internal pure returns (int128) {
            unchecked {
              int256 result = x >> 64;
              require (result >= MIN_64x64 && result <= MAX_64x64);
              return int128 (result);
            }
          }
          /**
           * Convert signed 64.64 fixed point number into signed 128.128 fixed point
           * number.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 128.128 fixed point number
           */
          function to128x128 (int128 x) internal pure returns (int256) {
            unchecked {
              return int256 (x) << 64;
            }
          }
          /**
           * Calculate x + y.  Revert on overflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @param y signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function add (int128 x, int128 y) internal pure returns (int128) {
            unchecked {
              int256 result = int256(x) + y;
              require (result >= MIN_64x64 && result <= MAX_64x64);
              return int128 (result);
            }
          }
          /**
           * Calculate x - y.  Revert on overflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @param y signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function sub (int128 x, int128 y) internal pure returns (int128) {
            unchecked {
              int256 result = int256(x) - y;
              require (result >= MIN_64x64 && result <= MAX_64x64);
              return int128 (result);
            }
          }
          /**
           * Calculate x * y rounding down.  Revert on overflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @param y signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function mul (int128 x, int128 y) internal pure returns (int128) {
            unchecked {
              int256 result = int256(x) * y >> 64;
              require (result >= MIN_64x64 && result <= MAX_64x64);
              return int128 (result);
            }
          }
          /**
           * Calculate x * y rounding towards zero, where x is signed 64.64 fixed point
           * number and y is signed 256-bit integer number.  Revert on overflow.
           *
           * @param x signed 64.64 fixed point number
           * @param y signed 256-bit integer number
           * @return signed 256-bit integer number
           */
          function muli (int128 x, int256 y) internal pure returns (int256) {
            unchecked {
              if (x == MIN_64x64) {
                require (y >= -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF &&
                  y <= 0x1000000000000000000000000000000000000000000000000);
                return -y << 63;
              } else {
                bool negativeResult = false;
                if (x < 0) {
                  x = -x;
                  negativeResult = true;
                }
                if (y < 0) {
                  y = -y; // We rely on overflow behavior here
                  negativeResult = !negativeResult;
                }
                uint256 absoluteResult = mulu (x, uint256 (y));
                if (negativeResult) {
                  require (absoluteResult <=
                    0x8000000000000000000000000000000000000000000000000000000000000000);
                  return -int256 (absoluteResult); // We rely on overflow behavior here
                } else {
                  require (absoluteResult <=
                    0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
                  return int256 (absoluteResult);
                }
              }
            }
          }
          /**
           * Calculate x * y rounding down, where x is signed 64.64 fixed point number
           * and y is unsigned 256-bit integer number.  Revert on overflow.
           *
           * @param x signed 64.64 fixed point number
           * @param y unsigned 256-bit integer number
           * @return unsigned 256-bit integer number
           */
          function mulu (int128 x, uint256 y) internal pure returns (uint256) {
            unchecked {
              if (y == 0) return 0;
              require (x >= 0);
              uint256 lo = (uint256 (int256 (x)) * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) >> 64;
              uint256 hi = uint256 (int256 (x)) * (y >> 128);
              require (hi <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
              hi <<= 64;
              require (hi <=
                0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - lo);
              return hi + lo;
            }
          }
          /**
           * Calculate x / y rounding towards zero.  Revert on overflow or when y is
           * zero.
           *
           * @param x signed 64.64-bit fixed point number
           * @param y signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function div (int128 x, int128 y) internal pure returns (int128) {
            unchecked {
              require (y != 0);
              int256 result = (int256 (x) << 64) / y;
              require (result >= MIN_64x64 && result <= MAX_64x64);
              return int128 (result);
            }
          }
          /**
           * Calculate x / y rounding towards zero, where x and y are signed 256-bit
           * integer numbers.  Revert on overflow or when y is zero.
           *
           * @param x signed 256-bit integer number
           * @param y signed 256-bit integer number
           * @return signed 64.64-bit fixed point number
           */
          function divi (int256 x, int256 y) internal pure returns (int128) {
            unchecked {
              require (y != 0);
              bool negativeResult = false;
              if (x < 0) {
                x = -x; // We rely on overflow behavior here
                negativeResult = true;
              }
              if (y < 0) {
                y = -y; // We rely on overflow behavior here
                negativeResult = !negativeResult;
              }
              uint128 absoluteResult = divuu (uint256 (x), uint256 (y));
              if (negativeResult) {
                require (absoluteResult <= 0x80000000000000000000000000000000);
                return -int128 (absoluteResult); // We rely on overflow behavior here
              } else {
                require (absoluteResult <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
                return int128 (absoluteResult); // We rely on overflow behavior here
              }
            }
          }
          /**
           * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
           * integer numbers.  Revert on overflow or when y is zero.
           *
           * @param x unsigned 256-bit integer number
           * @param y unsigned 256-bit integer number
           * @return signed 64.64-bit fixed point number
           */
          function divu (uint256 x, uint256 y) internal pure returns (int128) {
            unchecked {
              require (y != 0);
              uint128 result = divuu (x, y);
              require (result <= uint128 (MAX_64x64));
              return int128 (result);
            }
          }
          /**
           * Calculate -x.  Revert on overflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function neg (int128 x) internal pure returns (int128) {
            unchecked {
              require (x != MIN_64x64);
              return -x;
            }
          }
          /**
           * Calculate |x|.  Revert on overflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function abs (int128 x) internal pure returns (int128) {
            unchecked {
              require (x != MIN_64x64);
              return x < 0 ? -x : x;
            }
          }
          /**
           * Calculate 1 / x rounding towards zero.  Revert on overflow or when x is
           * zero.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function inv (int128 x) internal pure returns (int128) {
            unchecked {
              require (x != 0);
              int256 result = int256 (0x100000000000000000000000000000000) / x;
              require (result >= MIN_64x64 && result <= MAX_64x64);
              return int128 (result);
            }
          }
          /**
           * Calculate arithmetics average of x and y, i.e. (x + y) / 2 rounding down.
           *
           * @param x signed 64.64-bit fixed point number
           * @param y signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function avg (int128 x, int128 y) internal pure returns (int128) {
            unchecked {
              return int128 ((int256 (x) + int256 (y)) >> 1);
            }
          }
          /**
           * Calculate geometric average of x and y, i.e. sqrt (x * y) rounding down.
           * Revert on overflow or in case x * y is negative.
           *
           * @param x signed 64.64-bit fixed point number
           * @param y signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function gavg (int128 x, int128 y) internal pure returns (int128) {
            unchecked {
              int256 m = int256 (x) * int256 (y);
              require (m >= 0);
              require (m <
                  0x4000000000000000000000000000000000000000000000000000000000000000);
              return int128 (sqrtu (uint256 (m)));
            }
          }
          /**
           * Calculate x^y assuming 0^0 is 1, where x is signed 64.64 fixed point number
           * and y is unsigned 256-bit integer number.  Revert on overflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @param y uint256 value
           * @return signed 64.64-bit fixed point number
           */
          function pow (int128 x, uint256 y) internal pure returns (int128) {
            unchecked {
              bool negative = x < 0 && y & 1 == 1;
              uint256 absX = uint128 (x < 0 ? -x : x);
              uint256 absResult;
              absResult = 0x100000000000000000000000000000000;
              if (absX <= 0x10000000000000000) {
                absX <<= 63;
                while (y != 0) {
                  if (y & 0x1 != 0) {
                    absResult = absResult * absX >> 127;
                  }
                  absX = absX * absX >> 127;
                  if (y & 0x2 != 0) {
                    absResult = absResult * absX >> 127;
                  }
                  absX = absX * absX >> 127;
                  if (y & 0x4 != 0) {
                    absResult = absResult * absX >> 127;
                  }
                  absX = absX * absX >> 127;
                  if (y & 0x8 != 0) {
                    absResult = absResult * absX >> 127;
                  }
                  absX = absX * absX >> 127;
                  y >>= 4;
                }
                absResult >>= 64;
              } else {
                uint256 absXShift = 63;
                if (absX < 0x1000000000000000000000000) { absX <<= 32; absXShift -= 32; }
                if (absX < 0x10000000000000000000000000000) { absX <<= 16; absXShift -= 16; }
                if (absX < 0x1000000000000000000000000000000) { absX <<= 8; absXShift -= 8; }
                if (absX < 0x10000000000000000000000000000000) { absX <<= 4; absXShift -= 4; }
                if (absX < 0x40000000000000000000000000000000) { absX <<= 2; absXShift -= 2; }
                if (absX < 0x80000000000000000000000000000000) { absX <<= 1; absXShift -= 1; }
                uint256 resultShift = 0;
                while (y != 0) {
                  require (absXShift < 64);
                  if (y & 0x1 != 0) {
                    absResult = absResult * absX >> 127;
                    resultShift += absXShift;
                    if (absResult > 0x100000000000000000000000000000000) {
                      absResult >>= 1;
                      resultShift += 1;
                    }
                  }
                  absX = absX * absX >> 127;
                  absXShift <<= 1;
                  if (absX >= 0x100000000000000000000000000000000) {
                      absX >>= 1;
                      absXShift += 1;
                  }
                  y >>= 1;
                }
                require (resultShift < 64);
                absResult >>= 64 - resultShift;
              }
              int256 result = negative ? -int256 (absResult) : int256 (absResult);
              require (result >= MIN_64x64 && result <= MAX_64x64);
              return int128 (result);
            }
          }
          /**
           * Calculate sqrt (x) rounding down.  Revert if x < 0.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function sqrt (int128 x) internal pure returns (int128) {
            unchecked {
              require (x >= 0);
              return int128 (sqrtu (uint256 (int256 (x)) << 64));
            }
          }
          /**
           * Calculate binary logarithm of x.  Revert if x <= 0.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function log_2 (int128 x) internal pure returns (int128) {
            unchecked {
              require (x > 0);
              int256 msb = 0;
              int256 xc = x;
              if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }
              if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
              if (xc >= 0x10000) { xc >>= 16; msb += 16; }
              if (xc >= 0x100) { xc >>= 8; msb += 8; }
              if (xc >= 0x10) { xc >>= 4; msb += 4; }
              if (xc >= 0x4) { xc >>= 2; msb += 2; }
              if (xc >= 0x2) msb += 1;  // No need to shift xc anymore
              int256 result = msb - 64 << 64;
              uint256 ux = uint256 (int256 (x)) << uint256 (127 - msb);
              for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) {
                ux *= ux;
                uint256 b = ux >> 255;
                ux >>= 127 + b;
                result += bit * int256 (b);
              }
              return int128 (result);
            }
          }
          /**
           * Calculate natural logarithm of x.  Revert if x <= 0.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function ln (int128 x) internal pure returns (int128) {
            unchecked {
              require (x > 0);
              return int128 (int256 (
                  uint256 (int256 (log_2 (x))) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF >> 128));
            }
          }
          /**
           * Calculate binary exponent of x.  Revert on overflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function exp_2 (int128 x) internal pure returns (int128) {
            unchecked {
              require (x < 0x400000000000000000); // Overflow
              if (x < -0x400000000000000000) return 0; // Underflow
              uint256 result = 0x80000000000000000000000000000000;
              if (x & 0x8000000000000000 > 0)
                result = result * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;
              if (x & 0x4000000000000000 > 0)
                result = result * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;
              if (x & 0x2000000000000000 > 0)
                result = result * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;
              if (x & 0x1000000000000000 > 0)
                result = result * 0x10B5586CF9890F6298B92B71842A98363 >> 128;
              if (x & 0x800000000000000 > 0)
                result = result * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;
              if (x & 0x400000000000000 > 0)
                result = result * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;
              if (x & 0x200000000000000 > 0)
                result = result * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;
              if (x & 0x100000000000000 > 0)
                result = result * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;
              if (x & 0x80000000000000 > 0)
                result = result * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;
              if (x & 0x40000000000000 > 0)
                result = result * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;
              if (x & 0x20000000000000 > 0)
                result = result * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;
              if (x & 0x10000000000000 > 0)
                result = result * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;
              if (x & 0x8000000000000 > 0)
                result = result * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;
              if (x & 0x4000000000000 > 0)
                result = result * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;
              if (x & 0x2000000000000 > 0)
                result = result * 0x1000162E525EE054754457D5995292026 >> 128;
              if (x & 0x1000000000000 > 0)
                result = result * 0x10000B17255775C040618BF4A4ADE83FC >> 128;
              if (x & 0x800000000000 > 0)
                result = result * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;
              if (x & 0x400000000000 > 0)
                result = result * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;
              if (x & 0x200000000000 > 0)
                result = result * 0x10000162E43F4F831060E02D839A9D16D >> 128;
              if (x & 0x100000000000 > 0)
                result = result * 0x100000B1721BCFC99D9F890EA06911763 >> 128;
              if (x & 0x80000000000 > 0)
                result = result * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;
              if (x & 0x40000000000 > 0)
                result = result * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;
              if (x & 0x20000000000 > 0)
                result = result * 0x100000162E430E5A18F6119E3C02282A5 >> 128;
              if (x & 0x10000000000 > 0)
                result = result * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;
              if (x & 0x8000000000 > 0)
                result = result * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;
              if (x & 0x4000000000 > 0)
                result = result * 0x10000002C5C8601CC6B9E94213C72737A >> 128;
              if (x & 0x2000000000 > 0)
                result = result * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;
              if (x & 0x1000000000 > 0)
                result = result * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;
              if (x & 0x800000000 > 0)
                result = result * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;
              if (x & 0x400000000 > 0)
                result = result * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;
              if (x & 0x200000000 > 0)
                result = result * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;
              if (x & 0x100000000 > 0)
                result = result * 0x100000000B17217F80F4EF5AADDA45554 >> 128;
              if (x & 0x80000000 > 0)
                result = result * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;
              if (x & 0x40000000 > 0)
                result = result * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;
              if (x & 0x20000000 > 0)
                result = result * 0x100000000162E42FEFB2FED257559BDAA >> 128;
              if (x & 0x10000000 > 0)
                result = result * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;
              if (x & 0x8000000 > 0)
                result = result * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;
              if (x & 0x4000000 > 0)
                result = result * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;
              if (x & 0x2000000 > 0)
                result = result * 0x1000000000162E42FEFA494F1478FDE05 >> 128;
              if (x & 0x1000000 > 0)
                result = result * 0x10000000000B17217F7D20CF927C8E94C >> 128;
              if (x & 0x800000 > 0)
                result = result * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;
              if (x & 0x400000 > 0)
                result = result * 0x100000000002C5C85FDF477B662B26945 >> 128;
              if (x & 0x200000 > 0)
                result = result * 0x10000000000162E42FEFA3AE53369388C >> 128;
              if (x & 0x100000 > 0)
                result = result * 0x100000000000B17217F7D1D351A389D40 >> 128;
              if (x & 0x80000 > 0)
                result = result * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;
              if (x & 0x40000 > 0)
                result = result * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;
              if (x & 0x20000 > 0)
                result = result * 0x100000000000162E42FEFA39FE95583C2 >> 128;
              if (x & 0x10000 > 0)
                result = result * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;
              if (x & 0x8000 > 0)
                result = result * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;
              if (x & 0x4000 > 0)
                result = result * 0x10000000000002C5C85FDF473E242EA38 >> 128;
              if (x & 0x2000 > 0)
                result = result * 0x1000000000000162E42FEFA39F02B772C >> 128;
              if (x & 0x1000 > 0)
                result = result * 0x10000000000000B17217F7D1CF7D83C1A >> 128;
              if (x & 0x800 > 0)
                result = result * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;
              if (x & 0x400 > 0)
                result = result * 0x100000000000002C5C85FDF473DEA871F >> 128;
              if (x & 0x200 > 0)
                result = result * 0x10000000000000162E42FEFA39EF44D91 >> 128;
              if (x & 0x100 > 0)
                result = result * 0x100000000000000B17217F7D1CF79E949 >> 128;
              if (x & 0x80 > 0)
                result = result * 0x10000000000000058B90BFBE8E7BCE544 >> 128;
              if (x & 0x40 > 0)
                result = result * 0x1000000000000002C5C85FDF473DE6ECA >> 128;
              if (x & 0x20 > 0)
                result = result * 0x100000000000000162E42FEFA39EF366F >> 128;
              if (x & 0x10 > 0)
                result = result * 0x1000000000000000B17217F7D1CF79AFA >> 128;
              if (x & 0x8 > 0)
                result = result * 0x100000000000000058B90BFBE8E7BCD6D >> 128;
              if (x & 0x4 > 0)
                result = result * 0x10000000000000002C5C85FDF473DE6B2 >> 128;
              if (x & 0x2 > 0)
                result = result * 0x1000000000000000162E42FEFA39EF358 >> 128;
              if (x & 0x1 > 0)
                result = result * 0x10000000000000000B17217F7D1CF79AB >> 128;
              result >>= uint256 (int256 (63 - (x >> 64)));
              require (result <= uint256 (int256 (MAX_64x64)));
              return int128 (int256 (result));
            }
          }
          /**
           * Calculate natural exponent of x.  Revert on overflow.
           *
           * @param x signed 64.64-bit fixed point number
           * @return signed 64.64-bit fixed point number
           */
          function exp (int128 x) internal pure returns (int128) {
            unchecked {
              require (x < 0x400000000000000000); // Overflow
              if (x < -0x400000000000000000) return 0; // Underflow
              return exp_2 (
                  int128 (int256 (x) * 0x171547652B82FE1777D0FFDA0D23A7D12 >> 128));
            }
          }
          /**
           * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
           * integer numbers.  Revert on overflow or when y is zero.
           *
           * @param x unsigned 256-bit integer number
           * @param y unsigned 256-bit integer number
           * @return unsigned 64.64-bit fixed point number
           */
          function divuu (uint256 x, uint256 y) private pure returns (uint128) {
            unchecked {
              require (y != 0);
              uint256 result;
              if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
                result = (x << 64) / y;
              else {
                uint256 msb = 192;
                uint256 xc = x >> 192;
                if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
                if (xc >= 0x10000) { xc >>= 16; msb += 16; }
                if (xc >= 0x100) { xc >>= 8; msb += 8; }
                if (xc >= 0x10) { xc >>= 4; msb += 4; }
                if (xc >= 0x4) { xc >>= 2; msb += 2; }
                if (xc >= 0x2) msb += 1;  // No need to shift xc anymore
                result = (x << 255 - msb) / ((y - 1 >> msb - 191) + 1);
                require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
                uint256 hi = result * (y >> 128);
                uint256 lo = result * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
                uint256 xh = x >> 192;
                uint256 xl = x << 64;
                if (xl < lo) xh -= 1;
                xl -= lo; // We rely on overflow behavior here
                lo = hi << 128;
                if (xl < lo) xh -= 1;
                xl -= lo; // We rely on overflow behavior here
                assert (xh == hi >> 128);
                result += xl / y;
              }
              require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
              return uint128 (result);
            }
          }
          /**
           * Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer
           * number.
           *
           * @param x unsigned 256-bit integer number
           * @return unsigned 128-bit integer number
           */
          function sqrtu (uint256 x) private pure returns (uint128) {
            unchecked {
              if (x == 0) return 0;
              else {
                uint256 xx = x;
                uint256 r = 1;
                if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; }
                if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; }
                if (xx >= 0x100000000) { xx >>= 32; r <<= 16; }
                if (xx >= 0x10000) { xx >>= 16; r <<= 8; }
                if (xx >= 0x100) { xx >>= 8; r <<= 4; }
                if (xx >= 0x10) { xx >>= 4; r <<= 2; }
                if (xx >= 0x4) { r <<= 1; }
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1; // Seven iterations should be enough
                uint256 r1 = x / r;
                return uint128 (r < r1 ? r : r1);
              }
            }
          }
        }
        // SPDX-License-Identifier: LGPL-3.0-or-later
        pragma solidity ^0.8.0;
        /**
         * @title Premia Exchange Helper
         * @dev deployed standalone and referenced by internal functions
         * @dev do NOT set approval to this contract!
         */
        interface IExchangeHelper {
            /**
             * @notice perform arbitrary swap transaction
             * @param sourceToken source token to pull into this address
             * @param targetToken target token to buy
             * @param sourceTokenAmount amount of source token to start the trade
             * @param callee exchange address to call to execute the trade.
             * @param allowanceTarget address for which to set allowance for the trade
             * @param data calldata to execute the trade
             * @param refundAddress address that un-used source token goes to
             * @return amountOut quantity of targetToken yielded by swap
             */
            function swapWithToken(
                address sourceToken,
                address targetToken,
                uint256 sourceTokenAmount,
                address callee,
                address allowanceTarget,
                bytes calldata data,
                address refundAddress
            ) external returns (uint256 amountOut);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import {ILayerZeroUserApplicationConfig} from "./ILayerZeroUserApplicationConfig.sol";
        interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig {
            /**
             * @notice send a LayerZero message to the specified address at a LayerZero endpoint.
             * @param dstChainId - the destination chain identifier
             * @param destination - the address on destination chain (in bytes). address length/format may vary by chains
             * @param payload - a custom bytes payload to send to the destination contract
             * @param refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
             * @param zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
             * @param adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination
             */
            function send(
                uint16 dstChainId,
                bytes calldata destination,
                bytes calldata payload,
                address payable refundAddress,
                address zroPaymentAddress,
                bytes calldata adapterParams
            ) external payable;
            /**
             * @notice used by the messaging library to publish verified payload
             * @param srcChainId - the source chain identifier
             * @param srcAddress - the source contract (as bytes) at the source chain
             * @param dstAddress - the address on destination chain
             * @param nonce - the unbound message ordering nonce
             * @param gasLimit - the gas limit for external contract execution
             * @param payload - verified payload to send to the destination contract
             */
            function receivePayload(
                uint16 srcChainId,
                bytes calldata srcAddress,
                address dstAddress,
                uint64 nonce,
                uint256 gasLimit,
                bytes calldata payload
            ) external;
            /*
             * @notice get the inboundNonce of a lzApp from a source chain which could be EVM or non-EVM chain
             * @param srcChainId - the source chain identifier
             * @param srcAddress - the source chain contract address
             */
            function getInboundNonce(
                uint16 srcChainId,
                bytes calldata srcAddress
            ) external view returns (uint64);
            /*
             * @notice get the outboundNonce from this source chain which, consequently, is always an EVM
             * @param srcAddress - the source chain contract address
             */
            function getOutboundNonce(
                uint16 dstChainId,
                address srcAddress
            ) external view returns (uint64);
            /*
             * @notice gets a quote in source native gas, for the amount that send() requires to pay for message delivery
             * @param dstChainId - the destination chain identifier
             * @param userApplication - the user app address on this EVM chain
             * @param payload - the custom message to send over LayerZero
             * @param payInZRO - if false, user app pays the protocol fee in native token
             * @param adapterParam - parameters for the adapter service, e.g. send some dust native token to dstChain
             */
            function estimateFees(
                uint16 dstChainId,
                address userApplication,
                bytes calldata payload,
                bool payInZRO,
                bytes calldata adapterParam
            ) external view returns (uint256 nativeFee, uint256 zroFee);
            /*
             * @notice get this Endpoint's immutable source identifier
             */
            function getChainId() external view returns (uint16);
            /*
             * @notice the interface to retry failed message on this Endpoint destination
             * @param srcChainId - the source chain identifier
             * @param srcAddress - the source chain contract address
             * @param payload - the payload to be retried
             */
            function retryPayload(
                uint16 srcChainId,
                bytes calldata srcAddress,
                bytes calldata payload
            ) external;
            /*
             * @notice query if any STORED payload (message blocking) at the endpoint.
             * @param srcChainId - the source chain identifier
             * @param srcAddress - the source chain contract address
             */
            function hasStoredPayload(
                uint16 srcChainId,
                bytes calldata srcAddress
            ) external view returns (bool);
            /*
             * @notice query if the libraryAddress is valid for sending msgs.
             * @param userApplication - the user app address on this EVM chain
             */
            function getSendLibraryAddress(
                address userApplication
            ) external view returns (address);
            /*
             * @notice query if the libraryAddress is valid for receiving msgs.
             * @param userApplication - the user app address on this EVM chain
             */
            function getReceiveLibraryAddress(
                address userApplication
            ) external view returns (address);
            /*
             * @notice query if the non-reentrancy guard for send() is on
             * @return true if the guard is on. false otherwise
             */
            function isSendingPayload() external view returns (bool);
            /*
             * @notice query if the non-reentrancy guard for receive() is on
             * @return true if the guard is on. false otherwise
             */
            function isReceivingPayload() external view returns (bool);
            /*
             * @notice get the configuration of the LayerZero messaging library of the specified version
             * @param version - messaging library version
             * @param chainId - the chainId for the pending config change
             * @param userApplication - the contract address of the user application
             * @param configType - type of configuration. every messaging library has its own convention.
             */
            function getConfig(
                uint16 version,
                uint16 chainId,
                address userApplication,
                uint256 configType
            ) external view returns (bytes memory);
            /*
             * @notice get the send() LayerZero messaging library version
             * @param userApplication - the contract address of the user application
             */
            function getSendVersion(
                address userApplication
            ) external view returns (uint16);
            /*
             * @notice get the lzReceive() LayerZero messaging library version
             * @param userApplication - the contract address of the user application
             */
            function getReceiveVersion(
                address userApplication
            ) external view returns (uint16);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        interface ILayerZeroReceiver {
            /*
             * @notice LayerZero endpoint will invoke this function to deliver the message on the destination
             * @param srcChainId - the source endpoint identifier
             * @param srcAddress - the source sending contract address from the source chain
             * @param nonce - the ordered message nonce
             * @param payload - the signed payload is the UA bytes has encoded to be sent
             */
            function lzReceive(
                uint16 srcChainId,
                bytes calldata srcAddress,
                uint64 nonce,
                bytes calldata payload
            ) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        interface ILayerZeroUserApplicationConfig {
            /*
             * @notice set the configuration of the LayerZero messaging library of the specified version
             * @param version - messaging library version
             * @param chainId - the chainId for the pending config change
             * @param configType - type of configuration. every messaging library has its own convention.
             * @param config - configuration in the bytes. can encode arbitrary content.
             */
            function setConfig(
                uint16 version,
                uint16 chainId,
                uint256 configType,
                bytes calldata config
            ) external;
            /*
             * @notice set the send() LayerZero messaging library version to version
             * @param version - new messaging library version
             */
            function setSendVersion(uint16 version) external;
            /*
             * @notice set the lzReceive() LayerZero messaging library version to version
             * @param version - new messaging library version
             */
            function setReceiveVersion(uint16 version) external;
            /*
             * @notice Only when the UA needs to resume the message flow in blocking mode and clear the stored payload
             * @param srcChainId - the chainId of the source chain
             * @param srcAddress - the contract address of the source contract at the source chain
             */
            function forceResumeReceive(
                uint16 srcChainId,
                bytes calldata srcAddress
            ) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import {OwnableInternal} from "@solidstate/contracts/access/ownable/OwnableInternal.sol";
        import {ILayerZeroReceiver} from "../interfaces/ILayerZeroReceiver.sol";
        import {ILayerZeroUserApplicationConfig} from "../interfaces/ILayerZeroUserApplicationConfig.sol";
        import {ILayerZeroEndpoint} from "../interfaces/ILayerZeroEndpoint.sol";
        import {LzAppStorage} from "./LzAppStorage.sol";
        import {BytesLib} from "../util/BytesLib.sol";
        /*
         * a generic LzReceiver implementation
         */
        abstract contract LzApp is
            OwnableInternal,
            ILayerZeroReceiver,
            ILayerZeroUserApplicationConfig
        {
            using BytesLib for bytes;
            ILayerZeroEndpoint public immutable lzEndpoint;
            //    event SetPrecrime(address precrime);
            event SetTrustedRemoteAddress(uint16 _remoteChainId, bytes _remoteAddress);
            error LzApp__InvalidEndpointCaller();
            error LzApp__InvalidSource();
            error LzApp__NotTrustedSource();
            error LzApp__NoTrustedPathRecord();
            constructor(address endpoint) {
                lzEndpoint = ILayerZeroEndpoint(endpoint);
            }
            /**
             * @inheritdoc ILayerZeroReceiver
             */
            function lzReceive(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64 nonce,
                bytes memory payload
            ) public virtual {
                // lzReceive must be called by the endpoint for security
                if (msg.sender != address(lzEndpoint))
                    revert LzApp__InvalidEndpointCaller();
                // if will still block the message pathway from (srcChainId, srcAddress). should not receive message from untrusted remote.
                if (!_isTrustedRemote(srcChainId, srcAddress))
                    revert LzApp__InvalidSource();
                _blockingLzReceive(srcChainId, srcAddress, nonce, payload);
            }
            // abstract function - the default behaviour of LayerZero is blocking. See: NonblockingLzApp if you dont need to enforce ordered messaging
            function _blockingLzReceive(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64 nonce,
                bytes memory payload
            ) internal virtual;
            function _lzSend(
                uint16 dstChainId,
                bytes memory payload,
                address payable refundAddress,
                address zroPaymentAddress,
                bytes memory adapterParams,
                uint256 nativeFee
            ) internal virtual {
                bytes memory trustedRemote = LzAppStorage.layout().trustedRemote[
                    dstChainId
                ];
                if (trustedRemote.length == 0) revert LzApp__NotTrustedSource();
                lzEndpoint.send{value: nativeFee}(
                    dstChainId,
                    trustedRemote,
                    payload,
                    refundAddress,
                    zroPaymentAddress,
                    adapterParams
                );
            }
            //---------------------------UserApplication config----------------------------------------
            function getConfig(
                uint16 version,
                uint16 chainId,
                address,
                uint256 configType
            ) external view returns (bytes memory) {
                return
                    lzEndpoint.getConfig(version, chainId, address(this), configType);
            }
            /**
             * @inheritdoc ILayerZeroUserApplicationConfig
             */
            function setConfig(
                uint16 version,
                uint16 chainId,
                uint256 configType,
                bytes calldata config
            ) external onlyOwner {
                lzEndpoint.setConfig(version, chainId, configType, config);
            }
            /**
             * @inheritdoc ILayerZeroUserApplicationConfig
             */
            function setSendVersion(uint16 version) external onlyOwner {
                lzEndpoint.setSendVersion(version);
            }
            /**
             * @inheritdoc ILayerZeroUserApplicationConfig
             */
            function setReceiveVersion(uint16 version) external onlyOwner {
                lzEndpoint.setReceiveVersion(version);
            }
            /**
             * @inheritdoc ILayerZeroUserApplicationConfig
             */
            function forceResumeReceive(
                uint16 srcChainId,
                bytes calldata srcAddress
            ) external onlyOwner {
                lzEndpoint.forceResumeReceive(srcChainId, srcAddress);
            }
            function setTrustedRemoteAddress(
                uint16 remoteChainId,
                bytes calldata remoteAddress
            ) external onlyOwner {
                LzAppStorage.layout().trustedRemote[remoteChainId] = abi.encodePacked(
                    remoteAddress,
                    address(this)
                );
                emit SetTrustedRemoteAddress(remoteChainId, remoteAddress);
            }
            function getTrustedRemoteAddress(
                uint16 _remoteChainId
            ) external view returns (bytes memory) {
                bytes memory path = LzAppStorage.layout().trustedRemote[_remoteChainId];
                if (path.length == 0) revert LzApp__NoTrustedPathRecord();
                return path.slice(0, path.length - 20); // the last 20 bytes should be address(this)
            }
            //    function setPrecrime(address _precrime) external onlyOwner {
            //        LzAppStorage.layout().precrime = _precrime;
            //        emit SetPrecrime(_precrime);
            //    }
            //--------------------------- VIEW FUNCTION ----------------------------------------
            function isTrustedRemote(
                uint16 srcChainId,
                bytes memory srcAddress
            ) external view returns (bool) {
                return _isTrustedRemote(srcChainId, srcAddress);
            }
            function _isTrustedRemote(
                uint16 srcChainId,
                bytes memory srcAddress
            ) internal view returns (bool) {
                bytes memory trustedRemote = LzAppStorage.layout().trustedRemote[
                    srcChainId
                ];
                return
                    srcAddress.length == trustedRemote.length &&
                    trustedRemote.length > 0 &&
                    keccak256(trustedRemote) == keccak256(srcAddress);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        library LzAppStorage {
            bytes32 internal constant STORAGE_SLOT =
                keccak256("premia.contracts.storage.LzApp");
            struct Layout {
                mapping(uint16 => bytes) trustedRemote;
                address precrime;
            }
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import {LzApp} from "./LzApp.sol";
        import {NonblockingLzAppStorage} from "./NonblockingLzAppStorage.sol";
        import {ExcessivelySafeCall} from "../util/ExcessivelySafeCall.sol";
        /*
         * the default LayerZero messaging behaviour is blocking, i.e. any failed message will block the channel
         * this abstract class try-catch all fail messages and store locally for future retry. hence, non-blocking
         * NOTE: if the srcAddress is not configured properly, it will still block the message pathway from (srcChainId, srcAddress)
         */
        abstract contract NonblockingLzApp is LzApp {
            using ExcessivelySafeCall for address;
            error NonblockingLzApp__CallerNotLzApp();
            error NonblockingLzApp__InvalidPayload();
            error NonblockingLzApp__NoStoredMessage();
            constructor(address endpoint) LzApp(endpoint) {}
            event MessageFailed(
                uint16 srcChainId,
                bytes srcAddress,
                uint64 nonce,
                bytes payload,
                bytes reason
            );
            event RetryMessageSuccess(
                uint16 srcChainId,
                bytes srcAddress,
                uint64 nonce,
                bytes32 payloadHash
            );
            // overriding the virtual function in LzReceiver
            function _blockingLzReceive(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64 nonce,
                bytes memory payload
            ) internal virtual override {
                (bool success, bytes memory reason) = address(this).excessivelySafeCall(
                    gasleft(),
                    150,
                    abi.encodeWithSelector(
                        this.nonblockingLzReceive.selector,
                        srcChainId,
                        srcAddress,
                        nonce,
                        payload
                    )
                );
                // try-catch all errors/exceptions
                if (!success) {
                    NonblockingLzAppStorage.layout().failedMessages[srcChainId][
                        srcAddress
                    ][nonce] = keccak256(payload);
                    emit MessageFailed(srcChainId, srcAddress, nonce, payload, reason);
                }
            }
            function nonblockingLzReceive(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64 nonce,
                bytes memory payload
            ) public virtual {
                // only internal transaction
                if (msg.sender != address(this))
                    revert NonblockingLzApp__CallerNotLzApp();
                _nonblockingLzReceive(srcChainId, srcAddress, nonce, payload);
            }
            // override this function
            function _nonblockingLzReceive(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64 nonce,
                bytes memory payload
            ) internal virtual;
            function retryMessage(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64 nonce,
                bytes memory payload
            ) public payable virtual {
                NonblockingLzAppStorage.Layout storage l = NonblockingLzAppStorage
                    .layout();
                // assert there is message to retry
                bytes32 payloadHash = l.failedMessages[srcChainId][srcAddress][nonce];
                if (payloadHash == bytes32(0))
                    revert NonblockingLzApp__NoStoredMessage();
                if (keccak256(payload) != payloadHash)
                    revert NonblockingLzApp__InvalidPayload();
                // clear the stored message
                delete l.failedMessages[srcChainId][srcAddress][nonce];
                // execute the message. revert if it fails again
                _nonblockingLzReceive(srcChainId, srcAddress, nonce, payload);
                emit RetryMessageSuccess(srcChainId, srcAddress, nonce, payloadHash);
            }
            function failedMessages(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64 nonce
            ) external view returns (bytes32) {
                return
                    NonblockingLzAppStorage.layout().failedMessages[srcChainId][
                        srcAddress
                    ][nonce];
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        library NonblockingLzAppStorage {
            bytes32 internal constant STORAGE_SLOT =
                keccak256("premia.contracts.storage.NonblockingLzApp");
            struct Layout {
                mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) failedMessages;
            }
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import {IOFTCore} from "./IOFTCore.sol";
        import {ISolidStateERC20} from "@solidstate/contracts/token/ERC20/ISolidStateERC20.sol";
        /**
         * @dev Interface of the OFT standard
         */
        interface IOFT is IOFTCore, ISolidStateERC20 {
            error OFT_InsufficientAllowance();
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import {IERC20} from "@solidstate/contracts/interfaces/IERC20.sol";
        import {IERC165} from "@solidstate/contracts/interfaces/IERC165.sol";
        /**
         * @dev Interface of the IOFT core standard
         */
        interface IOFTCore is IERC165 {
            /**
             * @dev estimate send token `tokenId` to (`dstChainId`, `toAddress`)
             * dstChainId - L0 defined chain id to send tokens too
             * toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain
             * amount - amount of the tokens to transfer
             * useZro - indicates to use zro to pay L0 fees
             * adapterParam - flexible bytes array to indicate messaging adapter services in L0
             */
            function estimateSendFee(
                uint16 dstChainId,
                bytes calldata toAddress,
                uint256 amount,
                bool useZro,
                bytes calldata adapterParams
            ) external view returns (uint256 nativeFee, uint256 zroFee);
            /**
             * @dev send `amount` amount of token to (`dstChainId`, `toAddress`) from `from`
             * `from` the owner of token
             * `dstChainId` the destination chain identifier
             * `toAddress` can be any size depending on the `dstChainId`.
             * `amount` the quantity of tokens in wei
             * `refundAddress` the address LayerZero refunds if too much message fee is sent
             * `zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token)
             * `adapterParams` is a flexible bytes array to indicate messaging adapter services
             */
            function sendFrom(
                address from,
                uint16 dstChainId,
                bytes calldata toAddress,
                uint256 amount,
                address payable refundAddress,
                address zroPaymentAddress,
                bytes calldata adapterParams
            ) external payable;
            /**
             * @dev returns the circulating amount of tokens on current chain
             */
            function circulatingSupply() external view returns (uint256);
            /**
             * @dev Emitted when `amount` tokens are moved from the `sender` to (`dstChainId`, `toAddress`)
             * `nonce` is the outbound nonce
             */
            event SendToChain(
                address indexed sender,
                uint16 indexed dstChainId,
                bytes indexed toAddress,
                uint256 amount
            );
            /**
             * @dev Emitted when `amount` tokens are received from `srcChainId` into the `toAddress` on the local chain.
             * `nonce` is the inbound nonce.
             */
            event ReceiveFromChain(
                uint16 indexed srcChainId,
                bytes indexed srcAddress,
                address indexed toAddress,
                uint256 amount
            );
            event SetUseCustomAdapterParams(bool _useCustomAdapterParams);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import {ERC20Base, ERC20BaseStorage} from "@solidstate/contracts/token/ERC20/base/ERC20Base.sol";
        import {SolidStateERC20} from "@solidstate/contracts/token/ERC20/SolidStateERC20.sol";
        import {IERC20} from "@solidstate/contracts/interfaces/IERC20.sol";
        import {IERC165} from "@solidstate/contracts/interfaces/IERC165.sol";
        import {OFTCore} from "./OFTCore.sol";
        import {IOFT} from "./IOFT.sol";
        // override decimal() function is needed
        contract OFT is OFTCore, SolidStateERC20, IOFT {
            constructor(address lzEndpoint) OFTCore(lzEndpoint) {}
            function circulatingSupply()
                public
                view
                virtual
                override
                returns (uint256)
            {
                return totalSupply();
            }
            function _debitFrom(
                address from,
                uint16,
                bytes memory,
                uint256 amount
            ) internal virtual override {
                address spender = msg.sender;
                if (from != spender) {
                    unchecked {
                        mapping(address => uint256)
                            storage allowances = ERC20BaseStorage.layout().allowances[
                                spender
                            ];
                        uint256 allowance = allowances[spender];
                        if (amount > allowance) revert OFT_InsufficientAllowance();
                        _approve(
                            from,
                            spender,
                            allowances[spender] = allowance - amount
                        );
                    }
                }
                _burn(from, amount);
            }
            function _creditTo(
                uint16,
                address toAddress,
                uint256 amount
            ) internal virtual override {
                _mint(toAddress, amount);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
        import {NonblockingLzApp} from "../../lzApp/NonblockingLzApp.sol";
        import {IOFTCore} from "./IOFTCore.sol";
        import {ERC165, IERC165} from "@solidstate/contracts/introspection/ERC165.sol";
        import {BytesLib} from "../../util/BytesLib.sol";
        abstract contract OFTCore is NonblockingLzApp, ERC165, IOFTCore {
            using BytesLib for bytes;
            // packet type
            uint16 public constant PT_SEND = 0;
            constructor(address lzEndpoint) NonblockingLzApp(lzEndpoint) {}
            function estimateSendFee(
                uint16 dstChainId,
                bytes memory toAddress,
                uint256 amount,
                bool useZro,
                bytes memory adapterParams
            ) public view virtual override returns (uint256 nativeFee, uint256 zroFee) {
                // mock the payload for send()
                bytes memory payload = abi.encode(
                    PT_SEND,
                    abi.encodePacked(msg.sender),
                    toAddress,
                    amount
                );
                return
                    lzEndpoint.estimateFees(
                        dstChainId,
                        address(this),
                        payload,
                        useZro,
                        adapterParams
                    );
            }
            function sendFrom(
                address from,
                uint16 dstChainId,
                bytes memory toAddress,
                uint256 amount,
                address payable refundAddress,
                address zroPaymentAddress,
                bytes memory adapterParams
            ) public payable virtual override {
                _send(
                    from,
                    dstChainId,
                    toAddress,
                    amount,
                    refundAddress,
                    zroPaymentAddress,
                    adapterParams
                );
            }
            function _nonblockingLzReceive(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64 nonce,
                bytes memory payload
            ) internal virtual override {
                uint16 packetType;
                assembly {
                    packetType := mload(add(payload, 32))
                }
                if (packetType == PT_SEND) {
                    _sendAck(srcChainId, srcAddress, nonce, payload);
                } else {
                    revert("OFTCore: unknown packet type");
                }
            }
            function _send(
                address from,
                uint16 dstChainId,
                bytes memory toAddress,
                uint256 amount,
                address payable refundAddress,
                address zroPaymentAddress,
                bytes memory adapterParams
            ) internal virtual {
                _debitFrom(from, dstChainId, toAddress, amount);
                bytes memory payload = abi.encode(
                    PT_SEND,
                    abi.encodePacked(from),
                    toAddress,
                    amount
                );
                _lzSend(
                    dstChainId,
                    payload,
                    refundAddress,
                    zroPaymentAddress,
                    adapterParams,
                    msg.value
                );
                emit SendToChain(from, dstChainId, toAddress, amount);
            }
            function _sendAck(
                uint16 srcChainId,
                bytes memory,
                uint64,
                bytes memory payload
            ) internal virtual {
                (, bytes memory from, bytes memory toAddressBytes, uint256 amount) = abi
                    .decode(payload, (uint16, bytes, bytes, uint256));
                address to = toAddressBytes.toAddress(0);
                _creditTo(srcChainId, to, amount);
                emit ReceiveFromChain(srcChainId, from, to, amount);
            }
            function _debitFrom(
                address from,
                uint16 dstChainId,
                bytes memory toAddress,
                uint256 amount
            ) internal virtual;
            function _creditTo(
                uint16 srcChainId,
                address toAddress,
                uint256 amount
            ) internal virtual;
        }
        // SPDX-License-Identifier: Unlicense
        /*
         * @title Solidity Bytes Arrays Utils
         * @author Gonçalo Sá <goncalo.sa@consensys.net>
         *
         * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
         *      The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
         */
        pragma solidity >=0.8.0 <0.9.0;
        library BytesLib {
            error BytesLib__Overflow();
            error BytesLib__OutOfBounds();
            function concat(
                bytes memory _preBytes,
                bytes memory _postBytes
            ) internal pure returns (bytes memory) {
                bytes memory tempBytes;
                assembly {
                    // Get a location of some free memory and store it in tempBytes as
                    // Solidity does for memory variables.
                    tempBytes := mload(0x40)
                    // Store the length of the first bytes array at the beginning of
                    // the memory for tempBytes.
                    let length := mload(_preBytes)
                    mstore(tempBytes, length)
                    // Maintain a memory counter for the current write location in the
                    // temp bytes array by adding the 32 bytes for the array length to
                    // the starting location.
                    let mc := add(tempBytes, 0x20)
                    // Stop copying when the memory counter reaches the length of the
                    // first bytes array.
                    let end := add(mc, length)
                    for {
                        // Initialize a copy counter to the start of the _preBytes data,
                        // 32 bytes into its memory.
                        let cc := add(_preBytes, 0x20)
                    } lt(mc, end) {
                        // Increase both counters by 32 bytes each iteration.
                        mc := add(mc, 0x20)
                        cc := add(cc, 0x20)
                    } {
                        // Write the _preBytes data into the tempBytes memory 32 bytes
                        // at a time.
                        mstore(mc, mload(cc))
                    }
                    // Add the length of _postBytes to the current length of tempBytes
                    // and store it as the new length in the first 32 bytes of the
                    // tempBytes memory.
                    length := mload(_postBytes)
                    mstore(tempBytes, add(length, mload(tempBytes)))
                    // Move the memory counter back from a multiple of 0x20 to the
                    // actual end of the _preBytes data.
                    mc := end
                    // Stop copying when the memory counter reaches the new combined
                    // length of the arrays.
                    end := add(mc, length)
                    for {
                        let cc := add(_postBytes, 0x20)
                    } lt(mc, end) {
                        mc := add(mc, 0x20)
                        cc := add(cc, 0x20)
                    } {
                        mstore(mc, mload(cc))
                    }
                    // Update the free-memory pointer by padding our last write location
                    // to 32 bytes: add 31 bytes to the end of tempBytes to move to the
                    // next 32 byte block, then round down to the nearest multiple of
                    // 32. If the sum of the length of the two arrays is zero then add
                    // one before rounding down to leave a blank 32 bytes (the length block with 0).
                    mstore(
                        0x40,
                        and(
                            add(add(end, iszero(add(length, mload(_preBytes)))), 31),
                            not(31) // Round down to the nearest 32 bytes.
                        )
                    )
                }
                return tempBytes;
            }
            function concatStorage(
                bytes storage _preBytes,
                bytes memory _postBytes
            ) internal {
                assembly {
                    // Read the first 32 bytes of _preBytes storage, which is the length
                    // of the array. (We don't need to use the offset into the slot
                    // because arrays use the entire slot.)
                    let fslot := sload(_preBytes.slot)
                    // Arrays of 31 bytes or less have an even value in their slot,
                    // while longer arrays have an odd value. The actual length is
                    // the slot divided by two for odd values, and the lowest order
                    // byte divided by two for even values.
                    // If the slot is even, bitwise and the slot with 255 and divide by
                    // two to get the length. If the slot is odd, bitwise and the slot
                    // with -1 and divide by two.
                    let slength := div(
                        and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)),
                        2
                    )
                    let mlength := mload(_postBytes)
                    let newlength := add(slength, mlength)
                    // slength can contain both the length and contents of the array
                    // if length < 32 bytes so let's prepare for that
                    // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
                    switch add(lt(slength, 32), lt(newlength, 32))
                    case 2 {
                        // Since the new array still fits in the slot, we just need to
                        // update the contents of the slot.
                        // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
                        sstore(
                            _preBytes.slot,
                            // all the modifications to the slot are inside this
                            // next block
                            add(
                                // we can just add to the slot contents because the
                                // bytes we want to change are the LSBs
                                fslot,
                                add(
                                    mul(
                                        div(
                                            // load the bytes from memory
                                            mload(add(_postBytes, 0x20)),
                                            // zero all bytes to the right
                                            exp(0x100, sub(32, mlength))
                                        ),
                                        // and now shift left the number of bytes to
                                        // leave space for the length in the slot
                                        exp(0x100, sub(32, newlength))
                                    ),
                                    // increase length by the double of the memory
                                    // bytes length
                                    mul(mlength, 2)
                                )
                            )
                        )
                    }
                    case 1 {
                        // The stored value fits in the slot, but the combined value
                        // will exceed it.
                        // get the keccak hash to get the contents of the array
                        mstore(0x0, _preBytes.slot)
                        let sc := add(keccak256(0x0, 0x20), div(slength, 32))
                        // save new length
                        sstore(_preBytes.slot, add(mul(newlength, 2), 1))
                        // The contents of the _postBytes array start 32 bytes into
                        // the structure. Our first read should obtain the `submod`
                        // bytes that can fit into the unused space in the last word
                        // of the stored array. To get this, we read 32 bytes starting
                        // from `submod`, so the data we read overlaps with the array
                        // contents by `submod` bytes. Masking the lowest-order
                        // `submod` bytes allows us to add that value directly to the
                        // stored value.
                        let submod := sub(32, slength)
                        let mc := add(_postBytes, submod)
                        let end := add(_postBytes, mlength)
                        let mask := sub(exp(0x100, submod), 1)
                        sstore(
                            sc,
                            add(
                                and(
                                    fslot,
                                    0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
                                ),
                                and(mload(mc), mask)
                            )
                        )
                        for {
                            mc := add(mc, 0x20)
                            sc := add(sc, 1)
                        } lt(mc, end) {
                            sc := add(sc, 1)
                            mc := add(mc, 0x20)
                        } {
                            sstore(sc, mload(mc))
                        }
                        mask := exp(0x100, sub(mc, end))
                        sstore(sc, mul(div(mload(mc), mask), mask))
                    }
                    default {
                        // get the keccak hash to get the contents of the array
                        mstore(0x0, _preBytes.slot)
                        // Start copying to the last used word of the stored array.
                        let sc := add(keccak256(0x0, 0x20), div(slength, 32))
                        // save new length
                        sstore(_preBytes.slot, add(mul(newlength, 2), 1))
                        // Copy over the first `submod` bytes of the new data as in
                        // case 1 above.
                        let slengthmod := mod(slength, 32)
                        let mlengthmod := mod(mlength, 32)
                        let submod := sub(32, slengthmod)
                        let mc := add(_postBytes, submod)
                        let end := add(_postBytes, mlength)
                        let mask := sub(exp(0x100, submod), 1)
                        sstore(sc, add(sload(sc), and(mload(mc), mask)))
                        for {
                            sc := add(sc, 1)
                            mc := add(mc, 0x20)
                        } lt(mc, end) {
                            sc := add(sc, 1)
                            mc := add(mc, 0x20)
                        } {
                            sstore(sc, mload(mc))
                        }
                        mask := exp(0x100, sub(mc, end))
                        sstore(sc, mul(div(mload(mc), mask), mask))
                    }
                }
            }
            function slice(
                bytes memory _bytes,
                uint256 _start,
                uint256 _length
            ) internal pure returns (bytes memory) {
                if (_length + 31 < _length) revert BytesLib__Overflow();
                if (_bytes.length < _start + _length) revert BytesLib__OutOfBounds();
                bytes memory tempBytes;
                assembly {
                    switch iszero(_length)
                    case 0 {
                        // Get a location of some free memory and store it in tempBytes as
                        // Solidity does for memory variables.
                        tempBytes := mload(0x40)
                        // The first word of the slice result is potentially a partial
                        // word read from the original array. To read it, we calculate
                        // the length of that partial word and start copying that many
                        // bytes into the array. The first word we copy will start with
                        // data we don't care about, but the last `lengthmod` bytes will
                        // land at the beginning of the contents of the new array. When
                        // we're done copying, we overwrite the full first word with
                        // the actual length of the slice.
                        let lengthmod := and(_length, 31)
                        // The multiplication in the next line is necessary
                        // because when slicing multiples of 32 bytes (lengthmod == 0)
                        // the following copy loop was copying the origin's length
                        // and then ending prematurely not copying everything it should.
                        let mc := add(
                            add(tempBytes, lengthmod),
                            mul(0x20, iszero(lengthmod))
                        )
                        let end := add(mc, _length)
                        for {
                            // The multiplication in the next line has the same exact purpose
                            // as the one above.
                            let cc := add(
                                add(
                                    add(_bytes, lengthmod),
                                    mul(0x20, iszero(lengthmod))
                                ),
                                _start
                            )
                        } lt(mc, end) {
                            mc := add(mc, 0x20)
                            cc := add(cc, 0x20)
                        } {
                            mstore(mc, mload(cc))
                        }
                        mstore(tempBytes, _length)
                        //update free-memory pointer
                        //allocating the array padded to 32 bytes like the compiler does now
                        mstore(0x40, and(add(mc, 31), not(31)))
                    }
                    //if we want a zero-length slice let's just return a zero-length array
                    default {
                        tempBytes := mload(0x40)
                        //zero out the 32 bytes slice we are about to return
                        //we need to do it because Solidity does not garbage collect
                        mstore(tempBytes, 0)
                        mstore(0x40, add(tempBytes, 0x20))
                    }
                }
                return tempBytes;
            }
            function toAddress(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (address) {
                if (_bytes.length < _start + 20) revert BytesLib__OutOfBounds();
                address tempAddress;
                assembly {
                    tempAddress := div(
                        mload(add(add(_bytes, 0x20), _start)),
                        0x1000000000000000000000000
                    )
                }
                return tempAddress;
            }
            function toUint8(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (uint8) {
                if (_bytes.length < _start + 1) revert BytesLib__OutOfBounds();
                uint8 tempUint;
                assembly {
                    tempUint := mload(add(add(_bytes, 0x1), _start))
                }
                return tempUint;
            }
            function toUint16(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (uint16) {
                if (_bytes.length < _start + 2) revert BytesLib__OutOfBounds();
                uint16 tempUint;
                assembly {
                    tempUint := mload(add(add(_bytes, 0x2), _start))
                }
                return tempUint;
            }
            function toUint32(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (uint32) {
                if (_bytes.length < _start + 4) revert BytesLib__OutOfBounds();
                uint32 tempUint;
                assembly {
                    tempUint := mload(add(add(_bytes, 0x4), _start))
                }
                return tempUint;
            }
            function toUint64(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (uint64) {
                if (_bytes.length < _start + 8) revert BytesLib__OutOfBounds();
                uint64 tempUint;
                assembly {
                    tempUint := mload(add(add(_bytes, 0x8), _start))
                }
                return tempUint;
            }
            function toUint96(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (uint96) {
                if (_bytes.length < _start + 12) revert BytesLib__OutOfBounds();
                uint96 tempUint;
                assembly {
                    tempUint := mload(add(add(_bytes, 0xc), _start))
                }
                return tempUint;
            }
            function toUint128(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (uint128) {
                if (_bytes.length < _start + 16) revert BytesLib__OutOfBounds();
                uint128 tempUint;
                assembly {
                    tempUint := mload(add(add(_bytes, 0x10), _start))
                }
                return tempUint;
            }
            function toUint256(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (uint256) {
                if (_bytes.length < _start + 32) revert BytesLib__OutOfBounds();
                uint256 tempUint;
                assembly {
                    tempUint := mload(add(add(_bytes, 0x20), _start))
                }
                return tempUint;
            }
            function toBytes32(
                bytes memory _bytes,
                uint256 _start
            ) internal pure returns (bytes32) {
                if (_bytes.length < _start + 32) revert BytesLib__OutOfBounds();
                bytes32 tempBytes32;
                assembly {
                    tempBytes32 := mload(add(add(_bytes, 0x20), _start))
                }
                return tempBytes32;
            }
            function equal(
                bytes memory _preBytes,
                bytes memory _postBytes
            ) internal pure returns (bool) {
                bool success = true;
                assembly {
                    let length := mload(_preBytes)
                    // if lengths don't match the arrays are not equal
                    switch eq(length, mload(_postBytes))
                    case 1 {
                        // cb is a circuit breaker in the for loop since there's
                        //  no said feature for inline assembly loops
                        // cb = 1 - don't breaker
                        // cb = 0 - break
                        let cb := 1
                        let mc := add(_preBytes, 0x20)
                        let end := add(mc, length)
                        for {
                            let cc := add(_postBytes, 0x20)
                            // the next line is the loop condition:
                            // while(uint256(mc < end) + cb == 2)
                        } eq(add(lt(mc, end), cb), 2) {
                            mc := add(mc, 0x20)
                            cc := add(cc, 0x20)
                        } {
                            // if any of these checks fails then arrays are not equal
                            if iszero(eq(mload(mc), mload(cc))) {
                                // unsuccess:
                                success := 0
                                cb := 0
                            }
                        }
                    }
                    default {
                        // unsuccess:
                        success := 0
                    }
                }
                return success;
            }
            function equalStorage(
                bytes storage _preBytes,
                bytes memory _postBytes
            ) internal view returns (bool) {
                bool success = true;
                assembly {
                    // we know _preBytes_offset is 0
                    let fslot := sload(_preBytes.slot)
                    // Decode the length of the stored array like in concatStorage().
                    let slength := div(
                        and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)),
                        2
                    )
                    let mlength := mload(_postBytes)
                    // if lengths don't match the arrays are not equal
                    switch eq(slength, mlength)
                    case 1 {
                        // slength can contain both the length and contents of the array
                        // if length < 32 bytes so let's prepare for that
                        // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
                        if iszero(iszero(slength)) {
                            switch lt(slength, 32)
                            case 1 {
                                // blank the last byte which is the length
                                fslot := mul(div(fslot, 0x100), 0x100)
                                if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
                                    // unsuccess:
                                    success := 0
                                }
                            }
                            default {
                                // cb is a circuit breaker in the for loop since there's
                                //  no said feature for inline assembly loops
                                // cb = 1 - don't breaker
                                // cb = 0 - break
                                let cb := 1
                                // get the keccak hash to get the contents of the array
                                mstore(0x0, _preBytes.slot)
                                let sc := keccak256(0x0, 0x20)
                                let mc := add(_postBytes, 0x20)
                                let end := add(mc, mlength)
                                // the next line is the loop condition:
                                // while(uint256(mc < end) + cb == 2)
                                for {
                                } eq(add(lt(mc, end), cb), 2) {
                                    sc := add(sc, 1)
                                    mc := add(mc, 0x20)
                                } {
                                    if iszero(eq(sload(sc), mload(mc))) {
                                        // unsuccess:
                                        success := 0
                                        cb := 0
                                    }
                                }
                            }
                        }
                    }
                    default {
                        // unsuccess:
                        success := 0
                    }
                }
                return success;
            }
        }
        // SPDX-License-Identifier: MIT OR Apache-2.0
        pragma solidity >=0.7.6;
        library ExcessivelySafeCall {
            uint256 constant LOW_28_MASK =
                0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
            /// @notice Use when you _really_ really _really_ don't trust the called
            /// contract. This prevents the called contract from causing reversion of
            /// the caller in as many ways as we can.
            /// @dev The main difference between this and a solidity low-level call is
            /// that we limit the number of bytes that the callee can cause to be
            /// copied to caller memory. This prevents stupid things like malicious
            /// contracts returning 10,000,000 bytes causing a local OOG when copying
            /// to memory.
            /// @param _target The address to call
            /// @param _gas The amount of gas to forward to the remote contract
            /// @param _maxCopy The maximum number of bytes of returndata to copy
            /// to memory.
            /// @param _calldata The data to send to the remote contract
            /// @return success and returndata, as `.call()`. Returndata is capped to
            /// `_maxCopy` bytes.
            function excessivelySafeCall(
                address _target,
                uint256 _gas,
                uint16 _maxCopy,
                bytes memory _calldata
            ) internal returns (bool, bytes memory) {
                // set up for assembly call
                uint256 _toCopy;
                bool _success;
                bytes memory _returnData = new bytes(_maxCopy);
                // dispatch message to recipient
                // by assembly calling "handle" function
                // we call via assembly to avoid memcopying a very large returndata
                // returned by a malicious contract
                assembly {
                    _success := call(
                        _gas, // gas
                        _target, // recipient
                        0, // ether value
                        add(_calldata, 0x20), // inloc
                        mload(_calldata), // inlen
                        0, // outloc
                        0 // outlen
                    )
                    // limit our copy to 256 bytes
                    _toCopy := returndatasize()
                    if gt(_toCopy, _maxCopy) {
                        _toCopy := _maxCopy
                    }
                    // Store the length of the copied bytes
                    mstore(_returnData, _toCopy)
                    // copy the bytes from returndata[0:_toCopy]
                    returndatacopy(add(_returnData, 0x20), 0, _toCopy)
                }
                return (_success, _returnData);
            }
            /// @notice Use when you _really_ really _really_ don't trust the called
            /// contract. This prevents the called contract from causing reversion of
            /// the caller in as many ways as we can.
            /// @dev The main difference between this and a solidity low-level call is
            /// that we limit the number of bytes that the callee can cause to be
            /// copied to caller memory. This prevents stupid things like malicious
            /// contracts returning 10,000,000 bytes causing a local OOG when copying
            /// to memory.
            /// @param _target The address to call
            /// @param _gas The amount of gas to forward to the remote contract
            /// @param _maxCopy The maximum number of bytes of returndata to copy
            /// to memory.
            /// @param _calldata The data to send to the remote contract
            /// @return success and returndata, as `.call()`. Returndata is capped to
            /// `_maxCopy` bytes.
            function excessivelySafeStaticCall(
                address _target,
                uint256 _gas,
                uint16 _maxCopy,
                bytes memory _calldata
            ) internal view returns (bool, bytes memory) {
                // set up for assembly call
                uint256 _toCopy;
                bool _success;
                bytes memory _returnData = new bytes(_maxCopy);
                // dispatch message to recipient
                // by assembly calling "handle" function
                // we call via assembly to avoid memcopying a very large returndata
                // returned by a malicious contract
                assembly {
                    _success := staticcall(
                        _gas, // gas
                        _target, // recipient
                        add(_calldata, 0x20), // inloc
                        mload(_calldata), // inlen
                        0, // outloc
                        0 // outlen
                    )
                    // limit our copy to 256 bytes
                    _toCopy := returndatasize()
                    if gt(_toCopy, _maxCopy) {
                        _toCopy := _maxCopy
                    }
                    // Store the length of the copied bytes
                    mstore(_returnData, _toCopy)
                    // copy the bytes from returndata[0:_toCopy]
                    returndatacopy(add(_returnData, 0x20), 0, _toCopy)
                }
                return (_success, _returnData);
            }
            /**
             * @notice Swaps function selectors in encoded contract calls
             * @dev Allows reuse of encoded calldata for functions with identical
             * argument types but different names. It simply swaps out the first 4 bytes
             * for the new selector. This function modifies memory in place, and should
             * only be used with caution.
             * @param _newSelector The new 4-byte selector
             * @param _buf The encoded contract args
             */
            function swapSelector(
                bytes4 _newSelector,
                bytes memory _buf
            ) internal pure {
                require(_buf.length >= 4);
                uint256 _mask = LOW_28_MASK;
                assembly {
                    // load the first word of
                    let _word := mload(add(_buf, 0x20))
                    // mask out the top 4 bytes
                    // /x
                    _word := and(_word, _mask)
                    _word := or(_newSelector, _word)
                    mstore(add(_buf, 0x20), _word)
                }
            }
        }
        // SPDX-License-Identifier: LGPL-3.0-or-later
        pragma solidity ^0.8.0;
        import {PremiaStakingStorage} from "./PremiaStakingStorage.sol";
        import {IOFT} from "../layerZero/token/oft/IOFT.sol";
        import {IERC2612} from "@solidstate/contracts/token/ERC20/permit/IERC2612.sol";
        // IERC20Metadata inheritance not possible due to linearization issue
        interface IPremiaStaking is IERC2612, IOFT {
            error PremiaStaking__CantTransfer();
            error PremiaStaking__ExcessiveStakePeriod();
            error PremiaStaking__InsufficientSwapOutput();
            error PremiaStaking__NoPendingWithdrawal();
            error PremiaStaking__NotEnoughLiquidity();
            error PremiaStaking__PeriodTooShort();
            error PremiaStaking__StakeLocked();
            error PremiaStaking__StakeNotLocked();
            error PremiaStaking__WithdrawalStillPending();
            event Stake(
                address indexed user,
                uint256 amount,
                uint64 stakePeriod,
                uint64 lockedUntil
            );
            event Unstake(
                address indexed user,
                uint256 amount,
                uint256 fee,
                uint256 startDate
            );
            event Harvest(address indexed user, uint256 amount);
            event EarlyUnstakeRewardCollected(address indexed user, uint256 amount);
            event Withdraw(address indexed user, uint256 amount);
            event RewardsAdded(uint256 amount);
            struct StakeLevel {
                uint256 amount; // Amount to stake
                uint256 discountBPS; // Discount when amount is reached
            }
            struct SwapArgs {
                //min amount out to be used to purchase
                uint256 amountOutMin;
                // exchange address to call to execute the trade
                address callee;
                // address for which to set allowance for the trade
                address allowanceTarget;
                // data to execute the trade
                bytes data;
                // address to which refund excess tokens
                address refundAddress;
            }
            event BridgeLock(
                address indexed user,
                uint64 stakePeriod,
                uint64 lockedUntil
            );
            event UpdateLock(
                address indexed user,
                uint64 oldStakePeriod,
                uint64 newStakePeriod
            );
            /**
             * @notice Returns the reward token address
             * @return The reward token address
             */
            function getRewardToken() external view returns (address);
            /**
             * @notice add premia tokens as available tokens to be distributed as rewards
             * @param amount amount of premia tokens to add as rewards
             */
            function addRewards(uint256 amount) external;
            /**
             * @notice get amount of tokens that have not yet been distributed as rewards
             * @return rewards amount of tokens not yet distributed as rewards
             * @return unstakeRewards amount of PREMIA not yet claimed from early unstake fees
             */
            function getAvailableRewards()
                external
                view
                returns (uint256 rewards, uint256 unstakeRewards);
            /**
             * @notice get pending amount of tokens to be distributed as rewards to stakers
             * @return amount of tokens pending to be distributed as rewards
             */
            function getPendingRewards() external view returns (uint256);
            /**
             * @notice get pending withdrawal data of a user
             * @return amount pending withdrawal amount
             * @return startDate start timestamp of withdrawal
             * @return unlockDate timestamp at which withdrawal becomes available
             */
            function getPendingWithdrawal(
                address user
            )
                external
                view
                returns (uint256 amount, uint256 startDate, uint256 unlockDate);
            /**
             * @notice get the amount of PREMIA available for withdrawal
             * @return amount of PREMIA available for withdrawal
             */
            function getAvailablePremiaAmount() external view returns (uint256);
            /**
             * @notice Stake using IERC2612 permit
             * @param amount The amount of xPremia to stake
             * @param period The lockup period (in seconds)
             * @param deadline Deadline after which permit will fail
             * @param v V
             * @param r R
             * @param s S
             */
            function stakeWithPermit(
                uint256 amount,
                uint64 period,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) external;
            /**
             * @notice Lockup xPremia for protocol fee discounts
             *          Longer period of locking will apply a multiplier on the amount staked, in the fee discount calculation
             * @param amount The amount of xPremia to stake
             * @param period The lockup period (in seconds)
             */
            function stake(uint256 amount, uint64 period) external;
            /**
             * @notice update vxPremia lock
             * @param period The new lockup period (in seconds)
             */
            function updateLock(uint64 period) external;
            /**
             * @notice harvest rewards, convert to PREMIA using exchange helper, and stake
             * @param s swap arguments
             * @param stakePeriod The lockup period (in seconds)
             */
            function harvestAndStake(
                IPremiaStaking.SwapArgs memory s,
                uint64 stakePeriod
            ) external;
            /**
             * @notice Harvest rewards directly to user wallet
             */
            function harvest() external;
            /**
             * @notice Get pending rewards amount, including pending pool update
             * @param user User for which to calculate pending rewards
             * @return reward amount of pending rewards from protocol fees (in REWARD_TOKEN)
             * @return unstakeReward amount of pending rewards from early unstake fees (in PREMIA)
             */
            function getPendingUserRewards(
                address user
            ) external view returns (uint256 reward, uint256 unstakeReward);
            /**
             * @notice unstake tokens before end of the lock period, for a fee
             * @param amount the amount of vxPremia to unstake
             */
            function earlyUnstake(uint256 amount) external;
            /**
             * @notice get early unstake fee for given user
             * @param user address of the user
             * @return feePercentage % fee to pay for early unstake (1e4 = 100%)
             */
            function getEarlyUnstakeFeeBPS(
                address user
            ) external view returns (uint256 feePercentage);
            /**
             * @notice Initiate the withdrawal process by burning xPremia, starting the delay period
             * @param amount quantity of xPremia to unstake
             */
            function startWithdraw(uint256 amount) external;
            /**
             * @notice Withdraw underlying premia
             */
            function withdraw() external;
            //////////
            // View //
            //////////
            /**
             * Calculate the stake amount of a user, after applying the bonus from the lockup period chosen
             * @param user The user from which to query the stake amount
             * @return The user stake amount after applying the bonus
             */
            function getUserPower(address user) external view returns (uint256);
            /**
             * Return the total power across all users (applying the bonus from lockup period chosen)
             * @return The total power across all users
             */
            function getTotalPower() external view returns (uint256);
            /**
             * @notice Calculate the % of fee discount for user, based on his stake
             * @param user The _user for which the discount is for
             * @return Percentage of protocol fee discount (in basis point)
             *         Ex : 1000 = 10% fee discount
             */
            function getDiscountBPS(address user) external view returns (uint256);
            /**
             * @notice Get stake levels
             * @return Stake levels
             *         Ex : 2500 = -25%
             */
            function getStakeLevels() external returns (StakeLevel[] memory);
            /**
             * @notice Get stake period multiplier
             * @param period The duration (in seconds) for which tokens are locked
             * @return The multiplier for this staking period
             *         Ex : 20000 = x2
             */
            function getStakePeriodMultiplierBPS(
                uint256 period
            ) external returns (uint256);
            /**
             * @notice Get staking infos of a user
             * @param user The user address for which to get staking infos
             * @return The staking infos of the user
             */
            function getUserInfo(
                address user
            ) external view returns (PremiaStakingStorage.UserInfo memory);
        }
        // SPDX-License-Identifier: LGPL-3.0-or-later
        pragma solidity ^0.8.0;
        import {VxPremiaStorage} from "./VxPremiaStorage.sol";
        import {IPremiaStaking} from "./IPremiaStaking.sol";
        interface IVxPremia is IPremiaStaking {
            error VxPremia__NotEnoughVotingPower();
            event AddVote(
                address indexed voter,
                VxPremiaStorage.VoteVersion indexed version,
                bytes target,
                uint256 amount
            );
            event RemoveVote(
                address indexed voter,
                VxPremiaStorage.VoteVersion indexed version,
                bytes target,
                uint256 amount
            );
            /**
             * @notice get total votes for specific pools
             * @param version version of target (used to know how to decode data)
             * @param target ABI encoded target of the votes
             * @return total votes for specific pool
             */
            function getPoolVotes(
                VxPremiaStorage.VoteVersion version,
                bytes memory target
            ) external view returns (uint256);
            /**
             * @notice get votes of user
             * @param user user from which to get votes
             * @return votes of user
             */
            function getUserVotes(
                address user
            ) external view returns (VxPremiaStorage.Vote[] memory);
            /**
             * @notice add or remove votes, in the limit of the user voting power
             * @param votes votes to cast
             */
            function castVotes(VxPremiaStorage.Vote[] memory votes) external;
        }
        // SPDX-License-Identifier: BUSL-1.1
        // For further clarification please see https://license.premia.legal
        pragma solidity ^0.8.0;
        import {AddressUtils} from "@solidstate/contracts/utils/AddressUtils.sol";
        import {Math} from "@solidstate/contracts/utils/Math.sol";
        import {IERC20} from "@solidstate/contracts/interfaces/IERC20.sol";
        import {IERC2612} from "@solidstate/contracts/token/ERC20/permit/IERC2612.sol";
        import {SafeERC20} from "@solidstate/contracts/utils/SafeERC20.sol";
        import {ABDKMath64x64} from "abdk-libraries-solidity/ABDKMath64x64.sol";
        import {IExchangeHelper} from "../interfaces/IExchangeHelper.sol";
        import {IPremiaStaking} from "./IPremiaStaking.sol";
        import {PremiaStakingStorage} from "./PremiaStakingStorage.sol";
        import {OFT} from "../layerZero/token/oft/OFT.sol";
        import {OFTCore} from "../layerZero/token/oft/OFTCore.sol";
        import {IOFTCore} from "../layerZero/token/oft/IOFTCore.sol";
        import {BytesLib} from "../layerZero/util/BytesLib.sol";
        contract PremiaStaking is IPremiaStaking, OFT {
            using SafeERC20 for IERC20;
            using ABDKMath64x64 for int128;
            using AddressUtils for address;
            using BytesLib for bytes;
            address internal immutable PREMIA;
            address internal immutable REWARD_TOKEN;
            address internal immutable EXCHANGE_HELPER;
            int128 internal constant ONE_64x64 = 0x10000000000000000;
            int128 internal constant DECAY_RATE_64x64 = 0x487a423b63e; // 2.7e-7 -> Distribute around half of the current balance over a month
            uint256 internal constant INVERSE_BASIS_POINT = 1e4;
            uint64 internal constant MAX_PERIOD = 4 * 365 days;
            uint256 internal constant ACC_REWARD_PRECISION = 1e30;
            uint256 internal constant MAX_CONTRACT_DISCOUNT = 3000; // -30%
            uint256 internal constant WITHDRAWAL_DELAY = 10 days;
            struct UpdateArgsInternal {
                address user;
                uint256 balance;
                uint256 oldPower;
                uint256 newPower;
                uint256 reward;
                uint256 unstakeReward;
            }
            constructor(
                address lzEndpoint,
                address premia,
                address rewardToken,
                address exchangeHelper
            ) OFT(lzEndpoint) {
                PREMIA = premia;
                REWARD_TOKEN = rewardToken;
                EXCHANGE_HELPER = exchangeHelper;
            }
            function _beforeTokenTransfer(
                address from,
                address to,
                uint256
            ) internal virtual override {
                if (from == address(0) || to == address(0)) return;
                revert PremiaStaking__CantTransfer();
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getRewardToken() external view returns (address) {
                return REWARD_TOKEN;
            }
            function estimateSendFee(
                uint16 dstChainId,
                bytes memory toAddress,
                uint256 amount,
                bool useZro,
                bytes memory adapterParams
            )
                public
                view
                virtual
                override(OFTCore, IOFTCore)
                returns (uint256 nativeFee, uint256 zroFee)
            {
                // Convert bytes to address
                address to;
                assembly {
                    to := mload(add(toAddress, 32))
                }
                PremiaStakingStorage.UserInfo storage u = PremiaStakingStorage
                    .layout()
                    .userInfo[to];
                // mock the payload for send()
                bytes memory payload = abi.encode(
                    PT_SEND,
                    to,
                    amount,
                    u.stakePeriod,
                    u.lockedUntil
                );
                return
                    lzEndpoint.estimateFees(
                        dstChainId,
                        address(this),
                        payload,
                        useZro,
                        adapterParams
                    );
            }
            function _send(
                address from,
                uint16 dstChainId,
                bytes memory,
                uint256 amount,
                address payable refundAddress,
                address zroPaymentAddress,
                bytes memory adapterParams
            ) internal virtual override {
                _updateRewards();
                _beforeUnstake(from, amount);
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                PremiaStakingStorage.UserInfo storage u = l.userInfo[from];
                UpdateArgsInternal memory args = _getInitialUpdateArgsInternal(
                    l,
                    u,
                    from
                );
                bytes memory toAddress = abi.encodePacked(from);
                _debitFrom(from, dstChainId, toAddress, amount);
                args.newPower = _calculateUserPower(
                    args.balance - amount + args.unstakeReward,
                    u.stakePeriod
                );
                _updateUser(l, u, args);
                _lzSend(
                    dstChainId,
                    abi.encode(
                        PT_SEND,
                        toAddress,
                        amount,
                        u.stakePeriod,
                        u.lockedUntil
                    ),
                    refundAddress,
                    zroPaymentAddress,
                    adapterParams,
                    msg.value
                );
                emit SendToChain(from, dstChainId, toAddress, amount);
            }
            function _sendAck(
                uint16 srcChainId,
                bytes memory srcAddress,
                uint64,
                bytes memory payload
            ) internal virtual override {
                (
                    ,
                    bytes memory toAddressBytes,
                    uint256 amount,
                    uint64 stakePeriod,
                    uint64 lockedUntil
                ) = abi.decode(payload, (uint16, bytes, uint256, uint64, uint64));
                address to = toAddressBytes.toAddress(0);
                _creditTo(to, amount, stakePeriod, lockedUntil, true);
                emit ReceiveFromChain(srcChainId, srcAddress, to, amount);
            }
            function _creditTo(
                address toAddress,
                uint256 amount,
                uint64 stakePeriod,
                uint64 creditLockedUntil,
                bool bridge
            ) internal {
                unchecked {
                    _updateRewards();
                    PremiaStakingStorage.Layout storage l = PremiaStakingStorage
                        .layout();
                    PremiaStakingStorage.UserInfo storage u = l.userInfo[toAddress];
                    UpdateArgsInternal memory args = _getInitialUpdateArgsInternal(
                        l,
                        u,
                        toAddress
                    );
                    uint64 lockedUntil = u.lockedUntil;
                    uint64 lockLeft = uint64(
                        _calculateWeightedAverage(
                            creditLockedUntil > block.timestamp
                                ? creditLockedUntil - block.timestamp
                                : 0,
                            lockedUntil > block.timestamp
                                ? lockedUntil - block.timestamp
                                : 0,
                            amount + args.unstakeReward,
                            args.balance
                        )
                    );
                    u.lockedUntil = lockedUntil = uint64(block.timestamp) + lockLeft;
                    u.stakePeriod = uint64(
                        _calculateWeightedAverage(
                            stakePeriod,
                            u.stakePeriod,
                            amount + args.unstakeReward,
                            args.balance
                        )
                    );
                    args.newPower = _calculateUserPower(
                        args.balance + amount + args.unstakeReward,
                        u.stakePeriod
                    );
                    _mint(toAddress, amount);
                    _updateUser(l, u, args);
                    if (bridge) {
                        emit BridgeLock(toAddress, u.stakePeriod, lockedUntil);
                    } else {
                        emit Stake(toAddress, amount, u.stakePeriod, lockedUntil);
                    }
                }
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function addRewards(uint256 amount) external {
                _updateRewards();
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                IERC20(REWARD_TOKEN).safeTransferFrom(
                    msg.sender,
                    address(this),
                    amount
                );
                l.availableRewards += amount;
                emit RewardsAdded(amount);
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getAvailableRewards()
                external
                view
                returns (uint256 rewards, uint256 unstakeRewards)
            {
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                rewards = l.availableRewards - _getPendingRewards();
                unstakeRewards = l.availableUnstakeRewards;
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getPendingRewards() external view returns (uint256) {
                return _getPendingRewards();
            }
            function _getPendingRewards() internal view returns (uint256) {
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                return
                    l.availableRewards -
                    _decay(l.availableRewards, l.lastRewardUpdate, block.timestamp);
            }
            function _updateRewards() internal {
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                if (
                    l.lastRewardUpdate == 0 ||
                    l.totalPower == 0 ||
                    l.availableRewards == 0
                ) {
                    l.lastRewardUpdate = block.timestamp;
                    return;
                }
                uint256 pendingRewards = _getPendingRewards();
                l.accRewardPerShare +=
                    (pendingRewards * ACC_REWARD_PRECISION) /
                    l.totalPower;
                l.availableRewards -= pendingRewards;
                l.lastRewardUpdate = block.timestamp;
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function stakeWithPermit(
                uint256 amount,
                uint64 period,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) external {
                IERC2612(PREMIA).permit(
                    msg.sender,
                    address(this),
                    amount,
                    deadline,
                    v,
                    r,
                    s
                );
                IERC20(PREMIA).safeTransferFrom(msg.sender, address(this), amount);
                _stake(msg.sender, amount, period);
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function stake(uint256 amount, uint64 period) external {
                IERC20(PREMIA).safeTransferFrom(msg.sender, address(this), amount);
                _stake(msg.sender, amount, period);
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function updateLock(uint64 period) external {
                if (period > MAX_PERIOD) revert PremiaStaking__ExcessiveStakePeriod();
                _updateRewards();
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                PremiaStakingStorage.UserInfo storage u = l.userInfo[msg.sender];
                uint64 oldPeriod = u.stakePeriod;
                if (period <= oldPeriod) revert PremiaStaking__PeriodTooShort();
                UpdateArgsInternal memory args = _getInitialUpdateArgsInternal(
                    l,
                    u,
                    msg.sender
                );
                unchecked {
                    uint64 lockToAdd = period - oldPeriod;
                    u.lockedUntil =
                        uint64(Math.max(u.lockedUntil, block.timestamp)) +
                        lockToAdd;
                    u.stakePeriod = period;
                    args.newPower = _calculateUserPower(
                        args.balance + args.unstakeReward,
                        period
                    );
                }
                _updateUser(l, u, args);
                emit UpdateLock(msg.sender, oldPeriod, period);
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function harvestAndStake(
                IPremiaStaking.SwapArgs memory s,
                uint64 stakePeriod
            ) external {
                uint256 amountRewardToken = _harvest(msg.sender);
                if (amountRewardToken == 0) return;
                IERC20(REWARD_TOKEN).safeTransfer(EXCHANGE_HELPER, amountRewardToken);
                uint256 amountPremia = IExchangeHelper(EXCHANGE_HELPER).swapWithToken(
                    REWARD_TOKEN,
                    PREMIA,
                    amountRewardToken,
                    s.callee,
                    s.allowanceTarget,
                    s.data,
                    s.refundAddress
                );
                if (amountPremia < s.amountOutMin)
                    revert PremiaStaking__InsufficientSwapOutput();
                _stake(msg.sender, amountPremia, stakePeriod);
            }
            function _calculateWeightedAverage(
                uint256 A,
                uint256 B,
                uint256 weightA,
                uint256 weightB
            ) internal pure returns (uint256) {
                return (A * weightA + B * weightB) / (weightA + weightB);
            }
            function _stake(
                address toAddress,
                uint256 amount,
                uint64 stakePeriod
            ) internal {
                if (stakePeriod > MAX_PERIOD)
                    revert PremiaStaking__ExcessiveStakePeriod();
                unchecked {
                    _creditTo(
                        toAddress,
                        amount,
                        stakePeriod,
                        uint64(block.timestamp) + stakePeriod,
                        false
                    );
                }
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getPendingUserRewards(
                address user
            ) external view returns (uint256 reward, uint256 unstakeReward) {
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                PremiaStakingStorage.UserInfo storage u = l.userInfo[user];
                uint256 accRewardPerShare = l.accRewardPerShare;
                if (l.lastRewardUpdate > 0 && l.availableRewards > 0) {
                    accRewardPerShare +=
                        (_getPendingRewards() * ACC_REWARD_PRECISION) /
                        l.totalPower;
                }
                reward =
                    u.reward +
                    _calculateReward(
                        accRewardPerShare,
                        _calculateUserPower(_balanceOf(user), u.stakePeriod),
                        u.rewardDebt
                    );
                unstakeReward = _calculateReward(
                    l.accUnstakeRewardPerShare,
                    _calculateUserPower(_balanceOf(user), u.stakePeriod),
                    u.unstakeRewardDebt
                );
            }
            function harvest() external {
                uint256 amount = _harvest(msg.sender);
                IERC20(REWARD_TOKEN).safeTransfer(msg.sender, amount);
            }
            function _harvest(address account) internal returns (uint256 amount) {
                _updateRewards();
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                PremiaStakingStorage.UserInfo storage u = l.userInfo[account];
                UpdateArgsInternal memory args = _getInitialUpdateArgsInternal(
                    l,
                    u,
                    account
                );
                if (args.unstakeReward > 0) {
                    args.newPower = _calculateUserPower(
                        args.balance + args.unstakeReward,
                        u.stakePeriod
                    );
                } else {
                    args.newPower = args.oldPower;
                }
                _updateUser(l, u, args);
                amount = u.reward;
                u.reward = 0;
                emit Harvest(account, amount);
            }
            function _updateTotalPower(
                PremiaStakingStorage.Layout storage l,
                uint256 oldUserPower,
                uint256 newUserPower
            ) internal {
                if (newUserPower > oldUserPower) {
                    l.totalPower += newUserPower - oldUserPower;
                } else if (newUserPower < oldUserPower) {
                    l.totalPower -= oldUserPower - newUserPower;
                }
            }
            function _beforeUnstake(address user, uint256 amount) internal virtual {}
            /**
             * @inheritdoc IPremiaStaking
             */
            function earlyUnstake(uint256 amount) external {
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                _startWithdraw(
                    l,
                    l.userInfo[msg.sender],
                    amount,
                    (amount * _getEarlyUnstakeFeeBPS(msg.sender)) / 1e4
                );
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getEarlyUnstakeFeeBPS(
                address user
            ) external view returns (uint256 feePercentage) {
                return _getEarlyUnstakeFeeBPS(user);
            }
            function _getEarlyUnstakeFeeBPS(
                address user
            ) internal view returns (uint256 feePercentageBPS) {
                uint256 lockedUntil = PremiaStakingStorage
                    .layout()
                    .userInfo[user]
                    .lockedUntil;
                if (lockedUntil <= block.timestamp)
                    revert PremiaStaking__StakeNotLocked();
                uint256 lockLeft;
                unchecked {
                    lockLeft = lockedUntil - block.timestamp;
                }
                feePercentageBPS = (lockLeft * 2500) / 365 days; // 25% fee per year left
                if (feePercentageBPS > 7500) {
                    feePercentageBPS = 7500; // Capped at 75%
                }
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function startWithdraw(uint256 amount) external {
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                PremiaStakingStorage.UserInfo storage u = l.userInfo[msg.sender];
                if (u.lockedUntil > block.timestamp)
                    revert PremiaStaking__StakeLocked();
                _startWithdraw(l, u, amount, 0);
            }
            function _startWithdraw(
                PremiaStakingStorage.Layout storage l,
                PremiaStakingStorage.UserInfo storage u,
                uint256 amount,
                uint256 fee
            ) internal {
                if (_getAvailablePremiaAmount() < amount - fee)
                    revert PremiaStaking__NotEnoughLiquidity();
                _updateRewards();
                _beforeUnstake(msg.sender, amount);
                UpdateArgsInternal memory args = _getInitialUpdateArgsInternal(
                    l,
                    u,
                    msg.sender
                );
                _burn(msg.sender, amount);
                l.pendingWithdrawal += amount - fee;
                if (fee > 0) {
                    l.accUnstakeRewardPerShare +=
                        (fee * ACC_REWARD_PRECISION) /
                        (l.totalPower - args.oldPower); // User who early unstake doesnt collect any of the fee
                    l.availableUnstakeRewards += fee;
                }
                args.newPower = _calculateUserPower(
                    args.balance - amount + args.unstakeReward,
                    u.stakePeriod
                );
                _updateUser(l, u, args);
                l.withdrawals[msg.sender].amount += amount - fee;
                l.withdrawals[msg.sender].startDate = block.timestamp;
                emit Unstake(msg.sender, amount, fee, block.timestamp);
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function withdraw() external {
                _updateRewards();
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                uint256 startDate = l.withdrawals[msg.sender].startDate;
                if (startDate == 0) revert PremiaStaking__NoPendingWithdrawal();
                unchecked {
                    if (block.timestamp <= startDate + WITHDRAWAL_DELAY)
                        revert PremiaStaking__WithdrawalStillPending();
                }
                uint256 amount = l.withdrawals[msg.sender].amount;
                l.pendingWithdrawal -= amount;
                delete l.withdrawals[msg.sender];
                IERC20(PREMIA).safeTransfer(msg.sender, amount);
                emit Withdraw(msg.sender, amount);
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getTotalPower() external view returns (uint256) {
                return PremiaStakingStorage.layout().totalPower;
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getUserPower(address user) external view returns (uint256) {
                return
                    _calculateUserPower(
                        _balanceOf(user),
                        PremiaStakingStorage.layout().userInfo[user].stakePeriod
                    );
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getDiscountBPS(address user) external view returns (uint256) {
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                uint256 userPower = _calculateUserPower(
                    _balanceOf(user),
                    l.userInfo[user].stakePeriod
                );
                // If user is a contract, we use a different formula based on % of total power owned by the contract
                if (user.isContract()) {
                    // Require 50% of overall staked power for contract to have max discount
                    if (userPower >= l.totalPower >> 1) {
                        return MAX_CONTRACT_DISCOUNT;
                    } else {
                        return
                            (userPower * MAX_CONTRACT_DISCOUNT) / (l.totalPower >> 1);
                    }
                }
                IPremiaStaking.StakeLevel[] memory stakeLevels = _getStakeLevels();
                uint256 length = stakeLevels.length;
                unchecked {
                    for (uint256 i = 0; i < length; i++) {
                        IPremiaStaking.StakeLevel memory level = stakeLevels[i];
                        if (userPower < level.amount) {
                            uint256 amountPrevLevel;
                            uint256 discountPrevLevelBPS;
                            // If stake is lower, user is in this level, and we need to LERP with prev level to get discount value
                            if (i > 0) {
                                amountPrevLevel = stakeLevels[i - 1].amount;
                                discountPrevLevelBPS = stakeLevels[i - 1].discountBPS;
                            } else {
                                // If this is the first level, prev level is 0 / 0
                                amountPrevLevel = 0;
                                discountPrevLevelBPS = 0;
                            }
                            uint256 remappedDiscountBPS = level.discountBPS -
                                discountPrevLevelBPS;
                            uint256 remappedAmount = level.amount - amountPrevLevel;
                            uint256 remappedPower = userPower - amountPrevLevel;
                            uint256 levelProgressBPS = (remappedPower *
                                INVERSE_BASIS_POINT) / remappedAmount;
                            return
                                discountPrevLevelBPS +
                                ((remappedDiscountBPS * levelProgressBPS) /
                                    INVERSE_BASIS_POINT);
                        }
                    }
                    // If no match found it means user is >= max possible stake, and therefore has max discount possible
                    return stakeLevels[length - 1].discountBPS;
                }
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getStakeLevels()
                external
                pure
                returns (IPremiaStaking.StakeLevel[] memory stakeLevels)
            {
                return _getStakeLevels();
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getStakePeriodMultiplierBPS(
                uint256 period
            ) external pure returns (uint256) {
                return _getStakePeriodMultiplierBPS(period);
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getUserInfo(
                address user
            ) external view returns (PremiaStakingStorage.UserInfo memory) {
                return PremiaStakingStorage.layout().userInfo[user];
            }
            function getPendingWithdrawals() external view returns (uint256) {
                return PremiaStakingStorage.layout().pendingWithdrawal;
            }
            function getPendingWithdrawal(
                address user
            )
                external
                view
                returns (uint256 amount, uint256 startDate, uint256 unlockDate)
            {
                PremiaStakingStorage.Layout storage l = PremiaStakingStorage.layout();
                amount = l.withdrawals[user].amount;
                startDate = l.withdrawals[user].startDate;
                if (startDate > 0) {
                    unlockDate = startDate + WITHDRAWAL_DELAY;
                }
            }
            function _decay(
                uint256 pendingRewards,
                uint256 oldTimestamp,
                uint256 newTimestamp
            ) internal pure returns (uint256) {
                return
                    ONE_64x64
                        .sub(DECAY_RATE_64x64)
                        .pow(newTimestamp - oldTimestamp)
                        .mulu(pendingRewards);
            }
            function _getStakeLevels()
                internal
                pure
                returns (IPremiaStaking.StakeLevel[] memory stakeLevels)
            {
                stakeLevels = new IPremiaStaking.StakeLevel[](4);
                stakeLevels[0] = IPremiaStaking.StakeLevel(5000 * 1e18, 1000); // -10%
                stakeLevels[1] = IPremiaStaking.StakeLevel(50000 * 1e18, 2500); // -25%
                stakeLevels[2] = IPremiaStaking.StakeLevel(500000 * 1e18, 3500); // -35%
                stakeLevels[3] = IPremiaStaking.StakeLevel(2500000 * 1e18, 6000); // -60%
            }
            function _getStakePeriodMultiplierBPS(
                uint256 period
            ) internal pure returns (uint256) {
                uint256 oneYear = 365 days;
                if (period == 0) return 2500; // x0.25
                if (period >= 4 * oneYear) return 42500; // x4.25
                return 2500 + (period * 1e4) / oneYear; // 0.25x + 1.0x per year lockup
            }
            function _calculateUserPower(
                uint256 balance,
                uint64 stakePeriod
            ) internal pure returns (uint256) {
                return
                    (balance * _getStakePeriodMultiplierBPS(stakePeriod)) /
                    INVERSE_BASIS_POINT;
            }
            function _calculateReward(
                uint256 accRewardPerShare,
                uint256 power,
                uint256 rewardDebt
            ) internal pure returns (uint256) {
                return
                    ((accRewardPerShare * power) / ACC_REWARD_PRECISION) - rewardDebt;
            }
            function _calculateRewards(
                PremiaStakingStorage.Layout storage l,
                PremiaStakingStorage.UserInfo storage u,
                uint256 power
            ) internal view returns (uint256 reward, uint256 unstakeReward) {
                reward = _calculateReward(l.accRewardPerShare, power, u.rewardDebt);
                unstakeReward = _calculateReward(
                    l.accUnstakeRewardPerShare,
                    power,
                    u.unstakeRewardDebt
                );
            }
            function _creditRewards(
                PremiaStakingStorage.Layout storage l,
                PremiaStakingStorage.UserInfo storage u,
                address user,
                uint256 reward,
                uint256 unstakeReward
            ) internal {
                u.reward += reward;
                if (unstakeReward > 0) {
                    l.availableUnstakeRewards -= unstakeReward;
                    _mint(user, unstakeReward);
                    emit EarlyUnstakeRewardCollected(user, unstakeReward);
                }
            }
            function _updateRewardDebt(
                PremiaStakingStorage.Layout storage l,
                PremiaStakingStorage.UserInfo storage u,
                uint256 power
            ) internal {
                u.rewardDebt = _calculateRewardDebt(l.accRewardPerShare, power);
                u.unstakeRewardDebt = _calculateRewardDebt(
                    l.accUnstakeRewardPerShare,
                    power
                );
            }
            function _getInitialUpdateArgsInternal(
                PremiaStakingStorage.Layout storage l,
                PremiaStakingStorage.UserInfo storage u,
                address user
            ) internal view returns (UpdateArgsInternal memory) {
                UpdateArgsInternal memory args;
                args.user = user;
                args.balance = _balanceOf(user);
                if (args.balance > 0) {
                    args.oldPower = _calculateUserPower(args.balance, u.stakePeriod);
                }
                {
                    (uint256 reward, uint256 unstakeReward) = _calculateRewards(
                        l,
                        u,
                        args.oldPower
                    );
                    args.reward = reward;
                    args.unstakeReward = unstakeReward;
                }
                return args;
            }
            function _calculateRewardDebt(
                uint256 accRewardPerShare,
                uint256 power
            ) internal pure returns (uint256) {
                return (power * accRewardPerShare) / ACC_REWARD_PRECISION;
            }
            function _updateUser(
                PremiaStakingStorage.Layout storage l,
                PremiaStakingStorage.UserInfo storage u,
                UpdateArgsInternal memory args
            ) internal {
                _updateRewardDebt(l, u, args.newPower);
                _creditRewards(l, u, args.user, args.reward, args.unstakeReward);
                _updateTotalPower(l, args.oldPower, args.newPower);
            }
            /**
             * @inheritdoc IPremiaStaking
             */
            function getAvailablePremiaAmount() external view returns (uint256) {
                return _getAvailablePremiaAmount();
            }
            function _getAvailablePremiaAmount() internal view returns (uint256) {
                return
                    IERC20(PREMIA).balanceOf(address(this)) -
                    PremiaStakingStorage.layout().pendingWithdrawal;
            }
        }
        // SPDX-License-Identifier: BUSL-1.1
        // For further clarification please see https://license.premia.legal
        pragma solidity ^0.8.0;
        library PremiaStakingStorage {
            bytes32 internal constant STORAGE_SLOT =
                keccak256("premia.contracts.staking.PremiaStaking");
            struct Withdrawal {
                uint256 amount; // Premia amount
                uint256 startDate; // Will unlock at startDate + withdrawalDelay
            }
            struct UserInfo {
                uint256 reward; // Amount of rewards accrued which havent been claimed yet
                uint256 rewardDebt; // Debt to subtract from reward calculation
                uint256 unstakeRewardDebt; // Debt to subtract from reward calculation from early unstake fee
                uint64 stakePeriod; // Stake period selected by user
                uint64 lockedUntil; // Timestamp at which the lock ends
            }
            struct Layout {
                uint256 pendingWithdrawal;
                uint256 _deprecated_withdrawalDelay;
                mapping(address => Withdrawal) withdrawals;
                uint256 availableRewards;
                uint256 lastRewardUpdate; // Timestamp of last reward distribution update
                uint256 totalPower; // Total power of all staked tokens (underlying amount with multiplier applied)
                mapping(address => UserInfo) userInfo;
                uint256 accRewardPerShare;
                uint256 accUnstakeRewardPerShare;
                uint256 availableUnstakeRewards;
            }
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
        }
        // SPDX-License-Identifier: BUSL-1.1
        // For further clarification please see https://license.premia.legal
        pragma solidity ^0.8.0;
        import {PremiaStaking} from "./PremiaStaking.sol";
        import {PremiaStakingStorage} from "./PremiaStakingStorage.sol";
        import {VxPremiaStorage} from "./VxPremiaStorage.sol";
        import {IVxPremia} from "./IVxPremia.sol";
        /**
         * @author Premia
         * @title A contract allowing you to use your locked Premia as voting power for mining weights
         */
        contract VxPremia is IVxPremia, PremiaStaking {
            constructor(
                address lzEndpoint,
                address premia,
                address rewardToken,
                address exchangeHelper
            ) PremiaStaking(lzEndpoint, premia, rewardToken, exchangeHelper) {}
            function _beforeUnstake(address user, uint256 amount) internal override {
                uint256 votingPowerUnstaked = _calculateUserPower(
                    amount,
                    PremiaStakingStorage.layout().userInfo[user].stakePeriod
                );
                _subtractExtraUserVotes(
                    VxPremiaStorage.layout(),
                    user,
                    votingPowerUnstaked
                );
            }
            /**
             * @notice subtract user votes, starting from the end of the list, if not enough voting power is left after amountUnstaked is unstaked
             */
            function _subtractExtraUserVotes(
                VxPremiaStorage.Layout storage l,
                address user,
                uint256 amountUnstaked
            ) internal {
                uint256 votingPower = _calculateUserPower(
                    _balanceOf(user),
                    PremiaStakingStorage.layout().userInfo[user].stakePeriod
                );
                uint256 votingPowerUsed = _calculateUserVotingPowerUsed(user);
                uint256 votingPowerLeftAfterUnstake = votingPower - amountUnstaked;
                unchecked {
                    if (votingPowerUsed > votingPowerLeftAfterUnstake) {
                        _subtractUserVotes(
                            l,
                            user,
                            votingPowerUsed - votingPowerLeftAfterUnstake
                        );
                    }
                }
            }
            /**
             * @notice subtract user votes, starting from the end of the list
             */
            function _subtractUserVotes(
                VxPremiaStorage.Layout storage l,
                address user,
                uint256 amount
            ) internal {
                uint256 toSubtract = amount;
                VxPremiaStorage.Vote[] storage userVotes = l.userVotes[user];
                unchecked {
                    for (uint256 i = userVotes.length; i > 0; ) {
                        VxPremiaStorage.Vote storage vote = userVotes[--i];
                        uint256 votesRemoved;
                        if (toSubtract <= vote.amount) {
                            votesRemoved = toSubtract;
                            vote.amount -= toSubtract;
                        } else {
                            votesRemoved = vote.amount;
                            userVotes.pop();
                        }
                        toSubtract -= votesRemoved;
                        emit RemoveVote(user, vote.version, vote.target, votesRemoved);
                        if (toSubtract == 0) break;
                    }
                }
            }
            function _calculateUserVotingPowerUsed(
                address user
            ) internal view returns (uint256 votingPowerUsed) {
                VxPremiaStorage.Vote[] memory userVotes = VxPremiaStorage
                    .layout()
                    .userVotes[user];
                unchecked {
                    for (uint256 i = 0; i < userVotes.length; i++) {
                        votingPowerUsed += userVotes[i].amount;
                    }
                }
            }
            /**
             * @inheritdoc IVxPremia
             */
            function getPoolVotes(
                VxPremiaStorage.VoteVersion version,
                bytes memory target
            ) external view returns (uint256) {
                return VxPremiaStorage.layout().votes[version][target];
            }
            /**
             * @inheritdoc IVxPremia
             */
            function getUserVotes(
                address user
            ) external view returns (VxPremiaStorage.Vote[] memory) {
                return VxPremiaStorage.layout().userVotes[user];
            }
            /**
             * @inheritdoc IVxPremia
             */
            function castVotes(VxPremiaStorage.Vote[] memory votes) external {
                VxPremiaStorage.Layout storage l = VxPremiaStorage.layout();
                uint256 userVotingPower = _calculateUserPower(
                    _balanceOf(msg.sender),
                    PremiaStakingStorage.layout().userInfo[msg.sender].stakePeriod
                );
                VxPremiaStorage.Vote[] storage userVotes = l.userVotes[msg.sender];
                // Remove previous votes
                for (uint256 i = userVotes.length; i > 0; ) {
                    VxPremiaStorage.Vote memory vote = userVotes[--i];
                    l.votes[vote.version][vote.target] -= vote.amount;
                    emit RemoveVote(msg.sender, vote.version, vote.target, vote.amount);
                    userVotes.pop();
                }
                // Cast new votes
                uint256 votingPowerUsed = 0;
                for (uint256 i = 0; i < votes.length; i++) {
                    VxPremiaStorage.Vote memory vote = votes[i];
                    votingPowerUsed += votes[i].amount;
                    if (votingPowerUsed > userVotingPower)
                        revert VxPremia__NotEnoughVotingPower();
                    userVotes.push(votes[i]);
                    l.votes[vote.version][vote.target] += vote.amount;
                    emit AddVote(msg.sender, vote.version, vote.target, vote.amount);
                }
            }
        }
        // SPDX-License-Identifier: BUSL-1.1
        // For further clarification please see https://license.premia.legal
        pragma solidity ^0.8.0;
        library VxPremiaStorage {
            bytes32 internal constant STORAGE_SLOT =
                keccak256("premia.contracts.staking.VxPremia");
            enum VoteVersion {
                V2 // poolAddress : 20 bytes / isCallPool : 2 bytes
            }
            struct Vote {
                uint256 amount;
                VoteVersion version;
                bytes target;
            }
            struct Layout {
                mapping(address => Vote[]) userVotes;
                // Vote version -> Pool identifier -> Vote amount
                mapping(VoteVersion => mapping(bytes => uint256)) votes;
            }
            function layout() internal pure returns (Layout storage l) {
                bytes32 slot = STORAGE_SLOT;
                assembly {
                    l.slot := slot
                }
            }
        }
        

        File 4 of 4: FiatTokenV2_1
        // File: @openzeppelin/contracts/math/SafeMath.sol
        
        // SPDX-License-Identifier: MIT
        
        pragma solidity ^0.6.0;
        
        /**
         * @dev Wrappers over Solidity's arithmetic operations with added overflow
         * checks.
         *
         * Arithmetic operations in Solidity wrap on overflow. This can easily result
         * in bugs, because programmers usually assume that an overflow raises an
         * error, which is the standard behavior in high level programming languages.
         * `SafeMath` restores this intuition by reverting the transaction when an
         * operation overflows.
         *
         * Using this library instead of the unchecked operations eliminates an entire
         * class of bugs, so it's recommended to use it always.
         */
        library SafeMath {
            /**
             * @dev Returns the addition of two unsigned integers, reverting on
             * overflow.
             *
             * Counterpart to Solidity's `+` operator.
             *
             * Requirements:
             *
             * - Addition cannot overflow.
             */
            function add(uint256 a, uint256 b) internal pure returns (uint256) {
                uint256 c = a + b;
                require(c >= a, "SafeMath: addition overflow");
        
                return c;
            }
        
            /**
             * @dev Returns the subtraction of two unsigned integers, reverting on
             * overflow (when the result is negative).
             *
             * Counterpart to Solidity's `-` operator.
             *
             * Requirements:
             *
             * - Subtraction cannot overflow.
             */
            function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                return sub(a, b, "SafeMath: subtraction overflow");
            }
        
            /**
             * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
             * overflow (when the result is negative).
             *
             * Counterpart to Solidity's `-` operator.
             *
             * Requirements:
             *
             * - Subtraction cannot overflow.
             */
            function sub(
                uint256 a,
                uint256 b,
                string memory errorMessage
            ) internal pure returns (uint256) {
                require(b <= a, errorMessage);
                uint256 c = a - b;
        
                return c;
            }
        
            /**
             * @dev Returns the multiplication of two unsigned integers, reverting on
             * overflow.
             *
             * Counterpart to Solidity's `*` operator.
             *
             * Requirements:
             *
             * - Multiplication cannot overflow.
             */
            function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                // benefit is lost if 'b' is also tested.
                // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                if (a == 0) {
                    return 0;
                }
        
                uint256 c = a * b;
                require(c / a == b, "SafeMath: multiplication overflow");
        
                return c;
            }
        
            /**
             * @dev Returns the integer division of two unsigned integers. Reverts on
             * division by zero. The result is rounded towards zero.
             *
             * Counterpart to Solidity's `/` operator. Note: this function uses a
             * `revert` opcode (which leaves remaining gas untouched) while Solidity
             * uses an invalid opcode to revert (consuming all remaining gas).
             *
             * Requirements:
             *
             * - The divisor cannot be zero.
             */
            function div(uint256 a, uint256 b) internal pure returns (uint256) {
                return div(a, b, "SafeMath: division by zero");
            }
        
            /**
             * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
             * division by zero. The result is rounded towards zero.
             *
             * Counterpart to Solidity's `/` operator. Note: this function uses a
             * `revert` opcode (which leaves remaining gas untouched) while Solidity
             * uses an invalid opcode to revert (consuming all remaining gas).
             *
             * Requirements:
             *
             * - The divisor cannot be zero.
             */
            function div(
                uint256 a,
                uint256 b,
                string memory errorMessage
            ) internal pure returns (uint256) {
                require(b > 0, errorMessage);
                uint256 c = a / b;
                // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        
                return c;
            }
        
            /**
             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
             * Reverts when dividing by zero.
             *
             * Counterpart to Solidity's `%` operator. This function uses a `revert`
             * opcode (which leaves remaining gas untouched) while Solidity uses an
             * invalid opcode to revert (consuming all remaining gas).
             *
             * Requirements:
             *
             * - The divisor cannot be zero.
             */
            function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                return mod(a, b, "SafeMath: modulo by zero");
            }
        
            /**
             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
             * Reverts with custom message when dividing by zero.
             *
             * Counterpart to Solidity's `%` operator. This function uses a `revert`
             * opcode (which leaves remaining gas untouched) while Solidity uses an
             * invalid opcode to revert (consuming all remaining gas).
             *
             * Requirements:
             *
             * - The divisor cannot be zero.
             */
            function mod(
                uint256 a,
                uint256 b,
                string memory errorMessage
            ) internal pure returns (uint256) {
                require(b != 0, errorMessage);
                return a % b;
            }
        }
        
        // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
        
        pragma solidity ^0.6.0;
        
        /**
         * @dev Interface of the ERC20 standard as defined in the EIP.
         */
        interface IERC20 {
            /**
             * @dev Returns the amount of tokens in existence.
             */
            function totalSupply() external view returns (uint256);
        
            /**
             * @dev Returns the amount of tokens owned by `account`.
             */
            function balanceOf(address account) external view returns (uint256);
        
            /**
             * @dev Moves `amount` tokens from the caller's account to `recipient`.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a {Transfer} event.
             */
            function transfer(address recipient, uint256 amount)
                external
                returns (bool);
        
            /**
             * @dev Returns the remaining number of tokens that `spender` will be
             * allowed to spend on behalf of `owner` through {transferFrom}. This is
             * zero by default.
             *
             * This value changes when {approve} or {transferFrom} are called.
             */
            function allowance(address owner, address spender)
                external
                view
                returns (uint256);
        
            /**
             * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * IMPORTANT: Beware that changing an allowance with this method brings the risk
             * that someone may use both the old and the new allowance by unfortunate
             * transaction ordering. One possible solution to mitigate this race
             * condition is to first reduce the spender's allowance to 0 and set the
             * desired value afterwards:
             * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
             *
             * Emits an {Approval} event.
             */
            function approve(address spender, uint256 amount) external returns (bool);
        
            /**
             * @dev Moves `amount` tokens from `sender` to `recipient` using the
             * allowance mechanism. `amount` is then deducted from the caller's
             * allowance.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a {Transfer} event.
             */
            function transferFrom(
                address sender,
                address recipient,
                uint256 amount
            ) external returns (bool);
        
            /**
             * @dev Emitted when `value` tokens are moved from one account (`from`) to
             * another (`to`).
             *
             * Note that `value` may be zero.
             */
            event Transfer(address indexed from, address indexed to, uint256 value);
        
            /**
             * @dev Emitted when the allowance of a `spender` for an `owner` is set by
             * a call to {approve}. `value` is the new allowance.
             */
            event Approval(
                address indexed owner,
                address indexed spender,
                uint256 value
            );
        }
        
        // File: contracts/v1/AbstractFiatTokenV1.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        abstract contract AbstractFiatTokenV1 is IERC20 {
            function _approve(
                address owner,
                address spender,
                uint256 value
            ) internal virtual;
        
            function _transfer(
                address from,
                address to,
                uint256 value
            ) internal virtual;
        }
        
        // File: contracts/v1/Ownable.sol
        
        /**
         * Copyright (c) 2018 zOS Global Limited.
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        pragma solidity 0.6.12;
        
        /**
         * @notice The Ownable contract has an owner address, and provides basic
         * authorization control functions
         * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol
         * Modifications:
         * 1. Consolidate OwnableStorage into this contract (7/13/18)
         * 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20)
         * 3. Make public functions external (5/27/20)
         */
        contract Ownable {
            // Owner of the contract
            address private _owner;
        
            /**
             * @dev Event to show ownership has been transferred
             * @param previousOwner representing the address of the previous owner
             * @param newOwner representing the address of the new owner
             */
            event OwnershipTransferred(address previousOwner, address newOwner);
        
            /**
             * @dev The constructor sets the original owner of the contract to the sender account.
             */
            constructor() public {
                setOwner(msg.sender);
            }
        
            /**
             * @dev Tells the address of the owner
             * @return the address of the owner
             */
            function owner() external view returns (address) {
                return _owner;
            }
        
            /**
             * @dev Sets a new owner address
             */
            function setOwner(address newOwner) internal {
                _owner = newOwner;
            }
        
            /**
             * @dev Throws if called by any account other than the owner.
             */
            modifier onlyOwner() {
                require(msg.sender == _owner, "Ownable: caller is not the owner");
                _;
            }
        
            /**
             * @dev Allows the current owner to transfer control of the contract to a newOwner.
             * @param newOwner The address to transfer ownership to.
             */
            function transferOwnership(address newOwner) external onlyOwner {
                require(
                    newOwner != address(0),
                    "Ownable: new owner is the zero address"
                );
                emit OwnershipTransferred(_owner, newOwner);
                setOwner(newOwner);
            }
        }
        
        // File: contracts/v1/Pausable.sol
        
        /**
         * Copyright (c) 2016 Smart Contract Solutions, Inc.
         * Copyright (c) 2018-2020 CENTRE SECZ0
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @notice Base contract which allows children to implement an emergency stop
         * mechanism
         * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol
         * Modifications:
         * 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018)
         * 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018)
         * 3. Removed whenPaused (6/14/2018)
         * 4. Switches ownable library to use ZeppelinOS (7/12/18)
         * 5. Remove constructor (7/13/18)
         * 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20)
         * 7. Make public functions external (5/27/20)
         */
        contract Pausable is Ownable {
            event Pause();
            event Unpause();
            event PauserChanged(address indexed newAddress);
        
            address public pauser;
            bool public paused = false;
        
            /**
             * @dev Modifier to make a function callable only when the contract is not paused.
             */
            modifier whenNotPaused() {
                require(!paused, "Pausable: paused");
                _;
            }
        
            /**
             * @dev throws if called by any account other than the pauser
             */
            modifier onlyPauser() {
                require(msg.sender == pauser, "Pausable: caller is not the pauser");
                _;
            }
        
            /**
             * @dev called by the owner to pause, triggers stopped state
             */
            function pause() external onlyPauser {
                paused = true;
                emit Pause();
            }
        
            /**
             * @dev called by the owner to unpause, returns to normal state
             */
            function unpause() external onlyPauser {
                paused = false;
                emit Unpause();
            }
        
            /**
             * @dev update the pauser role
             */
            function updatePauser(address _newPauser) external onlyOwner {
                require(
                    _newPauser != address(0),
                    "Pausable: new pauser is the zero address"
                );
                pauser = _newPauser;
                emit PauserChanged(pauser);
            }
        }
        
        // File: contracts/v1/Blacklistable.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title Blacklistable Token
         * @dev Allows accounts to be blacklisted by a "blacklister" role
         */
        contract Blacklistable is Ownable {
            address public blacklister;
            mapping(address => bool) internal blacklisted;
        
            event Blacklisted(address indexed _account);
            event UnBlacklisted(address indexed _account);
            event BlacklisterChanged(address indexed newBlacklister);
        
            /**
             * @dev Throws if called by any account other than the blacklister
             */
            modifier onlyBlacklister() {
                require(
                    msg.sender == blacklister,
                    "Blacklistable: caller is not the blacklister"
                );
                _;
            }
        
            /**
             * @dev Throws if argument account is blacklisted
             * @param _account The address to check
             */
            modifier notBlacklisted(address _account) {
                require(
                    !blacklisted[_account],
                    "Blacklistable: account is blacklisted"
                );
                _;
            }
        
            /**
             * @dev Checks if account is blacklisted
             * @param _account The address to check
             */
            function isBlacklisted(address _account) external view returns (bool) {
                return blacklisted[_account];
            }
        
            /**
             * @dev Adds account to blacklist
             * @param _account The address to blacklist
             */
            function blacklist(address _account) external onlyBlacklister {
                blacklisted[_account] = true;
                emit Blacklisted(_account);
            }
        
            /**
             * @dev Removes account from blacklist
             * @param _account The address to remove from the blacklist
             */
            function unBlacklist(address _account) external onlyBlacklister {
                blacklisted[_account] = false;
                emit UnBlacklisted(_account);
            }
        
            function updateBlacklister(address _newBlacklister) external onlyOwner {
                require(
                    _newBlacklister != address(0),
                    "Blacklistable: new blacklister is the zero address"
                );
                blacklister = _newBlacklister;
                emit BlacklisterChanged(blacklister);
            }
        }
        
        // File: contracts/v1/FiatTokenV1.sol
        
        /**
         *
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title FiatToken
         * @dev ERC20 Token backed by fiat reserves
         */
        contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable {
            using SafeMath for uint256;
        
            string public name;
            string public symbol;
            uint8 public decimals;
            string public currency;
            address public masterMinter;
            bool internal initialized;
        
            mapping(address => uint256) internal balances;
            mapping(address => mapping(address => uint256)) internal allowed;
            uint256 internal totalSupply_ = 0;
            mapping(address => bool) internal minters;
            mapping(address => uint256) internal minterAllowed;
        
            event Mint(address indexed minter, address indexed to, uint256 amount);
            event Burn(address indexed burner, uint256 amount);
            event MinterConfigured(address indexed minter, uint256 minterAllowedAmount);
            event MinterRemoved(address indexed oldMinter);
            event MasterMinterChanged(address indexed newMasterMinter);
        
            function initialize(
                string memory tokenName,
                string memory tokenSymbol,
                string memory tokenCurrency,
                uint8 tokenDecimals,
                address newMasterMinter,
                address newPauser,
                address newBlacklister,
                address newOwner
            ) public {
                require(!initialized, "FiatToken: contract is already initialized");
                require(
                    newMasterMinter != address(0),
                    "FiatToken: new masterMinter is the zero address"
                );
                require(
                    newPauser != address(0),
                    "FiatToken: new pauser is the zero address"
                );
                require(
                    newBlacklister != address(0),
                    "FiatToken: new blacklister is the zero address"
                );
                require(
                    newOwner != address(0),
                    "FiatToken: new owner is the zero address"
                );
        
                name = tokenName;
                symbol = tokenSymbol;
                currency = tokenCurrency;
                decimals = tokenDecimals;
                masterMinter = newMasterMinter;
                pauser = newPauser;
                blacklister = newBlacklister;
                setOwner(newOwner);
                initialized = true;
            }
        
            /**
             * @dev Throws if called by any account other than a minter
             */
            modifier onlyMinters() {
                require(minters[msg.sender], "FiatToken: caller is not a minter");
                _;
            }
        
            /**
             * @dev Function to mint tokens
             * @param _to The address that will receive the minted tokens.
             * @param _amount The amount of tokens to mint. Must be less than or equal
             * to the minterAllowance of the caller.
             * @return A boolean that indicates if the operation was successful.
             */
            function mint(address _to, uint256 _amount)
                external
                whenNotPaused
                onlyMinters
                notBlacklisted(msg.sender)
                notBlacklisted(_to)
                returns (bool)
            {
                require(_to != address(0), "FiatToken: mint to the zero address");
                require(_amount > 0, "FiatToken: mint amount not greater than 0");
        
                uint256 mintingAllowedAmount = minterAllowed[msg.sender];
                require(
                    _amount <= mintingAllowedAmount,
                    "FiatToken: mint amount exceeds minterAllowance"
                );
        
                totalSupply_ = totalSupply_.add(_amount);
                balances[_to] = balances[_to].add(_amount);
                minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
                emit Mint(msg.sender, _to, _amount);
                emit Transfer(address(0), _to, _amount);
                return true;
            }
        
            /**
             * @dev Throws if called by any account other than the masterMinter
             */
            modifier onlyMasterMinter() {
                require(
                    msg.sender == masterMinter,
                    "FiatToken: caller is not the masterMinter"
                );
                _;
            }
        
            /**
             * @dev Get minter allowance for an account
             * @param minter The address of the minter
             */
            function minterAllowance(address minter) external view returns (uint256) {
                return minterAllowed[minter];
            }
        
            /**
             * @dev Checks if account is a minter
             * @param account The address to check
             */
            function isMinter(address account) external view returns (bool) {
                return minters[account];
            }
        
            /**
             * @notice Amount of remaining tokens spender is allowed to transfer on
             * behalf of the token owner
             * @param owner     Token owner's address
             * @param spender   Spender's address
             * @return Allowance amount
             */
            function allowance(address owner, address spender)
                external
                override
                view
                returns (uint256)
            {
                return allowed[owner][spender];
            }
        
            /**
             * @dev Get totalSupply of token
             */
            function totalSupply() external override view returns (uint256) {
                return totalSupply_;
            }
        
            /**
             * @dev Get token balance of an account
             * @param account address The account
             */
            function balanceOf(address account)
                external
                override
                view
                returns (uint256)
            {
                return balances[account];
            }
        
            /**
             * @notice Set spender's allowance over the caller's tokens to be a given
             * value.
             * @param spender   Spender's address
             * @param value     Allowance amount
             * @return True if successful
             */
            function approve(address spender, uint256 value)
                external
                override
                whenNotPaused
                notBlacklisted(msg.sender)
                notBlacklisted(spender)
                returns (bool)
            {
                _approve(msg.sender, spender, value);
                return true;
            }
        
            /**
             * @dev Internal function to set allowance
             * @param owner     Token owner's address
             * @param spender   Spender's address
             * @param value     Allowance amount
             */
            function _approve(
                address owner,
                address spender,
                uint256 value
            ) internal override {
                require(owner != address(0), "ERC20: approve from the zero address");
                require(spender != address(0), "ERC20: approve to the zero address");
                allowed[owner][spender] = value;
                emit Approval(owner, spender, value);
            }
        
            /**
             * @notice Transfer tokens by spending allowance
             * @param from  Payer's address
             * @param to    Payee's address
             * @param value Transfer amount
             * @return True if successful
             */
            function transferFrom(
                address from,
                address to,
                uint256 value
            )
                external
                override
                whenNotPaused
                notBlacklisted(msg.sender)
                notBlacklisted(from)
                notBlacklisted(to)
                returns (bool)
            {
                require(
                    value <= allowed[from][msg.sender],
                    "ERC20: transfer amount exceeds allowance"
                );
                _transfer(from, to, value);
                allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
                return true;
            }
        
            /**
             * @notice Transfer tokens from the caller
             * @param to    Payee's address
             * @param value Transfer amount
             * @return True if successful
             */
            function transfer(address to, uint256 value)
                external
                override
                whenNotPaused
                notBlacklisted(msg.sender)
                notBlacklisted(to)
                returns (bool)
            {
                _transfer(msg.sender, to, value);
                return true;
            }
        
            /**
             * @notice Internal function to process transfers
             * @param from  Payer's address
             * @param to    Payee's address
             * @param value Transfer amount
             */
            function _transfer(
                address from,
                address to,
                uint256 value
            ) internal override {
                require(from != address(0), "ERC20: transfer from the zero address");
                require(to != address(0), "ERC20: transfer to the zero address");
                require(
                    value <= balances[from],
                    "ERC20: transfer amount exceeds balance"
                );
        
                balances[from] = balances[from].sub(value);
                balances[to] = balances[to].add(value);
                emit Transfer(from, to, value);
            }
        
            /**
             * @dev Function to add/update a new minter
             * @param minter The address of the minter
             * @param minterAllowedAmount The minting amount allowed for the minter
             * @return True if the operation was successful.
             */
            function configureMinter(address minter, uint256 minterAllowedAmount)
                external
                whenNotPaused
                onlyMasterMinter
                returns (bool)
            {
                minters[minter] = true;
                minterAllowed[minter] = minterAllowedAmount;
                emit MinterConfigured(minter, minterAllowedAmount);
                return true;
            }
        
            /**
             * @dev Function to remove a minter
             * @param minter The address of the minter to remove
             * @return True if the operation was successful.
             */
            function removeMinter(address minter)
                external
                onlyMasterMinter
                returns (bool)
            {
                minters[minter] = false;
                minterAllowed[minter] = 0;
                emit MinterRemoved(minter);
                return true;
            }
        
            /**
             * @dev allows a minter to burn some of its own tokens
             * Validates that caller is a minter and that sender is not blacklisted
             * amount is less than or equal to the minter's account balance
             * @param _amount uint256 the amount of tokens to be burned
             */
            function burn(uint256 _amount)
                external
                whenNotPaused
                onlyMinters
                notBlacklisted(msg.sender)
            {
                uint256 balance = balances[msg.sender];
                require(_amount > 0, "FiatToken: burn amount not greater than 0");
                require(balance >= _amount, "FiatToken: burn amount exceeds balance");
        
                totalSupply_ = totalSupply_.sub(_amount);
                balances[msg.sender] = balance.sub(_amount);
                emit Burn(msg.sender, _amount);
                emit Transfer(msg.sender, address(0), _amount);
            }
        
            function updateMasterMinter(address _newMasterMinter) external onlyOwner {
                require(
                    _newMasterMinter != address(0),
                    "FiatToken: new masterMinter is the zero address"
                );
                masterMinter = _newMasterMinter;
                emit MasterMinterChanged(masterMinter);
            }
        }
        
        // File: @openzeppelin/contracts/utils/Address.sol
        
        pragma solidity ^0.6.2;
        
        /**
         * @dev Collection of functions related to the address type
         */
        library Address {
            /**
             * @dev Returns true if `account` is a contract.
             *
             * [IMPORTANT]
             * ====
             * It is unsafe to assume that an address for which this function returns
             * false is an externally-owned account (EOA) and not a contract.
             *
             * Among others, `isContract` will return false for the following
             * types of addresses:
             *
             *  - an externally-owned account
             *  - a contract in construction
             *  - an address where a contract will be created
             *  - an address where a contract lived, but was destroyed
             * ====
             */
            function isContract(address account) internal view returns (bool) {
                // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                // for accounts without code, i.e. `keccak256('')`
                bytes32 codehash;
        
                    bytes32 accountHash
                 = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    codehash := extcodehash(account)
                }
                return (codehash != accountHash && codehash != 0x0);
            }
        
            /**
             * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
             * `recipient`, forwarding all available gas and reverting on errors.
             *
             * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
             * of certain opcodes, possibly making contracts go over the 2300 gas limit
             * imposed by `transfer`, making them unable to receive funds via
             * `transfer`. {sendValue} removes this limitation.
             *
             * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
             *
             * IMPORTANT: because control is transferred to `recipient`, care must be
             * taken to not create reentrancy vulnerabilities. Consider using
             * {ReentrancyGuard} or the
             * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
             */
            function sendValue(address payable recipient, uint256 amount) internal {
                require(
                    address(this).balance >= amount,
                    "Address: insufficient balance"
                );
        
                // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                (bool success, ) = recipient.call{ value: amount }("");
                require(
                    success,
                    "Address: unable to send value, recipient may have reverted"
                );
            }
        
            /**
             * @dev Performs a Solidity function call using a low level `call`. A
             * plain`call` is an unsafe replacement for a function call: use this
             * function instead.
             *
             * If `target` reverts with a revert reason, it is bubbled up by this
             * function (like regular Solidity function calls).
             *
             * Returns the raw returned data. To convert to the expected return value,
             * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
             *
             * Requirements:
             *
             * - `target` must be a contract.
             * - calling `target` with `data` must not revert.
             *
             * _Available since v3.1._
             */
            function functionCall(address target, bytes memory data)
                internal
                returns (bytes memory)
            {
                return functionCall(target, data, "Address: low-level call failed");
            }
        
            /**
             * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
             * `errorMessage` as a fallback revert reason when `target` reverts.
             *
             * _Available since v3.1._
             */
            function functionCall(
                address target,
                bytes memory data,
                string memory errorMessage
            ) internal returns (bytes memory) {
                return _functionCallWithValue(target, data, 0, errorMessage);
            }
        
            /**
             * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
             * but also transferring `value` wei to `target`.
             *
             * Requirements:
             *
             * - the calling contract must have an ETH balance of at least `value`.
             * - the called Solidity function must be `payable`.
             *
             * _Available since v3.1._
             */
            function functionCallWithValue(
                address target,
                bytes memory data,
                uint256 value
            ) internal returns (bytes memory) {
                return
                    functionCallWithValue(
                        target,
                        data,
                        value,
                        "Address: low-level call with value failed"
                    );
            }
        
            /**
             * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
             * with `errorMessage` as a fallback revert reason when `target` reverts.
             *
             * _Available since v3.1._
             */
            function functionCallWithValue(
                address target,
                bytes memory data,
                uint256 value,
                string memory errorMessage
            ) internal returns (bytes memory) {
                require(
                    address(this).balance >= value,
                    "Address: insufficient balance for call"
                );
                return _functionCallWithValue(target, data, value, errorMessage);
            }
        
            function _functionCallWithValue(
                address target,
                bytes memory data,
                uint256 weiValue,
                string memory errorMessage
            ) private returns (bytes memory) {
                require(isContract(target), "Address: call to non-contract");
        
                // solhint-disable-next-line avoid-low-level-calls
                (bool success, bytes memory returndata) = target.call{
                    value: weiValue
                }(data);
                if (success) {
                    return returndata;
                } else {
                    // Look for revert reason and bubble it up if present
                    if (returndata.length > 0) {
                        // The easiest way to bubble the revert reason is using memory via assembly
        
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            let returndata_size := mload(returndata)
                            revert(add(32, returndata), returndata_size)
                        }
                    } else {
                        revert(errorMessage);
                    }
                }
            }
        }
        
        // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
        
        pragma solidity ^0.6.0;
        
        /**
         * @title SafeERC20
         * @dev Wrappers around ERC20 operations that throw on failure (when the token
         * contract returns false). Tokens that return no value (and instead revert or
         * throw on failure) are also supported, non-reverting calls are assumed to be
         * successful.
         * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
         * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
         */
        library SafeERC20 {
            using SafeMath for uint256;
            using Address for address;
        
            function safeTransfer(
                IERC20 token,
                address to,
                uint256 value
            ) internal {
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(token.transfer.selector, to, value)
                );
            }
        
            function safeTransferFrom(
                IERC20 token,
                address from,
                address to,
                uint256 value
            ) internal {
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
                );
            }
        
            /**
             * @dev Deprecated. This function has issues similar to the ones found in
             * {IERC20-approve}, and its usage is discouraged.
             *
             * Whenever possible, use {safeIncreaseAllowance} and
             * {safeDecreaseAllowance} instead.
             */
            function safeApprove(
                IERC20 token,
                address spender,
                uint256 value
            ) internal {
                // safeApprove should only be called when setting an initial allowance,
                // or when resetting it to zero. To increase and decrease it, use
                // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                // solhint-disable-next-line max-line-length
                require(
                    (value == 0) || (token.allowance(address(this), spender) == 0),
                    "SafeERC20: approve from non-zero to non-zero allowance"
                );
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(token.approve.selector, spender, value)
                );
            }
        
            function safeIncreaseAllowance(
                IERC20 token,
                address spender,
                uint256 value
            ) internal {
                uint256 newAllowance = token.allowance(address(this), spender).add(
                    value
                );
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(
                        token.approve.selector,
                        spender,
                        newAllowance
                    )
                );
            }
        
            function safeDecreaseAllowance(
                IERC20 token,
                address spender,
                uint256 value
            ) internal {
                uint256 newAllowance = token.allowance(address(this), spender).sub(
                    value,
                    "SafeERC20: decreased allowance below zero"
                );
                _callOptionalReturn(
                    token,
                    abi.encodeWithSelector(
                        token.approve.selector,
                        spender,
                        newAllowance
                    )
                );
            }
        
            /**
             * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
             * on the return value: the return value is optional (but if data is returned, it must not be false).
             * @param token The token targeted by the call.
             * @param data The call data (encoded using abi.encode or one of its variants).
             */
            function _callOptionalReturn(IERC20 token, bytes memory data) private {
                // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                // the target address contains contract code and also asserts for success in the low-level call.
        
                bytes memory returndata = address(token).functionCall(
                    data,
                    "SafeERC20: low-level call failed"
                );
                if (returndata.length > 0) {
                    // Return data is optional
                    // solhint-disable-next-line max-line-length
                    require(
                        abi.decode(returndata, (bool)),
                        "SafeERC20: ERC20 operation did not succeed"
                    );
                }
            }
        }
        
        // File: contracts/v1.1/Rescuable.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        contract Rescuable is Ownable {
            using SafeERC20 for IERC20;
        
            address private _rescuer;
        
            event RescuerChanged(address indexed newRescuer);
        
            /**
             * @notice Returns current rescuer
             * @return Rescuer's address
             */
            function rescuer() external view returns (address) {
                return _rescuer;
            }
        
            /**
             * @notice Revert if called by any account other than the rescuer.
             */
            modifier onlyRescuer() {
                require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
                _;
            }
        
            /**
             * @notice Rescue ERC20 tokens locked up in this contract.
             * @param tokenContract ERC20 token contract address
             * @param to        Recipient address
             * @param amount    Amount to withdraw
             */
            function rescueERC20(
                IERC20 tokenContract,
                address to,
                uint256 amount
            ) external onlyRescuer {
                tokenContract.safeTransfer(to, amount);
            }
        
            /**
             * @notice Assign the rescuer role to a given address.
             * @param newRescuer New rescuer's address
             */
            function updateRescuer(address newRescuer) external onlyOwner {
                require(
                    newRescuer != address(0),
                    "Rescuable: new rescuer is the zero address"
                );
                _rescuer = newRescuer;
                emit RescuerChanged(newRescuer);
            }
        }
        
        // File: contracts/v1.1/FiatTokenV1_1.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title FiatTokenV1_1
         * @dev ERC20 Token backed by fiat reserves
         */
        contract FiatTokenV1_1 is FiatTokenV1, Rescuable {
        
        }
        
        // File: contracts/v2/AbstractFiatTokenV2.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
            function _increaseAllowance(
                address owner,
                address spender,
                uint256 increment
            ) internal virtual;
        
            function _decreaseAllowance(
                address owner,
                address spender,
                uint256 decrement
            ) internal virtual;
        }
        
        // File: contracts/util/ECRecover.sol
        
        /**
         * Copyright (c) 2016-2019 zOS Global Limited
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title ECRecover
         * @notice A library that provides a safe ECDSA recovery function
         */
        library ECRecover {
            /**
             * @notice Recover signer's address from a signed message
             * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
             * Modifications: Accept v, r, and s as separate arguments
             * @param digest    Keccak-256 hash digest of the signed message
             * @param v         v of the signature
             * @param r         r of the signature
             * @param s         s of the signature
             * @return Signer address
             */
            function recover(
                bytes32 digest,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal pure returns (address) {
                // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
                // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                //
                // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                // these malleable signatures as well.
                if (
                    uint256(s) >
                    0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
                ) {
                    revert("ECRecover: invalid signature 's' value");
                }
        
                if (v != 27 && v != 28) {
                    revert("ECRecover: invalid signature 'v' value");
                }
        
                // If the signature is valid (and not malleable), return the signer address
                address signer = ecrecover(digest, v, r, s);
                require(signer != address(0), "ECRecover: invalid signature");
        
                return signer;
            }
        }
        
        // File: contracts/util/EIP712.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title EIP712
         * @notice A library that provides EIP712 helper functions
         */
        library EIP712 {
            /**
             * @notice Make EIP712 domain separator
             * @param name      Contract name
             * @param version   Contract version
             * @return Domain separator
             */
            function makeDomainSeparator(string memory name, string memory version)
                internal
                view
                returns (bytes32)
            {
                uint256 chainId;
                assembly {
                    chainId := chainid()
                }
                return
                    keccak256(
                        abi.encode(
                            // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
                            0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                            keccak256(bytes(name)),
                            keccak256(bytes(version)),
                            chainId,
                            address(this)
                        )
                    );
            }
        
            /**
             * @notice Recover signer's address from a EIP712 signature
             * @param domainSeparator   Domain separator
             * @param v                 v of the signature
             * @param r                 r of the signature
             * @param s                 s of the signature
             * @param typeHashAndData   Type hash concatenated with data
             * @return Signer's address
             */
            function recover(
                bytes32 domainSeparator,
                uint8 v,
                bytes32 r,
                bytes32 s,
                bytes memory typeHashAndData
            ) internal pure returns (address) {
                bytes32 digest = keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        domainSeparator,
                        keccak256(typeHashAndData)
                    )
                );
                return ECRecover.recover(digest, v, r, s);
            }
        }
        
        // File: contracts/v2/EIP712Domain.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title EIP712 Domain
         */
        contract EIP712Domain {
            /**
             * @dev EIP712 Domain Separator
             */
            bytes32 public DOMAIN_SEPARATOR;
        }
        
        // File: contracts/v2/EIP3009.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title EIP-3009
         * @notice Provide internal implementation for gas-abstracted transfers
         * @dev Contracts that inherit from this must wrap these with publicly
         * accessible functions, optionally adding modifiers where necessary
         */
        abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
            // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
            bytes32
                public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
        
            // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
            bytes32
                public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
        
            // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
            bytes32
                public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
        
            /**
             * @dev authorizer address => nonce => bool (true if nonce is used)
             */
            mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
        
            event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
            event AuthorizationCanceled(
                address indexed authorizer,
                bytes32 indexed nonce
            );
        
            /**
             * @notice Returns the state of an authorization
             * @dev Nonces are randomly generated 32-byte data unique to the
             * authorizer's address
             * @param authorizer    Authorizer's address
             * @param nonce         Nonce of the authorization
             * @return True if the nonce is used
             */
            function authorizationState(address authorizer, bytes32 nonce)
                external
                view
                returns (bool)
            {
                return _authorizationStates[authorizer][nonce];
            }
        
            /**
             * @notice Execute a transfer with a signed authorization
             * @param from          Payer's address (Authorizer)
             * @param to            Payee's address
             * @param value         Amount to be transferred
             * @param validAfter    The time after which this is valid (unix time)
             * @param validBefore   The time before which this is valid (unix time)
             * @param nonce         Unique nonce
             * @param v             v of the signature
             * @param r             r of the signature
             * @param s             s of the signature
             */
            function _transferWithAuthorization(
                address from,
                address to,
                uint256 value,
                uint256 validAfter,
                uint256 validBefore,
                bytes32 nonce,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal {
                _requireValidAuthorization(from, nonce, validAfter, validBefore);
        
                bytes memory data = abi.encode(
                    TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
                    from,
                    to,
                    value,
                    validAfter,
                    validBefore,
                    nonce
                );
                require(
                    EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
                    "FiatTokenV2: invalid signature"
                );
        
                _markAuthorizationAsUsed(from, nonce);
                _transfer(from, to, value);
            }
        
            /**
             * @notice Receive a transfer with a signed authorization from the payer
             * @dev This has an additional check to ensure that the payee's address
             * matches the caller of this function to prevent front-running attacks.
             * @param from          Payer's address (Authorizer)
             * @param to            Payee's address
             * @param value         Amount to be transferred
             * @param validAfter    The time after which this is valid (unix time)
             * @param validBefore   The time before which this is valid (unix time)
             * @param nonce         Unique nonce
             * @param v             v of the signature
             * @param r             r of the signature
             * @param s             s of the signature
             */
            function _receiveWithAuthorization(
                address from,
                address to,
                uint256 value,
                uint256 validAfter,
                uint256 validBefore,
                bytes32 nonce,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal {
                require(to == msg.sender, "FiatTokenV2: caller must be the payee");
                _requireValidAuthorization(from, nonce, validAfter, validBefore);
        
                bytes memory data = abi.encode(
                    RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
                    from,
                    to,
                    value,
                    validAfter,
                    validBefore,
                    nonce
                );
                require(
                    EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
                    "FiatTokenV2: invalid signature"
                );
        
                _markAuthorizationAsUsed(from, nonce);
                _transfer(from, to, value);
            }
        
            /**
             * @notice Attempt to cancel an authorization
             * @param authorizer    Authorizer's address
             * @param nonce         Nonce of the authorization
             * @param v             v of the signature
             * @param r             r of the signature
             * @param s             s of the signature
             */
            function _cancelAuthorization(
                address authorizer,
                bytes32 nonce,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal {
                _requireUnusedAuthorization(authorizer, nonce);
        
                bytes memory data = abi.encode(
                    CANCEL_AUTHORIZATION_TYPEHASH,
                    authorizer,
                    nonce
                );
                require(
                    EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer,
                    "FiatTokenV2: invalid signature"
                );
        
                _authorizationStates[authorizer][nonce] = true;
                emit AuthorizationCanceled(authorizer, nonce);
            }
        
            /**
             * @notice Check that an authorization is unused
             * @param authorizer    Authorizer's address
             * @param nonce         Nonce of the authorization
             */
            function _requireUnusedAuthorization(address authorizer, bytes32 nonce)
                private
                view
            {
                require(
                    !_authorizationStates[authorizer][nonce],
                    "FiatTokenV2: authorization is used or canceled"
                );
            }
        
            /**
             * @notice Check that authorization is valid
             * @param authorizer    Authorizer's address
             * @param nonce         Nonce of the authorization
             * @param validAfter    The time after which this is valid (unix time)
             * @param validBefore   The time before which this is valid (unix time)
             */
            function _requireValidAuthorization(
                address authorizer,
                bytes32 nonce,
                uint256 validAfter,
                uint256 validBefore
            ) private view {
                require(
                    now > validAfter,
                    "FiatTokenV2: authorization is not yet valid"
                );
                require(now < validBefore, "FiatTokenV2: authorization is expired");
                _requireUnusedAuthorization(authorizer, nonce);
            }
        
            /**
             * @notice Mark an authorization as used
             * @param authorizer    Authorizer's address
             * @param nonce         Nonce of the authorization
             */
            function _markAuthorizationAsUsed(address authorizer, bytes32 nonce)
                private
            {
                _authorizationStates[authorizer][nonce] = true;
                emit AuthorizationUsed(authorizer, nonce);
            }
        }
        
        // File: contracts/v2/EIP2612.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title EIP-2612
         * @notice Provide internal implementation for gas-abstracted approvals
         */
        abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
            // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
            bytes32
                public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
        
            mapping(address => uint256) private _permitNonces;
        
            /**
             * @notice Nonces for permit
             * @param owner Token owner's address (Authorizer)
             * @return Next nonce
             */
            function nonces(address owner) external view returns (uint256) {
                return _permitNonces[owner];
            }
        
            /**
             * @notice Verify a signed approval permit and execute if valid
             * @param owner     Token owner's address (Authorizer)
             * @param spender   Spender's address
             * @param value     Amount of allowance
             * @param deadline  The time at which this expires (unix time)
             * @param v         v of the signature
             * @param r         r of the signature
             * @param s         s of the signature
             */
            function _permit(
                address owner,
                address spender,
                uint256 value,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal {
                require(deadline >= now, "FiatTokenV2: permit is expired");
        
                bytes memory data = abi.encode(
                    PERMIT_TYPEHASH,
                    owner,
                    spender,
                    value,
                    _permitNonces[owner]++,
                    deadline
                );
                require(
                    EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner,
                    "EIP2612: invalid signature"
                );
        
                _approve(owner, spender, value);
            }
        }
        
        // File: contracts/v2/FiatTokenV2.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        /**
         * @title FiatToken V2
         * @notice ERC20 Token backed by fiat reserves, version 2
         */
        contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
            uint8 internal _initializedVersion;
        
            /**
             * @notice Initialize v2
             * @param newName   New token name
             */
            function initializeV2(string calldata newName) external {
                // solhint-disable-next-line reason-string
                require(initialized && _initializedVersion == 0);
                name = newName;
                DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(newName, "2");
                _initializedVersion = 1;
            }
        
            /**
             * @notice Increase the allowance by a given increment
             * @param spender   Spender's address
             * @param increment Amount of increase in allowance
             * @return True if successful
             */
            function increaseAllowance(address spender, uint256 increment)
                external
                whenNotPaused
                notBlacklisted(msg.sender)
                notBlacklisted(spender)
                returns (bool)
            {
                _increaseAllowance(msg.sender, spender, increment);
                return true;
            }
        
            /**
             * @notice Decrease the allowance by a given decrement
             * @param spender   Spender's address
             * @param decrement Amount of decrease in allowance
             * @return True if successful
             */
            function decreaseAllowance(address spender, uint256 decrement)
                external
                whenNotPaused
                notBlacklisted(msg.sender)
                notBlacklisted(spender)
                returns (bool)
            {
                _decreaseAllowance(msg.sender, spender, decrement);
                return true;
            }
        
            /**
             * @notice Execute a transfer with a signed authorization
             * @param from          Payer's address (Authorizer)
             * @param to            Payee's address
             * @param value         Amount to be transferred
             * @param validAfter    The time after which this is valid (unix time)
             * @param validBefore   The time before which this is valid (unix time)
             * @param nonce         Unique nonce
             * @param v             v of the signature
             * @param r             r of the signature
             * @param s             s of the signature
             */
            function transferWithAuthorization(
                address from,
                address to,
                uint256 value,
                uint256 validAfter,
                uint256 validBefore,
                bytes32 nonce,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                _transferWithAuthorization(
                    from,
                    to,
                    value,
                    validAfter,
                    validBefore,
                    nonce,
                    v,
                    r,
                    s
                );
            }
        
            /**
             * @notice Receive a transfer with a signed authorization from the payer
             * @dev This has an additional check to ensure that the payee's address
             * matches the caller of this function to prevent front-running attacks.
             * @param from          Payer's address (Authorizer)
             * @param to            Payee's address
             * @param value         Amount to be transferred
             * @param validAfter    The time after which this is valid (unix time)
             * @param validBefore   The time before which this is valid (unix time)
             * @param nonce         Unique nonce
             * @param v             v of the signature
             * @param r             r of the signature
             * @param s             s of the signature
             */
            function receiveWithAuthorization(
                address from,
                address to,
                uint256 value,
                uint256 validAfter,
                uint256 validBefore,
                bytes32 nonce,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
                _receiveWithAuthorization(
                    from,
                    to,
                    value,
                    validAfter,
                    validBefore,
                    nonce,
                    v,
                    r,
                    s
                );
            }
        
            /**
             * @notice Attempt to cancel an authorization
             * @dev Works only if the authorization is not yet used.
             * @param authorizer    Authorizer's address
             * @param nonce         Nonce of the authorization
             * @param v             v of the signature
             * @param r             r of the signature
             * @param s             s of the signature
             */
            function cancelAuthorization(
                address authorizer,
                bytes32 nonce,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) external whenNotPaused {
                _cancelAuthorization(authorizer, nonce, v, r, s);
            }
        
            /**
             * @notice Update allowance with a signed permit
             * @param owner       Token owner's address (Authorizer)
             * @param spender     Spender's address
             * @param value       Amount of allowance
             * @param deadline    Expiration time, seconds since the epoch
             * @param v           v of the signature
             * @param r           r of the signature
             * @param s           s of the signature
             */
            function permit(
                address owner,
                address spender,
                uint256 value,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) external whenNotPaused notBlacklisted(owner) notBlacklisted(spender) {
                _permit(owner, spender, value, deadline, v, r, s);
            }
        
            /**
             * @notice Internal function to increase the allowance by a given increment
             * @param owner     Token owner's address
             * @param spender   Spender's address
             * @param increment Amount of increase
             */
            function _increaseAllowance(
                address owner,
                address spender,
                uint256 increment
            ) internal override {
                _approve(owner, spender, allowed[owner][spender].add(increment));
            }
        
            /**
             * @notice Internal function to decrease the allowance by a given decrement
             * @param owner     Token owner's address
             * @param spender   Spender's address
             * @param decrement Amount of decrease
             */
            function _decreaseAllowance(
                address owner,
                address spender,
                uint256 decrement
            ) internal override {
                _approve(
                    owner,
                    spender,
                    allowed[owner][spender].sub(
                        decrement,
                        "ERC20: decreased allowance below zero"
                    )
                );
            }
        }
        
        // File: contracts/v2/FiatTokenV2_1.sol
        
        /**
         * Copyright (c) 2018-2020 CENTRE SECZ
         *
         * Permission is hereby granted, free of charge, to any person obtaining a copy
         * of this software and associated documentation files (the "Software"), to deal
         * in the Software without restriction, including without limitation the rights
         * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         * copies of the Software, and to permit persons to whom the Software is
         * furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included in
         * copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         * SOFTWARE.
         */
        
        pragma solidity 0.6.12;
        
        // solhint-disable func-name-mixedcase
        
        /**
         * @title FiatToken V2.1
         * @notice ERC20 Token backed by fiat reserves, version 2.1
         */
        contract FiatTokenV2_1 is FiatTokenV2 {
            /**
             * @notice Initialize v2.1
             * @param lostAndFound  The address to which the locked funds are sent
             */
            function initializeV2_1(address lostAndFound) external {
                // solhint-disable-next-line reason-string
                require(_initializedVersion == 1);
        
                uint256 lockedAmount = balances[address(this)];
                if (lockedAmount > 0) {
                    _transfer(address(this), lostAndFound, lockedAmount);
                }
                blacklisted[address(this)] = true;
        
                _initializedVersion = 2;
            }
        
            /**
             * @notice Version string for the EIP712 domain separator
             * @return Version string
             */
            function version() external view returns (string memory) {
                return "2";
            }
        }