Contract Address Details

0x08afA83c3Dc2ed7A78EB035a60fAeE879A432E8C

Contract Name
ExchangeUpgradeable
Creator
0xe4bc3d–a61c7a at 0x155721–6774e0
Balance
0 ADIL ( )
Tokens
Fetching tokens...
Transactions
0 Transactions
Transfers
0 Transfers
Gas Used
Fetching gas used...
Last Balance Update
13171771
Contract name:
ExchangeUpgradeable




Optimization enabled
true
Compiler version
v0.8.7+commit.e28d00a7




Optimization runs
200
Verified at
2025-03-21 14:32:56.254125Z

contracts/ExchangeUpgradeable.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "./libraries/OwnableUpgradeable.sol";
import "./interfaces/IERC20.sol";
import "./libraries/VerifySignature.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

contract ExchangeUpgradeable is OwnerOperator, VerifySignature {
    using SafeMath for uint256;
    using EnumerableSet for EnumerableSet.Bytes32Set;
    using EnumerableSet for EnumerableSet.UintSet;

    event DepositExchange(TxnInfo txnInfo);
    event WithdrawExchange(TxnInfo txnInfo);

    enum TxnStatus {
        Initialize,
        Completed
    }

    struct TxnInfo {
        bytes32 txnId;
        address sender;
        address tokenIn;
        uint256 amountIn;
        address tokenOut;
        uint256 amountOut;
        ExchangeInfo exchangeInfo;
        TxnStatus status;
        uint256 timestamp;
    }

    struct ExchangeInfo {
        uint256 rate;
        uint256 fee;
        uint256 feePercent;
        uint256 amountBonus;
        uint8 decimalsIn;
        uint8 decimalsOut;
        uint256 maxAmount;
        uint256 amountLimit;
    }

    struct ExchangeToken {
        address from;
        address to;
    }

    uint256 public timeExpiredSignature;
    uint256 public limitTime;
    uint256 private PRICE_PRECISION;
    string private MESSAGE_HASH;

    address public signerAddress;
    address public adminReceiver;
    address public adminTransfer;

    // exchange token address => token address: ExchangeInfo
    mapping(address => mapping(address => ExchangeInfo)) internal exchangeInfos;

    // address user => [timestamp by date => amount]
    mapping(address => mapping(uint256 => uint256)) internal amountSwapByUsers;

    // address user => hash (token from - token to) => timestamps[]
    mapping(address => mapping(bytes32 => EnumerableSet.UintSet))
        internal timeSwapByUsers;

    // hashTxnId => TxnInfo
    mapping(bytes32 => TxnInfo) public txnInfos;

    EnumerableSet.Bytes32Set internal arrTxnInfos;


    function init( address _signerAddress, address _adminReceiver, address _adminTransfer) external initializer {
        super.initialize();
        signerAddress = _signerAddress;
        adminReceiver = _adminReceiver;
        adminTransfer = _adminTransfer;
        timeExpiredSignature = 30 minutes;
        limitTime = 24 hours;
        PRICE_PRECISION = 1e9;
        MESSAGE_HASH = "EXCHANGE";
    }

    /**
        @dev get exchange info
        @param from address
        @param to address
        @return ExchangeInfo
     */
    function getExchangeInfo(address from, address to)
        external
        view
        returns (ExchangeInfo memory)
    {
        return exchangeInfos[from][to];
    }

    /**
        @dev set exchange info
        @param from address
        @param to address
        @param exchangeInfo ExchangeInfo
     */
    function setExchangeInfo(
        address from,
        address to,
        ExchangeInfo memory exchangeInfo
    ) external onlyOperator {
        exchangeInfos[from][to] = exchangeInfo;
    }

    /**
        @dev set signer address
        @param _address address
     */
    function setSignerAddress(address _address) external onlyOperator {
        signerAddress = _address;
    }

    /**
        @dev set admin receiver
        @param _address address
     */
    function setAdminReceiver(address _address) external onlyOperator {
        adminReceiver = _address;
    }

    /**
        @dev set admin transfer
        @param _address address
     */
    function setAdminTransfer(address _address) external onlyOperator {
        adminTransfer = _address;
    }

    /**
        @dev set time expired signature
        @param time uint256
     */
    function setTimeExpiredSignature(uint256 time) external onlyOperator {
        timeExpiredSignature = time;
    }

    /**
        @dev get txn info
        @param txnId bytes32
     */
    function getTxnInfo(bytes32 txnId) external view returns (TxnInfo memory) {
        return txnInfos[txnId];
    }

    /**
        @dev get txn infos
        @return bytes32[]
     */
    function getTxnInfos() external view returns (bytes32[] memory) {
        return arrTxnInfos.values();
    }

    /**
        @dev hash transaction id
     */
    function hashTxnId(
        address sender,
        address tokenIn,
        uint256 amountIn,
        address tokenOut,
        uint256 amountOut,
        uint256 timestamp
    ) internal pure returns (bytes32) {
        return
            keccak256(
                abi.encodePacked(
                    sender,
                    tokenIn,
                    amountIn,
                    tokenOut,
                    amountOut,
                    timestamp
                )
            );
    }

    /**
        @dev remove txn info
        @param txnId bytes32
     */
    function removeTxnInfo(bytes32 txnId) external onlyOperator {
        require(
            txnInfos[txnId].txnId != bytes32(0),
            "Transaction doesn't exists"
        );
        delete txnInfos[txnId];
    }

    /**
        @dev parse decimals
        @param decimals uint8
        @return uint256
     */
    function parseDecimals(uint8 decimals) internal pure returns (uint256) {
        return 10**decimals;
    }

    /**
        @dev encode exchange
        @param from address
        @param to address
     */
    function encodeExchange(address from, address to)
        private
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(from, to));
    }

    /**
        @dev calc total amount by user
        @param sender address
        @param bytesAddress bytes32
     */
    function calcTotalAmountByUser(address sender, bytes32 bytesAddress)
        internal
        returns (uint256)
    {
        uint256 totalAmount = 0;
        uint256[] memory timeSwapByUser = timeSwapByUsers[sender][bytesAddress]
            .values();

        // if timenow - timeSwap > 24 hours: then remove item
        // else: totalAmount += amount swap
        for (uint256 idx = 0; idx < timeSwapByUser.length; idx++) {
            if ((block.timestamp - timeSwapByUser[idx]) > limitTime) {
                delete amountSwapByUsers[sender][timeSwapByUser[idx]];
                timeSwapByUsers[sender][bytesAddress].remove(
                    timeSwapByUser[idx]
                );
            } else {
                totalAmount = totalAmount.add(
                    amountSwapByUsers[sender][timeSwapByUser[idx]]
                );
            }
        }
        return totalAmount;
    }

    /**
        @dev Get total amount by user
        @param user address
        @param tokenFrom address
        @param tokenTo address
        @return uint256
     */
    function getTotalAmountByUser(
        address user,
        address tokenFrom,
        address tokenTo
    ) external view returns (uint256) {
        uint256 totalAmount = 0;
        bytes32 bytesAddress = encodeExchange(tokenFrom, tokenTo);
        uint256[] memory timeSwapByUser = timeSwapByUsers[user][bytesAddress]
            .values();

        for (uint256 idx = 0; idx < timeSwapByUser.length; idx++) {
            if ((block.timestamp - timeSwapByUser[idx]) <= limitTime) {
                totalAmount = totalAmount.add(
                    amountSwapByUsers[user][timeSwapByUser[idx]]
                );
            }
        }
        return totalAmount;
    }

    /**
        @dev exchange token to token
     */
    function depositExchange(
        address tokenIn,
        uint256 amountIn,
        address tokenOut,
        uint256 amountOutMin,
        uint256 timestamp,
        bytes memory signature
    ) external payable {
        require(
            verifySignature(
                signerAddress,
                msg.sender,
                tokenIn,
                amountIn,
                tokenOut,
                amountOutMin,
                timestamp,
                MESSAGE_HASH,
                signature
            ),
            "Signature is invalid"
        );
        require(timestamp <= block.timestamp, "Timestamp is invalid");
        require(
            block.timestamp - timestamp <= timeExpiredSignature,
            "Signature is expired"
        );

        ExchangeInfo memory exchangeInfo = exchangeInfos[tokenIn][tokenOut];
        require(exchangeInfo.rate > 0, "Rate token can't be zero");

        uint256 amountFee = amountIn < exchangeInfo.maxAmount
                ? exchangeInfo.fee
                : amountIn.mul(exchangeInfo.feePercent).div(PRICE_PRECISION);
        require(amountFee < amountIn, "Amount fee must be less than amount");
        uint256 amountInSubFee = amountIn.sub(amountFee);
        uint256 amountOutWithoutDecimals = (
            amountInSubFee.mul(exchangeInfo.rate)
        ).div(PRICE_PRECISION);
        uint256 amountOut = (
            amountOutWithoutDecimals.mul(
                parseDecimals(exchangeInfo.decimalsOut)
            )
        ).div(parseDecimals(exchangeInfo.decimalsIn));
        require(amountOut >= amountOutMin, "Slippage exceeded");

        if (tokenIn != address(0x0)) {
            IERC20 erc20Token = IERC20(tokenIn);
            require(
                erc20Token.allowance(msg.sender, address(this)) >= amountIn,
                "Allowance insufficient."
            );
            erc20Token.transferFrom(msg.sender, adminReceiver, amountIn);
        }

        bytes32 bytesAddress = encodeExchange(tokenIn, tokenOut);
        uint256 totalAmount = calcTotalAmountByUser(msg.sender, bytesAddress);
        require(
            totalAmount + amountIn <= exchangeInfo.amountLimit,
            "Amount exceeded limit"
        );
        if (
            !timeSwapByUsers[msg.sender][bytesAddress].contains(block.timestamp)
        ) {
            timeSwapByUsers[msg.sender][bytesAddress].add(block.timestamp);
        }
        amountSwapByUsers[msg.sender][block.timestamp] += amountIn;
        bytes32 txnId = hashTxnId(
            msg.sender,
            tokenIn,
            amountIn,
            tokenOut,
            amountOut,
            timestamp
        );
        require(
            txnInfos[txnId].txnId == bytes32(0),
            "Transaction is already exists"
        );
        txnInfos[txnId] = TxnInfo(
            txnId,
            msg.sender,
            tokenIn,
            amountIn,
            tokenOut,
            amountOut,
            exchangeInfo,
            TxnStatus.Initialize,
            timestamp
        );
        arrTxnInfos.add(txnId);
        emit DepositExchange(txnInfos[txnId]);
    }

    /**
        @dev withdraw exchange
        @param txnId bytes32
     */
    function withdrawExchange(
        bytes32 txnId,
        address sender,
        address tokenIn,
        uint256 amountIn,
        address tokenOut,
        uint256 amountOut,
        ExchangeInfo memory exchangeInfo,
        uint256 timestamp
    ) external payable onlyOperator {
        TxnInfo memory txnInfo = TxnInfo(
            txnId,
            sender,
            tokenIn,
            amountIn,
            tokenOut,
            amountOut,
            exchangeInfo,
            TxnStatus.Completed,
            timestamp
        );
        require(
            txnInfos[txnId].txnId == bytes32(0),
            "Transaction is already exists"
        );
        bytes32 newTxnId = hashTxnId(
            sender,
            tokenIn,
            amountIn,
            tokenOut,
            amountOut,
            timestamp
        );
        require(newTxnId == txnId, "TxnId is invalid");

        if (exchangeInfo.amountBonus > 0) {
            require(
                msg.value >= exchangeInfo.amountBonus,
                "Amount bonus is not enough"
            );
            payable(sender).transfer(exchangeInfo.amountBonus);
        }
        arrTxnInfos.add(txnId);
        txnInfos[txnId] = txnInfo;

        if (txnInfo.tokenOut != address(0x0)) {
            IERC20 erc20Token = IERC20(txnInfo.tokenOut);
            require(
                erc20Token.allowance(adminTransfer, address(this)) >= amountOut,
                "Insufficient allowance"
            );
            erc20Token.transferFrom(adminTransfer, txnInfo.sender, amountOut);
        } else {
            require(
                msg.value >= (amountOut + exchangeInfo.amountBonus),
                "Amount out is not enough"
            );
            // native token
            payable(txnInfo.sender).transfer(amountOut);
        }
        txnInfos[txnId].status = TxnStatus.Completed;
        emit WithdrawExchange(txnInfo);
    }

    /**
        @dev clear txn infos
        @param timestamp uint256
     */
    function clearTxnInfos(uint256 timestamp) external onlyOperator {
        uint256 txnLength = arrTxnInfos.length();
        uint256 countNotDel;
        for (uint256 idx = 0; idx < txnLength; idx++) {
            bytes32 txnInfo = arrTxnInfos.at(countNotDel);
            if (txnInfos[txnInfo].timestamp <= timestamp) {
                delete txnInfos[txnInfo];
                arrTxnInfos.remove(txnInfo);
            } else {
                countNotDel += 1;
            }
        }
    }

    /**
        @dev remove exchange info
        @param from addess
        @param to address
     */
    function removeExchangeInfo(address from, address to)
        external
        onlyOperator
    {
        delete exchangeInfos[from][to];
    }

    /**
        @dev withdraw native token
        @param amount uint256
     */
    function withdraw(uint256 amount) external onlyOwner {
        require(address(this).balance >= amount, "Insufficient balance");
        payable(msg.sender).transfer(amount);
    }

    /**
        @dev withdraw erc20 token
        @param tokenAddress address
        @param amount uint256
     */
    function withdrawToken(address tokenAddress, uint256 amount)
        external
        onlyOwner
    {
        IERC20 erc20Token = IERC20(tokenAddress);
        require(
            erc20Token.balanceOf(address(this)) >= amount,
            "Insufficient balance"
        );
        erc20Token.transfer(msg.sender, amount);
    }

    receive() external payable {}
}
        

contracts/libraries/VerifySignature.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

abstract contract VerifySignature {
    function getMessageHash(
        address _to,
        address _tokenIn,
        uint256 _amountIn,
        address _tokenOut,
        uint256 _amountOutMin,
        uint256 _timestamp,
        string memory _msgHash
    ) public pure returns (bytes32) {
        return
            keccak256(
                abi.encodePacked(
                    _to,
                    _tokenIn,
                    _amountIn,
                    _tokenOut,
                    _amountOutMin,
                    _timestamp,
                    _msgHash
                )
            );
    }

    function getEthSignedMessageHash(bytes32 _messageHash)
        public
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encodePacked(
                    "\x19Ethereum Signed Message:\n32",
                    _messageHash
                )
            );
    }

    function verifySignature(
        address _signer,
        address _to,
        address _tokenIn,
        uint256 _amountIn,
        address _tokenOut,
        uint256 _amountOutMin,
        uint256 _timestamp,
        string memory _msgHash,
        bytes memory _signature
    ) public pure returns (bool) {
        bytes32 messageHash = getMessageHash(
            _to,
            _tokenIn,
            _amountIn,
            _tokenOut,
            _amountOutMin,
            _timestamp,
            _msgHash
        );
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, _signature) == _signer;
    }

    function recoverSigner(
        bytes32 _ethSignedMessageHash,
        bytes memory _signature
    ) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    function splitSignature(bytes memory _sig)
        public
        pure
        returns (
            bytes32 r,
            bytes32 s,
            uint8 v
        )
    {
        require(_sig.length == 65, "invalid signature length");
        assembly {
            r := mload(add(_sig, 32))
            s := mload(add(_sig, 64))
            v := byte(0, mload(add(_sig, 96)))
        }
    }
}
          

@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    function __Ownable_init() internal onlyInitializing {
        __Ownable_init_unchained();
    }

    function __Ownable_init_unchained() internal onlyInitializing {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}
          

@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}
          

@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @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://consensys.net/diligence/blog/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.8.0/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");

        (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 functionCallWithValue(target, data, 0, "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");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // 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
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}
          

@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}
          

@openzeppelin/contracts/utils/math/SafeMath.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // 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 (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @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) {
        return a + b;
    }

    /**
     * @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 a - b;
    }

    /**
     * @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) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting 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 a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting 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) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * 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) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}
          

@openzeppelin/contracts/utils/structs/EnumerableSet.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}
          

contracts/interfaces/IERC20.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);

    function transfer(address to, uint256 amount) external;

    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external;
}
          

contracts/libraries/OwnableUpgradeable.sol

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

abstract contract OwnerOperator is OwnableUpgradeable {
    mapping(address => bool) public operators;

    function initialize() public initializer {
        __Context_init_unchained();
        __Ownable_init_unchained();
    }

    modifier operatorOrOwner() {
        require(
            operators[msg.sender] || owner() == msg.sender,
            "OwnerOperator: !operator, !owner"
        );
        _;
    }

    modifier onlyOperator() {
        require(operators[msg.sender], "OwnerOperator: !operator");
        _;
    }

    function addOperator(address operator) external virtual onlyOwner {
        require(
            operator != address(0),
            "OwnerOperator: operator is the zero address"
        );
        operators[operator] = true;
    }

    function removeOperator(address operator) external virtual onlyOwner {
        require(
            operator != address(0),
            "OwnerOperator: operator is the zero address"
        );
        operators[operator] = false;
    }
}
          

Contract ABI

[{"type":"event","name":"DepositExchange","inputs":[{"type":"tuple","name":"txnInfo","internalType":"struct ExchangeUpgradeable.TxnInfo","indexed":false,"components":[{"type":"bytes32","name":"txnId","internalType":"bytes32"},{"type":"address","name":"sender","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"uint256","name":"amountIn","internalType":"uint256"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"amountOut","internalType":"uint256"},{"type":"tuple","name":"exchangeInfo","internalType":"struct ExchangeUpgradeable.ExchangeInfo","components":[{"type":"uint256","name":"rate","internalType":"uint256"},{"type":"uint256","name":"fee","internalType":"uint256"},{"type":"uint256","name":"feePercent","internalType":"uint256"},{"type":"uint256","name":"amountBonus","internalType":"uint256"},{"type":"uint8","name":"decimalsIn","internalType":"uint8"},{"type":"uint8","name":"decimalsOut","internalType":"uint8"},{"type":"uint256","name":"maxAmount","internalType":"uint256"},{"type":"uint256","name":"amountLimit","internalType":"uint256"}]},{"type":"uint8","name":"status","internalType":"enum ExchangeUpgradeable.TxnStatus"},{"type":"uint256","name":"timestamp","internalType":"uint256"}]}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"type":"uint8","name":"version","internalType":"uint8","indexed":false}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"WithdrawExchange","inputs":[{"type":"tuple","name":"txnInfo","internalType":"struct ExchangeUpgradeable.TxnInfo","indexed":false,"components":[{"type":"bytes32","name":"txnId","internalType":"bytes32"},{"type":"address","name":"sender","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"uint256","name":"amountIn","internalType":"uint256"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"amountOut","internalType":"uint256"},{"type":"tuple","name":"exchangeInfo","internalType":"struct ExchangeUpgradeable.ExchangeInfo","components":[{"type":"uint256","name":"rate","internalType":"uint256"},{"type":"uint256","name":"fee","internalType":"uint256"},{"type":"uint256","name":"feePercent","internalType":"uint256"},{"type":"uint256","name":"amountBonus","internalType":"uint256"},{"type":"uint8","name":"decimalsIn","internalType":"uint8"},{"type":"uint8","name":"decimalsOut","internalType":"uint8"},{"type":"uint256","name":"maxAmount","internalType":"uint256"},{"type":"uint256","name":"amountLimit","internalType":"uint256"}]},{"type":"uint8","name":"status","internalType":"enum ExchangeUpgradeable.TxnStatus"},{"type":"uint256","name":"timestamp","internalType":"uint256"}]}],"anonymous":false},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"addOperator","inputs":[{"type":"address","name":"operator","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"adminReceiver","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"adminTransfer","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"clearTxnInfos","inputs":[{"type":"uint256","name":"timestamp","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"depositExchange","inputs":[{"type":"address","name":"tokenIn","internalType":"address"},{"type":"uint256","name":"amountIn","internalType":"uint256"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"amountOutMin","internalType":"uint256"},{"type":"uint256","name":"timestamp","internalType":"uint256"},{"type":"bytes","name":"signature","internalType":"bytes"}]},{"type":"function","stateMutability":"pure","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"getEthSignedMessageHash","inputs":[{"type":"bytes32","name":"_messageHash","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"","internalType":"struct ExchangeUpgradeable.ExchangeInfo","components":[{"type":"uint256","name":"rate","internalType":"uint256"},{"type":"uint256","name":"fee","internalType":"uint256"},{"type":"uint256","name":"feePercent","internalType":"uint256"},{"type":"uint256","name":"amountBonus","internalType":"uint256"},{"type":"uint8","name":"decimalsIn","internalType":"uint8"},{"type":"uint8","name":"decimalsOut","internalType":"uint8"},{"type":"uint256","name":"maxAmount","internalType":"uint256"},{"type":"uint256","name":"amountLimit","internalType":"uint256"}]}],"name":"getExchangeInfo","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"}]},{"type":"function","stateMutability":"pure","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"getMessageHash","inputs":[{"type":"address","name":"_to","internalType":"address"},{"type":"address","name":"_tokenIn","internalType":"address"},{"type":"uint256","name":"_amountIn","internalType":"uint256"},{"type":"address","name":"_tokenOut","internalType":"address"},{"type":"uint256","name":"_amountOutMin","internalType":"uint256"},{"type":"uint256","name":"_timestamp","internalType":"uint256"},{"type":"string","name":"_msgHash","internalType":"string"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getTotalAmountByUser","inputs":[{"type":"address","name":"user","internalType":"address"},{"type":"address","name":"tokenFrom","internalType":"address"},{"type":"address","name":"tokenTo","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"","internalType":"struct ExchangeUpgradeable.TxnInfo","components":[{"type":"bytes32","name":"txnId","internalType":"bytes32"},{"type":"address","name":"sender","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"uint256","name":"amountIn","internalType":"uint256"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"amountOut","internalType":"uint256"},{"type":"tuple","name":"exchangeInfo","internalType":"struct ExchangeUpgradeable.ExchangeInfo","components":[{"type":"uint256","name":"rate","internalType":"uint256"},{"type":"uint256","name":"fee","internalType":"uint256"},{"type":"uint256","name":"feePercent","internalType":"uint256"},{"type":"uint256","name":"amountBonus","internalType":"uint256"},{"type":"uint8","name":"decimalsIn","internalType":"uint8"},{"type":"uint8","name":"decimalsOut","internalType":"uint8"},{"type":"uint256","name":"maxAmount","internalType":"uint256"},{"type":"uint256","name":"amountLimit","internalType":"uint256"}]},{"type":"uint8","name":"status","internalType":"enum ExchangeUpgradeable.TxnStatus"},{"type":"uint256","name":"timestamp","internalType":"uint256"}]}],"name":"getTxnInfo","inputs":[{"type":"bytes32","name":"txnId","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32[]","name":"","internalType":"bytes32[]"}],"name":"getTxnInfos","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"init","inputs":[{"type":"address","name":"_signerAddress","internalType":"address"},{"type":"address","name":"_adminReceiver","internalType":"address"},{"type":"address","name":"_adminTransfer","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"initialize","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"limitTime","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"operators","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"pure","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"recoverSigner","inputs":[{"type":"bytes32","name":"_ethSignedMessageHash","internalType":"bytes32"},{"type":"bytes","name":"_signature","internalType":"bytes"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeExchangeInfo","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeOperator","inputs":[{"type":"address","name":"operator","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeTxnInfo","inputs":[{"type":"bytes32","name":"txnId","internalType":"bytes32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setAdminReceiver","inputs":[{"type":"address","name":"_address","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setAdminTransfer","inputs":[{"type":"address","name":"_address","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setExchangeInfo","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"},{"type":"tuple","name":"exchangeInfo","internalType":"struct ExchangeUpgradeable.ExchangeInfo","components":[{"type":"uint256","name":"rate","internalType":"uint256"},{"type":"uint256","name":"fee","internalType":"uint256"},{"type":"uint256","name":"feePercent","internalType":"uint256"},{"type":"uint256","name":"amountBonus","internalType":"uint256"},{"type":"uint8","name":"decimalsIn","internalType":"uint8"},{"type":"uint8","name":"decimalsOut","internalType":"uint8"},{"type":"uint256","name":"maxAmount","internalType":"uint256"},{"type":"uint256","name":"amountLimit","internalType":"uint256"}]}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setSignerAddress","inputs":[{"type":"address","name":"_address","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setTimeExpiredSignature","inputs":[{"type":"uint256","name":"time","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"signerAddress","inputs":[]},{"type":"function","stateMutability":"pure","outputs":[{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"}],"name":"splitSignature","inputs":[{"type":"bytes","name":"_sig","internalType":"bytes"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"timeExpiredSignature","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"txnId","internalType":"bytes32"},{"type":"address","name":"sender","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"uint256","name":"amountIn","internalType":"uint256"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"amountOut","internalType":"uint256"},{"type":"tuple","name":"exchangeInfo","internalType":"struct ExchangeUpgradeable.ExchangeInfo","components":[{"type":"uint256","name":"rate","internalType":"uint256"},{"type":"uint256","name":"fee","internalType":"uint256"},{"type":"uint256","name":"feePercent","internalType":"uint256"},{"type":"uint256","name":"amountBonus","internalType":"uint256"},{"type":"uint8","name":"decimalsIn","internalType":"uint8"},{"type":"uint8","name":"decimalsOut","internalType":"uint8"},{"type":"uint256","name":"maxAmount","internalType":"uint256"},{"type":"uint256","name":"amountLimit","internalType":"uint256"}]},{"type":"uint8","name":"status","internalType":"enum ExchangeUpgradeable.TxnStatus"},{"type":"uint256","name":"timestamp","internalType":"uint256"}],"name":"txnInfos","inputs":[{"type":"bytes32","name":"","internalType":"bytes32"}]},{"type":"function","stateMutability":"pure","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"verifySignature","inputs":[{"type":"address","name":"_signer","internalType":"address"},{"type":"address","name":"_to","internalType":"address"},{"type":"address","name":"_tokenIn","internalType":"address"},{"type":"uint256","name":"_amountIn","internalType":"uint256"},{"type":"address","name":"_tokenOut","internalType":"address"},{"type":"uint256","name":"_amountOutMin","internalType":"uint256"},{"type":"uint256","name":"_timestamp","internalType":"uint256"},{"type":"string","name":"_msgHash","internalType":"string"},{"type":"bytes","name":"_signature","internalType":"bytes"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"withdraw","inputs":[{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"withdrawExchange","inputs":[{"type":"bytes32","name":"txnId","internalType":"bytes32"},{"type":"address","name":"sender","internalType":"address"},{"type":"address","name":"tokenIn","internalType":"address"},{"type":"uint256","name":"amountIn","internalType":"uint256"},{"type":"address","name":"tokenOut","internalType":"address"},{"type":"uint256","name":"amountOut","internalType":"uint256"},{"type":"tuple","name":"exchangeInfo","internalType":"struct ExchangeUpgradeable.ExchangeInfo","components":[{"type":"uint256","name":"rate","internalType":"uint256"},{"type":"uint256","name":"fee","internalType":"uint256"},{"type":"uint256","name":"feePercent","internalType":"uint256"},{"type":"uint256","name":"amountBonus","internalType":"uint256"},{"type":"uint8","name":"decimalsIn","internalType":"uint8"},{"type":"uint8","name":"decimalsOut","internalType":"uint8"},{"type":"uint256","name":"maxAmount","internalType":"uint256"},{"type":"uint256","name":"amountLimit","internalType":"uint256"}]},{"type":"uint256","name":"timestamp","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"withdrawToken","inputs":[{"type":"address","name":"tokenAddress","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"receive","stateMutability":"payable"}]
            

Deployed ByteCode

0x6080604052600436106102085760003560e01c80638da5cb5b11610118578063b165caba116100a0578063d7b80cd71161006f578063d7b80cd7146106ed578063dd032d341461070d578063e695448814610723578063f2fde38b14610743578063fa5408011461076357600080fd5b8063b165caba14610677578063b3b95e3014610697578063b82dfc8c146106ad578063c9de0556146106da57600080fd5b80639e281a98116100e75780639e281a98146105b9578063a7bb5803146105d9578063a87d9ffd14610617578063ac8a584a14610637578063af51462b1461065757600080fd5b80638da5cb5b1461053b5780639666ee5c1461055957806397aba7f9146105795780639870d7fe1461059957600080fd5b80635b7633d01161019b5780638129fc1c1161016a5780638129fc1c146104b35780638280f695146104c85780638373ba88146104e85780638afd119e146104fb5780638c64b3d81461051b57600080fd5b80635b7633d0146104195780636d3ceff714610451578063715018a6146104715780637ceacbd11461048657600080fd5b8063184b9559116101d7578063184b9559146102cf5780631b664395146102ef5780632e1a7d4d146103d957806350ed82bd146103f957600080fd5b80630414300c14610214578063046dc1661461023f5780630b4e0a4d1461026157806313e7c9d81461028f57600080fd5b3661020f57005b600080fd5b34801561022057600080fd5b50610229610783565b6040516102369190613081565b60405180910390f35b34801561024b57600080fd5b5061025f61025a366004612b8e565b610794565b005b34801561026d57600080fd5b5061028161027c366004612bdc565b6107ee565b604051908152602001610236565b34801561029b57600080fd5b506102bf6102aa366004612b8e565b60656020526000908152604090205460ff1681565b6040519015158152602001610236565b3480156102db57600080fd5b5061025f6102ea366004612bdc565b6108df565b3480156102fb57600080fd5b506103c461030a366004612e4f565b60706020908152600091825260409182902080546001820154600283015460038401546004850154600586015488516101008181018b5260068901548252600789015499820199909952600888015499810199909952600987015460608a0152600a87015460ff80821660808c0152989004881660a08a0152600b87015460c08a0152600c87015460e08a0152600d870154600e9097015495986001600160a01b0395861698948616979396929095169490939092169089565b604051610236999897969594939291906130c5565b3480156103e557600080fd5b5061025f6103f4366004612e4f565b610a2e565b34801561040557600080fd5b5061025f610414366004612e4f565b610aae565b34801561042557600080fd5b50606a54610439906001600160a01b031681565b6040516001600160a01b039091168152602001610236565b34801561045d57600080fd5b506102bf61046c366004612c1f565b610be1565b34801561047d57600080fd5b5061025f610c30565b34801561049257600080fd5b506104a66104a1366004612ba9565b610c44565b6040516102369190613245565b3480156104bf57600080fd5b5061025f610cd9565b3480156104d457600080fd5b5061025f6104e3366004612ba9565b610dab565b61025f6104f6366004612e68565b610e35565b34801561050757600080fd5b50606c54610439906001600160a01b031681565b34801561052757600080fd5b5061025f610536366004612b8e565b6113a3565b34801561054757600080fd5b506033546001600160a01b0316610439565b34801561056557600080fd5b50606b54610439906001600160a01b031681565b34801561058557600080fd5b50610439610594366004612ee5565b6113f4565b3480156105a557600080fd5b5061025f6105b4366004612b8e565b611473565b3480156105c557600080fd5b5061025f6105d4366004612dab565b6114c5565b3480156105e557600080fd5b506105f96105f4366004612f2c565b6115f2565b60408051938452602084019290925260ff1690820152606001610236565b34801561062357600080fd5b50610281610632366004612d1f565b611666565b34801561064357600080fd5b5061025f610652366004612b8e565b6116a8565b34801561066357600080fd5b5061025f610672366004612e4f565b6116f7565b34801561068357600080fd5b5061025f610692366004612e4f565b61172b565b3480156106a357600080fd5b5061028160675481565b3480156106b957600080fd5b506106cd6106c8366004612e4f565b611847565b6040516102369190613254565b61025f6106e8366004612dd5565b611975565b3480156106f957600080fd5b5061025f610708366004612b8e565b61219e565b34801561071957600080fd5b5061028160665481565b34801561072f57600080fd5b5061025f61073e366004612ce3565b6121ef565b34801561074f57600080fd5b5061025f61075e366004612b8e565b6122aa565b34801561076f57600080fd5b5061028161077e366004612e4f565b612320565b606061078f6071612373565b905090565b3360009081526065602052604090205460ff166107cc5760405162461bcd60e51b81526004016107c39061312a565b60405180910390fd5b606a80546001600160a01b0319166001600160a01b0392909216919091179055565b600080806107fc8585612387565b6001600160a01b0387166000908152606f6020908152604080832084845290915281209192509061082c90612373565b905060005b81518110156108d35760675482828151811061084f5761084f6135b5565b6020026020010151426108629190613506565b116108c1576001600160a01b0388166000908152606e6020526040812083516108be9290859085908110610898576108986135b5565b6020026020010151815260200190815260200160002054856123d190919063ffffffff16565b93505b806108cb81613558565b915050610831565b50919695505050505050565b600054610100900460ff16158080156108ff5750600054600160ff909116105b806109195750303b158015610919575060005460ff166001145b6109355760405162461bcd60e51b81526004016107c3906131ac565b6000805460ff191660011790558015610958576000805461ff0019166101001790555b610960610cd9565b606a80546001600160a01b038087166001600160a01b031992831617909255606b8054868416908316179055606c80549285169290911691909117905561070860665562015180606755633b9aca006068556040805180820190915260088082526745584348414e474560c01b60209092019182526109e1916069916128ff565b508015610a28576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b610a366123dd565b80471015610a7d5760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064016107c3565b604051339082156108fc029083906000818181858888f19350505050158015610aaa573d6000803e3d6000fd5b5050565b3360009081526065602052604090205460ff16610add5760405162461bcd60e51b81526004016107c39061312a565b6000610ae96071612437565b90506000805b82811015610a28576000610b04607184612441565b6000818152607060205260409020600e01549091508510610bc05760008181526070602052604081208181556001810180546001600160a01b0319908116909155600282018054821690556003820183905560048201805490911690556005810182905560068101829055600781018290556008810182905560098101829055600a8101805461ffff19169055600b8101829055600c8101829055600d8101805460ff19169055600e0155610bba60718261244d565b50610bce565b610bcb6001846133bf565b92505b5080610bd981613558565b915050610aef565b600080610bf38a8a8a8a8a8a8a611666565b90506000610c0082612320565b90508b6001600160a01b0316610c1682866113f4565b6001600160a01b0316149c9b505050505050505050505050565b610c386123dd565b610c426000612459565b565b610c4c612983565b506001600160a01b038083166000908152606d60209081526040808320938516835292815290829020825161010080820185528254825260018301549382019390935260028201549381019390935260038101546060840152600481015460ff808216608086015292900490911660a0830152600581015460c08301526006015460e08201525b92915050565b600054610100900460ff1615808015610cf95750600054600160ff909116105b80610d135750303b158015610d13575060005460ff166001145b610d2f5760405162461bcd60e51b81526004016107c3906131ac565b6000805460ff191660011790558015610d52576000805461ff0019166101001790555b610d5a6124ab565b610d626124d2565b8015610da8576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50565b3360009081526065602052604090205460ff16610dda5760405162461bcd60e51b81526004016107c39061312a565b6001600160a01b039182166000908152606d602090815260408083209390941682529190915290812081815560018101829055600281018290556003810182905560048101805461ffff191690556005810182905560060155565b3360009081526065602052604090205460ff16610e645760405162461bcd60e51b81526004016107c39061312a565b60006040518061012001604052808a8152602001896001600160a01b03168152602001886001600160a01b03168152602001878152602001866001600160a01b03168152602001858152602001848152602001600180811115610ec957610ec9613589565b8152602090810184905260008b81526070909152604090205490915015610f325760405162461bcd60e51b815260206004820152601d60248201527f5472616e73616374696f6e20697320616c72656164792065786973747300000060448201526064016107c3565b6000610f42898989898988612502565b9050898114610f865760405162461bcd60e51b815260206004820152601060248201526f151e1b9259081a5cc81a5b9d985b1a5960821b60448201526064016107c3565b60608401511561101f578360600151341015610fe45760405162461bcd60e51b815260206004820152601a60248201527f416d6f756e7420626f6e7573206973206e6f7420656e6f75676800000000000060448201526064016107c3565b60608401516040516001600160a01b038b169180156108fc02916000818181858888f1935050505015801561101d573d6000803e3d6000fd5b505b61102a60718b612568565b5060008a8152607060209081526040918290208451815581850151600180830180546001600160a01b039384166001600160a01b0319918216179091558588015160028501805491851691831691909117905560608089015160038601556080808a01516004870180549190961693169290921790935560a080890151600586015560c0808a015180516006880155968701516007870155968601516008860155928501516009850155840151600a840180549386015160ff9081166101000261ffff199095169216919091179290921790915592820151600b82015560e091820151600c82015590840151600d82018054869460ff199190911690838181111561113757611137613589565b02179055506101009190910151600e9091015560808201516001600160a01b0316156112a6576080820151606c54604051636eb1769f60e11b81526001600160a01b039182166004820152306024820152879183169063dd62ed3e9060440160206040518083038186803b1580156111ae57600080fd5b505afa1580156111c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111e69190612f69565b101561122d5760405162461bcd60e51b8152602060048201526016602482015275496e73756666696369656e7420616c6c6f77616e636560501b60448201526064016107c3565b606c5460208401516040516323b872dd60e01b81526001600160a01b039283166004820152908216602482015260448101889052908216906323b872dd90606401600060405180830381600087803b15801561128857600080fd5b505af115801561129c573d6000803e3d6000fd5b5050505050611343565b60608401516112b590866133bf565b3410156113045760405162461bcd60e51b815260206004820152601860248201527f416d6f756e74206f7574206973206e6f7420656e6f756768000000000000000060448201526064016107c3565b81602001516001600160a01b03166108fc869081150290604051600060405180830381858888f19350505050158015611341573d6000803e3d6000fd5b505b60008a81526070602052604090819020600d01805460ff19166001179055517f36c9a33bc46e8fdb9df72edaf07f2b8a8bf68be19a9df01a59a4c93a8d100eb99061138f908490613254565b60405180910390a150505050505050505050565b3360009081526065602052604090205460ff166113d25760405162461bcd60e51b81526004016107c39061312a565b606b80546001600160a01b0319166001600160a01b0392909216919091179055565b600080600080611403856115f2565b6040805160008152602081018083528b905260ff8316918101919091526060810184905260808101839052929550909350915060019060a0016020604051602081039080840390855afa15801561145e573d6000803e3d6000fd5b5050604051601f190151979650505050505050565b61147b6123dd565b6001600160a01b0381166114a15760405162461bcd60e51b81526004016107c390613161565b6001600160a01b03166000908152606560205260409020805460ff19166001179055565b6114cd6123dd565b6040516370a0823160e01b8152306004820152829082906001600160a01b038316906370a082319060240160206040518083038186803b15801561151057600080fd5b505afa158015611524573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115489190612f69565b101561158d5760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064016107c3565b60405163a9059cbb60e01b8152336004820152602481018390526001600160a01b0382169063a9059cbb90604401600060405180830381600087803b1580156115d557600080fd5b505af11580156115e9573d6000803e3d6000fd5b50505050505050565b600080600083516041146116485760405162461bcd60e51b815260206004820152601860248201527f696e76616c6964207369676e6174757265206c656e677468000000000000000060448201526064016107c3565b50505060208101516040820151606090920151909260009190911a90565b6000878787878787876040516020016116859796959493929190612ff8565b604051602081830303815290604052805190602001209050979650505050505050565b6116b06123dd565b6001600160a01b0381166116d65760405162461bcd60e51b81526004016107c390613161565b6001600160a01b03166000908152606560205260409020805460ff19169055565b3360009081526065602052604090205460ff166117265760405162461bcd60e51b81526004016107c39061312a565b606655565b3360009081526065602052604090205460ff1661175a5760405162461bcd60e51b81526004016107c39061312a565b6000818152607060205260409020546117b55760405162461bcd60e51b815260206004820152601a60248201527f5472616e73616374696f6e20646f65736e27742065786973747300000000000060448201526064016107c3565b60009081526070602052604081208181556001810180546001600160a01b0319908116909155600282018054821690556003820183905560048201805490911690556005810182905560068101829055600781018290556008810182905560098101829055600a8101805461ffff19169055600b8101829055600c8101829055600d8101805460ff19169055600e0155565b61184f6129ce565b600082815260706020908152604091829020825161012081018452815481526001808301546001600160a01b0390811683860152600284015481168387015260038401546060808501919091526004850154909116608080850191909152600585015460a08086019190915287516101008181018a5260068801548252600788015498820198909852600887015498810198909852600986015492880192909252600a85015460ff8082169289019290925295909504851690860152600b83015460c086810191909152600c84015460e08088019190915290830195909552600d8301549194929392850192919091169081111561194f5761194f613589565b600181111561196057611960613589565b8152602001600e820154815250509050919050565b611a25606a60009054906101000a90046001600160a01b0316338888888888606980546119a19061351d565b80601f01602080910402602001604051908101604052809291908181526020018280546119cd9061351d565b8015611a1a5780601f106119ef57610100808354040283529160200191611a1a565b820191906000526020600020905b8154815290600101906020018083116119fd57829003601f168201915b505050505089610be1565b611a685760405162461bcd60e51b815260206004820152601460248201527314da59db985d1d5c99481a5cc81a5b9d985b1a5960621b60448201526064016107c3565b42821115611aaf5760405162461bcd60e51b8152602060048201526014602482015273151a5b595cdd185b5c081a5cc81a5b9d985b1a5960621b60448201526064016107c3565b606654611abc8342613506565b1115611b015760405162461bcd60e51b815260206004820152601460248201527314da59db985d1d5c99481a5cc8195e1c1a5c995960621b60448201526064016107c3565b6001600160a01b038087166000908152606d6020908152604080832093881683529281529082902082516101008082018552825480835260018401549483019490945260028301549482019490945260038201546060820152600482015460ff808216608084015294900490931660a0840152600581015460c08401526006015460e0830152611bd35760405162461bcd60e51b815260206004820152601860248201527f5261746520746f6b656e2063616e2774206265207a65726f000000000000000060448201526064016107c3565b60008160c001518710611c0857611c03606854611bfd84604001518a61257490919063ffffffff16565b90612580565b611c0e565b81602001515b9050868110611c6b5760405162461bcd60e51b815260206004820152602360248201527f416d6f756e7420666565206d757374206265206c657373207468616e20616d6f6044820152621d5b9d60ea1b60648201526084016107c3565b6000611c77888361258c565b90506000611c98606854611bfd86600001518561257490919063ffffffff16565b90506000611cc3611cac8660800151612598565b611bfd611cbc8860a00151612598565b8590612574565b905087811015611d095760405162461bcd60e51b815260206004820152601160248201527014db1a5c1c1859d948195e18d959591959607a1b60448201526064016107c3565b6001600160a01b038b1615611e5557604051636eb1769f60e11b81523360048201523060248201528b908b906001600160a01b0383169063dd62ed3e9060440160206040518083038186803b158015611d6157600080fd5b505afa158015611d75573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d999190612f69565b1015611de75760405162461bcd60e51b815260206004820152601760248201527f416c6c6f77616e636520696e73756666696369656e742e00000000000000000060448201526064016107c3565b606b546040516323b872dd60e01b81523360048201526001600160a01b039182166024820152604481018d9052908216906323b872dd90606401600060405180830381600087803b158015611e3b57600080fd5b505af1158015611e4f573d6000803e3d6000fd5b50505050505b6000611e618c8b612387565b90506000611e6f33836125a5565b60e0880151909150611e818d836133bf565b1115611ec75760405162461bcd60e51b8152602060048201526015602482015274105b5bdd5b9d08195e18d959591959081b1a5b5a5d605a1b60448201526064016107c3565b336000908152606f602090815260408083208584529091529020611eeb904261271f565b611f1557336000908152606f602090815260408083208584529091529020611f139042612568565b505b336000908152606e60209081526040808320428452909152812080548e9290611f3f9084906133bf565b9091555060009050611f55338f8f8f888f612502565b60008181526070602052604090205490915015611fb45760405162461bcd60e51b815260206004820152601d60248201527f5472616e73616374696f6e20697320616c72656164792065786973747300000060448201526064016107c3565b604051806101200160405280828152602001336001600160a01b031681526020018f6001600160a01b031681526020018e81526020018d6001600160a01b031681526020018581526020018981526020016000600181111561201857612018613589565b815260209081018c90526000838152607082526040908190208351815583830151600180830180546001600160a01b039384166001600160a01b0319918216179091558685015160028501805491851691831691909117905560608088015160038601556080808901516004870180549190961693169290921790935560a080880151600586015560c08089015180516006880155978801516007870155958701516008860155928601516009850155850151600a840180549387015160ff9081166101000261ffff199095169216919091179290921790915591830151600b82015560e092830151600c82015591830151600d830180549192909160ff191690838181111561212a5761212a613589565b02179055506101009190910151600e90910155612148607182612568565b506000818152607060205260409081902090517fc3531fe01da154e9dd13d1dd2ed101da79e2731930403d73043717c2cb5875e691612186916132f4565b60405180910390a15050505050505050505050505050565b3360009081526065602052604090205460ff166121cd5760405162461bcd60e51b81526004016107c39061312a565b606c80546001600160a01b0319166001600160a01b0392909216919091179055565b3360009081526065602052604090205460ff1661221e5760405162461bcd60e51b81526004016107c39061312a565b6001600160a01b039283166000908152606d602090815260408083209490951682529283528390208151815591810151600183015591820151600282015560608201516003820155608082015160048201805460a085015160ff9081166101000261ffff1990921693169290921791909117905560c0820151600582015560e090910151600690910155565b6122b26123dd565b6001600160a01b0381166123175760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016107c3565b610da881612459565b6040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c8101829052600090605c01604051602081830303815290604052805190602001209050919050565b6060600061238083612737565b9392505050565b6040516bffffffffffffffffffffffff19606084811b8216602084015283901b16603482015260009060480160405160208183030381529060405280519060200120905092915050565b600061238082846133bf565b6033546001600160a01b03163314610c425760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016107c3565b6000610cd3825490565b60006123808383612793565b600061238083836127bd565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff16610c425760405162461bcd60e51b81526004016107c3906131fa565b600054610100900460ff166124f95760405162461bcd60e51b81526004016107c3906131fa565b610c4233612459565b604080516bffffffffffffffffffffffff19606098891b811660208084019190915297891b8116603483015260488201969096529390961b9093166068830152607c820152609c808201929092528351808203909201825260bc01909252815191012090565b600061238083836128b0565b600061238082846134e7565b600061238082846133d7565b60006123808284613506565b6000610cd382600a61343c565b6001600160a01b0382166000908152606f602090815260408083208484529091528120819081906125d590612373565b905060005b8151811015612715576067548282815181106125f8576125f86135b5565b60200260200101514261260b9190613506565b11156126a9576001600160a01b0386166000908152606e602052604081208351909190849084908110612640576126406135b5565b60200260200101518152602001908152602001600020600090556126a382828151811061266f5761266f6135b5565b6020908102919091018101516001600160a01b0389166000908152606f835260408082208a8352909352919091209061244d565b50612703565b6001600160a01b0386166000908152606e60205260408120835161270092908590859081106126da576126da6135b5565b6020026020010151815260200190815260200160002054846123d190919063ffffffff16565b92505b8061270d81613558565b9150506125da565b5090949350505050565b60008181526001830160205260408120541515612380565b60608160000180548060200260200160405190810160405280929190818152602001828054801561278757602002820191906000526020600020905b815481526020019060010190808311612773575b50505050509050919050565b60008260000182815481106127aa576127aa6135b5565b9060005260206000200154905092915050565b600081815260018301602052604081205480156128a65760006127e1600183613506565b85549091506000906127f590600190613506565b905081811461285a576000866000018281548110612815576128156135b5565b9060005260206000200154905080876000018481548110612838576128386135b5565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061286b5761286b61359f565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610cd3565b6000915050610cd3565b60008181526001830160205260408120546128f757508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610cd3565b506000610cd3565b82805461290b9061351d565b90600052602060002090601f01602090048101928261292d5760008555612973565b82601f1061294657805160ff1916838001178555612973565b82800160010185558215612973579182015b82811115612973578251825591602001919060010190612958565b5061297f929150612a21565b5090565b60405180610100016040528060008152602001600081526020016000815260200160008152602001600060ff168152602001600060ff16815260200160008152602001600081525090565b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a081019190915260c08101612a0d612983565b815260200160008152602001600081525090565b5b8082111561297f5760008155600101612a22565b80356001600160a01b0381168114612a4d57600080fd5b919050565b600082601f830112612a6357600080fd5b813567ffffffffffffffff80821115612a7e57612a7e6135cb565b604051601f8301601f19908116603f01168101908282118183101715612aa657612aa66135cb565b81604052838152866020858801011115612abf57600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000610100808385031215612af357600080fd5b6040519081019067ffffffffffffffff82118183101715612b1657612b166135cb565b8160405280925083358152602084013560208201526040840135604082015260608401356060820152612b4b60808501612b7d565b6080820152612b5c60a08501612b7d565b60a082015260c084013560c082015260e084013560e0820152505092915050565b803560ff81168114612a4d57600080fd5b600060208284031215612ba057600080fd5b61238082612a36565b60008060408385031215612bbc57600080fd5b612bc583612a36565b9150612bd360208401612a36565b90509250929050565b600080600060608486031215612bf157600080fd5b612bfa84612a36565b9250612c0860208501612a36565b9150612c1660408501612a36565b90509250925092565b60008060008060008060008060006101208a8c031215612c3e57600080fd5b612c478a612a36565b9850612c5560208b01612a36565b9750612c6360408b01612a36565b965060608a01359550612c7860808b01612a36565b945060a08a0135935060c08a0135925060e08a013567ffffffffffffffff80821115612ca357600080fd5b612caf8d838e01612a52565b93506101008c0135915080821115612cc657600080fd5b50612cd38c828d01612a52565b9150509295985092959850929598565b60008060006101408486031215612cf957600080fd5b612d0284612a36565b9250612d1060208501612a36565b9150612c168560408601612adf565b600080600080600080600060e0888a031215612d3a57600080fd5b612d4388612a36565b9650612d5160208901612a36565b955060408801359450612d6660608901612a36565b93506080880135925060a0880135915060c088013567ffffffffffffffff811115612d9057600080fd5b612d9c8a828b01612a52565b91505092959891949750929550565b60008060408385031215612dbe57600080fd5b612dc783612a36565b946020939093013593505050565b60008060008060008060c08789031215612dee57600080fd5b612df787612a36565b955060208701359450612e0c60408801612a36565b9350606087013592506080870135915060a087013567ffffffffffffffff811115612e3657600080fd5b612e4289828a01612a52565b9150509295509295509295565b600060208284031215612e6157600080fd5b5035919050565b6000806000806000806000806101e0898b031215612e8557600080fd5b88359750612e9560208a01612a36565b9650612ea360408a01612a36565b955060608901359450612eb860808a01612a36565b935060a08901359250612ece8a60c08b01612adf565b91506101c089013590509295985092959890939650565b60008060408385031215612ef857600080fd5b82359150602083013567ffffffffffffffff811115612f1657600080fd5b612f2285828601612a52565b9150509250929050565b600060208284031215612f3e57600080fd5b813567ffffffffffffffff811115612f5557600080fd5b612f6184828501612a52565b949350505050565b600060208284031215612f7b57600080fd5b5051919050565b60028110612fa057634e487b7160e01b600052602160045260246000fd5b9052565b8051825260208101516020830152604081015160408301526060810151606083015260ff608082015116608083015260ff60a08201511660a083015260c081015160c083015260e081015160e08301525050565b60006bffffffffffffffffffffffff19808a60601b168352808960601b166014840152876028840152808760601b1660488401525084605c83015283607c830152825160005b8181101561305b576020818601810151609c86840101520161303e565b8181111561306d576000609c83860101525b5091909101609c0198975050505050505050565b6020808252825182820181905260009190848201906040850190845b818110156130b95783518352928401929184019160010161309d565b50909695505050505050565b8981526001600160a01b0389811660208301528881166040830152606082018890528616608082015260a08101859052610200810161310760c0830186612fa4565b6131156101c0830185612f82565b826101e08301529a9950505050505050505050565b60208082526018908201527f4f776e65724f70657261746f723a20216f70657261746f720000000000000000604082015260600190565b6020808252602b908201527f4f776e65724f70657261746f723a206f70657261746f7220697320746865207a60408201526a65726f206164647265737360a81b606082015260800190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6101008101610cd38284612fa4565b815181526020808301516001600160a01b03169082015260408083015161020083019161328b908401826001600160a01b03169052565b506060830151606083015260808301516132b060808401826001600160a01b03169052565b5060a083015160a083015260c08301516132cd60c0840182612fa4565b5060e08301516132e16101c0840182612f82565b506101008301516101e083015292915050565b8154815260018201546001600160a01b039081166020830152600283015481166040830152600383015460608301526004830154166080820152600582015460a0820152600682015460c0820152600782015460e08201526008808301546101008301526009830154610120830152600a83015460ff808216610140850152911c16610160820152600b820154610180820152600c8201546101a08201526102008101600d83015460ff166133ad6101c0840182612f82565b50600e8301546101e083015292915050565b600082198211156133d2576133d2613573565b500190565b6000826133f457634e487b7160e01b600052601260045260246000fd5b500490565b600181815b8085111561343457816000190482111561341a5761341a613573565b8085161561342757918102915b93841c93908002906133fe565b509250929050565b600061238060ff84168360008261345557506001610cd3565b8161346257506000610cd3565b816001811461347857600281146134825761349e565b6001915050610cd3565b60ff84111561349357613493613573565b50506001821b610cd3565b5060208310610133831016604e8410600b84101617156134c1575081810a610cd3565b6134cb83836133f9565b80600019048211156134df576134df613573565b029392505050565b600081600019048311821515161561350157613501613573565b500290565b60008282101561351857613518613573565b500390565b600181811c9082168061353157607f821691505b6020821081141561355257634e487b7160e01b600052602260045260246000fd5b50919050565b600060001982141561356c5761356c613573565b5060010190565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fdfea26469706673582212206e59e2b5384a5fec4055a5f3f5e83f273125dc9cc62b416a178ec2aaabd1747864736f6c63430008070033