ETH Price: $3,710.07 (+4.15%)

Contract

0x5FaBCe763446344a912d5CbF84b0e6541A376454
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
DynamicPoolV2

Compiler Version
v0.8.18+commit.87f61d96

Optimization Enabled:
Yes with 1000 runs

Other Settings:
default evmVersion
File 1 of 23 : DynamicPoolV2.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.14;

import '../libraries/DSMath.sol';
import '../interfaces/IRelativePriceProvider.sol';
import './HighCovRatioFeePoolV2.sol';

/**
 * @title Dynamic Pool V2
 * @notice Manages deposits, withdrawals and swaps. Holds a mapping of assets and parameters.
 * @dev Supports dynamic assets. Assume r* to be close to 1.
 * Be aware that r* changes when the relative price of the asset updates
 * Change log:
 * - add `gap` to prevent storage collision for future upgrades
 * - Inherite from `HighCovRatioFeePoolV2` instead of `Pool`
 */
contract DynamicPoolV2 is HighCovRatioFeePoolV2 {
    using DSMath for uint256;
    using SignedSafeMath for int256;

    uint256[50] private gap;

    /**
     * @notice multiply / divide the cash, liability and amount of a swap by relative price
     * Invariant: D = Sum of P_i * L_i * (r_i - A / r_i)
     */
    function _quoteFactor(IAsset fromAsset, IAsset toAsset) internal view override returns (uint256) {
        uint256 fromAssetRelativePrice = IRelativePriceProvider(address(fromAsset)).getRelativePrice();
        // theoretically we should multiply toCash, toLiability and idealToAmount by toAssetRelativePrice
        // however we simplify the calculation by dividing "from amounts" by toAssetRelativePrice
        uint256 toAssetRelativePrice = IRelativePriceProvider(address(toAsset)).getRelativePrice();

        return (1e18 * fromAssetRelativePrice) / toAssetRelativePrice;
    }

    /**
     * @dev Invariant: D = Sum of P_i * L_i * (r_i - A / r_i)
     */
    function _globalInvariantFunc() internal view override returns (int256 D, int256 SL) {
        int256 A = int256(ampFactor);

        for (uint256 i; i < _sizeOfAssetList(); ++i) {
            IAsset asset = _getAsset(_getKeyAtIndex(i));

            // overflow is unrealistic
            int256 A_i = int256(uint256(asset.cash()));
            int256 L_i = int256(uint256(asset.liability()));
            int256 P_i = int256(IRelativePriceProvider(address(asset)).getRelativePrice());

            // Assume when L_i == 0, A_i always == 0
            if (L_i == 0) {
                // avoid division of 0
                continue;
            }

            int256 r_i = A_i.wdiv(L_i);
            SL += P_i.wmul(L_i);
            D += P_i.wmul(L_i).wmul(r_i - A.wdiv(r_i));
        }
    }
}

File 2 of 23 : 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 "../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;
}

File 3 of 23 : 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;
    }
}

File 4 of 23 : PausableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    function __Pausable_init() internal onlyInitializing {
        __Pausable_init_unchained();
    }

    function __Pausable_init_unchained() internal onlyInitializing {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }

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

File 5 of 23 : ReentrancyGuardUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

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

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }

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

File 6 of 23 : 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);
        }
    }
}

File 7 of 23 : ContextUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;
import "../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;
    }

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

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

pragma solidity ^0.8.0;

import "../utils/Context.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 Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _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);
    }
}

File 9 of 23 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 10 of 23 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

File 11 of 23 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
     * 0 before setting it to a non-zero value.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}

File 12 of 23 : Address.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 Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * 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);
        }
    }
}

File 13 of 23 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @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 Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

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

File 14 of 23 : IAsset.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;

import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

interface IAsset is IERC20 {
    function underlyingToken() external view returns (address);

    function pool() external view returns (address);

    function cash() external view returns (uint120);

    function liability() external view returns (uint120);

    function decimals() external view returns (uint8);

    function underlyingTokenDecimals() external view returns (uint8);

    function setPool(address pool_) external;

    function underlyingTokenBalance() external view returns (uint256);

    function transferUnderlyingToken(address to, uint256 amount) external;

    function mint(address to, uint256 amount) external;

    function burn(address to, uint256 amount) external;

    function addCash(uint256 amount) external;

    function removeCash(uint256 amount) external;

    function addLiability(uint256 amount) external;

    function removeLiability(uint256 amount) external;
}

File 15 of 23 : IPool.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;

interface IPool {
    function getTokens() external view returns (address[] memory);

    function addressOfAsset(address token) external view returns (address);

    function deposit(
        address token,
        uint256 amount,
        uint256 minimumLiquidity,
        address to,
        uint256 deadline,
        bool shouldStake
    ) external returns (uint256 liquidity);

    function withdraw(
        address token,
        uint256 liquidity,
        uint256 minimumAmount,
        address to,
        uint256 deadline
    ) external returns (uint256 amount);

    function withdrawFromOtherAsset(
        address fromToken,
        address toToken,
        uint256 liquidity,
        uint256 minimumAmount,
        address to,
        uint256 deadline
    ) external returns (uint256 amount);

    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minimumToAmount,
        address to,
        uint256 deadline
    ) external returns (uint256 actualToAmount, uint256 haircut);

    function quotePotentialDeposit(
        address token,
        uint256 amount
    ) external view returns (uint256 liquidity, uint256 reward);

    function quotePotentialSwap(
        address fromToken,
        address toToken,
        int256 fromAmount
    ) external view returns (uint256 potentialOutcome, uint256 haircut);

    function quotePotentialWithdraw(
        address token,
        uint256 liquidity
    ) external view returns (uint256 amount, uint256 fee);

    function quoteAmountIn(
        address fromToken,
        address toToken,
        int256 toAmount
    ) external view returns (uint256 amountIn, uint256 haircut);
}

File 16 of 23 : IRelativePriceProvider.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;

interface IRelativePriceProvider {
    /**
     * @notice get the relative price in WAD
     */
    function getRelativePrice() external view returns (uint256);
}

File 17 of 23 : DSMath.sol
// SPDX-License-Identifier: GPL-3.0

/// math.sol -- mixin for inline numerical wizardry

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.5;

library DSMath {
    uint256 public constant WAD = 10 ** 18;

    // Babylonian Method
    function sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y > 3) {
            z = y;
            uint256 x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }

    //rounds to zero if x*y < WAD / 2
    function wmul(uint256 x, uint256 y) internal pure returns (uint256) {
        return ((x * y) + (WAD / 2)) / WAD;
    }

    function wdiv(uint256 x, uint256 y) internal pure returns (uint256) {
        return ((x * WAD) + (y / 2)) / y;
    }

    // Convert x to WAD (18 decimals) from d decimals.
    function toWad(uint256 x, uint8 d) internal pure returns (uint256) {
        if (d < 18) {
            return x * 10 ** (18 - d);
        } else if (d > 18) {
            return (x / (10 ** (d - 18)));
        }
        return x;
    }

    // Convert x from WAD (18 decimals) to d decimals.
    function fromWad(uint256 x, uint8 d) internal pure returns (uint256) {
        if (d < 18) {
            return (x / (10 ** (18 - d)));
        } else if (d > 18) {
            return x * 10 ** (d - 18);
        }
        return x;
    }
}

File 18 of 23 : SignedSafeMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.3.2 (utils/math/SignedSafeMath.sol)

pragma solidity ^0.8.5;

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SignedSafeMath` is no longer needed starting with Solidity 0.8. The compiler
 * now has built in overflow checking.
 */
library SignedSafeMath {
    int256 public constant WAD = 10 ** 18;

    //rounds to zero if x*y < WAD / 2
    function wdiv(int256 x, int256 y) internal pure returns (int256) {
        return ((x * WAD) + (y / 2)) / y;
    }

    //rounds to zero if x*y < WAD / 2
    function wmul(int256 x, int256 y) internal pure returns (int256) {
        return ((x * y) + (WAD / 2)) / WAD;
    }

    // Babylonian Method (typecast as int)
    function sqrt(int256 y) internal pure returns (int256 z) {
        if (y > 3) {
            z = y;
            int256 x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }

    // Babylonian Method with initial guess (typecast as int)
    function sqrt(int256 y, int256 guess) internal pure returns (int256 z) {
        if (y > 3) {
            if (guess > 0 && guess <= y) {
                z = guess;
            } else if (guess < 0 && -guess <= y) {
                z = -guess;
            } else {
                z = y;
            }
            int256 x = (y / z + z) / 2;
            while (x != z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }

    // Convert x to WAD (18 decimals) from d decimals.
    function toWad(int256 x, uint8 d) internal pure returns (int256) {
        if (d < 18) {
            return x * int256(10 ** (18 - d));
        } else if (d > 18) {
            return (x / int256(10 ** (d - 18)));
        }
        return x;
    }

    // Convert x from WAD (18 decimals) to d decimals.
    function fromWad(int256 x, uint8 d) internal pure returns (int256) {
        if (d < 18) {
            return (x / int256(10 ** (18 - d)));
        } else if (d > 18) {
            return x * int256(10 ** (d - 18));
        }
        return x;
    }

    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, 'value must be positive');
        return uint256(value);
    }

    function toInt256(uint256 value) internal pure returns (int256) {
        require(value <= uint256(type(int256).max), 'value must be positive');
        return int256(value);
    }

    function abs(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            return uint256(-value);
        } else {
            return uint256(value);
        }
    }
}

File 19 of 23 : CoreV2.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.5;

import '../libraries/DSMath.sol';
import '../libraries/SignedSafeMath.sol';

/**
 * @title CoreV2
 * @notice Handles math operations of Wombat protocol. Assume all params are signed integer with 18 decimals
 * @dev Uses OpenZeppelin's SignedSafeMath and DSMath's WAD for calculations.
 */
contract CoreV2 {
    using DSMath for uint256;
    using SignedSafeMath for int256;
    int256 internal constant WAD_I = 10 ** 18;
    uint256 internal constant WAD = 10 ** 18;

    error CORE_UNDERFLOW();

    /**
     * @notice Core Wombat stableswap equation
     * @dev This function always returns >= 0
     * @param Ax asset of token x
     * @param Ay asset of token y
     * @param Lx liability of token x
     * @param Ly liability of token y
     * @param Dx delta x, i.e. token x amount inputted
     * @param A amplification factor
     * @return quote The quote for amount of token y swapped for token x amount inputted
     */
    function _swapQuoteFunc(
        int256 Ax,
        int256 Ay,
        int256 Lx,
        int256 Ly,
        int256 Dx,
        int256 A
    ) internal pure returns (uint256 quote) {
        if (Lx == 0 || Ly == 0) {
            // in case div of 0
            revert CORE_UNDERFLOW();
        }
        int256 D = Ax + Ay - A.wmul((Lx * Lx) / Ax + (Ly * Ly) / Ay); // flattened _invariantFunc
        int256 rx_ = (Ax + Dx).wdiv(Lx);
        int256 b = (Lx * (rx_ - A.wdiv(rx_))) / Ly - D.wdiv(Ly); // flattened _coefficientFunc
        int256 ry_ = _solveQuad(b, A);
        int256 Dy = Ly.wmul(ry_) - Ay;
        if (Dy < 0) {
            quote = uint256(-Dy);
        } else {
            quote = uint256(Dy);
        }
    }

    /**
     * @notice Solve quadratic equation
     * @dev This function always returns >= 0
     * @param b quadratic equation b coefficient
     * @param c quadratic equation c coefficient
     * @return x
     */
    function _solveQuad(int256 b, int256 c) internal pure returns (int256) {
        return (((b * b) + (c * 4 * WAD_I)).sqrt(b) - b) / 2;
    }

    /**
     * @notice Equation to get invariant constant between token x and token y
     * @dev This function always returns >= 0
     * @param Lx liability of token x
     * @param rx cov ratio of token x
     * @param Ly liability of token x
     * @param ry cov ratio of token y
     * @param A amplification factor
     * @return The invariant constant between token x and token y ("D")
     */
    function _invariantFunc(int256 Lx, int256 rx, int256 Ly, int256 ry, int256 A) internal pure returns (int256) {
        int256 a = Lx.wmul(rx) + Ly.wmul(ry);
        int256 b = A.wmul(Lx.wdiv(rx) + Ly.wdiv(ry));
        return a - b;
    }

    /**
     * @notice Equation to get quadratic equation b coefficient
     * @dev This function can return >= 0 or <= 0
     * @param Lx liability of token x
     * @param Ly liability of token y
     * @param rx_ new asset coverage ratio of token x
     * @param D invariant constant
     * @param A amplification factor
     * @return The quadratic equation b coefficient ("b")
     */
    function _coefficientFunc(int256 Lx, int256 Ly, int256 rx_, int256 D, int256 A) internal pure returns (int256) {
        return Lx.wmul(rx_ - A.wdiv(rx_)).wdiv(Ly) - D.wdiv(Ly);
    }

    /**
     * @return v positive value indicates a reward and negative value indicates a fee
     */
    function depositRewardImpl(
        int256 D,
        int256 SL,
        int256 delta_i,
        int256 A_i,
        int256 L_i,
        int256 A
    ) internal pure returns (int256 v) {
        if (L_i == 0) {
            // early return in case of div of 0
            return 0;
        }
        if (delta_i + SL == 0) {
            return L_i - A_i;
        }

        int256 r_i_ = _targetedCovRatio(SL, delta_i, A_i, L_i, D, A);
        v = A_i + delta_i - (L_i + delta_i).wmul(r_i_);
    }

    /**
     * @dev should be used only when r* = 1
     */
    function withdrawalAmountInEquilImpl(
        int256 delta_i,
        int256 A_i,
        int256 L_i,
        int256 A
    ) internal pure returns (int256 amount) {
        int256 L_i_ = L_i + delta_i;
        int256 r_i = A_i.wdiv(L_i);
        int256 rho = L_i.wmul(r_i - A.wdiv(r_i));
        int256 beta = (rho + delta_i.wmul(WAD_I - A)) / 2;
        int256 A_i_ = beta + (beta * beta + A.wmul(L_i_ * L_i_)).sqrt(beta);
        amount = A_i - A_i_;
    }

    /**
     * @notice return the deposit reward in token amount when target liquidity (LP amount) is known
     */
    function exactDepositLiquidityInEquilImpl(
        int256 D_i,
        int256 A_i,
        int256 L_i,
        int256 A
    ) internal pure returns (int256 liquidity) {
        if (L_i == 0) {
            // if this is a deposit, there is no reward/fee
            // if this is a withdrawal, it should have been reverted
            return D_i;
        }
        if (A_i + D_i < 0) {
            // impossible
            revert CORE_UNDERFLOW();
        }

        int256 r_i = A_i.wdiv(L_i);
        int256 k = D_i + A_i;
        int256 b = k.wmul(WAD_I - A) + 2 * A.wmul(L_i);
        int256 c = k.wmul(A_i - (A * L_i) / r_i) - k.wmul(k) + A.wmul(L_i).wmul(L_i);
        int256 l = b * b - 4 * A * c;
        return (-b + l.sqrt(b)).wdiv(A) / 2;
    }

    function _targetedCovRatio(
        int256 SL,
        int256 delta_i,
        int256 A_i,
        int256 L_i,
        int256 D,
        int256 A
    ) internal pure returns (int256 r_i_) {
        int256 r_i = A_i.wdiv(L_i);
        int256 er = _equilCovRatio(D, SL, A);
        int256 er_ = _newEquilCovRatio(er, SL, delta_i);
        int256 D_ = _newInvariantFunc(er_, A, SL, delta_i);

        // Summation of k∈T\{i} is D - L_i.wmul(r_i - A.wdiv(r_i))
        int256 b_ = (D - A_i + (L_i * A) / r_i - D_).wdiv(L_i + delta_i);
        r_i_ = _solveQuad(b_, A);
    }

    function _equilCovRatio(int256 D, int256 SL, int256 A) internal pure returns (int256 er) {
        int256 b = -(D.wdiv(SL));
        er = _solveQuad(b, A);
    }

    function _newEquilCovRatio(int256 er, int256 SL, int256 delta_i) internal pure returns (int256 er_) {
        er_ = (delta_i + SL.wmul(er)).wdiv(delta_i + SL);
    }

    function _newInvariantFunc(int256 er_, int256 A, int256 SL, int256 delta_i) internal pure returns (int256 D_) {
        D_ = (SL + delta_i).wmul(er_ - A.wdiv(er_));
    }

    /**
     * @notice From Yellow Paper (Haircut).
     * @dev Applies haircut rate to amount
     * @param amount The amount that will receive the discount
     * @param rate The rate to be applied
     * @return The result of operation.
     */
    function _haircut(uint256 amount, uint256 rate) internal pure returns (uint256) {
        return amount.wmul(rate);
    }
}

File 20 of 23 : HighCovRatioFeePoolV2.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.5;

import '../libraries/DSMath.sol';
import './PoolV2.sol';

/**
 * @title HighCovRatioFeePoolV2
 * @dev Pool with high cov ratio fee protection
 * Change log:
 * - add `gap` to prevent storage collision for future upgrades
 */
contract HighCovRatioFeePoolV2 is PoolV2 {
    using DSMath for uint256;

    uint128 public startCovRatio; // 1.5
    uint128 public endCovRatio; // 1.8

    uint256[50] private gap;

    error WOMBAT_COV_RATIO_LIMIT_EXCEEDED();

    function initialize(uint256 ampFactor_, uint256 haircutRate_) public override {
        super.initialize(ampFactor_, haircutRate_);
        startCovRatio = 15e17;
        endCovRatio = 18e17;
    }

    function setCovRatioFeeParam(uint128 startCovRatio_, uint128 endCovRatio_) external onlyOwner {
        if (startCovRatio_ < 1e18 || startCovRatio_ > endCovRatio_) revert WOMBAT_INVALID_VALUE();

        startCovRatio = startCovRatio_;
        endCovRatio = endCovRatio_;
    }

    /**
     * @notice Calculate the high cov ratio fee in the to-asset in a swap.
     * @dev When cov ratio is in the range [startCovRatio, endCovRatio], the marginal cov ratio is
     * (r - startCovRatio) / (endCovRatio - startCovRatio). Here we approximate the high cov ratio cut
     * by calculating the "average" fee.
     * Note: `finalCovRatio` should be greater than `initCovRatio`
     */
    function _highCovRatioFee(uint256 initCovRatio, uint256 finalCovRatio) internal view returns (uint256 fee) {
        if (finalCovRatio > endCovRatio) {
            // invalid swap
            revert WOMBAT_COV_RATIO_LIMIT_EXCEEDED();
        } else if (finalCovRatio <= startCovRatio || finalCovRatio <= initCovRatio) {
            return 0;
        }

        unchecked {
            // 1. Calculate the area of fee(r) = (r - startCovRatio) / (endCovRatio - startCovRatio)
            // when r increase from initCovRatio to finalCovRatio
            // 2. Then multiply it by (endCovRatio - startCovRatio) / (finalCovRatio - initCovRatio)
            // to get the average fee over the range
            uint256 a = initCovRatio <= startCovRatio
                ? 0
                : (initCovRatio - startCovRatio) * (initCovRatio - startCovRatio);
            uint256 b = (finalCovRatio - startCovRatio) * (finalCovRatio - startCovRatio);
            fee = ((b - a) / (finalCovRatio - initCovRatio) / 2).wdiv(endCovRatio - startCovRatio);
        }
    }

    /**
     * @dev Exact output swap (fromAmount < 0) should be only used by off-chain quoting function as it is a gas monster
     */
    function _quoteFrom(
        IAsset fromAsset,
        IAsset toAsset,
        int256 fromAmount
    ) internal view override returns (uint256 actualToAmount, uint256 haircut) {
        (actualToAmount, haircut) = super._quoteFrom(fromAsset, toAsset, fromAmount);

        if (fromAmount >= 0) {
            // normal quote
            uint256 fromAssetCash = fromAsset.cash();
            uint256 fromAssetLiability = fromAsset.liability();
            uint256 finalFromAssetCovRatio = (fromAssetCash + uint256(fromAmount)).wdiv(fromAssetLiability);

            if (finalFromAssetCovRatio > startCovRatio) {
                // charge high cov ratio fee
                uint256 highCovRatioFee = _highCovRatioFee(
                    fromAssetCash.wdiv(fromAssetLiability),
                    finalFromAssetCovRatio
                ).wmul(actualToAmount);

                actualToAmount -= highCovRatioFee;
                unchecked {
                    haircut += highCovRatioFee;
                }
            }
        } else {
            // reverse quote
            uint256 toAssetCash = toAsset.cash();
            uint256 toAssetLiability = toAsset.liability();
            uint256 finalToAssetCovRatio = (toAssetCash + uint256(actualToAmount)).wdiv(toAssetLiability);
            if (finalToAssetCovRatio <= startCovRatio) {
                // happy path: no high cov ratio fee is charged
                return (actualToAmount, haircut);
            } else if (toAssetCash.wdiv(toAssetLiability) >= endCovRatio) {
                // the to-asset exceeds it's cov ratio limit, further swap to increase cov ratio is impossible
                revert WOMBAT_COV_RATIO_LIMIT_EXCEEDED();
            }

            // reverse quote: cov ratio of the to-asset exceed endCovRatio. direct reverse quote is not supported
            // we binary search for a upper bound
            actualToAmount = _findUpperBound(toAsset, fromAsset, uint256(-fromAmount));
            (, haircut) = _quoteFrom(toAsset, fromAsset, int256(actualToAmount));
        }
    }

    /**
     * @notice Binary search to find the upper bound of `fromAmount` required to swap `fromAsset` to `toAmount` of `toAsset`
     * @dev This function should only used as off-chain view function as it is a gas monster
     */
    function _findUpperBound(
        IAsset fromAsset,
        IAsset toAsset,
        uint256 toAmount
    ) internal view returns (uint256 upperBound) {
        uint8 decimals = fromAsset.underlyingTokenDecimals();
        uint256 toWadFactor = DSMath.toWad(1, decimals);
        // the search value uses the same number of digits as the token
        uint256 high = (uint256(fromAsset.liability()).wmul(endCovRatio) - fromAsset.cash()).fromWad(decimals);
        uint256 low = 1;

        // verify `high` is a valid upper bound
        uint256 quote;
        (quote, ) = _quoteFrom(fromAsset, toAsset, int256(high * toWadFactor));
        if (quote < toAmount) revert WOMBAT_COV_RATIO_LIMIT_EXCEEDED();

        // Note: we might limit the maximum number of rounds if the request is always rejected by the RPC server
        while (low < high) {
            unchecked {
                uint256 mid = (low + high) / 2;
                (quote, ) = _quoteFrom(fromAsset, toAsset, int256(mid * toWadFactor));
                if (quote >= toAmount) {
                    high = mid;
                } else {
                    low = mid + 1;
                }
            }
        }
        return high * toWadFactor;
    }

    /**
     * @dev take into account high cov ratio fee
     */
    function quotePotentialWithdrawFromOtherAsset(
        address fromToken,
        address toToken,
        uint256 liquidity
    ) external view override returns (uint256 amount, uint256 withdrewAmount) {
        _checkLiquidity(liquidity);
        _checkSameAddress(fromToken, toToken);
        (amount, withdrewAmount) = _quotePotentialWithdrawFromOtherAsset(fromToken, toToken, liquidity);

        IAsset fromAsset = _assetOf(fromToken);
        IAsset toAsset = _assetOf(toToken);
        uint256 fromAssetCash = fromAsset.cash() - withdrewAmount;
        uint256 fromAssetLiability = fromAsset.liability() - liquidity;
        uint256 finalFromAssetCovRatio = (fromAssetCash + uint256(withdrewAmount)).wdiv(fromAssetLiability);

        if (finalFromAssetCovRatio > startCovRatio) {
            uint256 highCovRatioFee = _highCovRatioFee(fromAssetCash.wdiv(fromAssetLiability), finalFromAssetCovRatio)
                .wmul(amount);

            amount -= highCovRatioFee;
        }
        withdrewAmount = withdrewAmount.fromWad(fromAsset.underlyingTokenDecimals());
        amount = amount.fromWad(toAsset.underlyingTokenDecimals());
    }
}

File 21 of 23 : PausableAssets.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.5;

/**
 * @title PausableAssets
 * @notice Handles assets pause and unpause of Wombat protocol.
 * @dev Allows pausing and unpausing of deposit and swap operations
 */
contract PausableAssets {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event PausedAsset(address token, address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event UnpausedAsset(address token, address account);

    // We use the asset's underlying token as the key to check whether an asset is paused.
    // A pool will never have two assets with the same underlying token.
    mapping(address => bool) private _pausedAssets;

    error WOMBAT_ASSET_ALREADY_PAUSED();
    error WOMBAT_ASSET_NOT_PAUSED();

    /**
     * @dev Function to return if the asset is paused.
     * The return value is only useful when true.
     * When the return value is false, the asset can be either not paused or not exist.
     */
    function isPaused(address token) public view returns (bool) {
        return _pausedAssets[token];
    }

    /**
     * @dev Function to make a function callable only when the asset is not paused.
     *
     * Requirements:
     *
     * - The asset must not be paused.
     */
    function requireAssetNotPaused(address token) internal view {
        if (_pausedAssets[token]) revert WOMBAT_ASSET_ALREADY_PAUSED();
    }

    /**
     * @dev Function to make a function callable only when the asset is paused.
     *
     * Requirements:
     *
     * - The asset must be paused.
     */
    function requireAssetPaused(address token) internal view {
        if (!_pausedAssets[token]) revert WOMBAT_ASSET_NOT_PAUSED();
    }

    /**
     * @dev Triggers paused state.
     *
     * Requirements:
     *
     * - The asset must not be paused.
     */
    function _pauseAsset(address token) internal {
        requireAssetNotPaused(token);
        _pausedAssets[token] = true;
        emit PausedAsset(token, msg.sender);
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The asset must be paused.
     */
    function _unpauseAsset(address token) internal {
        requireAssetPaused(token);
        _pausedAssets[token] = false;
        emit UnpausedAsset(token, msg.sender);
    }
}

File 22 of 23 : PoolV2.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.5;

import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';

import './CoreV2.sol';
import '../interfaces/IAsset.sol';
import './PausableAssets.sol';
import '../../wombat-governance/interfaces/IMasterWombat.sol';
import '../interfaces/IPool.sol';

/**
 * @title Pool V2
 * @notice Manages deposits, withdrawals and swaps. Holds a mapping of assets and parameters.
 * @dev The main entry-point of Wombat protocol
 * Note: All variables are 18 decimals, except from that of underlying tokens
 * Change log:
 * - add `gap` to prevent storage collision for future upgrades
 */
contract PoolV2 is
    Initializable,
    IPool,
    OwnableUpgradeable,
    ReentrancyGuardUpgradeable,
    PausableUpgradeable,
    PausableAssets,
    CoreV2
{
    using DSMath for uint256;
    using SafeERC20 for IERC20;
    using SignedSafeMath for int256;

    /// @notice Asset Map struct holds assets
    struct AssetMap {
        address[] keys;
        mapping(address => IAsset) values;
        mapping(address => uint256) indexOf;
    }

    /* Storage */

    /// @notice Amplification factor
    uint256 public ampFactor;

    /// @notice Haircut rate
    uint256 public haircutRate;

    /// @notice Retention ratio: the ratio of haircut that should stay in the pool
    uint256 public retentionRatio;

    /// @notice LP dividend ratio : the ratio of haircut that should distribute to LP
    uint256 public lpDividendRatio;

    /// @notice The threshold to mint fee (unit: WAD)
    uint256 public mintFeeThreshold;

    /// @notice Dev address
    address public dev;

    address public feeTo;

    address public masterWombat;

    /// @notice Dividend collected by each asset (unit: WAD)
    mapping(IAsset => uint256) internal _feeCollected;

    /// @notice A record of assets inside Pool
    AssetMap internal _assets;

    // Slots reserved for future use
    uint128 internal _used1; // Remember to initialize before use.
    uint128 internal _used2; // Remember to initialize before use.
    uint256[49] private gap;

    /* Events */

    /// @notice An event thats emitted when an asset is added to Pool
    event AssetAdded(address indexed token, address indexed asset);

    /// @notice An event thats emitted when asset is removed from Pool
    event AssetRemoved(address indexed token, address indexed asset);

    /// @notice An event thats emitted when a deposit is made to Pool
    event Deposit(address indexed sender, address token, uint256 amount, uint256 liquidity, address indexed to);

    /// @notice An event thats emitted when a withdrawal is made from Pool
    event Withdraw(address indexed sender, address token, uint256 amount, uint256 liquidity, address indexed to);

    /// @notice An event thats emitted when a swap is made in Pool
    event Swap(
        address indexed sender,
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 toAmount,
        address indexed to
    );

    event SetDev(address addr);
    event SetMasterWombat(address addr);
    event SetFeeTo(address addr);

    event SetMintFeeThreshold(uint256 value);
    event SetFee(uint256 lpDividendRatio, uint256 retentionRatio);
    event SetAmpFactor(uint256 value);
    event SetHaircutRate(uint256 value);

    event FillPool(address token, uint256 amount);
    event TransferTipBucket(address token, uint256 amount, address to);

    /* Errors */

    error WOMBAT_FORBIDDEN();
    error WOMBAT_EXPIRED();

    error WOMBAT_ASSET_NOT_EXISTS();
    error WOMBAT_ASSET_ALREADY_EXIST();

    error WOMBAT_ZERO_ADDRESS();
    error WOMBAT_ZERO_AMOUNT();
    error WOMBAT_ZERO_LIQUIDITY();
    error WOMBAT_INVALID_VALUE();
    error WOMBAT_SAME_ADDRESS();
    error WOMBAT_AMOUNT_TOO_LOW();
    error WOMBAT_CASH_NOT_ENOUGH();

    /* Pesudo modifiers to safe gas */

    function _checkLiquidity(uint256 liquidity) internal pure {
        if (liquidity == 0) revert WOMBAT_ZERO_LIQUIDITY();
    }

    function _checkAddress(address to) internal pure {
        if (to == address(0)) revert WOMBAT_ZERO_ADDRESS();
    }

    function _checkSameAddress(address from, address to) internal pure {
        if (from == to) revert WOMBAT_SAME_ADDRESS();
    }

    function _checkAmount(uint256 minAmt, uint256 amt) internal pure {
        if (minAmt > amt) revert WOMBAT_AMOUNT_TOO_LOW();
    }

    function _ensure(uint256 deadline) internal view {
        if (deadline < block.timestamp) revert WOMBAT_EXPIRED();
    }

    function _onlyDev() internal view {
        if (dev != msg.sender) revert WOMBAT_FORBIDDEN();
    }

    /* Construtor and setters */

    /**
     * @notice Initializes pool. Dev is set to be the account calling this function.
     */
    function initialize(uint256 ampFactor_, uint256 haircutRate_) public virtual initializer {
        __Ownable_init();
        __ReentrancyGuard_init_unchained();
        __Pausable_init_unchained();

        ampFactor = ampFactor_;
        haircutRate = haircutRate_;

        lpDividendRatio = WAD;

        dev = msg.sender;
    }

    /**
     * Permisioneed functions
     */

    /**
     * @notice Adds asset to pool, reverts if asset already exists in pool
     * @param token The address of token
     * @param asset The address of the Wombat Asset contract
     */
    function addAsset(address token, address asset) external onlyOwner {
        _checkAddress(asset);
        _checkAddress(token);

        if (_containsAsset(token)) revert WOMBAT_ASSET_ALREADY_EXIST();
        _assets.values[token] = IAsset(asset);
        _assets.indexOf[token] = _assets.keys.length;
        _assets.keys.push(token);

        emit AssetAdded(token, asset);
    }

    /**
     * @notice Removes asset from asset struct
     * @dev Can only be called by owner
     * @param token The address of token to remove
     */
    function removeAsset(address token) external onlyOwner {
        if (!_containsAsset(token)) revert WOMBAT_ASSET_NOT_EXISTS();

        address asset = address(_getAsset(token));
        delete _assets.values[token];

        uint256 index = _assets.indexOf[token];
        uint256 lastIndex = _assets.keys.length - 1;
        address lastKey = _assets.keys[lastIndex];

        _assets.indexOf[lastKey] = index;
        delete _assets.indexOf[token];

        _assets.keys[index] = lastKey;
        _assets.keys.pop();

        emit AssetRemoved(token, asset);
    }

    /**
     * @notice Changes the contract dev. Can only be set by the contract owner.
     * @param dev_ new contract dev address
     */
    function setDev(address dev_) external onlyOwner {
        _checkAddress(dev_);
        dev = dev_;
        emit SetDev(dev_);
    }

    function setMasterWombat(address masterWombat_) external onlyOwner {
        _checkAddress(masterWombat_);
        masterWombat = masterWombat_;
        emit SetMasterWombat(masterWombat_);
    }

    /**
     * @notice Changes the pools amplification factor. Can only be set by the contract owner.
     * @param ampFactor_ new pool's amplification factor
     */
    function setAmpFactor(uint256 ampFactor_) external onlyOwner {
        if (ampFactor_ > WAD) revert WOMBAT_INVALID_VALUE(); // ampFactor_ should not be set bigger than 1
        ampFactor = ampFactor_;
        emit SetAmpFactor(ampFactor_);
    }

    /**
     * @notice Changes the pools haircutRate. Can only be set by the contract owner.
     * @param haircutRate_ new pool's haircutRate_
     */
    function setHaircutRate(uint256 haircutRate_) external onlyOwner {
        if (haircutRate_ > WAD) revert WOMBAT_INVALID_VALUE(); // haircutRate_ should not be set bigger than 1
        haircutRate = haircutRate_;
        emit SetHaircutRate(haircutRate_);
    }

    function setFee(uint256 lpDividendRatio_, uint256 retentionRatio_) external onlyOwner {
        unchecked {
            if (retentionRatio_ + lpDividendRatio_ > WAD) revert WOMBAT_INVALID_VALUE();
        }
        _mintAllFees();
        retentionRatio = retentionRatio_;
        lpDividendRatio = lpDividendRatio_;
        emit SetFee(lpDividendRatio_, retentionRatio_);
    }

    /**
     * @dev unit of amount should be in WAD
     */
    function transferTipBucket(address token, uint256 amount, address to) external onlyOwner {
        IAsset asset = _assetOf(token);
        uint256 tipBucketBal = tipBucketBalance(token);

        if (amount > tipBucketBal) {
            // revert if there's not enough amount in the tip bucket
            revert WOMBAT_INVALID_VALUE();
        }

        asset.transferUnderlyingToken(to, amount.fromWad(asset.underlyingTokenDecimals()));
        emit TransferTipBucket(token, amount, to);
    }

    /**
     * @notice Changes the fee beneficiary. Can only be set by the contract owner.
     * This value cannot be set to 0 to avoid unsettled fee.
     * @param feeTo_ new fee beneficiary
     */
    function setFeeTo(address feeTo_) external onlyOwner {
        _checkAddress(feeTo_);
        feeTo = feeTo_;
        emit SetFeeTo(feeTo_);
    }

    /**
     * @notice Set min fee to mint
     */
    function setMintFeeThreshold(uint256 mintFeeThreshold_) external onlyOwner {
        mintFeeThreshold = mintFeeThreshold_;
        emit SetMintFeeThreshold(mintFeeThreshold_);
    }

    /**
     * @dev pause pool, restricting certain operations
     */
    function pause() external {
        _onlyDev();
        _pause();
    }

    /**
     * @dev unpause pool, enabling certain operations
     */
    function unpause() external {
        _onlyDev();
        _unpause();
    }

    /**
     * @dev pause asset, restricting deposit and swap operations
     */
    function pauseAsset(address token) external {
        _onlyDev();
        if (!_containsAsset(token)) revert WOMBAT_ASSET_NOT_EXISTS();
        _pauseAsset(token);
    }

    /**
     * @dev unpause asset, enabling deposit and swap operations
     */
    function unpauseAsset(address token) external {
        _onlyDev();
        _unpauseAsset(token);
    }

    /**
     * @notice Move fund from tip bucket to the pool to keep r* = 1 as error accumulates
     * unit of amount should be in WAD
     */
    function fillPool(address token, uint256 amount) external {
        _onlyDev();
        IAsset asset = _assetOf(token);
        uint256 tipBucketBal = asset.underlyingTokenBalance().toWad(asset.underlyingTokenDecimals()) -
            asset.cash() -
            _feeCollected[asset];

        if (amount > tipBucketBal) {
            // revert if there's not enough amount in the tip bucket
            revert WOMBAT_INVALID_VALUE();
        }

        asset.addCash(amount);
        emit FillPool(token, amount);
    }

    /* Assets */

    /**
     * @notice Return list of tokens in the pool
     */
    function getTokens() external view override returns (address[] memory) {
        return _assets.keys;
    }

    /**
     * @notice get length of asset list
     * @return the size of the asset list
     */
    function _sizeOfAssetList() internal view returns (uint256) {
        return _assets.keys.length;
    }

    /**
     * @notice Gets asset with token address key
     * @param key The address of token
     * @return the corresponding asset in state
     */
    function _getAsset(address key) internal view returns (IAsset) {
        return _assets.values[key];
    }

    /**
     * @notice Gets key (address) at index
     * @param index the index
     * @return the key of index
     */
    function _getKeyAtIndex(uint256 index) internal view returns (address) {
        return _assets.keys[index];
    }

    /**
     * @notice Looks if the asset is contained by the list
     * @param token The address of token to look for
     * @return bool true if the asset is in asset list, false otherwise
     */
    function _containsAsset(address token) internal view returns (bool) {
        return _assets.values[token] != IAsset(address(0));
    }

    /**
     * @notice Gets Asset corresponding to ERC20 token. Reverts if asset does not exists in Pool.
     * @param token The address of ERC20 token
     */
    function _assetOf(address token) internal view returns (IAsset) {
        if (!_containsAsset(token)) revert WOMBAT_ASSET_NOT_EXISTS();
        return _assets.values[token];
    }

    /**
     * @notice Gets Asset corresponding to ERC20 token. Reverts if asset does not exists in Pool.
     * @dev to be used externally
     * @param token The address of ERC20 token
     */
    function addressOfAsset(address token) external view override returns (address) {
        return address(_assetOf(token));
    }

    /* Deposit */

    /**
     * This function calculate the exactly amount of liquidity of the deposit. Assumes r* = 1
     */
    function _exactDepositToInEquil(
        IAsset asset,
        uint256 amount
    ) internal view returns (uint256 lpTokenToMint, uint256 liabilityToMint, uint256 reward) {
        liabilityToMint = exactDepositLiquidityInEquilImpl(
            int256(amount),
            int256(uint256(asset.cash())),
            int256(uint256(asset.liability())),
            int256(ampFactor)
        ).toUint256();

        if (liabilityToMint >= amount) {
            unchecked {
                reward = liabilityToMint - amount;
            }
        } else {
            // rounding error
            liabilityToMint = amount;
        }

        // Calculate amount of LP to mint : ( deposit + reward ) * TotalAssetSupply / Liability
        uint256 liability = asset.liability();
        lpTokenToMint = (liability == 0 ? liabilityToMint : (liabilityToMint * asset.totalSupply()) / liability);
    }

    /**
     * @notice Deposits asset in Pool
     * @param asset The asset to be deposited
     * @param amount The amount to be deposited
     * @param to The user accountable for deposit, receiving the Wombat assets (lp)
     * @return liquidity Total asset liquidity minted
     */
    function _deposit(
        IAsset asset,
        uint256 amount,
        uint256 minimumLiquidity,
        address to
    ) internal returns (uint256 liquidity) {
        // collect fee before deposit
        _mintFeeIfNeeded(asset);

        uint256 liabilityToMint;
        (liquidity, liabilityToMint, ) = _exactDepositToInEquil(asset, amount);

        _checkLiquidity(liquidity);
        _checkAmount(minimumLiquidity, liquidity);

        asset.addCash(amount);
        asset.addLiability(liabilityToMint);
        asset.mint(to, liquidity);
    }

    /**
     * @notice Deposits amount of tokens into pool ensuring deadline
     * @dev Asset needs to be created and added to pool before any operation. This function assumes tax free token.
     * @param token The token address to be deposited
     * @param amount The amount to be deposited
     * @param to The user accountable for deposit, receiving the Wombat assets (lp)
     * @param deadline The deadline to be respected
     * @return liquidity Total asset liquidity minted
     */
    function deposit(
        address token,
        uint256 amount,
        uint256 minimumLiquidity,
        address to,
        uint256 deadline,
        bool shouldStake
    ) external override nonReentrant whenNotPaused returns (uint256 liquidity) {
        if (amount == 0) revert WOMBAT_ZERO_AMOUNT();
        _checkAddress(to);
        _ensure(deadline);
        requireAssetNotPaused(token);

        IAsset asset = _assetOf(token);
        IERC20(token).safeTransferFrom(address(msg.sender), address(asset), amount);

        if (!shouldStake) {
            liquidity = _deposit(asset, amount.toWad(asset.underlyingTokenDecimals()), minimumLiquidity, to);
        } else {
            _checkAddress(masterWombat);
            // deposit and stake on behalf of the user
            liquidity = _deposit(asset, amount.toWad(asset.underlyingTokenDecimals()), minimumLiquidity, address(this));

            asset.approve(masterWombat, liquidity);

            uint256 pid = IMasterWombat(masterWombat).getAssetPid(address(asset));
            IMasterWombat(masterWombat).depositFor(pid, liquidity, to);
        }

        emit Deposit(msg.sender, token, amount, liquidity, to);
    }

    /**
     * @notice Quotes potential deposit from pool
     * @dev To be used by frontend
     * @param token The token to deposit by user
     * @param amount The amount to deposit
     * @return liquidity The potential liquidity user would receive
     * @return reward
     */
    function quotePotentialDeposit(
        address token,
        uint256 amount
    ) external view override returns (uint256 liquidity, uint256 reward) {
        IAsset asset = _assetOf(token);
        (liquidity, , reward) = _exactDepositToInEquil(asset, amount.toWad(asset.underlyingTokenDecimals()));
    }

    /* Withdraw */

    /**
     * @notice Calculates fee and liability to burn in case of withdrawal
     * @param asset The asset willing to be withdrawn
     * @param liquidity The liquidity willing to be withdrawn
     * @return amount Total amount to be withdrawn from Pool
     * @return liabilityToBurn Total liability to be burned by Pool
     * @return fee
     */
    function _withdrawFrom(
        IAsset asset,
        uint256 liquidity
    ) internal view returns (uint256 amount, uint256 liabilityToBurn, uint256 fee) {
        liabilityToBurn = (asset.liability() * liquidity) / asset.totalSupply();
        _checkLiquidity(liabilityToBurn);

        amount = withdrawalAmountInEquilImpl(
            -int256(liabilityToBurn),
            int256(uint256(asset.cash())),
            int256(uint256(asset.liability())),
            int256(ampFactor)
        ).toUint256();

        if (liabilityToBurn >= amount) {
            fee = liabilityToBurn - amount;
        } else {
            // rounding error
            amount = liabilityToBurn;
        }
    }

    /**
     * @notice Withdraws liquidity amount of asset to `to` address ensuring minimum amount required
     * @param asset The asset to be withdrawn
     * @param liquidity The liquidity to be withdrawn
     * @param minimumAmount The minimum amount that will be accepted by user
     * @return amount The total amount withdrawn
     */
    function _withdraw(IAsset asset, uint256 liquidity, uint256 minimumAmount) internal returns (uint256 amount) {
        // collect fee before withdraw
        _mintFeeIfNeeded(asset);

        // calculate liabilityToBurn and Fee
        uint256 liabilityToBurn;
        (amount, liabilityToBurn, ) = _withdrawFrom(asset, liquidity);
        _checkAmount(minimumAmount, amount);

        asset.burn(address(asset), liquidity);
        asset.removeCash(amount);
        asset.removeLiability(liabilityToBurn);

        // revert if cov ratio < 1% to avoid precision error
        if (asset.liability() > 0 && uint256(asset.cash()).wdiv(asset.liability()) < WAD / 100)
            revert WOMBAT_FORBIDDEN();
    }

    /**
     * @notice Withdraws liquidity amount of asset to `to` address ensuring minimum amount required
     * @param token The token to be withdrawn
     * @param liquidity The liquidity to be withdrawn
     * @param minimumAmount The minimum amount that will be accepted by user
     * @param to The user receiving the withdrawal
     * @param deadline The deadline to be respected
     * @return amount The total amount withdrawn
     */
    function withdraw(
        address token,
        uint256 liquidity,
        uint256 minimumAmount,
        address to,
        uint256 deadline
    ) external override nonReentrant whenNotPaused returns (uint256 amount) {
        _checkLiquidity(liquidity);
        _checkAddress(to);
        _ensure(deadline);

        IAsset asset = _assetOf(token);
        // request lp token from user
        IERC20(asset).safeTransferFrom(address(msg.sender), address(asset), liquidity);
        uint8 decimals = asset.underlyingTokenDecimals();
        amount = _withdraw(asset, liquidity, minimumAmount.toWad(decimals)).fromWad(decimals);
        asset.transferUnderlyingToken(to, amount);

        emit Withdraw(msg.sender, token, amount, liquidity, to);
    }

    /**
     * @notice Enables withdrawing liquidity from an asset using LP from a different asset
     * @param fromToken The corresponding token user holds the LP (Asset) from
     * @param toToken The token wanting to be withdrawn (needs to be well covered)
     * @param liquidity The liquidity to be withdrawn (in fromToken decimal)
     * @param minimumAmount The minimum amount that will be accepted by user
     * @param to The user receiving the withdrawal
     * @param deadline The deadline to be respected
     * @return toAmount The total amount withdrawn
     */
    function withdrawFromOtherAsset(
        address fromToken,
        address toToken,
        uint256 liquidity,
        uint256 minimumAmount,
        address to,
        uint256 deadline
    ) external override nonReentrant whenNotPaused returns (uint256 toAmount) {
        _checkAddress(to);
        _checkLiquidity(liquidity);
        _checkSameAddress(fromToken, toToken);
        _ensure(deadline);
        requireAssetNotPaused(fromToken);

        // Withdraw and swap
        IAsset fromAsset = _assetOf(fromToken);
        IAsset toAsset = _assetOf(toToken);

        IERC20(fromAsset).safeTransferFrom(address(msg.sender), address(fromAsset), liquidity);
        uint256 fromAmountInWad = _withdraw(fromAsset, liquidity, 0);
        (toAmount, ) = _swap(
            fromAsset,
            toAsset,
            fromAmountInWad,
            minimumAmount.toWad(toAsset.underlyingTokenDecimals())
        );

        toAmount = toAmount.fromWad(toAsset.underlyingTokenDecimals());
        toAsset.transferUnderlyingToken(to, toAmount);

        emit Withdraw(msg.sender, toToken, toAmount, liquidity, to);
    }

    /**
     * @notice Quotes potential withdrawal from pool
     * @dev To be used by frontend
     * @param token The token to be withdrawn by user
     * @param liquidity The liquidity (amount of lp assets) to be withdrawn
     * @return amount The potential amount user would receive
     * @return fee The fee that would be applied
     */
    function quotePotentialWithdraw(
        address token,
        uint256 liquidity
    ) external view override returns (uint256 amount, uint256 fee) {
        _checkLiquidity(liquidity);
        IAsset asset = _assetOf(token);
        (amount, , fee) = _withdrawFrom(asset, liquidity);
        amount = amount.fromWad(asset.underlyingTokenDecimals());
    }

    function _quotePotentialWithdrawFromOtherAsset(
        address fromToken,
        address toToken,
        uint256 liquidity
    ) internal view returns (uint256 amount, uint256 withdrewAmount) {
        IAsset fromAsset = _assetOf(fromToken);
        IAsset toAsset = _assetOf(toToken);

        // quote withdraw
        (withdrewAmount, , ) = _withdrawFrom(fromAsset, liquidity);

        // quote swap
        uint256 fromCash = fromAsset.cash() - withdrewAmount;
        uint256 fromLiability = fromAsset.liability() - liquidity;

        uint256 scaleFactor = _quoteFactor(fromAsset, toAsset);
        if (scaleFactor != WAD) {
            // apply scale factor on from-amounts
            fromCash = (fromCash * scaleFactor) / 1e18;
            fromLiability = (fromLiability * scaleFactor) / 1e18;
            withdrewAmount = (withdrewAmount * scaleFactor) / 1e18;
        }

        uint256 idealToAmount = _swapQuoteFunc(
            int256(fromCash),
            int256(uint256(toAsset.cash())),
            int256(fromLiability),
            int256(uint256(toAsset.liability())),
            int256(withdrewAmount),
            int256(ampFactor)
        );
        // remove haircut
        amount = idealToAmount - idealToAmount.wmul(haircutRate);
    }

    /**
     * @notice Quotes potential withdrawal from other asset from the pool
     * @dev To be used by frontend
     * @param fromToken The corresponding token user holds the LP (Asset) from
     * @param toToken The token wanting to be withdrawn (needs to be well covered)
     * @param liquidity The liquidity (amount of the lp assets) to be withdrawn
     * @return amount The potential amount user would receive
     * @return withdrewAmount The amount of the from-token that is withdrew
     */
    function quotePotentialWithdrawFromOtherAsset(
        address fromToken,
        address toToken,
        uint256 liquidity
    ) external view virtual returns (uint256 amount, uint256 withdrewAmount) {
        _checkLiquidity(liquidity);
        _checkSameAddress(fromToken, toToken);

        (amount, withdrewAmount) = _quotePotentialWithdrawFromOtherAsset(fromToken, toToken, liquidity);

        IAsset fromAsset = _assetOf(fromToken);
        IAsset toAsset = _assetOf(toToken);
        withdrewAmount = withdrewAmount.fromWad(fromAsset.underlyingTokenDecimals());
        amount = amount.fromWad(toAsset.underlyingTokenDecimals());
    }

    /* Swap */

    /**
     * @notice Return the scale factor that should applied on from-amounts in a swap given
     * the from-asset and the to-asset.
     * @dev not applicable to a plain pool
     */
    function _quoteFactor(
        IAsset, // fromAsset
        IAsset // toAsset
    ) internal view virtual returns (uint256) {
        // virtual function; do nothing
        return 1e18;
    }

    /**
     * @notice Quotes the actual amount user would receive in a swap, taking in account slippage and haircut
     * @param fromAsset The initial asset
     * @param toAsset The asset wanted by user
     * @param fromAmount The amount to quote
     * @return actualToAmount The actual amount user would receive
     * @return haircut The haircut that will be applied
     */
    function _quoteFrom(
        IAsset fromAsset,
        IAsset toAsset,
        int256 fromAmount
    ) internal view virtual returns (uint256 actualToAmount, uint256 haircut) {
        // exact output swap quote should count haircut before swap
        if (fromAmount < 0) {
            fromAmount = fromAmount.wdiv(WAD_I - int256(haircutRate));
        }

        uint256 fromCash = uint256(fromAsset.cash());
        uint256 fromLiability = uint256(fromAsset.liability());
        uint256 toCash = uint256(toAsset.cash());

        uint256 scaleFactor = _quoteFactor(fromAsset, toAsset);
        if (scaleFactor != WAD) {
            // apply scale factor on from-amounts
            fromCash = (fromCash * scaleFactor) / 1e18;
            fromLiability = (fromLiability * scaleFactor) / 1e18;
            fromAmount = (fromAmount * int256(scaleFactor)) / 1e18;
        }

        uint256 idealToAmount = _swapQuoteFunc(
            int256(fromCash),
            int256(toCash),
            int256(fromLiability),
            int256(uint256(toAsset.liability())),
            fromAmount,
            int256(ampFactor)
        );
        if ((fromAmount > 0 && toCash < idealToAmount) || (fromAmount < 0 && fromAsset.cash() < uint256(-fromAmount))) {
            revert WOMBAT_CASH_NOT_ENOUGH();
        }

        if (fromAmount > 0) {
            // normal quote
            haircut = idealToAmount.wmul(haircutRate);
            actualToAmount = idealToAmount - haircut;
        } else {
            // exact output swap quote count haircut in the fromAmount
            actualToAmount = idealToAmount;
            haircut = (uint256(-fromAmount)).wmul(haircutRate);
        }
    }

    /**
     * expect fromAmount and minimumToAmount to be in WAD
     */
    function _swap(
        IAsset fromAsset,
        IAsset toAsset,
        uint256 fromAmount,
        uint256 minimumToAmount
    ) internal returns (uint256 actualToAmount, uint256 haircut) {
        (actualToAmount, haircut) = _quoteFrom(fromAsset, toAsset, int256(fromAmount));
        _checkAmount(minimumToAmount, actualToAmount);

        unchecked {
            _feeCollected[toAsset] += haircut;
        }

        fromAsset.addCash(fromAmount);

        // haircut is removed from cash to maintain r* = 1. It is distributed during _mintFee()
        unchecked {
            toAsset.removeCash(actualToAmount + haircut);
        }

        // revert if cov ratio < 1% to avoid precision error
        if (uint256(toAsset.cash()).wdiv(toAsset.liability()) < WAD / 100) revert WOMBAT_FORBIDDEN();
    }

    /**
     * @notice Swap fromToken for toToken, ensures deadline and minimumToAmount and sends quoted amount to `to` address
     * @dev This function assumes tax free token.
     * @param fromToken The token being inserted into Pool by user for swap
     * @param toToken The token wanted by user, leaving the Pool
     * @param fromAmount The amount of from token inserted
     * @param minimumToAmount The minimum amount that will be accepted by user as result
     * @param to The user receiving the result of swap
     * @param deadline The deadline to be respected
     */
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minimumToAmount,
        address to,
        uint256 deadline
    ) external override nonReentrant whenNotPaused returns (uint256 actualToAmount, uint256 haircut) {
        _checkSameAddress(fromToken, toToken);
        if (fromAmount == 0) revert WOMBAT_ZERO_AMOUNT();
        _checkAddress(to);
        _ensure(deadline);
        requireAssetNotPaused(fromToken);

        IAsset fromAsset = _assetOf(fromToken);
        IAsset toAsset = _assetOf(toToken);

        uint8 toDecimal = toAsset.underlyingTokenDecimals();

        (actualToAmount, haircut) = _swap(
            fromAsset,
            toAsset,
            fromAmount.toWad(fromAsset.underlyingTokenDecimals()),
            minimumToAmount.toWad(toDecimal)
        );

        actualToAmount = actualToAmount.fromWad(toDecimal);
        haircut = haircut.fromWad(toDecimal);

        IERC20(fromToken).safeTransferFrom(msg.sender, address(fromAsset), fromAmount);
        toAsset.transferUnderlyingToken(to, actualToAmount);

        emit Swap(msg.sender, fromToken, toToken, fromAmount, actualToAmount, to);
    }

    /**
     * @notice Given an input asset amount and token addresses, calculates the
     * maximum output token amount (accounting for fees and slippage).
     * @dev In reverse quote, the haircut is in the `fromAsset`
     * @param fromToken The initial ERC20 token
     * @param toToken The token wanted by user
     * @param fromAmount The given input amount
     * @return potentialOutcome The potential amount user would receive
     * @return haircut The haircut that would be applied
     */
    function quotePotentialSwap(
        address fromToken,
        address toToken,
        int256 fromAmount
    ) public view override returns (uint256 potentialOutcome, uint256 haircut) {
        _checkSameAddress(fromToken, toToken);
        if (fromAmount == 0) revert WOMBAT_ZERO_AMOUNT();

        IAsset fromAsset = _assetOf(fromToken);
        IAsset toAsset = _assetOf(toToken);

        fromAmount = fromAmount.toWad(fromAsset.underlyingTokenDecimals());
        (potentialOutcome, haircut) = _quoteFrom(fromAsset, toAsset, fromAmount);
        potentialOutcome = potentialOutcome.fromWad(toAsset.underlyingTokenDecimals());
        if (fromAmount >= 0) {
            haircut = haircut.fromWad(toAsset.underlyingTokenDecimals());
        } else {
            haircut = haircut.fromWad(fromAsset.underlyingTokenDecimals());
        }
    }

    /**
     * @notice Returns the minimum input asset amount required to buy the given output asset amount
     * (accounting for fees and slippage)
     * @dev To be used by frontend
     * @param fromToken The initial ERC20 token
     * @param toToken The token wanted by user
     * @param toAmount The given output amount
     * @return amountIn The input amount required
     * @return haircut The haircut that would be applied
     */
    function quoteAmountIn(
        address fromToken,
        address toToken,
        int256 toAmount
    ) external view override returns (uint256 amountIn, uint256 haircut) {
        return quotePotentialSwap(toToken, fromToken, -toAmount);
    }

    /* Queries */

    /**
     * @notice Returns the exchange rate of the LP token
     * @param token The address of the token
     * @return xr The exchange rate of LP token
     */
    function exchangeRate(address token) external view returns (uint256 xr) {
        IAsset asset = _assetOf(token);
        if (asset.totalSupply() == 0) return WAD;
        return xr = uint256(asset.liability()).wdiv(uint256(asset.totalSupply()));
    }

    function globalEquilCovRatio() external view returns (uint256 equilCovRatio, uint256 invariantInUint) {
        int256 invariant;
        int256 SL;
        (invariant, SL) = _globalInvariantFunc();
        equilCovRatio = uint256(_equilCovRatio(invariant, SL, int256(ampFactor)));
        invariantInUint = uint256(invariant);
    }

    function tipBucketBalance(address token) public view returns (uint256 balance) {
        IAsset asset = _assetOf(token);
        unchecked {
            return
                asset.underlyingTokenBalance().toWad(asset.underlyingTokenDecimals()) -
                asset.cash() -
                _feeCollected[asset];
        }
    }

    /* Utils */

    function _globalInvariantFunc() internal view virtual returns (int256 D, int256 SL) {
        int256 A = int256(ampFactor);

        for (uint256 i; i < _sizeOfAssetList(); ++i) {
            IAsset asset = _getAsset(_getKeyAtIndex(i));

            // overflow is unrealistic
            int256 A_i = int256(uint256(asset.cash()));
            int256 L_i = int256(uint256(asset.liability()));

            // Assume when L_i == 0, A_i always == 0
            if (L_i == 0) {
                // avoid division of 0
                continue;
            }

            int256 r_i = A_i.wdiv(L_i);
            SL += L_i;
            D += L_i.wmul(r_i - A.wdiv(r_i));
        }
    }

    function _mintFeeIfNeeded(IAsset asset) internal {
        uint256 feeCollected = _feeCollected[asset];
        if (feeCollected == 0 || feeCollected < mintFeeThreshold) {
            return;
        } else {
            _mintFee(asset);
        }
    }

    /**
     * @notice Private function to send fee collected to the fee beneficiary
     * @param asset The address of the asset to collect fee
     */
    function _mintFee(IAsset asset) internal returns (uint256 feeCollected) {
        feeCollected = _feeCollected[asset];
        if (feeCollected == 0) {
            // early return
            return 0;
        }
        {
            // dividend to veWOM
            uint256 dividend = feeCollected.wmul(WAD - lpDividendRatio - retentionRatio);

            if (dividend > 0) {
                asset.transferUnderlyingToken(feeTo, dividend.fromWad(asset.underlyingTokenDecimals()));
            }
        }
        {
            // dividend to LP
            uint256 lpDividend = feeCollected.wmul(lpDividendRatio);
            if (lpDividend > 0) {
                // exact deposit to maintain r* = 1
                // increase the value of the LP token, i.e. assetsPerShare
                (, uint256 liabilityToMint, ) = _exactDepositToInEquil(asset, lpDividend);
                asset.addLiability(liabilityToMint);
                asset.addCash(lpDividend);
            }
        }
        // remainings are sent to the tipbucket

        _feeCollected[asset] = 0;
    }

    function _mintAllFees() internal {
        for (uint256 i; i < _sizeOfAssetList(); ++i) {
            IAsset asset = _getAsset(_getKeyAtIndex(i));
            _mintFee(asset);
        }
    }

    /**
     * @notice Send fee collected to the fee beneficiary
     * @param token The address of the token to collect fee
     */
    function mintFee(address token) external returns (uint256 feeCollected) {
        return _mintFee(_assetOf(token));
    }
}

File 23 of 23 : IMasterWombat.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;

/**
 * @dev Interface of the MasterWombat
 */
interface IMasterWombat {
    function getAssetPid(address asset) external view returns (uint256 pid);

    function poolLength() external view returns (uint256);

    function pendingTokens(
        uint256 _pid,
        address _user
    )
        external
        view
        returns (
            uint256 pendingRewards,
            address bonusTokenAddress,
            string memory bonusTokenSymbol,
            uint256 pendingBonusToken
        );

    function rewarderBonusTokenInfo(
        uint256 _pid
    ) external view returns (address bonusTokenAddress, string memory bonusTokenSymbol);

    function massUpdatePools() external;

    function updatePool(uint256 _pid) external;

    function deposit(uint256 _pid, uint256 _amount) external returns (uint256, uint256);

    function multiClaim(
        uint256[] memory _pids
    ) external returns (uint256 transfered, uint256[] memory rewards, uint256[] memory additionalRewards);

    function withdraw(uint256 _pid, uint256 _amount) external returns (uint256, uint256);

    function emergencyWithdraw(uint256 _pid) external;

    function migrate(uint256[] calldata _pids) external;

    function depositFor(uint256 _pid, uint256 _amount, address _user) external;

    function updateFactor(address _user, uint256 _newVeWomBalance) external;
}

Settings
{
  "viaIR": true,
  "optimizer": {
    "enabled": true,
    "runs": 1000
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "metadata": {
    "useLiteralContent": true
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[],"name":"CORE_UNDERFLOW","type":"error"},{"inputs":[],"name":"WOMBAT_AMOUNT_TOO_LOW","type":"error"},{"inputs":[],"name":"WOMBAT_ASSET_ALREADY_EXIST","type":"error"},{"inputs":[],"name":"WOMBAT_ASSET_ALREADY_PAUSED","type":"error"},{"inputs":[],"name":"WOMBAT_ASSET_NOT_EXISTS","type":"error"},{"inputs":[],"name":"WOMBAT_ASSET_NOT_PAUSED","type":"error"},{"inputs":[],"name":"WOMBAT_CASH_NOT_ENOUGH","type":"error"},{"inputs":[],"name":"WOMBAT_COV_RATIO_LIMIT_EXCEEDED","type":"error"},{"inputs":[],"name":"WOMBAT_EXPIRED","type":"error"},{"inputs":[],"name":"WOMBAT_FORBIDDEN","type":"error"},{"inputs":[],"name":"WOMBAT_INVALID_VALUE","type":"error"},{"inputs":[],"name":"WOMBAT_SAME_ADDRESS","type":"error"},{"inputs":[],"name":"WOMBAT_ZERO_ADDRESS","type":"error"},{"inputs":[],"name":"WOMBAT_ZERO_AMOUNT","type":"error"},{"inputs":[],"name":"WOMBAT_ZERO_LIQUIDITY","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"asset","type":"address"}],"name":"AssetAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"asset","type":"address"}],"name":"AssetRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidity","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FillPool","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"PausedAsset","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetAmpFactor","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"}],"name":"SetDev","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"lpDividendRatio","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"retentionRatio","type":"uint256"}],"name":"SetFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"}],"name":"SetFeeTo","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetHaircutRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"}],"name":"SetMasterWombat","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SetMintFeeThreshold","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"fromToken","type":"address"},{"indexed":false,"internalType":"address","name":"toToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"fromAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toAmount","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"TransferTipBucket","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"UnpausedAsset","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidity","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Withdraw","type":"event"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"asset","type":"address"}],"name":"addAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"addressOfAsset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ampFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minimumLiquidity","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"shouldStake","type":"bool"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dev","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endCovRatio","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"exchangeRate","outputs":[{"internalType":"uint256","name":"xr","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"fillPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getTokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalEquilCovRatio","outputs":[{"internalType":"uint256","name":"equilCovRatio","type":"uint256"},{"internalType":"uint256","name":"invariantInUint","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"haircutRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"ampFactor_","type":"uint256"},{"internalType":"uint256","name":"haircutRate_","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lpDividendRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"masterWombat","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"mintFee","outputs":[{"internalType":"uint256","name":"feeCollected","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintFeeThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"pauseAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"int256","name":"toAmount","type":"int256"}],"name":"quoteAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"haircut","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"quotePotentialDeposit","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"reward","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"int256","name":"fromAmount","type":"int256"}],"name":"quotePotentialSwap","outputs":[{"internalType":"uint256","name":"potentialOutcome","type":"uint256"},{"internalType":"uint256","name":"haircut","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"name":"quotePotentialWithdraw","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"name":"quotePotentialWithdrawFromOtherAsset","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"withdrewAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"removeAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"retentionRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"ampFactor_","type":"uint256"}],"name":"setAmpFactor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"startCovRatio_","type":"uint128"},{"internalType":"uint128","name":"endCovRatio_","type":"uint128"}],"name":"setCovRatioFeeParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"dev_","type":"address"}],"name":"setDev","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"lpDividendRatio_","type":"uint256"},{"internalType":"uint256","name":"retentionRatio_","type":"uint256"}],"name":"setFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"feeTo_","type":"address"}],"name":"setFeeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"haircutRate_","type":"uint256"}],"name":"setHaircutRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"masterWombat_","type":"address"}],"name":"setMasterWombat","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"mintFeeThreshold_","type":"uint256"}],"name":"setMintFeeThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startCovRatio","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"fromAmount","type":"uint256"},{"internalType":"uint256","name":"minimumToAmount","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swap","outputs":[{"internalType":"uint256","name":"actualToAmount","type":"uint256"},{"internalType":"uint256","name":"haircut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"tipBucketBalance","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"transferTipBucket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"unpauseAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"minimumAmount","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"minimumAmount","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"withdrawFromOtherAsset","outputs":[{"internalType":"uint256","name":"toAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]

6080806040523461001657614e92908161001c8239f35b600080fdfe608080604052600436101561001357600080fd5b600090813560e01c908163017b8292146129eb57508063017e7e58146129c45780630705999d14612951578063070f81d91461275e57806309a5fca3146123525780630f91f06f14611f4f5780633753b14a14611e9e5780633bd61ba814611e775780633f4ba83a14611dda5780634a5e42b114611c785780634a6fee0e14611be45780634fdd64e514611bd057806352f7c98814611b145780635b14f18314611ad75780635c975abb14611ab45780635cfe092e14611a9657806361858e23146119fc5780636281baef146116985780636922d5ca1461167a578063715018a61461161e57806374cbbdb9146115fc578063815bfd29146115de5780638456cb59146115835780638a2dfe091461132e5780638da5cb5b14611307578063907448ed1461127a57806391cca3db1461125357806393aeea02146111005780639908fc8b14610eaf5780639abacb4114610de95780639e4416b714610d8b578063a4275ceb1461096b578063aa6ca808146108a5578063d043c56514610857578063d0dd0e5614610839578063d30ffeda14610815578063d477f05f146107a2578063da4899971461065e578063dc3b7c8b1461063a578063e4a3011614610487578063e9249cc714610457578063f2fde38b146103b0578063f46901ed1461033d578063f57e84d5146102b5578063f8b49e7214610281578063fb7f5cc2146102595763fca8f3081461022657600080fd5b3461025657604061024a61024461023c36612a92565b929092613833565b916147f9565b82519182526020820152f35b80fd5b503461025657806003193601126102565760206001600160801b036101085416604051908152f35b50346102565760203660031901126102565760206102ad6102a86102a3612a07565b613064565b614c89565b604051908152f35b5034610256576020366003190112610256576004356102d2612add565b670de0b6b3a76400008111610313576020817fc6e60196bbe5909add58d5ae8a27082013670bce0855321773c33085af5c32439260cb55604051908152a180f35b60046040517f12b019e1000000000000000000000000000000000000000000000000000000008152fd5b5034610256576020366003190112610256577ff6b59ffc88fbb27f33470b919e00b41139ee340eb349521f0cbbc1504ce29c3e60206001600160a01b03610382612a07565b61038a612add565b61039381612f03565b16806001600160a01b031960d054161760d055604051908152a180f35b5034610256576020366003190112610256576103ca612a07565b6103d2612add565b6001600160a01b038116156103ed576103ea90612b35565b80f35b608460405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b50346102565760203660031901126102565760206001600160a01b0361047e6102a3612a07565b16604051908152f35b50346102565761049636612ac7565b9082549160ff8360081c16159182809361062d575b8015610616575b156105ac5760ff198481166001178655938361059b575b506104e360ff865460081c166104de81612e45565b612e45565b6104ec33612b35565b84549361050c60ff8660081c1661050281612e45565b6001606555612e45565b6097541660975560ca5560cb55670de0b6b3a764000060cd55336001600160a01b031960cf54161760cf55610563575b506101087718fae27693b40000000000000000000014d1120d7b1600008282541617905580f35b61ff00191681557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160018152a13861053c565b61ffff1916610101178555386104c9565b608460405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152fd5b50303b1580156104b25750600160ff8516146104b2565b50600160ff8516106104ab565b50346102565760203660031901126102565760206102ad610659612a07565b6149ba565b503461025657604036600319011261025657610678612a07565b610680612a33565b610688612add565b61069181612f03565b61069a82612f03565b6106bd826001600160a01b0380911660005260d460205260406000205416151590565b610778576001600160a01b03809116911680835260d460205260408320826001600160a01b031982541617905560d35460d5602052806040852055680100000000000000008110156107645761073b61071d82600185940160d355612eb6565b819391549060031b916001600160a01b03809116831b921b19161790565b90557f0bb5715f0f217c2fe9a0c877ea87d474380c641102f3440ee2a4c8b9d97909188380a380f35b602484634e487b7160e01b81526041600452fd5b60046040517f8991c4c4000000000000000000000000000000000000000000000000000000008152fd5b5034610256576020366003190112610256577f8daa6bb00de042a7d51816fcee4a3d5c1e91a2128fd64fa1a9a38afa0d26beb560206001600160a01b036107e7612a07565b6107ef612add565b6107f881612f03565b16806001600160a01b031960cf54161760cf55604051908152a180f35b50346102565760203660031901126102565760206102ad610834612a07565b614b0e565b5034610256578060031936011261025657602060cb54604051908152f35b5034610256576020366003190112610256577f935ce123b6388f8dbf76890f8240a48198f1e0a884f26939aa604a46bb65b7066020600435610897612add565b8060ce55604051908152a180f35b5034610256578060031936011261025657604051809160d354908183526020809301809260d383527f915c3eb987b20e1af620c1403197bf687fb7f18513b3a73fde6e78c7072c41a690835b81811061094e5750505084610907910385612ba7565b60405193838594850191818652518092526040850193925b82811061092e57505050500390f35b83516001600160a01b03168552869550938101939281019260010161091f565b82546001600160a01b0316845292860192600192830192016108f1565b503461025657606036600319011261025657610985612a07565b61098d612a33565b610998604435612dd6565b6109a28183612e07565b6109ab82613064565b91836109b683613064565b6109c260443586613a95565b5050948560405163961be39160e01b81526020816004816001600160a01b0387165afa8015610d25576001600160781b038391610a07938891610ce5575b5016612be8565b9060405163705727b560e01b81526020816004816001600160a01b0388165afa8015610c7657610a508691610a56938991610cab575b506001600160781b036044359116612be8565b94613ec7565b670de0b6b3a764000090818103610d5b575b5050506040519263961be39160e01b84526020846004816001600160a01b0385165afa938415610d25578594610d30575b5060206001600160a01b039160046040518094819363705727b560e01b8352165afa8015610d255788610b1095610b0a95610b0a95610af8956001600160a01b039a95610d04575b506001600160781b038060ca549616931690613d3b565b610b0460cb5482612c64565b90612be8565b93613064565b92169260405163961be39160e01b8152602081600481885afa8015610c76576001600160781b038391610b4a938991610ce5575016612be8565b60405163705727b560e01b8152602081600481895afa8015610cda57610b83918891610cab57506001600160781b036044359116612be8565b610b9681610b918585612c0b565b612c8e565b916001600160801b0361010854168311610c81575b505050604051926020846004816339420b4560e11b988982525afa918215610c7657610bec6020936004926001600160a01b03978a92610c5e575b50612cf7565b9560405195869384928352165afa918215610c535790610c15929160409592610c225750612cf7565b9082519182526020820152f35b610c4591925060203d602011610c4c575b610c3d8183612ba7565b810190612c18565b9038610be6565b503d610c33565b6040513d86823e3d90fd5b610c45919250863d8811610c4c57610c3d8183612ba7565b6040513d88823e3d90fd5b93610c9d8193610c98610b0494610ca29798612c8e565b612d42565b612c64565b90388080610bab565b610ccd915060203d602011610cd3575b610cc58183612ba7565b810190612bc9565b38610a3d565b503d610cbb565b6040513d89823e3d90fd5b610cfe915060203d602011610cd357610cc58183612ba7565b38610a00565b610d1e91955060203d602011610cd357610cc58183612ba7565b9338610ae1565b6040513d87823e3d90fd5b6001600160a01b03919450610d53602091823d8411610cd357610cc58183612ba7565b949150610a99565b90610d8192949950610d7a8282610d73828298612c31565b049b612c31565b0493612c31565b0495388080610a68565b503461025657602036600319011261025657600435610da8612add565b670de0b6b3a76400008111610313576020817f294c562dcfcd9be941287844539aeba4ae641cd1fc9a2883337c6b2e87cbd98a9260ca55604051908152a180f35b503461025657602036600319011261025657610e03612a07565b610e0b612f8d565b6001600160a01b03811680835260c960205260ff60408420541615610e8557825260c960209081526040808420805460ff1916905580516001600160a01b03909316835233918301919091527fdb198487a4c70aa000c3b1020f52688b4b86d3ebcedc84a793e03ed3b480902c9190819081015b0390a180f35b60046040517f855b7373000000000000000000000000000000000000000000000000000000008152fd5b503461025657610ebe36612a49565b610ecc9593919294956130c1565b610ed4612f3d565b610ede8685612e07565b81156110d657610ef690610ef184612f03565b613394565b610eff83612fa1565b610f0883613064565b91610f1286613064565b91604051956339420b4560e11b8088526020886004816001600160a01b0389165afa978815611085576000986110b5575b506040519081526020816004816001600160a01b038a165afa908115611085576001600160a01b03610fae8a610fa8610fa18b610fb9988c610f9b86610f958e9c8b9c600091611096575b508d61302a565b9361302a565b92613f9a565b9d90612cf7565b9b612cf7565b981696163387613116565b6001600160a01b0383163b156110915760006040518094639e79eaa560e01b825281836001600160a01b03826110098d8a60048401602090939291936001600160a01b0360408201951681520152565b0393165af18015611085576040976001600160a01b03948592611076575b5088519586521660208501528684015284606084015216907f54787c404bb33c88e86f4baf88183a3b0141d0a848e6a9f7a13b66ae3a9b73d160803392a3600160655582519182526020820152f35b61107f90612b7d565b89611027565b6040513d6000823e3d90fd5b600080fd5b6110af915060203d602011610c4c57610c3d8183612ba7565b38610f8e565b6110cf91985060203d602011610c4c57610c3d8183612ba7565b9689610f43565b60046040517fb483c10f000000000000000000000000000000000000000000000000000000008152fd5b50346102565760603660031901126102565761111a612a07565b602435906044356001600160a01b03808216918281036110915761113c612add565b61114584613064565b9061114f85614b0e565b86116103135782879216906040516339420b4560e11b8152602081600481865afa8015610c5357611188918591611235575b5088612cf7565b823b1561123157604051639e79eaa560e01b81526001600160a01b0392909216600483015260248201529082908290604490829084905af1801561122657611208575b50927f5f22a22ebe30686f71d42a70ccacf870a335e22ee2ac6d257af1b2551582eda692856060936040519316835260208301526040820152a180f35b6112159094939294612b7d565b61122257909184386111cb565b8480fd5b6040513d84823e3d90fd5b8380fd5b61124d915060203d8111610c4c57610c3d8183612ba7565b38611181565b503461025657806003193601126102565760206001600160a01b0360cf5416604051908152f35b50346102565760403660031901126102565760206001600160a01b0361129e612a07565b6112b86112b1602435926102a384612dd6565b9182613a95565b94919290506004604051809581936339420b4560e11b8352165afa918215610c535790610c159291604095926112ee5750612cf7565b610c4591925060203d8111610c4c57610c3d8183612ba7565b503461025657806003193601126102565760206001600160a01b0360335416604051908152f35b5034610256578060031936011261025657808160ca54908060d354905b81831061138557604085610c158661138061137b8b61137661136c876137d8565b6002830590613780565b613818565b613833565b613e89565b9091936113c561139486612eb6565b91906001600160a01b03928391549060031b1c166001600160a01b0380911660005260d46020526040600020541690565b169560405163961be39160e01b815260209081816004818c5afa908115610d25578591611566575b506040519063705727b560e01b825282826004818d5afa918215610c76578692611547575b506004836001600160781b038094169b604051928380926317d1d06960e11b82525afa938415610cda578794611517575b5050891561150857899161145791166137d8565b918160029384820561146891613780565b9061147291613818565b918291670de0b6b3a7640000958661148b8194846137f5565b6114949061375c565b0561149e91613780565b9b6114a8916137f5565b6114b19061375c565b05926114bc896137d8565b9082056114c891613780565b906114d291613818565b6114db916137bf565b6114e4916137f5565b6114ed9061375c565b056114f791613780565b9361150190614aff565b919061134b565b50505095509361150190614aff565b9080929450813d8311611540575b61152f8183612ba7565b810103126110915751913880611443565b503d611525565b61155f919250833d8511610cd357610cc58183612ba7565b9038611412565b61157d9150823d8411610cd357610cc58183612ba7565b386113ed565b503461025657806003193601126102565761159c612f8d565b6115a4612f3d565b600160ff1960975416176097557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b5034610256578060031936011261025657602060cd54604051908152f35b503461025657806003193601126102565760206101085460801c604051908152f35b5034610256578060031936011261025657611637612add565b60006001600160a01b036033546001600160a01b03198116603355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b5034610256578060031936011261025657602060cc54604051908152f35b50346102565760c0366003190112610256576116b2612a07565b602435604435916116c1612a1d565b9260a435908115918215036119f8576116d86130c1565b6116e0612f3d565b83156110d6576116ef85612f03565b6116fa608435613394565b61170383612fa1565b61170c83613064565b6001600160a01b039283821690611727878333888a16613116565b156117f6576020600491604051928380926339420b4560e11b82525afa9081156117eb579261178e9261178889937ff5dd9317b9e63ac316ce44acc85f670b54b339cfa3e9076e1dd55065b922314b979660209c916117ce575b508961302a565b906133c6565b945b6117be8660405193849316963396846040919493926001600160a01b03606083019616825260208201520152565b0390a36001606555604051908152f35b6117e591508c3d8111610c4c57610c3d8183612ba7565b38611781565b6040513d8a823e3d90fd5b906118098460d199949895995416612f03565b604051966339420b4560e11b88526020978881600481875afa908115610d25576118479392916118409187916119e157508961302a565b30926133c6565b60d1546040517f095ea7b30000000000000000000000000000000000000000000000000000000081529089166001600160a01b0316600482015260248101829052909690818160448187875af18015610c53576119b4575b508760d1541691604051907faf929a8000000000000000000000000000000000000000000000000000000000825260048201528181602481865afa918215610c53578492611986575b5050813b156119825782916064839260405194859384927f90210d7e00000000000000000000000000000000000000000000000000000000845260048401528b60248401528c891660448401525af180156112265761196e575b50506020947ff5dd9317b9e63ac316ce44acc85f670b54b339cfa3e9076e1dd55065b922314b91611790565b6119788291612b7d565b6102565780611942565b8280fd5b90809250813d83116119ad575b61199d8183612ba7565b81010312611091575138806118e8565b503d611993565b6119d390823d84116119da575b6119cb8183612ba7565b8101906130a9565b503861189f565b503d6119c1565b6117e591508b3d8d11610c4c57610c3d8183612ba7565b8580fd5b503461025657604036600319011261025657611a196102a3612a07565b6040516339420b4560e11b81526020816004816001600160a01b0386165afa908115611a8b57611a609291611a5a9160409591611a6d575b5060243561302a565b9061354e565b8351928352602083015250f35b611a85915060203d8111610c4c57610c3d8183612ba7565b38611a51565b6040513d85823e3d90fd5b5034610256578060031936011261025657602060ce54604051908152f35b5034610256578060031936011261025657602060ff609754166040519015158152f35b50346102565760203660031901126102565760ff60406020926001600160a01b03611b00612a07565b16815260c984522054166040519015158152f35b503461025657611b2336612ac7565b90611b2c612add565b670de0b6b3a76400008183011161031357825b60d354811015611b945780611b896102a86001600160a01b03611b64611b8f95612eb6565b90549060031b1c166001600160a01b0380911660005260d46020526040600020541690565b50614aff565b611b3f565b506040907f032dc6a2d839eb179729a55633fdf1c41a1fc4739394154117005db2b354b9b5928060cc558160cd5582519182526020820152a180f35b503461025657604061024a61024436612a92565b5034610256576040366003190112610256576004356001600160801b038082168092036110915760243590811680820361109157611c20612add565b670de0b6b3a76400008310908115611c6e575b506103135761010891837fffffffffffffffffffffffffffffffff0000000000000000000000000000000084549360801b1692161717905580f35b9050821138611c33565b503461025657602036600319011261025657611c92612a07565b611c9a612add565b611cbd816001600160a01b0380911660005260d460205260406000205416151590565b15611db0576001600160a01b038091169081835260d4602052604083208054906001600160a01b03198216905560d5602052604084205460d3549060001991828101908111611d9c5761071d85611d16611d3b93612eb6565b90549060031b1c169283895260d56020528060408a2055878952886040812055612eb6565b905560d3548015611d885701611d5081612eb6565b8482549160031b1b1916905560d35516907f0fa1e4606af435f32f05b3804033d2933e691fab32ee74d2db6fa82d2741f1ea8380a380f35b602486634e487b7160e01b81526031600452fd5b602487634e487b7160e01b81526011600452fd5b60046040517fecb004d4000000000000000000000000000000000000000000000000000000008152fd5b5034610256578060031936011261025657611df3612f8d565b60975460ff811615611e335760ff19166097557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b606460405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f74207061757365640000000000000000000000006044820152fd5b503461025657806003193601126102565760206001600160a01b0360d15416604051908152f35b503461025657602036600319011261025657611eb8612a07565b611ec0612f8d565b611ee3816001600160a01b0380911660005260d460205260406000205416151590565b15611db057610e7f81611f167fdcb65c0553aaa433aadd180404ff195259c48f78aa50f877ebcb4bb215129a4e93612fa1565b6001600160a01b031680845260c960209081526040808620805460ff191660011790558051928352339183019190915290918291820190565b503461025657611fa0611f97611f6436612a49565b611f73979395929691976130c1565b611f7b612f3d565b611f8488612f03565b611f8d86612dd6565b610ef18584612e07565b6102a381612fa1565b92611faa82613064565b906001600160a01b03851694611fc285873381613116565b600095611fce82614c46565b611fd88683613a95565b509097823b1561025657604051632770a7eb60e21b81526001600160a01b038416600482015260248101899052818160448183885af1801561122657612343575b5090823b1561232b576040516313f3df3160e31b8152896004820152828160248183885af18015611a8b5790839161232f575b5050823b1561232b576040519063d8b8785360e01b82526004820152818160248183875af1801561122657908291612317575b505060405163705727b560e01b91828252602082600481875afa91821561230a5781926122e9575b506001600160781b0380921615159384612230575b5050505061221f576001600160a01b03831695604051936339420b4560e11b948581526020816004818c5afa80156110855761210495610f9b926000926121fe575b5061302a565b50604051918252602082600481885afa9081156110855761212c926000926121dd5750612cf7565b92803b1561109157604051639e79eaa560e01b81526001600160a01b038616600482015260248101859052906000908290604490829084905af18015611085576020957ffb80d861da582b723be2d19507ce3e03851820c464abea89156ec77e089b1ad9926001600160a01b03926121ce575b506117be6040519283921695873396846040919493926001600160a01b03606083019616825260208201520152565b6121d790612b7d565b8761219f565b6121f791925060203d602011610c4c57610c3d8183612ba7565b9087610be6565b61221891925060203d602011610c4c57610c3d8183612ba7565b908c6120fe565b60046040516316fde3bd60e21b8152fd5b90919293506040519363961be39160e01b8552602085600481855afa948515611a8b5783956122c7575b5060209060046040518094819382525afa9182156122bb5792662386f26fc1000094928192612292959161229c575b50169116612c8e565b10888080806120bc565b6122b5915060203d602011610cd357610cc58183612ba7565b8d612289565b604051903d90823e3d90fd5b60209195506122e290823d8411610cd357610cc58183612ba7565b949061225a565b61230391925060203d602011610cd357610cc58183612ba7565b908b6120a7565b50604051903d90823e3d90fd5b61232090612b7d565b61025657808a61207f565b5080fd5b61233890612b7d565b61232b57818b61204c565b61234c90612b7d565b8a612019565b50346102565760a03660031901126102565761236c612a07565b612374612a1d565b61237c6130c1565b612384612f3d565b61238f602435612dd6565b61239881612f03565b6123a3608435613394565b6123ac82613064565b916123c46024356001600160a01b0385163381613116565b6040516339420b4560e11b81526020816004816001600160a01b0388165afa908115610d2557859161273f575b506123fe8160443561302a565b9061240885614c46565b61242161241760243587613a95565b509190809461351d565b6001600160a01b0386163b1561109157604051632770a7eb60e21b81526001600160a01b03871660048201526024803590820152600090818180604481010381836001600160a01b038d165af1801561122657612730575b50906001600160a01b0387163b1561232b576040516313f3df3160e31b81528460048201528281602481836001600160a01b038d165af18015611a8b5790839161271c575b50506001600160a01b0387163b1561232b576040519063d8b8785360e01b825260048201528181602481836001600160a01b038c165af1801561122657908291612708575b505060405163705727b560e01b908181526020816004816001600160a01b038c165afa908115611a8b57906001600160781b039184916126e9575b50161515918261262b575b505061221f5761255891612cf7565b926001600160a01b0381163b1561122257846040518092639e79eaa560e01b825281836001600160a01b03826125a88b8b60048401602090939291936001600160a01b0360408201951681520152565b0393165af18015610d2557612617575b50602093507ffb80d861da582b723be2d19507ce3e03851820c464abea89156ec77e089b1ad96001600160a01b03604051931692806117be33948760243591846040919493926001600160a01b03606083019616825260208201520152565b6126218591612b7d565b61123157836125b8565b60405163961be39160e01b81529250906020836004816001600160a01b038c165afa9283156112265782936126c8575b506040519081526020816004816001600160a01b038c165afa9182156122bb576001600160781b036126a193662386f26fc1000095938293916126a95750169116612c8e565b103880612549565b6126c2915060203d602011610cd357610cc58183612ba7565b38612289565b6126e291935060203d602011610cd357610cc58183612ba7565b913861265b565b612702915060203d602011610cd357610cc58183612ba7565b3861253e565b61271190612b7d565b610256578038612503565b61272590612b7d565b61232b5781386124be565b61273990612b7d565b38612479565b612758915060203d602011610c4c57610c3d8183612ba7565b386123f1565b503461025657604036600319011261025657612778612a07565b90602435612784612f8d565b6001600160a01b0361279584613064565b16604051632672469960e21b81526020908181600481865afa908115610d25578591612924575b50604051906339420b4560e11b82528282600481875afa918215610c7657906127ec92918792612905575061302a565b60405163961be39160e01b81528281600481875afa908115610c765761283b93926001600160781b0360d29361282b938a916128e8575b501690612be8565b9184875252604085205490612be8565b821161031357803b156119825782809160246040518094819362b64f3d60e51b83528760048401525af18015611a8b576128b7575b50604080516001600160a01b039094168452602084019190915290917f4941e18a2bcbb0f9fa0081238f26793a8ad8c202b913ae8bf5f7e523f68ff1379181908101610e7f565b916128e27f4941e18a2bcbb0f9fa0081238f26793a8ad8c202b913ae8bf5f7e523f68ff13793612b7d565b91612870565b6128ff9150863d8811610cd357610cc58183612ba7565b38612823565b61291d919250843d8611610c4c57610c3d8183612ba7565b90386120fe565b90508181813d831161294a575b61293b8183612ba7565b810103126112225751386127bc565b503d612931565b5034610256576020366003190112610256577ffeb9010869b6ccec4557ddbbce947afeace5efc66cdff52c5e533c09336a8f2d60206001600160a01b03612996612a07565b61299e612add565b6129a781612f03565b16806001600160a01b031960d154161760d155604051908152a180f35b503461025657806003193601126102565760206001600160a01b0360d05416604051908152f35b90503461232b578160031936011261232b5760209060ca548152f35b600435906001600160a01b038216820361109157565b606435906001600160a01b038216820361109157565b602435906001600160a01b038216820361109157565b60c0906003190112611091576001600160a01b03600435818116810361109157916024358281168103611091579160443591606435916084359081168103611091579060a43590565b6060906003190112611091576001600160a01b0390600435828116810361109157916024359081168103611091579060443590565b6040906003190112611091576004359060243590565b6001600160a01b03603354163303612af157565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b603354906001600160a01b0380911691826001600160a01b0319821617603355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3565b67ffffffffffffffff8111612b9157604052565b634e487b7160e01b600052604160045260246000fd5b90601f8019910116810190811067ffffffffffffffff821117612b9157604052565b9081602091031261109157516001600160781b03811681036110915790565b91908203918211612bf557565b634e487b7160e01b600052601160045260246000fd5b91908201809211612bf557565b90816020910312611091575160ff811681036110915790565b81810292918115918404141715612bf557565b8115612c4e570490565b634e487b7160e01b600052601260045260246000fd5b90612c6e91612c31565b6706f05b59d3b200008101809111612bf557670de0b6b3a7640000900490565b90670de0b6b3a764000091828102928184041490151715612bf557612cba612cbf928260011c90612c0b565b612c44565b90565b60ff6011199116019060ff8211612bf557565b60ff166012039060ff8211612bf557565b60ff16604d8111612bf557600a0a90565b9060ff81166012811015612d21575090612d1b612d16612cbf93612cd5565b612ce6565b90612c44565b601210612d2c575090565b90612d3c612d16612cbf93612cc2565b90612c31565b610108548060801c91828411600014612d66576004604051630c2b153f60e31b8152fd5b6001600160801b0382168411801590612dcc575b612dc357612cbf936001600160801b0392831691612dac9190838111612db8576000905b848303920391800203612c44565b60011c92031690612c8e565b838103800290612d9e565b50505050600090565b5080841115612d7a565b15612ddd57565b60046040517f3f3835e7000000000000000000000000000000000000000000000000000000008152fd5b6001600160a01b03908116911614612e1b57565b60046040517feeb7911f000000000000000000000000000000000000000000000000000000008152fd5b15612e4c57565b608460405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152fd5b60d354811015612eed5760d36000527f915c3eb987b20e1af620c1403197bf687fb7f18513b3a73fde6e78c7072c41a60190600090565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b031615612f1357565b60046040517f0bd3e3ed000000000000000000000000000000000000000000000000000000008152fd5b60ff60975416612f4957565b606460405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a20706175736564000000000000000000000000000000006044820152fd5b6001600160a01b0360cf5416330361221f57565b6001600160a01b031660005260c960205260ff60406000205416612fc157565b60046040517f8654830a000000000000000000000000000000000000000000000000000000008152fd5b60ff811660128110156130055750612d16612cbf91612cd5565b6012106130125750600190565b612d1661301e91612cc2565b8015612c4e5760010490565b9060ff81166012811015613049575090612d3c612d16612cbf93612cd5565b601210613054575090565b90612d1b612d16612cbf93612cc2565b613087816001600160a01b0380911660005260d460205260406000205416151590565b15611db0576001600160a01b0380911660005260d46020526040600020541690565b90816020910312611091575180151581036110915790565b6002606554146130d2576002606555565b606460405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fd5b93929190604051602093848201927f23b872dd0000000000000000000000000000000000000000000000000000000084526001600160a01b039485809216602485015216604483015260648201526064815260a081019267ffffffffffffffff9682851088861117612b9157169060e0810184811088821117612b91576040528484527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656460c0820152600080938192519082855af1903d156132b4573d9687116132a057613205949596604051906131f788601f19601f8401160183612ba7565b81528093873d92013e6132c1565b805190828215928315613288575b5050501561321e5750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152fd5b61329893508201810191016130a9565b388281613213565b602483634e487b7160e01b81526041600452fd5b9150613205939495506060915b9192901561332257508151156132d5575090565b3b156132de5790565b606460405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152fd5b8251909150156133355750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b82851061337b575050604492506000838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350613358565b421161339c57565b60046040517f549b6335000000000000000000000000000000000000000000000000000000008152fd5b6001600160a01b03919294936133db82614c46565b6133e5848361354e565b509390926133fd8480996133f882612dd6565b61351d565b1690813b1561109157604093845162b64f3d60e51b81526000966004820152868160248183885af1801561351357613500575b50823b156119f85784519063283c3d8160e21b82526004820152858160248183875af180156134f6579086916134e2575b5050813b156112225783517f40c10f190000000000000000000000000000000000000000000000000000000081526001600160a01b03919091166004820152602481019290925290919083908390604490829084905af19081156134d957506134c8575050565b6134d28291612b7d565b6102565750565b513d84823e3d90fd5b6134eb90612b7d565b611222578438613461565b85513d88823e3d90fd5b61350c90969196612b7d565b9438613430565b86513d89823e3d90fd5b1161352457565b60046040517ff512a720000000000000000000000000000000000000000000000000000000008152fd5b6000916001600160a01b036000921660409081519363961be39160e01b85526020938486600481865afa9586156137035782966136e4575b5083519063705727b560e01b968783528683600481885afa9283156136da5784936136b3575b506135cc6135d19160ca54906001600160781b0380809716911685613844565b61370d565b978189106136aa57508703955b84519081528581600481875afa9081156136a0578391613683575b5016938461360a5750505050508192565b806004938551948580926318160ddd60e01b82525afa93841561367857508193613643575b50505090612cba6136409285612c31565b92565b9091809350813d8311613671575b61365b8183612ba7565b81010312610256575051612cba6136403861362f565b503d613651565b51913d9150823e3d90fd5b61369a9150863d8811610cd357610cc58183612ba7565b386135f9565b85513d85823e3d90fd5b969750966135de565b6135d19193506136d26135cc91893d8b11610cd357610cc58183612ba7565b9391506135ac565b86513d86823e3d90fd5b6136fc919650853d8711610cd357610cc58183612ba7565b9438613586565b84513d84823e3d90fd5b600081126137185790565b606460405162461bcd60e51b815260206004820152601660248201527f76616c7565206d75737420626520706f736974697665000000000000000000006044820152fd5b906706f05b59d3b200009160008382019384129112908015821691151617612bf557565b91909160008382019384129112908015821691151617612bf557565b90670de0b6b3a764000060008382039312818412811691841390151617612bf557565b81810392916000138015828513169184121617612bf557565b90670de0b6b3a764000091828102928184051490151715612bf557565b818102929160008212600160ff1b821416612bf5578184051490151715612bf557565b8115612c4e57600160ff1b8114600019831416612bf5570590565b600160ff1b8114612bf55760000390565b919081156139df5760006138588483613780565b126139b557613877816138718461137661136c846137d8565b94613780565b670de0b6b3a7640000918261389c6138976138918961379c565b856137f5565b61375c565b0590836138ac613897878a6137f5565b05908160011b916002830503612bf5576138c98593928493613780565b966138d4878a6137f5565b906138de91613818565b6138e7916137bf565b6138f190846137f5565b6138fa9061375c565b059180613906916137f5565b61390f9061375c565b05613919916137bf565b918161392582876137f5565b61392e9061375c565b0590613939916137f5565b6139429061375c565b0561394c91613780565b9161395782806137f5565b928160021b90600482058303612bf5576139a36139b19461399d61398d6002986139876139a896611376986137f5565b906137bf565b9161399781613833565b926139e6565b90613780565b6137d8565b84830590613780565b0590565b60046040517fae032660000000000000000000000000000000000000000000000000000000008152fd5b5050905090565b919060006003841315613a8657506000811380613a7c575b15613a4557915b600280613a1b85613a168186613818565b613780565b05915b848303613a2a57505050565b9193509081613a3d85613a168185613818565b059190613a1e565b6000811280613a6a575b15613a6357613a5d90613833565b91613a05565b5081613a05565b5082613a7582613833565b1315613a4f565b50828113156139fe565b929050613a8f57565b60019150565b916000926001600160a01b036000911690604080519063705727b560e01b938483526020968784600481855afa938415613d31578594613d0e575b50613ae5906001600160781b03809516612c31565b82516318160ddd60e01b81528881600481865afa908115613d04578691613cd5575b50613b1191612c44565b96613b1b88612dd6565b613b2488613833565b94835163961be39160e01b81528281600481875afa908115613ccb57839187918491613cae575b501697600486518096819382525afa938415613ca4575092613c87575b5050169060ca5490613b7a8184613780565b9083613b85866137d8565b60028205613b9291613780565b90613b9c91613818565b8094613ba7856137d8565b90670de0b6b3a764000096808894936002869505613bc491613780565b90613bce91613818565b613bd7916137bf565b613be0916137f5565b613be99061375c565b0591613bf48561379c565b613bfd916137f5565b613c069061375c565b05613c1091613780565b60029005928392613c2184806137f5565b9280613c2c916137f5565b613c35916137f5565b613c3e9061375c565b05613c4891613780565b90613c52916139e6565b613c5b91613780565b613c64916137bf565b613c6d9061370d565b92838310613c805750612cbf8383612be8565b9092508192565b613c9d9250803d10610cd357610cc58183612ba7565b3880613b68565b51903d90823e3d90fd5b613cc59150833d8511610cd357610cc58183612ba7565b38613b4b565b85513d84823e3d90fd5b90508881813d8311613cfd575b613cec8183612ba7565b810103126119f85751613b11613b07565b503d613ce2565b84513d88823e3d90fd5b613ae5919450613d2a90893d8b11610cd357610cc58183612ba7565b9390613ad0565b83513d87823e3d90fd5b9390949180158015613e81575b6139b55783818192613d5a8989613780565b9780613d6683806137f5565b90613d7091613818565b98670de0b6b3a7640000809a8c613d8788806137f5565b90613d9191613818565b613d9a91613780565b613da4908a6137f5565b613dad9061375c565b05613db7916137bf565b95613dc191613780565b613dca906137d8565b60028205613dd791613780565b90613de191613818565b80613deb876137d8565b60028205613df891613780565b90613e0291613818565b613e0b916137bf565b613e14916137f5565b90613e1e91613818565b91613e28906137d8565b60028205613e3591613780565b90613e3f91613818565b613e48916137bf565b90613e5291613e89565b613e5b916137f5565b613e649061375c565b0590613e6f916137bf565b6000811215612cbf57612cbf90613833565b508315613d48565b613e9381806137f5565b908260021b9280840560041490151715612bf557613ec281613ebd6139b19461399d6002976137d8565b6139e6565b6137bf565b604051916317d1d06960e11b9081845260209182856004816001600160a01b038098165afa94851561108557600095613f6a575b50906004839260405195869384928352165afa90811561108557600091613f3e575b509050670de0b6b3a76400009180830292830403612bf557612cbf91612c44565b82813d8311613f63575b613f528183612ba7565b810103126102565750518038613f1d565b503d613f48565b90948382813d8311613f93575b613f818183612ba7565b81010312610256575051936004613efb565b503d613f77565b90929391613fa9818584614145565b90948195613fb881809961351d565b6000926001600160a01b038093169485855260209360d285526040978887208481540190551690813b156119f8578591602483928a51948593849262b64f3d60e51b845260048401525af1801561413b57614128575b50843b15611231578551916313f3df3160e31b8352016004820152828160248183885af180156136a057908391614114575b505083519263961be39160e01b84528184600481845afa9384156136a05783946140ee575b50908060049286519384809263705727b560e01b82525afa9283156140e357916140ae9391662386f26fc100009593926140c6575b50506001600160781b038091169116612c8e565b106140b65750565b600490516316fde3bd60e21b8152fd5b6140dc9250803d10610cd357610cc58183612ba7565b388061409a565b8551903d90823e3d90fd5b829194509161410b600493823d8411610cd357610cc58183612ba7565b94919250614065565b61411d90612b7d565b61232b578138614040565b61413490949194612b7d565b923861400e565b87513d87823e3d90fd5b91929092614154818585614521565b80949180966000851215600014614270575050506001600160a01b0316906040519063961be39160e01b82526020908183600481875afa801561424e57600493600091614253575b50826001600160781b03809216956040519586809263705727b560e01b82525afa801561424e57610b91946141df94600092614231575b50501692839185612c0b565b916142026141f6610108546001600160801b031690565b6001600160801b031690565b831161420d57505050565b94610c9d61422c93610c986142259484979899612c8e565b8092612be8565b920190565b6142479250803d10610cd357610cc58183612ba7565b38806141d3565b611085565b61426a9150833d8511610cd357610cc58183612ba7565b3861419c565b8093949297506001600160a01b03919596501660405163961be39160e01b815260208082600481865afa91821561424e57600092614379575b506004816001600160781b03809416946040519283809263705727b560e01b82525afa91821561424e5760009261435c575b5050166142ec81610b918885612c0b565b6101085492906001600160801b0384161061430b575050505050509190565b909193955061431c92949750612c8e565b9060801c111561434b5761433261433991613833565b8483614398565b9061434691938491614145565b905090565b6004604051630c2b153f60e31b8152fd5b6143729250803d10610cd357610cc58183612ba7565b38806142db565b816143919293503d8411610cd357610cc58183612ba7565b90386142a9565b6040516339420b4560e11b81529193926020926001600160a01b038316908481600481855afa90811561424e57600091614504575b506143d781612feb565b946040519263705727b560e01b84528184600481845afa801561424e576004946000916144e7575b50826144246144146141f66101085460801c90565b6001600160781b03809416612c64565b926040519687809263961be39160e01b82525afa801561424e5761445c95614457946000926144ca575b50501690612be8565b612cf7565b91600195868361447661446f8888612c31565b8486614145565b501061434b579291905b8484106144965750505050612cbf929350612c31565b90919293808501881c9088856144af8985028688614145565b50106144c1575050935b929190614480565b909550016144b9565b6144e09250803d10610cd357610cc58183612ba7565b388061444e565b6144fe9150833d8511610cd357610cc58183612ba7565b386143ff565b61451b9150853d8711610c4c57610c3d8183612ba7565b386143cd565b919060008083126147d7575b6040805163961be39160e01b8082526001600160a01b038781169760209660049691949192888389818e5afa9283156147cd5784936147ae575b506001600160781b03809316908b88518b818c8163705727b560e01b958682525afa9081156147a4579086918891614787575b50169184169389518881528c818d81895afa90811561477d576145cb929188918f8b92614760575b50501699613ec7565b670de0b6b3a764000090818103614730575b50508a908a8a518096819382525afa92831561472657918b9161461094938794614707575b50888660ca54951692613d3b565b988289139788958b90876146fd575b50508515614689575b50505050506146635750501561464b575061464861422560cb5483612c64565b91565b919061465a612cbf9193613833565b60cb5490612c64565b517f9a6114b7000000000000000000000000000000000000000000000000000000008152fd5b90919280949550891294856146a8575b50505050503880808080614628565b8192939495508787518095819382525afa9384156140e357936146de575b50506146d186613833565b9116103880808080614699565b6146f5929350803d10610cd357610cc58183612ba7565b9038806146c6565b1095508a3861461f565b61471f9194508c8d3d10610cd357610cc58183612ba7565b9238614602565b88513d87823e3d90fd5b80828e949f959682826147496147579661475094612c31565b0498612c31565b04946137f5565b059b90386145dd565b6147769250803d10610cd357610cc58183612ba7565b388f6145c2565b8b513d8a823e3d90fd5b61479e91508d803d10610cd357610cc58183612ba7565b3861459a565b8a513d89823e3d90fd5b6147c6919350893d8b11610cd357610cc58183612ba7565b9138614567565b87513d86823e3d90fd5b916147f39061137661136c6147ed60cb5461379c565b926137d8565b9161452d565b929091926148078382612e07565b83156110d65761481961481f91613064565b92613064565b916001600160a01b03938482166040958651926339420b4560e11b908185526020958686600481885afa958615614969578996614870936148689260009261494a575b50614974565b988991614145565b9092819516938951938385528785600481895afa94851561493f5788956000926148a09284926149275750612cf7565b99126148fb575050600488518094819382525afa9283156148f057612cbf949596506000936148d1575b5050612cf7565b6148e8929350803d10610c4c57610c3d8183612ba7565b9038806148ca565b86513d6000823e3d90fd5b93509350600488518094819382525afa9283156148f057612cbf949596506000936148d1575050612cf7565b610c45919250883d8a11610c4c57610c3d8183612ba7565b8b513d6000823e3d90fd5b6149629192508a3d8c11610c4c57610c3d8183612ba7565b9038614862565b8a513d6000823e3d90fd5b9060ff81166012811015614999575090614993612d16612cbf93612cd5565b906137f5565b6012106149a4575090565b906149b4612d16612cbf93612cc2565b90613818565b6149cb6001600160a01b0391613064565b1660408051916318160ddd60e01b928381526020908181600481865afa908115614ab757600091614ad2575b5015614ac25782519363705727b560e01b85528185600481865afa948515614ab757600095614a96575b508190600485518095819382525afa928315614a8c5750600092614a54575b50506001600160781b03612cbf9216612c8e565b81819392933d8311614a85575b614a6b8183612ba7565b810103126102565750516001600160781b03612cbf614a40565b503d614a61565b513d6000823e3d90fd5b82919550614ab090823d8411610cd357610cc58183612ba7565b9490614a21565b84513d6000823e3d90fd5b50505050670de0b6b3a764000090565b908282813d8311614af8575b614ae88183612ba7565b81010312610256575051386149f7565b503d614ade565b6000198114612bf55760010190565b614b1f6001600160a01b0391613064565b166040805190632672469960e21b82526020918281600481875afa908115614c3b57600091614c0e575b508151906339420b4560e11b82528382600481885afa918215614beb5790614b799291600092614bf6575061302a565b9181519363961be39160e01b85528185600481845afa948515614beb57600095614bbc575b506001600160781b03929160d2916000525260002054921690030390565b60d291955091614be16001600160781b039493823d8411610cd357610cc58183612ba7565b9591509192614b9e565b83513d6000823e3d90fd5b61291d919250853d8711610c4c57610c3d8183612ba7565b908382813d8311614c34575b614c248183612ba7565b8101031261025657505138614b49565b503d614c1a565b82513d6000823e3d90fd5b6001600160a01b03811660005260d26020526040600020548015908115614c7d575b5015614c715750565b614c7a90614c89565b50565b905060ce541138614c68565b9060006001600160a01b039283811680835260d260205260409182842054958615614e535760cd54670de0b6b3a7640000908103908111614e3f57614cd4614cda9160cc5490612be8565b88612c64565b9081614db1575b5050614cef60cd5487612c64565b9081614d04575b5050825260d2602052812055565b81614d0e9161354e565b509050823b156112225783519063283c3d8160e21b82526004820152848160248183875af18015614da757908591614d93575b5050813b156112315782519062b64f3d60e51b82526004820152838160248183865af18015614d865790849115614cf657614d7b90612b7d565b611982578238614cf6565b50505051903d90823e3d90fd5b614d9c90612b7d565b611231578338614d41565b84513d87823e3d90fd5b60d05416908451906339420b4560e11b8252602082600481885afa9182156135135790614de5929188926112ee5750612cf7565b90833b156119f8578451639e79eaa560e01b81526001600160a01b039190911660048201526024810191909152848160448183875af18015614da757614e2c575b80614ce1565b614e3890949194612b7d565b9238614e26565b602486634e487b7160e01b81526011600452fd5b5092945050505056fea264697066735822122089da207b08b1fed51b5d9d793a7a6258e967bbfa337a2db97ee6319e3beb454264736f6c63430008120033

Deployed Bytecode

0x608080604052600436101561001357600080fd5b600090813560e01c908163017b8292146129eb57508063017e7e58146129c45780630705999d14612951578063070f81d91461275e57806309a5fca3146123525780630f91f06f14611f4f5780633753b14a14611e9e5780633bd61ba814611e775780633f4ba83a14611dda5780634a5e42b114611c785780634a6fee0e14611be45780634fdd64e514611bd057806352f7c98814611b145780635b14f18314611ad75780635c975abb14611ab45780635cfe092e14611a9657806361858e23146119fc5780636281baef146116985780636922d5ca1461167a578063715018a61461161e57806374cbbdb9146115fc578063815bfd29146115de5780638456cb59146115835780638a2dfe091461132e5780638da5cb5b14611307578063907448ed1461127a57806391cca3db1461125357806393aeea02146111005780639908fc8b14610eaf5780639abacb4114610de95780639e4416b714610d8b578063a4275ceb1461096b578063aa6ca808146108a5578063d043c56514610857578063d0dd0e5614610839578063d30ffeda14610815578063d477f05f146107a2578063da4899971461065e578063dc3b7c8b1461063a578063e4a3011614610487578063e9249cc714610457578063f2fde38b146103b0578063f46901ed1461033d578063f57e84d5146102b5578063f8b49e7214610281578063fb7f5cc2146102595763fca8f3081461022657600080fd5b3461025657604061024a61024461023c36612a92565b929092613833565b916147f9565b82519182526020820152f35b80fd5b503461025657806003193601126102565760206001600160801b036101085416604051908152f35b50346102565760203660031901126102565760206102ad6102a86102a3612a07565b613064565b614c89565b604051908152f35b5034610256576020366003190112610256576004356102d2612add565b670de0b6b3a76400008111610313576020817fc6e60196bbe5909add58d5ae8a27082013670bce0855321773c33085af5c32439260cb55604051908152a180f35b60046040517f12b019e1000000000000000000000000000000000000000000000000000000008152fd5b5034610256576020366003190112610256577ff6b59ffc88fbb27f33470b919e00b41139ee340eb349521f0cbbc1504ce29c3e60206001600160a01b03610382612a07565b61038a612add565b61039381612f03565b16806001600160a01b031960d054161760d055604051908152a180f35b5034610256576020366003190112610256576103ca612a07565b6103d2612add565b6001600160a01b038116156103ed576103ea90612b35565b80f35b608460405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b50346102565760203660031901126102565760206001600160a01b0361047e6102a3612a07565b16604051908152f35b50346102565761049636612ac7565b9082549160ff8360081c16159182809361062d575b8015610616575b156105ac5760ff198481166001178655938361059b575b506104e360ff865460081c166104de81612e45565b612e45565b6104ec33612b35565b84549361050c60ff8660081c1661050281612e45565b6001606555612e45565b6097541660975560ca5560cb55670de0b6b3a764000060cd55336001600160a01b031960cf54161760cf55610563575b506101087718fae27693b40000000000000000000014d1120d7b1600008282541617905580f35b61ff00191681557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160018152a13861053c565b61ffff1916610101178555386104c9565b608460405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152fd5b50303b1580156104b25750600160ff8516146104b2565b50600160ff8516106104ab565b50346102565760203660031901126102565760206102ad610659612a07565b6149ba565b503461025657604036600319011261025657610678612a07565b610680612a33565b610688612add565b61069181612f03565b61069a82612f03565b6106bd826001600160a01b0380911660005260d460205260406000205416151590565b610778576001600160a01b03809116911680835260d460205260408320826001600160a01b031982541617905560d35460d5602052806040852055680100000000000000008110156107645761073b61071d82600185940160d355612eb6565b819391549060031b916001600160a01b03809116831b921b19161790565b90557f0bb5715f0f217c2fe9a0c877ea87d474380c641102f3440ee2a4c8b9d97909188380a380f35b602484634e487b7160e01b81526041600452fd5b60046040517f8991c4c4000000000000000000000000000000000000000000000000000000008152fd5b5034610256576020366003190112610256577f8daa6bb00de042a7d51816fcee4a3d5c1e91a2128fd64fa1a9a38afa0d26beb560206001600160a01b036107e7612a07565b6107ef612add565b6107f881612f03565b16806001600160a01b031960cf54161760cf55604051908152a180f35b50346102565760203660031901126102565760206102ad610834612a07565b614b0e565b5034610256578060031936011261025657602060cb54604051908152f35b5034610256576020366003190112610256577f935ce123b6388f8dbf76890f8240a48198f1e0a884f26939aa604a46bb65b7066020600435610897612add565b8060ce55604051908152a180f35b5034610256578060031936011261025657604051809160d354908183526020809301809260d383527f915c3eb987b20e1af620c1403197bf687fb7f18513b3a73fde6e78c7072c41a690835b81811061094e5750505084610907910385612ba7565b60405193838594850191818652518092526040850193925b82811061092e57505050500390f35b83516001600160a01b03168552869550938101939281019260010161091f565b82546001600160a01b0316845292860192600192830192016108f1565b503461025657606036600319011261025657610985612a07565b61098d612a33565b610998604435612dd6565b6109a28183612e07565b6109ab82613064565b91836109b683613064565b6109c260443586613a95565b5050948560405163961be39160e01b81526020816004816001600160a01b0387165afa8015610d25576001600160781b038391610a07938891610ce5575b5016612be8565b9060405163705727b560e01b81526020816004816001600160a01b0388165afa8015610c7657610a508691610a56938991610cab575b506001600160781b036044359116612be8565b94613ec7565b670de0b6b3a764000090818103610d5b575b5050506040519263961be39160e01b84526020846004816001600160a01b0385165afa938415610d25578594610d30575b5060206001600160a01b039160046040518094819363705727b560e01b8352165afa8015610d255788610b1095610b0a95610b0a95610af8956001600160a01b039a95610d04575b506001600160781b038060ca549616931690613d3b565b610b0460cb5482612c64565b90612be8565b93613064565b92169260405163961be39160e01b8152602081600481885afa8015610c76576001600160781b038391610b4a938991610ce5575016612be8565b60405163705727b560e01b8152602081600481895afa8015610cda57610b83918891610cab57506001600160781b036044359116612be8565b610b9681610b918585612c0b565b612c8e565b916001600160801b0361010854168311610c81575b505050604051926020846004816339420b4560e11b988982525afa918215610c7657610bec6020936004926001600160a01b03978a92610c5e575b50612cf7565b9560405195869384928352165afa918215610c535790610c15929160409592610c225750612cf7565b9082519182526020820152f35b610c4591925060203d602011610c4c575b610c3d8183612ba7565b810190612c18565b9038610be6565b503d610c33565b6040513d86823e3d90fd5b610c45919250863d8811610c4c57610c3d8183612ba7565b6040513d88823e3d90fd5b93610c9d8193610c98610b0494610ca29798612c8e565b612d42565b612c64565b90388080610bab565b610ccd915060203d602011610cd3575b610cc58183612ba7565b810190612bc9565b38610a3d565b503d610cbb565b6040513d89823e3d90fd5b610cfe915060203d602011610cd357610cc58183612ba7565b38610a00565b610d1e91955060203d602011610cd357610cc58183612ba7565b9338610ae1565b6040513d87823e3d90fd5b6001600160a01b03919450610d53602091823d8411610cd357610cc58183612ba7565b949150610a99565b90610d8192949950610d7a8282610d73828298612c31565b049b612c31565b0493612c31565b0495388080610a68565b503461025657602036600319011261025657600435610da8612add565b670de0b6b3a76400008111610313576020817f294c562dcfcd9be941287844539aeba4ae641cd1fc9a2883337c6b2e87cbd98a9260ca55604051908152a180f35b503461025657602036600319011261025657610e03612a07565b610e0b612f8d565b6001600160a01b03811680835260c960205260ff60408420541615610e8557825260c960209081526040808420805460ff1916905580516001600160a01b03909316835233918301919091527fdb198487a4c70aa000c3b1020f52688b4b86d3ebcedc84a793e03ed3b480902c9190819081015b0390a180f35b60046040517f855b7373000000000000000000000000000000000000000000000000000000008152fd5b503461025657610ebe36612a49565b610ecc9593919294956130c1565b610ed4612f3d565b610ede8685612e07565b81156110d657610ef690610ef184612f03565b613394565b610eff83612fa1565b610f0883613064565b91610f1286613064565b91604051956339420b4560e11b8088526020886004816001600160a01b0389165afa978815611085576000986110b5575b506040519081526020816004816001600160a01b038a165afa908115611085576001600160a01b03610fae8a610fa8610fa18b610fb9988c610f9b86610f958e9c8b9c600091611096575b508d61302a565b9361302a565b92613f9a565b9d90612cf7565b9b612cf7565b981696163387613116565b6001600160a01b0383163b156110915760006040518094639e79eaa560e01b825281836001600160a01b03826110098d8a60048401602090939291936001600160a01b0360408201951681520152565b0393165af18015611085576040976001600160a01b03948592611076575b5088519586521660208501528684015284606084015216907f54787c404bb33c88e86f4baf88183a3b0141d0a848e6a9f7a13b66ae3a9b73d160803392a3600160655582519182526020820152f35b61107f90612b7d565b89611027565b6040513d6000823e3d90fd5b600080fd5b6110af915060203d602011610c4c57610c3d8183612ba7565b38610f8e565b6110cf91985060203d602011610c4c57610c3d8183612ba7565b9689610f43565b60046040517fb483c10f000000000000000000000000000000000000000000000000000000008152fd5b50346102565760603660031901126102565761111a612a07565b602435906044356001600160a01b03808216918281036110915761113c612add565b61114584613064565b9061114f85614b0e565b86116103135782879216906040516339420b4560e11b8152602081600481865afa8015610c5357611188918591611235575b5088612cf7565b823b1561123157604051639e79eaa560e01b81526001600160a01b0392909216600483015260248201529082908290604490829084905af1801561122657611208575b50927f5f22a22ebe30686f71d42a70ccacf870a335e22ee2ac6d257af1b2551582eda692856060936040519316835260208301526040820152a180f35b6112159094939294612b7d565b61122257909184386111cb565b8480fd5b6040513d84823e3d90fd5b8380fd5b61124d915060203d8111610c4c57610c3d8183612ba7565b38611181565b503461025657806003193601126102565760206001600160a01b0360cf5416604051908152f35b50346102565760403660031901126102565760206001600160a01b0361129e612a07565b6112b86112b1602435926102a384612dd6565b9182613a95565b94919290506004604051809581936339420b4560e11b8352165afa918215610c535790610c159291604095926112ee5750612cf7565b610c4591925060203d8111610c4c57610c3d8183612ba7565b503461025657806003193601126102565760206001600160a01b0360335416604051908152f35b5034610256578060031936011261025657808160ca54908060d354905b81831061138557604085610c158661138061137b8b61137661136c876137d8565b6002830590613780565b613818565b613833565b613e89565b9091936113c561139486612eb6565b91906001600160a01b03928391549060031b1c166001600160a01b0380911660005260d46020526040600020541690565b169560405163961be39160e01b815260209081816004818c5afa908115610d25578591611566575b506040519063705727b560e01b825282826004818d5afa918215610c76578692611547575b506004836001600160781b038094169b604051928380926317d1d06960e11b82525afa938415610cda578794611517575b5050891561150857899161145791166137d8565b918160029384820561146891613780565b9061147291613818565b918291670de0b6b3a7640000958661148b8194846137f5565b6114949061375c565b0561149e91613780565b9b6114a8916137f5565b6114b19061375c565b05926114bc896137d8565b9082056114c891613780565b906114d291613818565b6114db916137bf565b6114e4916137f5565b6114ed9061375c565b056114f791613780565b9361150190614aff565b919061134b565b50505095509361150190614aff565b9080929450813d8311611540575b61152f8183612ba7565b810103126110915751913880611443565b503d611525565b61155f919250833d8511610cd357610cc58183612ba7565b9038611412565b61157d9150823d8411610cd357610cc58183612ba7565b386113ed565b503461025657806003193601126102565761159c612f8d565b6115a4612f3d565b600160ff1960975416176097557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b5034610256578060031936011261025657602060cd54604051908152f35b503461025657806003193601126102565760206101085460801c604051908152f35b5034610256578060031936011261025657611637612add565b60006001600160a01b036033546001600160a01b03198116603355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b5034610256578060031936011261025657602060cc54604051908152f35b50346102565760c0366003190112610256576116b2612a07565b602435604435916116c1612a1d565b9260a435908115918215036119f8576116d86130c1565b6116e0612f3d565b83156110d6576116ef85612f03565b6116fa608435613394565b61170383612fa1565b61170c83613064565b6001600160a01b039283821690611727878333888a16613116565b156117f6576020600491604051928380926339420b4560e11b82525afa9081156117eb579261178e9261178889937ff5dd9317b9e63ac316ce44acc85f670b54b339cfa3e9076e1dd55065b922314b979660209c916117ce575b508961302a565b906133c6565b945b6117be8660405193849316963396846040919493926001600160a01b03606083019616825260208201520152565b0390a36001606555604051908152f35b6117e591508c3d8111610c4c57610c3d8183612ba7565b38611781565b6040513d8a823e3d90fd5b906118098460d199949895995416612f03565b604051966339420b4560e11b88526020978881600481875afa908115610d25576118479392916118409187916119e157508961302a565b30926133c6565b60d1546040517f095ea7b30000000000000000000000000000000000000000000000000000000081529089166001600160a01b0316600482015260248101829052909690818160448187875af18015610c53576119b4575b508760d1541691604051907faf929a8000000000000000000000000000000000000000000000000000000000825260048201528181602481865afa918215610c53578492611986575b5050813b156119825782916064839260405194859384927f90210d7e00000000000000000000000000000000000000000000000000000000845260048401528b60248401528c891660448401525af180156112265761196e575b50506020947ff5dd9317b9e63ac316ce44acc85f670b54b339cfa3e9076e1dd55065b922314b91611790565b6119788291612b7d565b6102565780611942565b8280fd5b90809250813d83116119ad575b61199d8183612ba7565b81010312611091575138806118e8565b503d611993565b6119d390823d84116119da575b6119cb8183612ba7565b8101906130a9565b503861189f565b503d6119c1565b6117e591508b3d8d11610c4c57610c3d8183612ba7565b8580fd5b503461025657604036600319011261025657611a196102a3612a07565b6040516339420b4560e11b81526020816004816001600160a01b0386165afa908115611a8b57611a609291611a5a9160409591611a6d575b5060243561302a565b9061354e565b8351928352602083015250f35b611a85915060203d8111610c4c57610c3d8183612ba7565b38611a51565b6040513d85823e3d90fd5b5034610256578060031936011261025657602060ce54604051908152f35b5034610256578060031936011261025657602060ff609754166040519015158152f35b50346102565760203660031901126102565760ff60406020926001600160a01b03611b00612a07565b16815260c984522054166040519015158152f35b503461025657611b2336612ac7565b90611b2c612add565b670de0b6b3a76400008183011161031357825b60d354811015611b945780611b896102a86001600160a01b03611b64611b8f95612eb6565b90549060031b1c166001600160a01b0380911660005260d46020526040600020541690565b50614aff565b611b3f565b506040907f032dc6a2d839eb179729a55633fdf1c41a1fc4739394154117005db2b354b9b5928060cc558160cd5582519182526020820152a180f35b503461025657604061024a61024436612a92565b5034610256576040366003190112610256576004356001600160801b038082168092036110915760243590811680820361109157611c20612add565b670de0b6b3a76400008310908115611c6e575b506103135761010891837fffffffffffffffffffffffffffffffff0000000000000000000000000000000084549360801b1692161717905580f35b9050821138611c33565b503461025657602036600319011261025657611c92612a07565b611c9a612add565b611cbd816001600160a01b0380911660005260d460205260406000205416151590565b15611db0576001600160a01b038091169081835260d4602052604083208054906001600160a01b03198216905560d5602052604084205460d3549060001991828101908111611d9c5761071d85611d16611d3b93612eb6565b90549060031b1c169283895260d56020528060408a2055878952886040812055612eb6565b905560d3548015611d885701611d5081612eb6565b8482549160031b1b1916905560d35516907f0fa1e4606af435f32f05b3804033d2933e691fab32ee74d2db6fa82d2741f1ea8380a380f35b602486634e487b7160e01b81526031600452fd5b602487634e487b7160e01b81526011600452fd5b60046040517fecb004d4000000000000000000000000000000000000000000000000000000008152fd5b5034610256578060031936011261025657611df3612f8d565b60975460ff811615611e335760ff19166097557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b606460405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f74207061757365640000000000000000000000006044820152fd5b503461025657806003193601126102565760206001600160a01b0360d15416604051908152f35b503461025657602036600319011261025657611eb8612a07565b611ec0612f8d565b611ee3816001600160a01b0380911660005260d460205260406000205416151590565b15611db057610e7f81611f167fdcb65c0553aaa433aadd180404ff195259c48f78aa50f877ebcb4bb215129a4e93612fa1565b6001600160a01b031680845260c960209081526040808620805460ff191660011790558051928352339183019190915290918291820190565b503461025657611fa0611f97611f6436612a49565b611f73979395929691976130c1565b611f7b612f3d565b611f8488612f03565b611f8d86612dd6565b610ef18584612e07565b6102a381612fa1565b92611faa82613064565b906001600160a01b03851694611fc285873381613116565b600095611fce82614c46565b611fd88683613a95565b509097823b1561025657604051632770a7eb60e21b81526001600160a01b038416600482015260248101899052818160448183885af1801561122657612343575b5090823b1561232b576040516313f3df3160e31b8152896004820152828160248183885af18015611a8b5790839161232f575b5050823b1561232b576040519063d8b8785360e01b82526004820152818160248183875af1801561122657908291612317575b505060405163705727b560e01b91828252602082600481875afa91821561230a5781926122e9575b506001600160781b0380921615159384612230575b5050505061221f576001600160a01b03831695604051936339420b4560e11b948581526020816004818c5afa80156110855761210495610f9b926000926121fe575b5061302a565b50604051918252602082600481885afa9081156110855761212c926000926121dd5750612cf7565b92803b1561109157604051639e79eaa560e01b81526001600160a01b038616600482015260248101859052906000908290604490829084905af18015611085576020957ffb80d861da582b723be2d19507ce3e03851820c464abea89156ec77e089b1ad9926001600160a01b03926121ce575b506117be6040519283921695873396846040919493926001600160a01b03606083019616825260208201520152565b6121d790612b7d565b8761219f565b6121f791925060203d602011610c4c57610c3d8183612ba7565b9087610be6565b61221891925060203d602011610c4c57610c3d8183612ba7565b908c6120fe565b60046040516316fde3bd60e21b8152fd5b90919293506040519363961be39160e01b8552602085600481855afa948515611a8b5783956122c7575b5060209060046040518094819382525afa9182156122bb5792662386f26fc1000094928192612292959161229c575b50169116612c8e565b10888080806120bc565b6122b5915060203d602011610cd357610cc58183612ba7565b8d612289565b604051903d90823e3d90fd5b60209195506122e290823d8411610cd357610cc58183612ba7565b949061225a565b61230391925060203d602011610cd357610cc58183612ba7565b908b6120a7565b50604051903d90823e3d90fd5b61232090612b7d565b61025657808a61207f565b5080fd5b61233890612b7d565b61232b57818b61204c565b61234c90612b7d565b8a612019565b50346102565760a03660031901126102565761236c612a07565b612374612a1d565b61237c6130c1565b612384612f3d565b61238f602435612dd6565b61239881612f03565b6123a3608435613394565b6123ac82613064565b916123c46024356001600160a01b0385163381613116565b6040516339420b4560e11b81526020816004816001600160a01b0388165afa908115610d2557859161273f575b506123fe8160443561302a565b9061240885614c46565b61242161241760243587613a95565b509190809461351d565b6001600160a01b0386163b1561109157604051632770a7eb60e21b81526001600160a01b03871660048201526024803590820152600090818180604481010381836001600160a01b038d165af1801561122657612730575b50906001600160a01b0387163b1561232b576040516313f3df3160e31b81528460048201528281602481836001600160a01b038d165af18015611a8b5790839161271c575b50506001600160a01b0387163b1561232b576040519063d8b8785360e01b825260048201528181602481836001600160a01b038c165af1801561122657908291612708575b505060405163705727b560e01b908181526020816004816001600160a01b038c165afa908115611a8b57906001600160781b039184916126e9575b50161515918261262b575b505061221f5761255891612cf7565b926001600160a01b0381163b1561122257846040518092639e79eaa560e01b825281836001600160a01b03826125a88b8b60048401602090939291936001600160a01b0360408201951681520152565b0393165af18015610d2557612617575b50602093507ffb80d861da582b723be2d19507ce3e03851820c464abea89156ec77e089b1ad96001600160a01b03604051931692806117be33948760243591846040919493926001600160a01b03606083019616825260208201520152565b6126218591612b7d565b61123157836125b8565b60405163961be39160e01b81529250906020836004816001600160a01b038c165afa9283156112265782936126c8575b506040519081526020816004816001600160a01b038c165afa9182156122bb576001600160781b036126a193662386f26fc1000095938293916126a95750169116612c8e565b103880612549565b6126c2915060203d602011610cd357610cc58183612ba7565b38612289565b6126e291935060203d602011610cd357610cc58183612ba7565b913861265b565b612702915060203d602011610cd357610cc58183612ba7565b3861253e565b61271190612b7d565b610256578038612503565b61272590612b7d565b61232b5781386124be565b61273990612b7d565b38612479565b612758915060203d602011610c4c57610c3d8183612ba7565b386123f1565b503461025657604036600319011261025657612778612a07565b90602435612784612f8d565b6001600160a01b0361279584613064565b16604051632672469960e21b81526020908181600481865afa908115610d25578591612924575b50604051906339420b4560e11b82528282600481875afa918215610c7657906127ec92918792612905575061302a565b60405163961be39160e01b81528281600481875afa908115610c765761283b93926001600160781b0360d29361282b938a916128e8575b501690612be8565b9184875252604085205490612be8565b821161031357803b156119825782809160246040518094819362b64f3d60e51b83528760048401525af18015611a8b576128b7575b50604080516001600160a01b039094168452602084019190915290917f4941e18a2bcbb0f9fa0081238f26793a8ad8c202b913ae8bf5f7e523f68ff1379181908101610e7f565b916128e27f4941e18a2bcbb0f9fa0081238f26793a8ad8c202b913ae8bf5f7e523f68ff13793612b7d565b91612870565b6128ff9150863d8811610cd357610cc58183612ba7565b38612823565b61291d919250843d8611610c4c57610c3d8183612ba7565b90386120fe565b90508181813d831161294a575b61293b8183612ba7565b810103126112225751386127bc565b503d612931565b5034610256576020366003190112610256577ffeb9010869b6ccec4557ddbbce947afeace5efc66cdff52c5e533c09336a8f2d60206001600160a01b03612996612a07565b61299e612add565b6129a781612f03565b16806001600160a01b031960d154161760d155604051908152a180f35b503461025657806003193601126102565760206001600160a01b0360d05416604051908152f35b90503461232b578160031936011261232b5760209060ca548152f35b600435906001600160a01b038216820361109157565b606435906001600160a01b038216820361109157565b602435906001600160a01b038216820361109157565b60c0906003190112611091576001600160a01b03600435818116810361109157916024358281168103611091579160443591606435916084359081168103611091579060a43590565b6060906003190112611091576001600160a01b0390600435828116810361109157916024359081168103611091579060443590565b6040906003190112611091576004359060243590565b6001600160a01b03603354163303612af157565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b603354906001600160a01b0380911691826001600160a01b0319821617603355167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3565b67ffffffffffffffff8111612b9157604052565b634e487b7160e01b600052604160045260246000fd5b90601f8019910116810190811067ffffffffffffffff821117612b9157604052565b9081602091031261109157516001600160781b03811681036110915790565b91908203918211612bf557565b634e487b7160e01b600052601160045260246000fd5b91908201809211612bf557565b90816020910312611091575160ff811681036110915790565b81810292918115918404141715612bf557565b8115612c4e570490565b634e487b7160e01b600052601260045260246000fd5b90612c6e91612c31565b6706f05b59d3b200008101809111612bf557670de0b6b3a7640000900490565b90670de0b6b3a764000091828102928184041490151715612bf557612cba612cbf928260011c90612c0b565b612c44565b90565b60ff6011199116019060ff8211612bf557565b60ff166012039060ff8211612bf557565b60ff16604d8111612bf557600a0a90565b9060ff81166012811015612d21575090612d1b612d16612cbf93612cd5565b612ce6565b90612c44565b601210612d2c575090565b90612d3c612d16612cbf93612cc2565b90612c31565b610108548060801c91828411600014612d66576004604051630c2b153f60e31b8152fd5b6001600160801b0382168411801590612dcc575b612dc357612cbf936001600160801b0392831691612dac9190838111612db8576000905b848303920391800203612c44565b60011c92031690612c8e565b838103800290612d9e565b50505050600090565b5080841115612d7a565b15612ddd57565b60046040517f3f3835e7000000000000000000000000000000000000000000000000000000008152fd5b6001600160a01b03908116911614612e1b57565b60046040517feeb7911f000000000000000000000000000000000000000000000000000000008152fd5b15612e4c57565b608460405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152fd5b60d354811015612eed5760d36000527f915c3eb987b20e1af620c1403197bf687fb7f18513b3a73fde6e78c7072c41a60190600090565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b031615612f1357565b60046040517f0bd3e3ed000000000000000000000000000000000000000000000000000000008152fd5b60ff60975416612f4957565b606460405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a20706175736564000000000000000000000000000000006044820152fd5b6001600160a01b0360cf5416330361221f57565b6001600160a01b031660005260c960205260ff60406000205416612fc157565b60046040517f8654830a000000000000000000000000000000000000000000000000000000008152fd5b60ff811660128110156130055750612d16612cbf91612cd5565b6012106130125750600190565b612d1661301e91612cc2565b8015612c4e5760010490565b9060ff81166012811015613049575090612d3c612d16612cbf93612cd5565b601210613054575090565b90612d1b612d16612cbf93612cc2565b613087816001600160a01b0380911660005260d460205260406000205416151590565b15611db0576001600160a01b0380911660005260d46020526040600020541690565b90816020910312611091575180151581036110915790565b6002606554146130d2576002606555565b606460405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fd5b93929190604051602093848201927f23b872dd0000000000000000000000000000000000000000000000000000000084526001600160a01b039485809216602485015216604483015260648201526064815260a081019267ffffffffffffffff9682851088861117612b9157169060e0810184811088821117612b91576040528484527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656460c0820152600080938192519082855af1903d156132b4573d9687116132a057613205949596604051906131f788601f19601f8401160183612ba7565b81528093873d92013e6132c1565b805190828215928315613288575b5050501561321e5750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152fd5b61329893508201810191016130a9565b388281613213565b602483634e487b7160e01b81526041600452fd5b9150613205939495506060915b9192901561332257508151156132d5575090565b3b156132de5790565b606460405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152fd5b8251909150156133355750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b82851061337b575050604492506000838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350613358565b421161339c57565b60046040517f549b6335000000000000000000000000000000000000000000000000000000008152fd5b6001600160a01b03919294936133db82614c46565b6133e5848361354e565b509390926133fd8480996133f882612dd6565b61351d565b1690813b1561109157604093845162b64f3d60e51b81526000966004820152868160248183885af1801561351357613500575b50823b156119f85784519063283c3d8160e21b82526004820152858160248183875af180156134f6579086916134e2575b5050813b156112225783517f40c10f190000000000000000000000000000000000000000000000000000000081526001600160a01b03919091166004820152602481019290925290919083908390604490829084905af19081156134d957506134c8575050565b6134d28291612b7d565b6102565750565b513d84823e3d90fd5b6134eb90612b7d565b611222578438613461565b85513d88823e3d90fd5b61350c90969196612b7d565b9438613430565b86513d89823e3d90fd5b1161352457565b60046040517ff512a720000000000000000000000000000000000000000000000000000000008152fd5b6000916001600160a01b036000921660409081519363961be39160e01b85526020938486600481865afa9586156137035782966136e4575b5083519063705727b560e01b968783528683600481885afa9283156136da5784936136b3575b506135cc6135d19160ca54906001600160781b0380809716911685613844565b61370d565b978189106136aa57508703955b84519081528581600481875afa9081156136a0578391613683575b5016938461360a5750505050508192565b806004938551948580926318160ddd60e01b82525afa93841561367857508193613643575b50505090612cba6136409285612c31565b92565b9091809350813d8311613671575b61365b8183612ba7565b81010312610256575051612cba6136403861362f565b503d613651565b51913d9150823e3d90fd5b61369a9150863d8811610cd357610cc58183612ba7565b386135f9565b85513d85823e3d90fd5b969750966135de565b6135d19193506136d26135cc91893d8b11610cd357610cc58183612ba7565b9391506135ac565b86513d86823e3d90fd5b6136fc919650853d8711610cd357610cc58183612ba7565b9438613586565b84513d84823e3d90fd5b600081126137185790565b606460405162461bcd60e51b815260206004820152601660248201527f76616c7565206d75737420626520706f736974697665000000000000000000006044820152fd5b906706f05b59d3b200009160008382019384129112908015821691151617612bf557565b91909160008382019384129112908015821691151617612bf557565b90670de0b6b3a764000060008382039312818412811691841390151617612bf557565b81810392916000138015828513169184121617612bf557565b90670de0b6b3a764000091828102928184051490151715612bf557565b818102929160008212600160ff1b821416612bf5578184051490151715612bf557565b8115612c4e57600160ff1b8114600019831416612bf5570590565b600160ff1b8114612bf55760000390565b919081156139df5760006138588483613780565b126139b557613877816138718461137661136c846137d8565b94613780565b670de0b6b3a7640000918261389c6138976138918961379c565b856137f5565b61375c565b0590836138ac613897878a6137f5565b05908160011b916002830503612bf5576138c98593928493613780565b966138d4878a6137f5565b906138de91613818565b6138e7916137bf565b6138f190846137f5565b6138fa9061375c565b059180613906916137f5565b61390f9061375c565b05613919916137bf565b918161392582876137f5565b61392e9061375c565b0590613939916137f5565b6139429061375c565b0561394c91613780565b9161395782806137f5565b928160021b90600482058303612bf5576139a36139b19461399d61398d6002986139876139a896611376986137f5565b906137bf565b9161399781613833565b926139e6565b90613780565b6137d8565b84830590613780565b0590565b60046040517fae032660000000000000000000000000000000000000000000000000000000008152fd5b5050905090565b919060006003841315613a8657506000811380613a7c575b15613a4557915b600280613a1b85613a168186613818565b613780565b05915b848303613a2a57505050565b9193509081613a3d85613a168185613818565b059190613a1e565b6000811280613a6a575b15613a6357613a5d90613833565b91613a05565b5081613a05565b5082613a7582613833565b1315613a4f565b50828113156139fe565b929050613a8f57565b60019150565b916000926001600160a01b036000911690604080519063705727b560e01b938483526020968784600481855afa938415613d31578594613d0e575b50613ae5906001600160781b03809516612c31565b82516318160ddd60e01b81528881600481865afa908115613d04578691613cd5575b50613b1191612c44565b96613b1b88612dd6565b613b2488613833565b94835163961be39160e01b81528281600481875afa908115613ccb57839187918491613cae575b501697600486518096819382525afa938415613ca4575092613c87575b5050169060ca5490613b7a8184613780565b9083613b85866137d8565b60028205613b9291613780565b90613b9c91613818565b8094613ba7856137d8565b90670de0b6b3a764000096808894936002869505613bc491613780565b90613bce91613818565b613bd7916137bf565b613be0916137f5565b613be99061375c565b0591613bf48561379c565b613bfd916137f5565b613c069061375c565b05613c1091613780565b60029005928392613c2184806137f5565b9280613c2c916137f5565b613c35916137f5565b613c3e9061375c565b05613c4891613780565b90613c52916139e6565b613c5b91613780565b613c64916137bf565b613c6d9061370d565b92838310613c805750612cbf8383612be8565b9092508192565b613c9d9250803d10610cd357610cc58183612ba7565b3880613b68565b51903d90823e3d90fd5b613cc59150833d8511610cd357610cc58183612ba7565b38613b4b565b85513d84823e3d90fd5b90508881813d8311613cfd575b613cec8183612ba7565b810103126119f85751613b11613b07565b503d613ce2565b84513d88823e3d90fd5b613ae5919450613d2a90893d8b11610cd357610cc58183612ba7565b9390613ad0565b83513d87823e3d90fd5b9390949180158015613e81575b6139b55783818192613d5a8989613780565b9780613d6683806137f5565b90613d7091613818565b98670de0b6b3a7640000809a8c613d8788806137f5565b90613d9191613818565b613d9a91613780565b613da4908a6137f5565b613dad9061375c565b05613db7916137bf565b95613dc191613780565b613dca906137d8565b60028205613dd791613780565b90613de191613818565b80613deb876137d8565b60028205613df891613780565b90613e0291613818565b613e0b916137bf565b613e14916137f5565b90613e1e91613818565b91613e28906137d8565b60028205613e3591613780565b90613e3f91613818565b613e48916137bf565b90613e5291613e89565b613e5b916137f5565b613e649061375c565b0590613e6f916137bf565b6000811215612cbf57612cbf90613833565b508315613d48565b613e9381806137f5565b908260021b9280840560041490151715612bf557613ec281613ebd6139b19461399d6002976137d8565b6139e6565b6137bf565b604051916317d1d06960e11b9081845260209182856004816001600160a01b038098165afa94851561108557600095613f6a575b50906004839260405195869384928352165afa90811561108557600091613f3e575b509050670de0b6b3a76400009180830292830403612bf557612cbf91612c44565b82813d8311613f63575b613f528183612ba7565b810103126102565750518038613f1d565b503d613f48565b90948382813d8311613f93575b613f818183612ba7565b81010312610256575051936004613efb565b503d613f77565b90929391613fa9818584614145565b90948195613fb881809961351d565b6000926001600160a01b038093169485855260209360d285526040978887208481540190551690813b156119f8578591602483928a51948593849262b64f3d60e51b845260048401525af1801561413b57614128575b50843b15611231578551916313f3df3160e31b8352016004820152828160248183885af180156136a057908391614114575b505083519263961be39160e01b84528184600481845afa9384156136a05783946140ee575b50908060049286519384809263705727b560e01b82525afa9283156140e357916140ae9391662386f26fc100009593926140c6575b50506001600160781b038091169116612c8e565b106140b65750565b600490516316fde3bd60e21b8152fd5b6140dc9250803d10610cd357610cc58183612ba7565b388061409a565b8551903d90823e3d90fd5b829194509161410b600493823d8411610cd357610cc58183612ba7565b94919250614065565b61411d90612b7d565b61232b578138614040565b61413490949194612b7d565b923861400e565b87513d87823e3d90fd5b91929092614154818585614521565b80949180966000851215600014614270575050506001600160a01b0316906040519063961be39160e01b82526020908183600481875afa801561424e57600493600091614253575b50826001600160781b03809216956040519586809263705727b560e01b82525afa801561424e57610b91946141df94600092614231575b50501692839185612c0b565b916142026141f6610108546001600160801b031690565b6001600160801b031690565b831161420d57505050565b94610c9d61422c93610c986142259484979899612c8e565b8092612be8565b920190565b6142479250803d10610cd357610cc58183612ba7565b38806141d3565b611085565b61426a9150833d8511610cd357610cc58183612ba7565b3861419c565b8093949297506001600160a01b03919596501660405163961be39160e01b815260208082600481865afa91821561424e57600092614379575b506004816001600160781b03809416946040519283809263705727b560e01b82525afa91821561424e5760009261435c575b5050166142ec81610b918885612c0b565b6101085492906001600160801b0384161061430b575050505050509190565b909193955061431c92949750612c8e565b9060801c111561434b5761433261433991613833565b8483614398565b9061434691938491614145565b905090565b6004604051630c2b153f60e31b8152fd5b6143729250803d10610cd357610cc58183612ba7565b38806142db565b816143919293503d8411610cd357610cc58183612ba7565b90386142a9565b6040516339420b4560e11b81529193926020926001600160a01b038316908481600481855afa90811561424e57600091614504575b506143d781612feb565b946040519263705727b560e01b84528184600481845afa801561424e576004946000916144e7575b50826144246144146141f66101085460801c90565b6001600160781b03809416612c64565b926040519687809263961be39160e01b82525afa801561424e5761445c95614457946000926144ca575b50501690612be8565b612cf7565b91600195868361447661446f8888612c31565b8486614145565b501061434b579291905b8484106144965750505050612cbf929350612c31565b90919293808501881c9088856144af8985028688614145565b50106144c1575050935b929190614480565b909550016144b9565b6144e09250803d10610cd357610cc58183612ba7565b388061444e565b6144fe9150833d8511610cd357610cc58183612ba7565b386143ff565b61451b9150853d8711610c4c57610c3d8183612ba7565b386143cd565b919060008083126147d7575b6040805163961be39160e01b8082526001600160a01b038781169760209660049691949192888389818e5afa9283156147cd5784936147ae575b506001600160781b03809316908b88518b818c8163705727b560e01b958682525afa9081156147a4579086918891614787575b50169184169389518881528c818d81895afa90811561477d576145cb929188918f8b92614760575b50501699613ec7565b670de0b6b3a764000090818103614730575b50508a908a8a518096819382525afa92831561472657918b9161461094938794614707575b50888660ca54951692613d3b565b988289139788958b90876146fd575b50508515614689575b50505050506146635750501561464b575061464861422560cb5483612c64565b91565b919061465a612cbf9193613833565b60cb5490612c64565b517f9a6114b7000000000000000000000000000000000000000000000000000000008152fd5b90919280949550891294856146a8575b50505050503880808080614628565b8192939495508787518095819382525afa9384156140e357936146de575b50506146d186613833565b9116103880808080614699565b6146f5929350803d10610cd357610cc58183612ba7565b9038806146c6565b1095508a3861461f565b61471f9194508c8d3d10610cd357610cc58183612ba7565b9238614602565b88513d87823e3d90fd5b80828e949f959682826147496147579661475094612c31565b0498612c31565b04946137f5565b059b90386145dd565b6147769250803d10610cd357610cc58183612ba7565b388f6145c2565b8b513d8a823e3d90fd5b61479e91508d803d10610cd357610cc58183612ba7565b3861459a565b8a513d89823e3d90fd5b6147c6919350893d8b11610cd357610cc58183612ba7565b9138614567565b87513d86823e3d90fd5b916147f39061137661136c6147ed60cb5461379c565b926137d8565b9161452d565b929091926148078382612e07565b83156110d65761481961481f91613064565b92613064565b916001600160a01b03938482166040958651926339420b4560e11b908185526020958686600481885afa958615614969578996614870936148689260009261494a575b50614974565b988991614145565b9092819516938951938385528785600481895afa94851561493f5788956000926148a09284926149275750612cf7565b99126148fb575050600488518094819382525afa9283156148f057612cbf949596506000936148d1575b5050612cf7565b6148e8929350803d10610c4c57610c3d8183612ba7565b9038806148ca565b86513d6000823e3d90fd5b93509350600488518094819382525afa9283156148f057612cbf949596506000936148d1575050612cf7565b610c45919250883d8a11610c4c57610c3d8183612ba7565b8b513d6000823e3d90fd5b6149629192508a3d8c11610c4c57610c3d8183612ba7565b9038614862565b8a513d6000823e3d90fd5b9060ff81166012811015614999575090614993612d16612cbf93612cd5565b906137f5565b6012106149a4575090565b906149b4612d16612cbf93612cc2565b90613818565b6149cb6001600160a01b0391613064565b1660408051916318160ddd60e01b928381526020908181600481865afa908115614ab757600091614ad2575b5015614ac25782519363705727b560e01b85528185600481865afa948515614ab757600095614a96575b508190600485518095819382525afa928315614a8c5750600092614a54575b50506001600160781b03612cbf9216612c8e565b81819392933d8311614a85575b614a6b8183612ba7565b810103126102565750516001600160781b03612cbf614a40565b503d614a61565b513d6000823e3d90fd5b82919550614ab090823d8411610cd357610cc58183612ba7565b9490614a21565b84513d6000823e3d90fd5b50505050670de0b6b3a764000090565b908282813d8311614af8575b614ae88183612ba7565b81010312610256575051386149f7565b503d614ade565b6000198114612bf55760010190565b614b1f6001600160a01b0391613064565b166040805190632672469960e21b82526020918281600481875afa908115614c3b57600091614c0e575b508151906339420b4560e11b82528382600481885afa918215614beb5790614b799291600092614bf6575061302a565b9181519363961be39160e01b85528185600481845afa948515614beb57600095614bbc575b506001600160781b03929160d2916000525260002054921690030390565b60d291955091614be16001600160781b039493823d8411610cd357610cc58183612ba7565b9591509192614b9e565b83513d6000823e3d90fd5b61291d919250853d8711610c4c57610c3d8183612ba7565b908382813d8311614c34575b614c248183612ba7565b8101031261025657505138614b49565b503d614c1a565b82513d6000823e3d90fd5b6001600160a01b03811660005260d26020526040600020548015908115614c7d575b5015614c715750565b614c7a90614c89565b50565b905060ce541138614c68565b9060006001600160a01b039283811680835260d260205260409182842054958615614e535760cd54670de0b6b3a7640000908103908111614e3f57614cd4614cda9160cc5490612be8565b88612c64565b9081614db1575b5050614cef60cd5487612c64565b9081614d04575b5050825260d2602052812055565b81614d0e9161354e565b509050823b156112225783519063283c3d8160e21b82526004820152848160248183875af18015614da757908591614d93575b5050813b156112315782519062b64f3d60e51b82526004820152838160248183865af18015614d865790849115614cf657614d7b90612b7d565b611982578238614cf6565b50505051903d90823e3d90fd5b614d9c90612b7d565b611231578338614d41565b84513d87823e3d90fd5b60d05416908451906339420b4560e11b8252602082600481885afa9182156135135790614de5929188926112ee5750612cf7565b90833b156119f8578451639e79eaa560e01b81526001600160a01b039190911660048201526024810191909152848160448183875af18015614da757614e2c575b80614ce1565b614e3890949194612b7d565b9238614e26565b602486634e487b7160e01b81526011600452fd5b5092945050505056fea264697066735822122089da207b08b1fed51b5d9d793a7a6258e967bbfa337a2db97ee6319e3beb454264736f6c63430008120033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.