APE Price: $1.24 (+5.31%)

Contract Diff Checker

Contract Name:
CyanPaymentPlanV2

Contract Source Code:

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

pragma solidity ^0.8.0;

import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
    function __AccessControl_init() internal onlyInitializing {
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `_msgSender()` is missing `role`.
     * Overriding this function changes the behavior of the {onlyRole} modifier.
     *
     * Format of the revert message is described in {_checkRole}.
     *
     * _Available since v4.6._
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        StringsUpgradeable.toHexString(account),
                        " is missing role ",
                        StringsUpgradeable.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * May emit a {RoleGranted} event.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _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;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControlUpgradeable {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.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]
 * ```
 * 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 Internal function that returns the initialized version. Returns `_initialized`
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Internal function that returns the initialized version. Returns `_initializing`
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.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 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;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-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 IERC20PermitUpgradeable {
    /**
     * @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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../extensions/draft-IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable {
    using AddressUpgradeable for address;

    function safeTransfer(
        IERC20Upgradeable token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20Upgradeable 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(
        IERC20Upgradeable 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));
    }

    function safeIncreaseAllowance(
        IERC20Upgradeable token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20Upgradeable token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    function safePermit(
        IERC20PermitUpgradeable 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(IERC20Upgradeable token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165Upgradeable.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721Upgradeable is IERC165Upgradeable {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.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
     * ====
     *
     * [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://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (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 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);
        }
    }
}

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../StringsUpgradeable.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSAUpgradeable {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", StringsUpgradeable.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
    function __ERC165_init() internal onlyInitializing {
    }

    function __ERC165_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165Upgradeable).interfaceId;
    }

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165Upgradeable {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

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

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library MathUpgradeable {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator,
        Rounding rounding
    ) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10**64) {
                value /= 10**64;
                result += 64;
            }
            if (value >= 10**32) {
                value /= 10**32;
                result += 32;
            }
            if (value >= 10**16) {
                value /= 10**16;
                result += 16;
            }
            if (value >= 10**8) {
                value /= 10**8;
                result += 8;
            }
            if (value >= 10**4) {
                value /= 10**4;
                result += 4;
            }
            if (value >= 10**2) {
                value /= 10**2;
                result += 2;
            }
            if (value >= 10**1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
        }
    }
}

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

pragma solidity ^0.8.0;

import "./math/MathUpgradeable.sol";

/**
 * @dev String operations.
 */
library StringsUpgradeable {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = MathUpgradeable.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, MathUpgradeable.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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 anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing 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);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The default value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overridden;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        _balances[to] += amount;

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.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);
}

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

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

enum ConduitItemType {
    NATIVE, // unused
    ERC20,
    ERC721,
    ERC1155
}

struct ConduitTransfer {
    ConduitItemType itemType;
    address collection;
    address from;
    address to;
    uint256 identifier;
    uint256 amount;
}

struct ConduitBatch1155Transfer {
    address collection;
    address from;
    address to;
    uint256[] ids;
    uint256[] amounts;
}

interface ICyanConduit {
    error ChannelClosed(address channel);
    error ChannelStatusAlreadySet(address channel, bool isOpen);
    error InvalidItemType();
    error InvalidAdmin();

    event ChannelUpdated(address indexed channel, bool open);

    function execute(ConduitTransfer[] calldata transfers) external returns (bytes4 magicValue);

    function executeBatch1155(ConduitBatch1155Transfer[] calldata batch1155Transfers)
        external
        returns (bytes4 magicValue);

    function executeWithBatch1155(
        ConduitTransfer[] calldata standardTransfers,
        ConduitBatch1155Transfer[] calldata batch1155Transfers
    ) external returns (bytes4 magicValue);

    function transferERC20(
        address from,
        address to,
        address token,
        uint256 amount
    ) external;

    function transferERC721(
        address from,
        address to,
        address collection,
        uint256 tokenId
    ) external;

    function transferERC1155(
        address from,
        address to,
        address collection,
        uint256 tokenId,
        uint256 amount
    ) external;

    function updateChannel(address channel, bool isOpen) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IFactory {
    function getOrDeployWallet(address) external returns (address);

    function getWalletOwner(address) external view returns (address);

    function getOwnerWallet(address) external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import { Item } from "../../main/payment-plan/PaymentPlanTypes.sol";

interface IWallet {
    function executeModule(bytes memory) external returns (bytes memory);

    function transferNonLockedERC721(
        address,
        uint256,
        address
    ) external;

    function transferNonLockedERC1155(
        address,
        uint256,
        uint256,
        address
    ) external;

    function transferNonLockedCryptoPunk(uint256, address) external;

    function setLockedERC721Token(
        address,
        uint256,
        bool
    ) external;

    function increaseLockedERC1155Token(
        address,
        uint256,
        uint256
    ) external;

    function decreaseLockedERC1155Token(
        address,
        uint256,
        uint256
    ) external;

    function setLockedCryptoPunk(uint256, bool) external;

    function autoPay(
        uint256,
        uint256,
        uint8
    ) external;

    function earlyUnwindOpensea(
        uint256,
        uint256,
        Item memory,
        bytes memory
    ) external;

    function earlyUnwindCyan(uint256, address) external;

    function isLockedNFT(address, uint256) external view returns (bool);

    function repayBendDaoLoan(
        address collection,
        uint256 tokenId,
        uint256 amount,
        address currency
    ) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "./IWallet.sol";

interface IWalletApeCoin is IWallet {
    function depositBAYCAndLock(uint32 tokenId, uint224 amount) external;

    function depositMAYCAndLock(uint32 tokenId, uint224 amount) external;

    function depositBAKCAndLock(
        address mainCollection,
        uint32 mainTokenId,
        uint32 bakcTokenId,
        uint224 amount
    ) external;

    function withdrawBAYCAndUnlock(uint32 tokenId) external;

    function withdrawMAYCAndUnlock(uint32 tokenId) external;

    function withdrawBAKCAndUnlock(uint32 tokenId) external;

    function autoCompound(uint256 poolId, uint32 tokenId) external;

    function getApeLockState(address collection, uint256 tokenId) external view returns (uint8);

    function completeApeCoinPlan(uint256 planId) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import { PaymentPlanStatus } from "../../main/payment-plan/PaymentPlanTypes.sol";

interface ICyanPaymentPlanV2 {
    function pay(uint256, bool) external payable;

    function getPlanStatus(uint256) external view returns (PaymentPlanStatus);

    function getCurrencyAddressByPlanId(uint256) external view returns (address);

    function earlyUnwindOpensea(
        uint256,
        uint256[2] calldata,
        uint256,
        bytes memory,
        uint256,
        bytes memory
    ) external;

    function earlyUnwindCyan(
        uint256,
        uint256[2] calldata,
        uint256,
        address,
        uint256,
        bytes memory
    ) external;

    function liquidate(
        uint256,
        uint256[2] calldata,
        uint256
    ) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface ICyanPeerPlan {
    enum PlanStatus {
        NONE,
        ACTIVE,
        DEFAULTED,
        COMPLETED,
        LIQUIDATED
    }
    struct LenderSignature {
        uint256 signedDate;
        uint256 expiryDate;
        uint32 maxUsageCount;
        bool extendable;
        bytes signature;
    }
    struct Plan {
        uint256 amount;
        address lenderAddress;
        address currencyAddress;
        uint32 interestRate;
        uint32 serviceFeeRate;
        uint32 term;
    }
    struct PaymentPlan {
        Plan plan;
        uint256 dueDate;
        address cyanWalletAddress;
        PlanStatus status;
        bool extendable;
    }
    struct Item {
        uint256 amount;
        uint256 tokenId;
        address contractAddress;
        // 1 -> ERC721
        // 2 -> ERC1155
        // 3 -> CryptoPunks
        uint8 itemType;
        bytes collectionSignature;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface ICyanVaultV2 {
    function getCurrencyAddress() external view returns (address);

    function lend(address to, uint256 amount) external;

    function earn(uint256 amount, uint256 profit) external payable;

    function nftDefaulted(uint256 unpaidAmount, uint256 estimatedPriceOfNFT) external;

    function withdrawLocked(address cyanWalletAddress) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

/// @title Cyan AddressProvider contract
/// @author Bulgantamir Gankhuyag - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
contract AddressProvider is Ownable {
    error AddressNotFound(bytes32 id);

    event AddressSet(bytes32 id, address newAddress);

    mapping(bytes32 => address) public addresses;

    constructor(address owner) {
        transferOwnership(owner);
    }

    // @dev Sets an address for an id replacing the address saved in the addresses map
    // @param id The id
    // @param newAddress The address to set
    function setAddress(bytes32 id, address newAddress) external onlyOwner {
        addresses[id] = newAddress;
        emit AddressSet(id, newAddress);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import { ICyanConduit } from "../interfaces/conduit/ICyanConduit.sol";
import { AddressProvider } from "../main/AddressProvider.sol";
import "../thirdparty/ICryptoPunk.sol";
import "../interfaces/core/IWallet.sol";
import "../interfaces/main/ICyanPeerPlan.sol";
import "./payment-plan/PaymentPlanTypes.sol";

library CyanWalletLogic {
    AddressProvider private constant addressProvider = AddressProvider(0xCF9A19D879769aDaE5e4f31503AAECDa82568E55);

    /**
     * @notice Allows operators to transfer out non locked tokens.
     *     Note: Can only transfer if token is not locked.
     * @param cyanWalletAddress Cyan Wallet address
     * @param to Receiver address
     * @param item Transferring item
     */
    function transferNonLockedItem(
        address cyanWalletAddress,
        address to,
        Item calldata item
    ) external {
        _transferNonLockedItem(cyanWalletAddress, to, item.contractAddress, item.tokenId, item.amount, item.itemType);
    }

    /**
     * @notice Allows operators to transfer out non locked tokens.
     *     Note: Can only transfer if token is not locked.
     * @param cyanWalletAddress Cyan Wallet address
     * @param to Receiver address
     * @param item Transferring item
     */
    function transferNonLockedItem(
        address cyanWalletAddress,
        address to,
        ICyanPeerPlan.Item calldata item
    ) external {
        _transferNonLockedItem(cyanWalletAddress, to, item.contractAddress, item.tokenId, item.amount, item.itemType);
    }

    function _transferNonLockedItem(
        address cyanWalletAddress,
        address to,
        address collection,
        uint256 tokenId,
        uint256 amount,
        uint8 itemType
    ) private {
        IWallet wallet = IWallet(cyanWalletAddress);
        if (itemType == 1) {
            // ERC721
            wallet.executeModule(
                abi.encodeWithSelector(IWallet.transferNonLockedERC721.selector, collection, tokenId, to)
            );
        } else if (itemType == 2) {
            // ERC1155
            wallet.executeModule(
                abi.encodeWithSelector(IWallet.transferNonLockedERC1155.selector, collection, tokenId, amount, to)
            );
        } else if (itemType == 3) {
            // CryptoPunks
            wallet.executeModule(abi.encodeWithSelector(IWallet.transferNonLockedCryptoPunk.selector, tokenId, to));
        } else {
            revert InvalidItem();
        }
    }

    /**
     * @notice Transfers token to CyanWallet and locks it
     * @param from From address
     * @param cyanWalletAddress Cyan Wallet address
     * @param item Transferring item
     */
    function transferItemAndLock(
        address from,
        address cyanWalletAddress,
        Item calldata item
    ) external {
        _transferItemAndLock(from, cyanWalletAddress, item.contractAddress, item.tokenId, item.amount, item.itemType);
    }

    /**
     * @notice Transfers token to CyanWallet and locks it
     * @param from From address
     * @param cyanWalletAddress Cyan Wallet address
     * @param item Transferring item
     */
    function transferItemAndLock(
        address from,
        address cyanWalletAddress,
        ICyanPeerPlan.Item calldata item
    ) external {
        _transferItemAndLock(from, cyanWalletAddress, item.contractAddress, item.tokenId, item.amount, item.itemType);
    }

    function _transferItemAndLock(
        address from,
        address cyanWalletAddress,
        address collection,
        uint256 tokenId,
        uint256 amount,
        uint8 itemType
    ) private {
        if (itemType == 3) {
            // CryptoPunks
            ICryptoPunk cryptoPunkContract = ICryptoPunk(collection);
            if (cryptoPunkContract.punkIndexToAddress(tokenId) != from) revert InvalidItem();
            cryptoPunkContract.buyPunk{ value: 0 }(tokenId);
            cryptoPunkContract.transferPunk(cyanWalletAddress, tokenId);
        } else {
            ICyanConduit conduit = ICyanConduit(addressProvider.addresses("CYAN_CONDUIT"));
            if (itemType == 1) {
                conduit.transferERC721(from, cyanWalletAddress, collection, tokenId);
            } else if (itemType == 2) {
                conduit.transferERC1155(from, cyanWalletAddress, collection, tokenId, amount);
            } else {
                revert InvalidItem();
            }
        }

        _setLockState(cyanWalletAddress, collection, tokenId, amount, itemType, true);
    }

    /**
     * @notice Update locking status of a token in Cyan Wallet
     * @param cyanWalletAddress Cyan Wallet address
     * @param item Locking/unlocking item
     * @param state Token will be locked if true
     */
    function setLockState(
        address cyanWalletAddress,
        Item calldata item,
        bool state
    ) public {
        _setLockState(cyanWalletAddress, item.contractAddress, item.tokenId, item.amount, item.itemType, state);
    }

    /**
     * @notice Update locking status of a token in Cyan Wallet
     * @param cyanWalletAddress Cyan Wallet address
     * @param item Locking/unlocking item
     * @param state Token will be locked if true
     */
    function setLockState(
        address cyanWalletAddress,
        ICyanPeerPlan.Item calldata item,
        bool state
    ) public {
        _setLockState(cyanWalletAddress, item.contractAddress, item.tokenId, item.amount, item.itemType, state);
    }

    function _setLockState(
        address cyanWalletAddress,
        address collection,
        uint256 tokenId,
        uint256 amount,
        uint8 itemType,
        bool state
    ) private {
        IWallet wallet = IWallet(cyanWalletAddress);
        if (itemType == 1) {
            // ERC721
            wallet.executeModule(
                abi.encodeWithSelector(IWallet.setLockedERC721Token.selector, collection, tokenId, state)
            );
        } else if (itemType == 2) {
            // ERC1155
            wallet.executeModule(
                abi.encodeWithSelector(
                    state ? IWallet.increaseLockedERC1155Token.selector : IWallet.decreaseLockedERC1155Token.selector,
                    collection,
                    tokenId,
                    amount
                )
            );
        } else if (itemType == 3) {
            // CryptoPunks
            wallet.executeModule(abi.encodeWithSelector(IWallet.setLockedCryptoPunk.selector, tokenId, state));
        } else {
            revert InvalidItem();
        }
    }

    /**
     * @notice Triggers Cyan Wallet's autoPay method
     * @param cyanWalletAddress Cyan Wallet address
     * @param planId Payment plan ID
     * @param amount Pay amount for the plan
     * @param autoRepayStatus Auto repayment status
     */
    function executeAutoPay(
        address cyanWalletAddress,
        uint256 planId,
        uint256 amount,
        uint8 autoRepayStatus
    ) external {
        IWallet(cyanWalletAddress).executeModule(
            abi.encodeWithSelector(IWallet.autoPay.selector, planId, amount, autoRepayStatus)
        );
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

import "../../interfaces/core/IFactory.sol";
import "../../interfaces/main/ICyanVaultV2.sol";
import "../../interfaces/main/ICyanPaymentPlanV2.sol";
import "../../interfaces/conduit/ICyanConduit.sol";

import "../AddressProvider.sol";
import "./PaymentPlanV2Logic.sol";
import "../CyanWalletLogic.sol";

/// @title Cyan Payment Plan - Main logic of BNPL and Pawn plan
/// @author Bulgantamir Gankhuyag - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
contract CyanPaymentPlanV2 is ICyanPaymentPlanV2, AccessControlUpgradeable, ReentrancyGuardUpgradeable {
    AddressProvider private constant addressProvider = AddressProvider(0xCF9A19D879769aDaE5e4f31503AAECDa82568E55);

    using SafeERC20Upgradeable for IERC20Upgradeable;

    event CreatedBNPL(uint256 indexed planId);
    event CreatedPawn(uint256 indexed planId, PawnCreateType createType);
    event UpdatedBNPL(uint256 indexed planId, PaymentPlanStatus indexed planStatus);
    event LiquidatedPaymentPlan(uint256 indexed planId, uint256 indexed estimatedPrice, uint256 indexed unpaidAmount);
    event Paid(uint256 indexed planId);
    event Completed(uint256 indexed planId);
    event CompletedByRevival(uint256 indexed planId, uint256 penaltyAmount);
    event CompletedEarly(uint256 indexed planId, uint8 indexed paidNumOfPayment);
    event EarlyUnwind(uint256 indexed planId);
    event Revived(uint256 indexed planId, uint256 penaltyAmount);
    event UpdatedCyanSigner(address indexed signer);
    event ClaimedServiceFee(address indexed currency, uint256 indexed amount);
    event UpdatedWalletFactory(address indexed factory);
    event SetAutoRepayStatus(uint256 indexed planId, uint8 indexed autoRepayStatus);

    mapping(uint256 => Item) public items;
    mapping(uint256 => PaymentPlan) public paymentPlan;
    mapping(address => uint256) public claimableServiceFee;

    bytes32 private constant CYAN_ROLE = keccak256("CYAN_ROLE");
    bytes32 private constant CYAN_AUTO_OPERATOR_ROLE = keccak256("CYAN_AUTO_OPERATOR_ROLE");
    bytes32 private constant CYAN_CONDUIT = "CYAN_CONDUIT";
    address private cyanSigner;
    address private walletFactory;
    uint256 private __unused; // unused variable to prevent storage slot collision

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(
        address _cyanSigner,
        address _cyanSuperAdmin,
        address _walletFactory
    ) external initializer {
        if (_cyanSigner == address(0) || _cyanSuperAdmin == address(0) || _walletFactory == address(0)) {
            revert InvalidAddress();
        }

        cyanSigner = _cyanSigner;
        walletFactory = _walletFactory;
        _setupRole(DEFAULT_ADMIN_ROLE, _cyanSuperAdmin);

        __AccessControl_init();
        __ReentrancyGuard_init();

        emit UpdatedCyanSigner(_cyanSigner);
        emit UpdatedWalletFactory(_walletFactory);
    }

    /**
     * @notice Creating a BNPL plan
     * @param item Item detail to BNPL
     * @param plan BNPL plan detail
     * @param planId Plan ID
     * @param signatureExpiryDate Signed expiry date
     * @param signature Signature from Cyan
     */
    function createBNPL(
        Item calldata item,
        Plan calldata plan,
        uint256 planId,
        uint256 signatureExpiryDate,
        bytes memory signature
    ) external payable nonReentrant {
        PaymentPlanV2Logic.requireCorrectPlanParams(true, item, plan, signatureExpiryDate);
        PaymentPlanV2Logic.verifySignature(item, plan, planId, signatureExpiryDate, block.chainid, cyanSigner, signature);

        if (paymentPlan[planId].plan.totalNumberOfPayments != 0) revert PaymentPlanAlreadyExists();

        (PaymentAmountInfo memory singleAmounts, , uint256 downPaymentAmount, ) = PaymentPlanV2Logic
            .calculatePaymentInfo(plan);

        address currencyAddress = PaymentPlanV2Logic.getCurrencyAddressByVaultAddress(item.cyanVaultAddress);
        receiveCurrency(currencyAddress, singleAmounts.serviceAmount + downPaymentAmount, msg.sender);

        address cyanWalletAddress = IFactory(walletFactory).getOrDeployWallet(msg.sender);
        paymentPlan[planId] = PaymentPlan(plan, block.timestamp, cyanWalletAddress, PaymentPlanStatus.BNPL_CREATED);
        items[planId] = item;
        emit CreatedBNPL(planId);
    }

    /**
     * @notice Lending loaned currency from Vault for BNPL payment plan
     * @param planIds Payment plan IDs
     */
    function fundBNPL(uint256[] calldata planIds) external nonReentrant onlyRole(CYAN_ROLE) {
        for (uint256 i; i < planIds.length; ++i) {
            uint256 planId = planIds[i];
            PaymentPlan storage _paymentPlan = paymentPlan[planId];
            ICyanVaultV2(payable(items[planId].cyanVaultAddress)).lend(msg.sender, _paymentPlan.plan.amount);

            if (_paymentPlan.plan.counterPaidPayments != 1) revert InvalidPaidCount();
            if (_paymentPlan.status != PaymentPlanStatus.BNPL_CREATED) revert InvalidStage();

            _paymentPlan.status = PaymentPlanStatus.BNPL_FUNDED;
            emit UpdatedBNPL(planId, PaymentPlanStatus.BNPL_FUNDED);
        }
    }

    /**
     * @notice Activate BNPL payment plan
     * @param planIds Payment plan IDs
     */
    function activateBNPL(uint256[] calldata planIds) external nonReentrant onlyRole(CYAN_ROLE) {
        for (uint256 i; i < planIds.length; ++i) {
            uint256 planId = planIds[i];
            PaymentPlan storage _paymentPlan = paymentPlan[planId];
            Item memory item = items[planId];

            uint256 serviceAmount = PaymentPlanV2Logic.activate(_paymentPlan, item);
            address currencyAddress = PaymentPlanV2Logic.getCurrencyAddressByVaultAddress(item.cyanVaultAddress);
            claimableServiceFee[currencyAddress] += serviceAmount;

            CyanWalletLogic.transferItemAndLock(msg.sender, _paymentPlan.cyanWalletAddress, item);
            emit UpdatedBNPL(planId, PaymentPlanStatus.BNPL_ACTIVE);
        }
    }

    /**
     * @notice Rejecting a BNPL payment plan
     * @param planId Payment Plan ID
     */
    function rejectBNPL(uint256 planId) external payable nonReentrant onlyRole(CYAN_ROLE) {
        PaymentPlan storage _paymentPlan = paymentPlan[planId];
        if (_paymentPlan.plan.counterPaidPayments != 1) revert InvalidPaidCount();
        if (
            _paymentPlan.status != PaymentPlanStatus.BNPL_CREATED &&
            _paymentPlan.status != PaymentPlanStatus.BNPL_FUNDED
        ) {
            revert InvalidStage();
        }

        (PaymentAmountInfo memory singleAmounts, , uint256 downPaymentAmount, ) = PaymentPlanV2Logic
            .calculatePaymentInfo(_paymentPlan.plan);

        // Returning downpayment to created user address
        address currencyAddress = getCurrencyAddressByPlanId(planId);
        address createdUserAddress = getMainWalletAddress(_paymentPlan.cyanWalletAddress);
        sendCurrency(currencyAddress, downPaymentAmount + singleAmounts.serviceAmount, createdUserAddress);
        if (_paymentPlan.status == PaymentPlanStatus.BNPL_FUNDED) {
            receiveCurrency(currencyAddress, _paymentPlan.plan.amount, msg.sender);

            // Returning funded amount back to Vault
            PaymentPlanV2Logic.transferEarnedAmountToCyanVault(
                items[planId].cyanVaultAddress,
                _paymentPlan.plan.amount,
                0
            );
        } else if (msg.value > 0) {
            revert InvalidAmount();
        }
        _paymentPlan.status = PaymentPlanStatus.BNPL_REJECTED;
        emit UpdatedBNPL(planId, PaymentPlanStatus.BNPL_REJECTED);
    }

    function createPawn(
        Item calldata item,
        Plan calldata plan,
        uint256 planId,
        uint256 signatureExpiryDate,
        bytes memory signature
    ) external nonReentrant {
        createPawn(item, plan, planId, PawnCreateType.REGULAR, signatureExpiryDate, signature);
    }

    function createPawnFromBendDao(
        Item calldata item,
        Plan calldata plan,
        uint256 planId,
        uint256 signatureExpiryDate,
        bytes memory signature
    ) external nonReentrant {
        createPawn(item, plan, planId, PawnCreateType.BEND_DAO, signatureExpiryDate, signature);
    }

    function createPawnByRefinance(
        Item calldata item,
        Plan calldata plan,
        uint256 planId,
        uint256 existingPlanId,
        uint256 signatureExpiryDate,
        bytes memory signature
    ) external payable nonReentrant {
        requireActivePlan(existingPlanId);
        PaymentPlan storage existingPaymentPlan = paymentPlan[existingPlanId];

        address lenderMainWalletAddress = checkIsPlanOwner(msg.sender, existingPaymentPlan.cyanWalletAddress);

        // check item and current plan's item
        Item memory existingPlanItem = items[existingPlanId];
        if (
            !(existingPlanItem.tokenId == item.tokenId &&
                existingPlanItem.contractAddress == item.contractAddress &&
                existingPlanItem.itemType == item.itemType &&
                existingPlanItem.amount == item.amount)
        ) revert InvalidItem();

        // check current plan currency and requested loan currency
        address currencyAddress = PaymentPlanV2Logic.getCurrencyAddressByVaultAddress(item.cyanVaultAddress);
        if (currencyAddress != getCurrencyAddressByPlanId(existingPlanId)) revert InvalidCurrency();

        (
            uint256 payAmountForCollateral,
            uint256 payAmountForInterest,
            uint256 payAmountForService,
            uint256 currentPayment,

        ) = getPaymentInfoByPlanId(existingPlanId, true);

        // creating new plan and lending requested loan amount to payment plan
        createPawn(item, plan, planId, PawnCreateType.REFINANCE, signatureExpiryDate, signature);

        // completing previous active plan
        if (plan.amount < currentPayment) {
            receiveCurrency(currencyAddress, currentPayment - plan.amount, msg.sender);
        } else {
            uint256 transferAmountToUser = plan.amount - currentPayment;
            if (transferAmountToUser > 0) {
                sendCurrency(currencyAddress, transferAmountToUser, lenderMainWalletAddress);
            }
        }

        claimableServiceFee[currencyAddress] += payAmountForService;
        PaymentPlanV2Logic.transferEarnedAmountToCyanVault(
            existingPlanItem.cyanVaultAddress,
            payAmountForCollateral,
            payAmountForInterest
        );

        emit CompletedEarly(
            existingPlanId,
            existingPaymentPlan.plan.totalNumberOfPayments - existingPaymentPlan.plan.counterPaidPayments
        );

        completePaymentPlan(existingPaymentPlan);
    }

    /**
     * @notice Internal function that creates a pawn plan
     * @param item Item detail to pawn
     * @param plan Pawn plan detail
     * @param planId Plan ID
     * @param signatureExpiryDate Signature expiry date
     * @param signature Signature from Cyan
     */
    function createPawn(
        Item calldata item,
        Plan calldata plan,
        uint256 planId,
        PawnCreateType createType,
        uint256 signatureExpiryDate,
        bytes memory signature
    ) private {
        if (paymentPlan[planId].plan.totalNumberOfPayments != 0) revert PaymentPlanAlreadyExists();
        address cyanWalletAddress = IFactory(walletFactory).getOrDeployWallet(msg.sender);
        address mainAddress = msg.sender;
        if (cyanWalletAddress == msg.sender) {
            mainAddress = getMainWalletAddress(cyanWalletAddress);
        }

        bool isTransferRequired = PaymentPlanV2Logic.createPawn(
            item,
            plan,
            planId,
            createType,
            signatureExpiryDate,
            mainAddress,
            cyanWalletAddress,
            cyanSigner,
            signature
        );

        if (createType != PawnCreateType.REFINANCE) {
            if (isTransferRequired) {
                CyanWalletLogic.transferItemAndLock(mainAddress, cyanWalletAddress, item);
            } else {
                CyanWalletLogic.setLockState(cyanWalletAddress, item, true);
            }
        }

        items[planId] = item;
        paymentPlan[planId] = PaymentPlan(plan, block.timestamp, cyanWalletAddress, PaymentPlanStatus.PAWN_ACTIVE);

        emit CreatedPawn(planId, createType);
    }

    /**
     * @notice Make a payment for the payment plan
     * @param planId Payment Plan ID
     * @param isEarlyPayment If true, payment will be made for the whole plan
     */
    function pay(uint256 planId, bool isEarlyPayment) external payable nonReentrant {
        requireActivePlan(planId);
        PaymentPlan storage _paymentPlan = paymentPlan[planId];

        uint8 numOfRemainingPayments = _paymentPlan.plan.totalNumberOfPayments - _paymentPlan.plan.counterPaidPayments;
        bool shouldComplete = isEarlyPayment || numOfRemainingPayments == 1;

        (
            uint256 payAmountForCollateral,
            uint256 payAmountForInterest,
            uint256 payAmountForService,
            uint256 currentPayment,

        ) = getPaymentInfoByPlanId(planId, shouldComplete);

        address currencyAddress = getCurrencyAddressByPlanId(planId);
        receiveCurrency(currencyAddress, currentPayment, msg.sender);

        claimableServiceFee[currencyAddress] += payAmountForService;
        PaymentPlanV2Logic.transferEarnedAmountToCyanVault(
            items[planId].cyanVaultAddress,
            payAmountForCollateral,
            payAmountForInterest
        );

        if (shouldComplete) {
            completePaymentPlan(_paymentPlan);
            CyanWalletLogic.setLockState(_paymentPlan.cyanWalletAddress, items[planId], false);

            if (isEarlyPayment) {
                emit CompletedEarly(planId, numOfRemainingPayments);
            } else {
                emit Completed(planId);
            }
        } else {
            ++_paymentPlan.plan.counterPaidPayments;
            emit Paid(planId);
        }
    }

    /**
     * @notice Liquidate defaulted payment plan
     * @param planId Payment Plan ID
     * @param apePlanIds Array of ape plan Ids [BAYC/MAYC Ape Plan ID, BAKC Ape Plan ID]
     * @param estimatedValue Estimated value of defaulted assets
     */
    function liquidate(
        uint256 planId,
        uint256[2] calldata apePlanIds,
        uint256 estimatedValue
    ) external nonReentrant {
        if (estimatedValue == 0) revert InvalidAmount();

        PaymentPlan storage _paymentPlan = paymentPlan[planId];
        Item memory _item = items[planId];

        if (msg.sender == _item.cyanVaultAddress) {
            requireActivePlan(planId);
        } else {
            if (!hasRole(CYAN_ROLE, msg.sender)) {
                revert InvalidSender();
            }
            requireDefaultedPlan(planId);
        }

        PaymentPlanV2Logic.checkAndCompleteApePlans(
            _paymentPlan.cyanWalletAddress,
            _item.contractAddress,
            _item.tokenId,
            apePlanIds
        );

        (uint256 unpaidAmount, , , , ) = getPaymentInfoByPlanId(planId, true);

        CyanWalletLogic.setLockState(_paymentPlan.cyanWalletAddress, _item, false);
        CyanWalletLogic.transferNonLockedItem(_paymentPlan.cyanWalletAddress, _item.cyanVaultAddress, _item);

        _paymentPlan.status = isBNPL(_paymentPlan.status)
            ? PaymentPlanStatus.BNPL_LIQUIDATED
            : PaymentPlanStatus.PAWN_LIQUIDATED;
        ICyanVaultV2(payable(_item.cyanVaultAddress)).nftDefaulted(unpaidAmount, estimatedValue);

        emit LiquidatedPaymentPlan(planId, estimatedValue, unpaidAmount);
    }

    /**
     * @notice Triggers auto repayment from the cyan wallet
     * @param planId Payment Plan ID
     */
    function triggerAutoRepay(uint256 planId) external onlyRole(CYAN_AUTO_OPERATOR_ROLE) {
        uint8 autoRepayStatus = paymentPlan[planId].plan.autoRepayStatus;
        if (autoRepayStatus != 1 && autoRepayStatus != 2) revert InvalidAutoRepaymentStatus();
        requireActivePlan(planId);

        (, , , uint256 payAmount, uint256 dueDate) = getPaymentInfoByPlanId(planId, false);
        if ((dueDate - 1 days) > block.timestamp) revert InvalidAutoRepaymentDate();

        address cyanWalletAddress = paymentPlan[planId].cyanWalletAddress;
        if (autoRepayStatus == 2) {
            // Auto-repay from main wallet
            address mainWalletAddress = getMainWalletAddress(cyanWalletAddress);
            address currencyAddress = getCurrencyAddressByPlanId(planId);
            ICyanConduit conduit = ICyanConduit(addressProvider.addresses(CYAN_CONDUIT));

            // Using WETH when currency is native currency
            if (currencyAddress == address(0)) {
                currencyAddress = addressProvider.addresses("WETH");
            }

            conduit.transferERC20(mainWalletAddress, cyanWalletAddress, currencyAddress, payAmount);
        }
        CyanWalletLogic.executeAutoPay(cyanWalletAddress, planId, payAmount, autoRepayStatus);
    }

    /**
     * @notice Early unwind the plan by Opensea offer
     * @param planId Payment Plan ID
     * @param sellPrice Sell price of the token
     * @param offer Offer data to fulfill seaport order
     */
    function earlyUnwindOpensea(
        uint256 planId,
        uint256[2] calldata apePlanIds,
        uint256 sellPrice,
        bytes calldata offer,
        uint256 signatureExpiryDate,
        bytes memory signature
    ) external nonReentrant {
        if (signatureExpiryDate < block.timestamp) revert InvalidSignature();
        PaymentPlanV2Logic.verifyEarlyUnwindByOpeanseaSignature(
            planId,
            sellPrice,
            offer,
            signatureExpiryDate,
            block.chainid,
            cyanSigner,
            signature
        );
        earlyUnwind(planId, apePlanIds, sellPrice, offer, address(0));
    }

    /**
     * @notice Early unwind the plan by Cyan offer
     * @param planId Payment Plan ID
     * @param sellPrice Sell price of the token
     * @param signatureExpiryDate Signature expiry date
     * @param cyanBuyerAddress Buyer address from Cyan
     * @param signature Signature signed by Cyan buyer
     */
    function earlyUnwindCyan(
        uint256 planId,
        uint256[2] calldata apePlanIds,
        uint256 sellPrice,
        address cyanBuyerAddress,
        uint256 signatureExpiryDate,
        bytes memory signature
    ) external nonReentrant {
        if (signatureExpiryDate < block.timestamp) revert InvalidSignature();
        PaymentPlanV2Logic.verifyEarlyUnwindByCyanSignature(
            planId,
            sellPrice,
            signatureExpiryDate,
            block.chainid,
            cyanBuyerAddress,
            signature
        );
        if (!hasRole(CYAN_ROLE, cyanBuyerAddress)) revert InvalidCyanBuyer();

        bytes memory offer; // creating empty offer data
        earlyUnwind(planId, apePlanIds, sellPrice, offer, cyanBuyerAddress);
    }

    /**
     * @notice Internal function to handle the common logic of early unwind operations
     * @param planId Payment Plan ID
     * @param sellPrice Sell price of the token
     * @param offer Offer data to fulfill seaport order
     * @param cyanBuyerAddress Buyer address from Cyan
     */
    function earlyUnwind(
        uint256 planId,
        uint256[2] calldata apePlanIds,
        uint256 sellPrice,
        bytes memory offer,
        address cyanBuyerAddress
    ) private {
        PaymentPlan storage _paymentPlan = paymentPlan[planId];
        Item memory _item = items[planId];
        requireActivePlan(planId);

        address currencyAddress = getCurrencyAddressByPlanId(planId);

        (
            uint256 payAmountForCollateral,
            uint256 payAmountForInterest,
            uint256 payAmountForService,
            uint256 currentPayment,

        ) = getPaymentInfoByPlanId(planId, true);

        if (msg.sender != _item.cyanVaultAddress) {
            checkIsPlanOwner(msg.sender, _paymentPlan.cyanWalletAddress);
        } else {
            if (currentPayment > sellPrice) revert InvalidAmount();
        }

        PaymentPlanV2Logic.checkAndCompleteApePlans(
            _paymentPlan.cyanWalletAddress,
            _item.contractAddress,
            _item.tokenId,
            apePlanIds
        );

        CyanWalletLogic.setLockState(_paymentPlan.cyanWalletAddress, _item, false);
        if (cyanBuyerAddress == address(0)) {
            if (currencyAddress != address(0)) revert InvalidCurrency();
            IWallet(_paymentPlan.cyanWalletAddress).executeModule(
                abi.encodeWithSelector(IWallet.earlyUnwindOpensea.selector, currentPayment, sellPrice, _item, offer)
            );
        } else {
            ICyanConduit(addressProvider.addresses(CYAN_CONDUIT)).transferERC20(
                cyanBuyerAddress,
                _paymentPlan.cyanWalletAddress,
                currencyAddress == address(0) ? addressProvider.addresses("WETH") : currencyAddress,
                sellPrice
            );
            IWallet(_paymentPlan.cyanWalletAddress).executeModule(
                abi.encodeWithSelector(IWallet.earlyUnwindCyan.selector, currentPayment, currencyAddress)
            );
            CyanWalletLogic.transferNonLockedItem(_paymentPlan.cyanWalletAddress, cyanBuyerAddress, _item);
        }

        PaymentPlanV2Logic.receiveCurrencyFromCyanWallet(
            currencyAddress,
            _paymentPlan.cyanWalletAddress,
            currentPayment
        );

        claimableServiceFee[currencyAddress] += payAmountForService;
        PaymentPlanV2Logic.transferEarnedAmountToCyanVault(
            _item.cyanVaultAddress,
            payAmountForCollateral,
            payAmountForInterest
        );
        completePaymentPlan(_paymentPlan);

        emit EarlyUnwind(planId);
    }

    receive() external payable {}

/**
     * @notice Revive defaulted payment plan with penalty
     * @param planId Payment Plan ID
     * @param penaltyAmount Amount that penalizes Defaulted plan revival
     * @param signatureExpiryDate Signature expiry date
     * @param signature Signature signed by Cyan signer
     */
    function revive(
        uint256 planId,
        uint256 penaltyAmount,
        uint256 signatureExpiryDate,
        bytes memory signature
    ) external payable nonReentrant {
        PaymentPlan storage _paymentPlan = paymentPlan[planId];
        if (signatureExpiryDate < block.timestamp) revert InvalidReviveDate();
        PaymentPlanV2Logic.verifyRevivalSignature(
            planId,
            penaltyAmount,
            signatureExpiryDate,
            block.chainid,
            _paymentPlan.plan.counterPaidPayments,
            cyanSigner,
            signature
        );
        requireDefaultedPlan(planId);

        (
            uint256 payAmountForCollateral,
            uint256 payAmountForInterest,
            uint256 payAmountForService,
            uint256 currentPayment,
            uint256 dueDate
        ) = getPaymentInfoByPlanId(planId, false);
        if (dueDate + _paymentPlan.plan.term <= block.timestamp) revert InvalidReviveDate();

        address currencyAddress = getCurrencyAddressByPlanId(planId);
        receiveCurrency(currencyAddress, currentPayment + penaltyAmount, msg.sender);

        claimableServiceFee[currencyAddress] += payAmountForService;
        PaymentPlanV2Logic.transferEarnedAmountToCyanVault(
            items[planId].cyanVaultAddress,
            payAmountForCollateral,
            payAmountForInterest + penaltyAmount
        );
        if (_paymentPlan.plan.counterPaidPayments + 1 == _paymentPlan.plan.totalNumberOfPayments) {
            completePaymentPlan(_paymentPlan);
            CyanWalletLogic.setLockState(_paymentPlan.cyanWalletAddress, items[planId], false);
            emit CompletedByRevival(planId, penaltyAmount);
        } else {
            ++_paymentPlan.plan.counterPaidPayments;
            emit Revived(planId, penaltyAmount);
        }
    }

    function getPaymentInfoByPlanId(uint256 planId, bool isEarlyPayment)
        public
        view
        returns (
            uint256,
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        Plan memory plan = paymentPlan[planId].plan;
        if (plan.totalNumberOfPayments == 0) revert PaymentPlanNotFound();

        return PaymentPlanV2Logic.getPaymentInfo(plan, isEarlyPayment, paymentPlan[planId].createdDate);
    }

    /**
     * @notice Check if payment plan is pending
     * @param planId Payment Plan ID
     * @return PaymentPlanStatus
     */
    function getPlanStatus(uint256 planId) public view returns (PaymentPlanStatus) {
        if (
            paymentPlan[planId].status == PaymentPlanStatus.BNPL_ACTIVE ||
            paymentPlan[planId].status == PaymentPlanStatus.PAWN_ACTIVE
        ) {
            (, , , , uint256 dueDate) = getPaymentInfoByPlanId(planId, false);
            bool isDefaulted = block.timestamp > dueDate;

            if (isDefaulted) {
                return
                    paymentPlan[planId].status == PaymentPlanStatus.BNPL_ACTIVE
                        ? PaymentPlanStatus.BNPL_DEFAULTED
                        : PaymentPlanStatus.PAWN_DEFAULTED;
            }
        }

        return paymentPlan[planId].status;
    }

    /**
     * @notice Updating Cyan signer address
     * @param _cyanSigner New Cyan signer address
     */
    function updateCyanSignerAddress(address _cyanSigner) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (_cyanSigner == address(0)) revert InvalidAddress();
        cyanSigner = _cyanSigner;
        emit UpdatedCyanSigner(_cyanSigner);
    }

    /**
     * @notice Claiming collected service fee amount
     * @param currencyAddress Currency address
     */
    function claimServiceFee(address currencyAddress) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
        uint256 amount = claimableServiceFee[currencyAddress];
        sendCurrency(currencyAddress, amount, msg.sender);
        claimableServiceFee[currencyAddress] = 0;
        emit ClaimedServiceFee(currencyAddress, amount);
    }

    /**
     * @notice Updating Cyan wallet factory address that used for deploying new wallets
     * @param factory New Cyan wallet factory address
     */
    function updateWalletFactoryAddress(address factory) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (factory == address(0)) revert InvalidAddress();
        walletFactory = factory;
        emit UpdatedWalletFactory(factory);
    }

    /**
     * @notice Setting auto repay status for a payment plan
     * @param planId Payment plan ID
     * @param autoRepayStatus Auto repay status
     */
    function setAutoRepayStatus(uint256 planId, uint8 autoRepayStatus) external {
        checkIsPlanOwner(msg.sender, paymentPlan[planId].cyanWalletAddress);

        paymentPlan[planId].plan.autoRepayStatus = autoRepayStatus;
        emit SetAutoRepayStatus(planId, autoRepayStatus);
    }

    /**
     * @notice Getting currency address by plan ID
     * @param planId Payment plan ID
     */
    function getCurrencyAddressByPlanId(uint256 planId) public view returns (address) {
        return PaymentPlanV2Logic.getCurrencyAddressByVaultAddress(items[planId].cyanVaultAddress);
    }

    /**
     * @notice Getting main wallet address by Cyan wallet address
     * @param cyanWalletAddress Cyan wallet address
     */
    function getMainWalletAddress(address cyanWalletAddress) private view returns (address) {
        return IFactory(walletFactory).getWalletOwner(cyanWalletAddress);
    }

    function requireActivePlan(uint256 planId) private view {
        PaymentPlanStatus status = getPlanStatus(planId);
        if (status != PaymentPlanStatus.BNPL_ACTIVE && status != PaymentPlanStatus.PAWN_ACTIVE) revert InvalidStage();
    }

    function requireDefaultedPlan(uint256 planId) private view {
        PaymentPlanStatus status = getPlanStatus(planId);
        if (status != PaymentPlanStatus.BNPL_DEFAULTED && status != PaymentPlanStatus.PAWN_DEFAULTED)
            revert InvalidStage();
    }

    /**
     * @notice Marks a payment plan as completed.
     * @param _paymentPlan A reference to the PaymentPlan structure being completed
     */
    function completePaymentPlan(PaymentPlan storage _paymentPlan) private {
        _paymentPlan.plan.counterPaidPayments = _paymentPlan.plan.totalNumberOfPayments;
        _paymentPlan.status = isBNPL(_paymentPlan.status)
            ? PaymentPlanStatus.BNPL_COMPLETED
            : PaymentPlanStatus.PAWN_COMPLETED;
    }

    /**
     * @notice Return true if plan is BNPL by checking status
     * @param status Payment plan status
     * @return Is BNPL
     */
    function isBNPL(PaymentPlanStatus status) private pure returns (bool) {
        return
            status == PaymentPlanStatus.BNPL_CREATED ||
            status == PaymentPlanStatus.BNPL_FUNDED ||
            status == PaymentPlanStatus.BNPL_ACTIVE ||
            status == PaymentPlanStatus.BNPL_DEFAULTED ||
            status == PaymentPlanStatus.BNPL_REJECTED ||
            status == PaymentPlanStatus.BNPL_COMPLETED ||
            status == PaymentPlanStatus.BNPL_LIQUIDATED;
    }

    /**
     * @notice Receives currency for transaction. Supports both native and ERC20 tokens.
     * @param currency The address of the currency (address(0) for native, token address for ERC20).
     * @param amount The amount of currency to receive
     * @param from The sender's address
     */
    function receiveCurrency(
        address currency,
        uint256 amount,
        address from
    ) private {
        if (currency == address(0)) {
            if (amount != msg.value) revert InvalidAmount();
        } else {
            if (msg.value != 0) revert InvalidAmount();
            ICyanConduit(addressProvider.addresses(CYAN_CONDUIT)).transferERC20(from, address(this), currency, amount);
        }
    }

    /**
     * @notice Sends currency to a specified address. Supports both native and ERC20 tokens.
     * @param currency The address of the currency (address(0) for native, token address for ERC20).
     * @param amount The amount of currency to send
     * @param to The recipient's address
     */
    function sendCurrency(
        address currency,
        uint256 amount,
        address to
    ) private {
        if (currency == address(0)) {
            (bool success, ) = payable(to).call{ value: amount }("");
            if (!success) revert EthTransferFailed();
            return;
        } else {
            IERC20Upgradeable erc20Contract = IERC20Upgradeable(currency);
            erc20Contract.safeTransfer(to, amount);
        }
    }

    function checkIsPlanOwner(address sender, address planCyanWallet) private view returns (address) {
        address _sender = sender;
        if (sender != planCyanWallet) {
            _sender = getMainWalletAddress(planCyanWallet);
            if (sender != _sender) revert InvalidSender();
        }
        return _sender;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// DataTypes
enum PawnCreateType {
    REGULAR,
    BEND_DAO,
    REFINANCE
}
enum PaymentPlanStatus {
    BNPL_CREATED,
    BNPL_FUNDED,
    BNPL_ACTIVE,
    BNPL_DEFAULTED,
    BNPL_REJECTED,
    BNPL_COMPLETED,
    BNPL_LIQUIDATED,
    PAWN_ACTIVE,
    PAWN_DEFAULTED,
    PAWN_COMPLETED,
    PAWN_LIQUIDATED
}
struct Plan {
    uint256 amount;
    uint32 downPaymentPercent;
    uint32 interestRate;
    uint32 serviceFeeRate;
    uint32 term;
    uint8 totalNumberOfPayments;
    uint8 counterPaidPayments;
    uint8 autoRepayStatus;
}
struct PaymentPlan {
    Plan plan;
    uint256 createdDate;
    address cyanWalletAddress;
    PaymentPlanStatus status;
}

struct Item {
    uint256 amount;
    uint256 tokenId;
    address contractAddress;
    address cyanVaultAddress;
    // 1 -> ERC721
    // 2 -> ERC1155
    // 3 -> CryptoPunks
    uint8 itemType;
}

struct PaymentAmountInfo {
    uint256 loanAmount;
    uint256 interestAmount;
    uint256 serviceAmount;
}

// Errors
error InvalidSender();
error InvalidBlockNumber();
error InvalidSignature();
error InvalidServiceFeeRate();
error InvalidTokenPrice();
error InvalidInterestRate();
error InvalidDownPaymentPercent();
error InvalidDownPayment();
error InvalidAmount();
error InvalidTerm();
error InvalidPaidCount();
error InvalidStage();
error InvalidAddress();
error InvalidAutoRepaymentDate();
error InvalidAutoRepaymentStatus();
error InvalidTotalNumberOfPayments();
error InvalidReviveDate();
error InvalidItem();
error InvalidBaseDiscountRate();
error InvalidApeCoinPlan();
error InvalidBendDaoPlan();
error InvalidCurrency();
error InvalidCyanBuyer();
error InvalidSelector();

error EthTransferFailed();

error PaymentPlanAlreadyExists();
error PaymentPlanNotFound();

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";

import "./PaymentPlanTypes.sol";
import "../../thirdparty/ICryptoPunk.sol";
import "../../thirdparty/IWETH.sol";
import "../../interfaces/core/IWalletApeCoin.sol";
import "../../interfaces/main/ICyanVaultV2.sol";
import "../../interfaces/core/IFactory.sol";
import { ICyanConduit } from "../../interfaces/conduit/ICyanConduit.sol";
import { ILendPoolLoan as IBDaoLendPoolLoan } from "../../thirdparty/benddao/ILendPoolLoan.sol";
import { DataTypes as BDaoDataTypes } from "../../thirdparty/benddao/DataTypes.sol";
import { AddressProvider } from "../../main/AddressProvider.sol";

/// @title Cyan Core Payment Plan V2 Logic
/// @author Bulgantamir Gankhuyag - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
library PaymentPlanV2Logic {
    AddressProvider private constant addressProvider = AddressProvider(0xCF9A19D879769aDaE5e4f31503AAECDa82568E55);

    using ECDSAUpgradeable for bytes32;
    using SafeERC20Upgradeable for IERC20Upgradeable;

    function checkAndCompleteApePlans(
        address cyanWalletAddress,
        address collection,
        uint256 tokenId,
        uint256[2] calldata apePlanIds
    ) external {
        IWalletApeCoin cyanWallet = IWalletApeCoin(cyanWalletAddress);

        _checkAndCompleteApePlan(cyanWallet, apePlanIds[0], collection, tokenId);
        _checkAndCompleteApePlan(cyanWallet, apePlanIds[1], collection, tokenId);
    }

    function _checkAndCompleteApePlan(
        IWalletApeCoin cyanWallet,
        uint256 apePlanId,
        address collection,
        uint256 tokenId
    ) private {
        if (apePlanId == 0) return;

        uint8 apeLockStateBefore = cyanWallet.getApeLockState(collection, tokenId);
        cyanWallet.executeModule(abi.encodeWithSelector(IWalletApeCoin.completeApeCoinPlan.selector, apePlanId));
        uint8 apeLockStateAfter = cyanWallet.getApeLockState(collection, tokenId);

        if (apeLockStateAfter >= apeLockStateBefore) revert InvalidApeCoinPlan();
    }

    /**
     * @notice Return expected payment plan for given price and interest rate
     * @param plan Plan details
     * @return Expected down payment amount
     * @return Expected total interest fee
     * @return Expected total service fee
     * @return Estimated subsequent payments after down payment
     * @return Expected total financing amount
     */
    function getExpectedPlan(Plan calldata plan)
        external
        pure
        returns (
            uint256,
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        if (plan.totalNumberOfPayments == 0) revert InvalidTotalNumberOfPayments();
        (
            PaymentAmountInfo memory singleAmounts,
            PaymentAmountInfo memory totalAmounts,
            uint256 downPaymentAmount,

        ) = calculatePaymentInfo(plan);
        uint256 totalFinancingAmount = plan.amount + totalAmounts.interestAmount + totalAmounts.serviceAmount;

        return (
            plan.downPaymentPercent > 0 ? downPaymentAmount + singleAmounts.serviceAmount : 0,
            totalAmounts.interestAmount,
            totalAmounts.serviceAmount,
            singleAmounts.loanAmount + singleAmounts.interestAmount + singleAmounts.serviceAmount,
            totalFinancingAmount
        );
    }

    function calculatePaymentInfo(Plan memory plan)
        internal
        pure
        returns (
            PaymentAmountInfo memory singleAmounts,
            PaymentAmountInfo memory totalAmounts,
            uint256 downPaymentAmount,
            uint8 payCountWithoutDownPayment
        )
    {
        payCountWithoutDownPayment = plan.totalNumberOfPayments - (plan.downPaymentPercent > 0 ? 1 : 0);
        downPaymentAmount = (plan.amount * plan.downPaymentPercent) / 10000;

        totalAmounts.loanAmount = plan.amount - downPaymentAmount;
        totalAmounts.interestAmount = (totalAmounts.loanAmount * plan.interestRate) / 10000;
        totalAmounts.serviceAmount = (plan.amount * plan.serviceFeeRate) / 10000;

        singleAmounts.loanAmount = totalAmounts.loanAmount / payCountWithoutDownPayment;
        singleAmounts.interestAmount = totalAmounts.interestAmount / payCountWithoutDownPayment;
        singleAmounts.serviceAmount = totalAmounts.serviceAmount / plan.totalNumberOfPayments;
    }

    /**
     * @notice Return payment info
     * @param plan Plan details
     * @param isEarlyPayment Is paying early
     * @return Remaining payment amount for collateral
     * @return Remaining payment amount for interest fee
     * @return Remaining payment amount for service fee
     * @return Remaining total payment amount
     */
    function getPaymentInfo(
        Plan memory plan,
        bool isEarlyPayment,
        uint256 createdDate
    )
        external
        view
        returns (
            uint256,
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        (PaymentAmountInfo memory singleAmounts, PaymentAmountInfo memory totalAmounts, , ) = calculatePaymentInfo(
            plan
        );

        uint8 paidCountWithoutDownPayment = plan.counterPaidPayments - (plan.downPaymentPercent > 0 ? 1 : 0);
        if (
            (plan.totalNumberOfPayments == 1 && plan.downPaymentPercent == 0) ||
            (plan.totalNumberOfPayments == 2 && plan.downPaymentPercent > 0)
        ) {
            // In case of single payment plan,
            // (single payment pawn, or downpayment+single payment bnpl)
            //  User will get discount from interest fee by only paying pro-rated interest fee
            uint256 completedPercent = ((block.timestamp - createdDate + 600) / 600) < (plan.term / 600)
                ? (((block.timestamp - createdDate + 600) / 600) * 100) / (plan.term / 600)
                : 100;
            singleAmounts.interestAmount = (singleAmounts.interestAmount * completedPercent) / 100;
        } else if (isEarlyPayment || (plan.totalNumberOfPayments - plan.counterPaidPayments) == 1) {
            // In case of early repayment,
            //  User will get discount from interest fee by only paying single interest fee
            singleAmounts.loanAmount = totalAmounts.loanAmount - singleAmounts.loanAmount * paidCountWithoutDownPayment;
            singleAmounts.serviceAmount =
                totalAmounts.serviceAmount -
                singleAmounts.serviceAmount *
                plan.counterPaidPayments;
        }

        return (
            singleAmounts.loanAmount,
            singleAmounts.interestAmount,
            singleAmounts.serviceAmount,
            singleAmounts.loanAmount + singleAmounts.interestAmount + singleAmounts.serviceAmount,
            createdDate + plan.term * (paidCountWithoutDownPayment + 1)
        );
    }

    function requireCorrectPlanParams(
        bool isBNPL,
        Item calldata item,
        Plan calldata plan,
        uint256 signatureExpiryDate
    ) public view {
        if (item.contractAddress == address(0)) revert InvalidAddress();
        if (item.cyanVaultAddress == address(0)) revert InvalidAddress();
        if (item.itemType < 1 || item.itemType > 3) revert InvalidItem();
        if (item.itemType == 1 && item.amount != 0) revert InvalidItem();
        if (item.itemType == 2 && item.amount == 0) revert InvalidItem();
        if (item.itemType == 3 && item.amount != 0) revert InvalidItem();

        if (signatureExpiryDate < block.timestamp) revert InvalidSignature();
        if (plan.serviceFeeRate > 400) revert InvalidServiceFeeRate();
        if (plan.amount == 0) revert InvalidTokenPrice();
        if (plan.interestRate == 0) revert InvalidInterestRate();
        if (plan.term == 0) revert InvalidTerm();

        if (isBNPL) {
            if (plan.downPaymentPercent == 0 || plan.downPaymentPercent >= 10000) revert InvalidDownPaymentPercent();
            if (plan.totalNumberOfPayments <= 1) revert InvalidTotalNumberOfPayments();
            if (plan.counterPaidPayments != 1) revert InvalidPaidCount();
        } else {
            if (plan.downPaymentPercent != 0) revert InvalidDownPaymentPercent();
            if (plan.totalNumberOfPayments == 0) revert InvalidTotalNumberOfPayments();
            if (plan.counterPaidPayments != 0) revert InvalidPaidCount();
        }
    }

    function verifySignature(
        Item calldata item,
        Plan calldata plan,
        uint256 planId,
        uint256 signatureExpiryDate,
        uint256 chainid,
        address signer,
        bytes memory signature
    ) public pure {
        bytes32 itemHash = keccak256(
            abi.encodePacked(item.cyanVaultAddress, item.contractAddress, item.tokenId, item.amount, item.itemType)
        );
        bytes32 planHash = keccak256(
            abi.encodePacked(
                plan.amount,
                plan.downPaymentPercent,
                plan.interestRate,
                plan.serviceFeeRate,
                plan.term,
                plan.totalNumberOfPayments,
                plan.counterPaidPayments,
                plan.autoRepayStatus
            )
        );
        bytes32 msgHash = keccak256(abi.encodePacked(itemHash, planHash, planId, signatureExpiryDate, chainid));
        bytes32 signedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash));
        if (signedHash.recover(signature) != signer) revert InvalidSignature();
    }

    function verifyRevivalSignature(
        uint256 planId,
        uint256 penaltyAmount,
        uint256 signatureExpiryDate,
        uint256 chainid,
        uint8 counterPaidPayments,
        address signer,
        bytes memory signature
    ) external pure {
        bytes32 msgHash = keccak256(
            abi.encodePacked(planId, penaltyAmount, signatureExpiryDate, chainid, counterPaidPayments)
        );
        bytes32 signedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash));
        if (signedHash.recover(signature) != signer) revert InvalidSignature();
    }

    function verifyEarlyUnwindByOpeanseaSignature(
        uint256 planId,
        uint256 sellPrice,
        bytes memory offer,
        uint256 signatureExpiryDate,
        uint256 chainid,
        address signer,
        bytes memory signature
    ) external pure {
        bytes32 offerHash = keccak256(abi.encodePacked(offer));
        bytes32 msgHash = keccak256(abi.encodePacked(planId, sellPrice, offerHash, signatureExpiryDate, chainid));
        bytes32 signedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash));
        if (signedHash.recover(signature) != signer) revert InvalidSignature();
    }

    function verifyEarlyUnwindByCyanSignature(
        uint256 planId,
        uint256 sellPrice,
        uint256 signatureExpiryDate,
        uint256 chainid,
        address cyanBuyerAddress,
        bytes memory signature
    ) external pure {
        bytes32 msgHash = keccak256(abi.encodePacked(planId, sellPrice, signatureExpiryDate, chainid));
        bytes32 signedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash));
        if (signedHash.recover(signature) != cyanBuyerAddress) revert InvalidSignature();
    }

    function receiveCurrencyFromCyanWallet(
        address currencyAddress,
        address from,
        uint256 amount
    ) external {
        if (currencyAddress == address(0)) {
            IWETH weth = IWETH(addressProvider.addresses("WETH"));
            weth.transferFrom(from, address(this), amount);
            weth.withdraw(amount);
        } else {
            IERC20Upgradeable(currencyAddress).safeTransferFrom(from, address(this), amount);
        }
    }

    /**
     * @notice Getting currency address by vault address
     * @param vaultAddress Cyan Vault address
     */
    function getCurrencyAddressByVaultAddress(address vaultAddress) internal view returns (address) {
        return ICyanVaultV2(payable(vaultAddress)).getCurrencyAddress();
    }

    function createPawn(
        Item calldata item,
        Plan calldata plan,
        uint256 planId,
        PawnCreateType createType,
        uint256 signatureExpiryDate,
        address mainWalletAddress,
        address cyanWalletAddress,
        address cyanSigner,
        bytes memory signature
    ) external returns (bool) {
        requireCorrectPlanParams(false, item, plan, signatureExpiryDate);
        verifySignature(item, plan, planId, signatureExpiryDate, block.chainid, cyanSigner, signature);

        if (createType == PawnCreateType.BEND_DAO) {
            ICyanVaultV2(payable(item.cyanVaultAddress)).lend(cyanWalletAddress, plan.amount);

            address currencyAddress = getCurrencyAddressByVaultAddress(item.cyanVaultAddress);
            migrateBendDaoPlan(item, plan, cyanWalletAddress, currencyAddress);

            if (IERC721Upgradeable(item.contractAddress).ownerOf(item.tokenId) != cyanWalletAddress) {
                revert InvalidBendDaoPlan();
            }
        } else if (createType == PawnCreateType.REFINANCE) {
            ICyanVaultV2(payable(item.cyanVaultAddress)).lend(address(this), plan.amount);
        } else {
            bool isTransferRequired = false;
            if (item.itemType == 1) {
                // ERC721, check if item is already in Cyan wallet
                if (IERC721Upgradeable(item.contractAddress).ownerOf(item.tokenId) != cyanWalletAddress) {
                    isTransferRequired = true;
                }
            } else if (item.itemType == 2) {
                // ERC1155, check if message sender is Cyan wallet
                if (msg.sender != cyanWalletAddress) {
                    isTransferRequired = true;
                }
            } else if (item.itemType == 3) {
                // CryptoPunk, check if item is already in Cyan wallet
                if (ICryptoPunk(item.contractAddress).punkIndexToAddress(item.tokenId) != cyanWalletAddress) {
                    isTransferRequired = true;
                }
            }
            ICyanVaultV2(payable(item.cyanVaultAddress)).lend(mainWalletAddress, plan.amount);
            return isTransferRequired;
        }
        return false;
    }

    function migrateBendDaoPlan(
        Item calldata item,
        Plan calldata plan,
        address cyanWallet,
        address currency
    ) private {
        IBDaoLendPoolLoan bendDaoLendPoolLoan = IBDaoLendPoolLoan(addressProvider.addresses("BENDDAO_LEND_POOL_LOAN"));
        uint256 loanId = bendDaoLendPoolLoan.getCollateralLoanId(item.contractAddress, item.tokenId);
        (, uint256 loanAmount) = bendDaoLendPoolLoan.getLoanReserveBorrowAmount(loanId);

        BDaoDataTypes.LoanData memory loanData = bendDaoLendPoolLoan.getLoan(loanId);
        if (loanData.state != BDaoDataTypes.LoanState.Active) revert InvalidBendDaoPlan();
        if (loanData.borrower != msg.sender) revert InvalidSender();
        if (plan.amount < loanAmount) revert InvalidAmount();
        if (loanData.reserveAsset != (currency == address(0) ? addressProvider.addresses("WETH") : currency))
            revert InvalidCurrency();

        IWallet(cyanWallet).executeModule(
            abi.encodeWithSelector(
                IWallet.repayBendDaoLoan.selector,
                item.contractAddress,
                item.tokenId,
                loanAmount,
                currency
            )
        );
        ICyanConduit(addressProvider.addresses("CYAN_CONDUIT")).transferERC721(
            loanData.borrower,
            cyanWallet,
            item.contractAddress,
            item.tokenId
        );
    }

    function activate(PaymentPlan storage _paymentPlan, Item calldata item) external returns (uint256) {
        if (_paymentPlan.plan.counterPaidPayments != 1) revert InvalidPaidCount();
        if (
            _paymentPlan.status != PaymentPlanStatus.BNPL_CREATED &&
            _paymentPlan.status != PaymentPlanStatus.BNPL_FUNDED
        ) revert InvalidStage();

        (PaymentAmountInfo memory singleAmounts, , uint256 downPaymentAmount, ) = PaymentPlanV2Logic
            .calculatePaymentInfo(_paymentPlan.plan);

        address cyanVaultAddress = item.cyanVaultAddress;

        if (_paymentPlan.status == PaymentPlanStatus.BNPL_CREATED) {
            // Admin already funded the plan, so Vault is transfering equal amount of currency back to admin.
            ICyanVaultV2(payable(cyanVaultAddress)).lend(msg.sender, _paymentPlan.plan.amount);
        }
        transferEarnedAmountToCyanVault(cyanVaultAddress, downPaymentAmount, 0);

        _paymentPlan.status = PaymentPlanStatus.BNPL_ACTIVE;
        return singleAmounts.serviceAmount;
    }

    /**
     * @notice Transfer earned amount to Cyan Vault
     * @param cyanVaultAddress Original price of the token
     * @param paidTokenPayment Paid token payment
     * @param paidInterestFee Paid interest fee
     */
    function transferEarnedAmountToCyanVault(
        address cyanVaultAddress,
        uint256 paidTokenPayment,
        uint256 paidInterestFee
    ) internal {
        ICyanVaultV2 cyanVault = ICyanVaultV2(payable(cyanVaultAddress));
        address currencyAddress = cyanVault.getCurrencyAddress();
        if (currencyAddress == address(0)) {
            cyanVault.earn{ value: paidTokenPayment + paidInterestFee }(paidTokenPayment, paidInterestFee);
        } else {
            IERC20Upgradeable erc20Contract = IERC20Upgradeable(currencyAddress);
            erc20Contract.approve(cyanVaultAddress, paidTokenPayment + paidInterestFee);
            cyanVault.earn(paidTokenPayment, paidInterestFee);
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

library DataTypes {
    struct ReserveData {
        //stores the reserve configuration
        ReserveConfigurationMap configuration;
        //the liquidity index. Expressed in ray
        uint128 liquidityIndex;
        //variable borrow index. Expressed in ray
        uint128 variableBorrowIndex;
        //the current supply rate. Expressed in ray
        uint128 currentLiquidityRate;
        //the current variable borrow rate. Expressed in ray
        uint128 currentVariableBorrowRate;
        uint40 lastUpdateTimestamp;
        //tokens addresses
        address bTokenAddress;
        address debtTokenAddress;
        //address of the interest rate strategy
        address interestRateAddress;
        //the id of the reserve. Represents the position in the list of the active reserves
        uint8 id;
    }

    struct NftData {
        //stores the nft configuration
        NftConfigurationMap configuration;
        //address of the bNFT contract
        address bNftAddress;
        //the id of the nft. Represents the position in the list of the active nfts
        uint8 id;
        uint256 maxSupply;
        uint256 maxTokenId;
    }

    struct ReserveConfigurationMap {
        //bit 0-15: LTV
        //bit 16-31: Liq. threshold
        //bit 32-47: Liq. bonus
        //bit 48-55: Decimals
        //bit 56: Reserve is active
        //bit 57: reserve is frozen
        //bit 58: borrowing is enabled
        //bit 59: stable rate borrowing enabled
        //bit 60-63: reserved
        //bit 64-79: reserve factor
        uint256 data;
    }

    struct NftConfigurationMap {
        //bit 0-15: LTV
        //bit 16-31: Liq. threshold
        //bit 32-47: Liq. bonus
        //bit 56: NFT is active
        //bit 57: NFT is frozen
        uint256 data;
    }

    /**
     * @dev Enum describing the current state of a loan
     * State change flow:
     *  Created -> Active -> Repaid
     *                    -> Auction -> Defaulted
     */
    enum LoanState {
        // We need a default that is not 'Created' - this is the zero value
        None,
        // The loan data is stored, but not initiated yet.
        Created,
        // The loan has been initialized, funds have been delivered to the borrower and the collateral is held.
        Active,
        // The loan is in auction, higest price liquidator will got chance to claim it.
        Auction,
        // The loan has been repaid, and the collateral has been returned to the borrower. This is a terminal state.
        Repaid,
        // The loan was delinquent and collateral claimed by the liquidator. This is a terminal state.
        Defaulted
    }

    struct LoanData {
        //the id of the nft loan
        uint256 loanId;
        //the current state of the loan
        LoanState state;
        //address of borrower
        address borrower;
        //address of nft asset token
        address nftAsset;
        //the id of nft token
        uint256 nftTokenId;
        //address of reserve asset token
        address reserveAsset;
        //scaled borrow amount. Expressed in ray
        uint256 scaledAmount;
        //start time of first bid time
        uint256 bidStartTimestamp;
        //bidder address of higest bid
        address bidderAddress;
        //price of higest bid
        uint256 bidPrice;
        //borrow amount of loan
        uint256 bidBorrowAmount;
        //bidder address of first bid
        address firstBidderAddress;
    }

    struct ExecuteDepositParams {
        address initiator;
        address asset;
        uint256 amount;
        address onBehalfOf;
        uint16 referralCode;
    }

    struct ExecuteWithdrawParams {
        address initiator;
        address asset;
        uint256 amount;
        address to;
    }

    struct ExecuteBorrowParams {
        address initiator;
        address asset;
        uint256 amount;
        address nftAsset;
        uint256 nftTokenId;
        address onBehalfOf;
        uint16 referralCode;
    }

    struct ExecuteBatchBorrowParams {
        address initiator;
        address[] assets;
        uint256[] amounts;
        address[] nftAssets;
        uint256[] nftTokenIds;
        address onBehalfOf;
        uint16 referralCode;
    }

    struct ExecuteRepayParams {
        address initiator;
        address nftAsset;
        uint256 nftTokenId;
        uint256 amount;
    }

    struct ExecuteBatchRepayParams {
        address initiator;
        address[] nftAssets;
        uint256[] nftTokenIds;
        uint256[] amounts;
    }

    struct ExecuteAuctionParams {
        address initiator;
        address nftAsset;
        uint256 nftTokenId;
        uint256 bidPrice;
        address onBehalfOf;
    }

    struct ExecuteRedeemParams {
        address initiator;
        address nftAsset;
        uint256 nftTokenId;
        uint256 amount;
        uint256 bidFine;
    }

    struct ExecuteLiquidateParams {
        address initiator;
        address nftAsset;
        uint256 nftTokenId;
        uint256 amount;
    }

    struct ExecuteLendPoolStates {
        uint256 pauseStartTime;
        uint256 pauseDurationTime;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "./DataTypes.sol";

interface ILendPoolLoan {
    function getCollateralLoanId(address nftAsset, uint256 nftTokenId) external view returns (uint256);

    function getLoan(uint256 loanId) external view returns (DataTypes.LoanData memory loanData);

    function getLoanReserveBorrowAmount(uint256 loanId) external view returns (address, uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface ICryptoPunk {
    function punkIndexToAddress(uint256) external view returns (address);

    function buyPunk(uint256) external payable;

    function transferPunk(address, uint256) external;

    function offerPunkForSale(uint256, uint256) external;

    function offerPunkForSaleToAddress(
        uint256,
        uint256,
        address
    ) external;

    function acceptBidForPunk(uint256, uint256) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/// @title Wrapped Etheruem Contract interface
interface IWETH is IERC20 {
    function withdraw(uint256 wad) external;

    function deposit() external payable;
}

Contract Name:
PriceAggregatorUtils

Contract Source Code:

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

import {CBORChainlink} from "./vendor/CBORChainlink.sol";
import {BufferChainlink} from "./vendor/BufferChainlink.sol";

/**
 * @title Library for common Chainlink functions
 * @dev Uses imported CBOR library for encoding to buffer
 */
library Chainlink {
  uint256 internal constant defaultBufferSize = 256; // solhint-disable-line const-name-snakecase

  using CBORChainlink for BufferChainlink.buffer;

  struct Request {
    bytes32 id;
    address callbackAddress;
    bytes4 callbackFunctionId;
    uint256 nonce;
    BufferChainlink.buffer buf;
  }

  /**
   * @notice Initializes a Chainlink request
   * @dev Sets the ID, callback address, and callback function signature on the request
   * @param self The uninitialized request
   * @param jobId The Job Specification ID
   * @param callbackAddr The callback address
   * @param callbackFunc The callback function signature
   * @return The initialized request
   */
  function initialize(
    Request memory self,
    bytes32 jobId,
    address callbackAddr,
    bytes4 callbackFunc
  ) internal pure returns (Chainlink.Request memory) {
    BufferChainlink.init(self.buf, defaultBufferSize);
    self.id = jobId;
    self.callbackAddress = callbackAddr;
    self.callbackFunctionId = callbackFunc;
    return self;
  }

  /**
   * @notice Sets the data for the buffer without encoding CBOR on-chain
   * @dev CBOR can be closed with curly-brackets {} or they can be left off
   * @param self The initialized request
   * @param data The CBOR data
   */
  function setBuffer(Request memory self, bytes memory data) internal pure {
    BufferChainlink.init(self.buf, data.length);
    BufferChainlink.append(self.buf, data);
  }

  /**
   * @notice Adds a string value to the request with a given key name
   * @param self The initialized request
   * @param key The name of the key
   * @param value The string value to add
   */
  function add(
    Request memory self,
    string memory key,
    string memory value
  ) internal pure {
    self.buf.encodeString(key);
    self.buf.encodeString(value);
  }

  /**
   * @notice Adds a bytes value to the request with a given key name
   * @param self The initialized request
   * @param key The name of the key
   * @param value The bytes value to add
   */
  function addBytes(
    Request memory self,
    string memory key,
    bytes memory value
  ) internal pure {
    self.buf.encodeString(key);
    self.buf.encodeBytes(value);
  }

  /**
   * @notice Adds a int256 value to the request with a given key name
   * @param self The initialized request
   * @param key The name of the key
   * @param value The int256 value to add
   */
  function addInt(
    Request memory self,
    string memory key,
    int256 value
  ) internal pure {
    self.buf.encodeString(key);
    self.buf.encodeInt(value);
  }

  /**
   * @notice Adds a uint256 value to the request with a given key name
   * @param self The initialized request
   * @param key The name of the key
   * @param value The uint256 value to add
   */
  function addUint(
    Request memory self,
    string memory key,
    uint256 value
  ) internal pure {
    self.buf.encodeString(key);
    self.buf.encodeUInt(value);
  }

  /**
   * @notice Adds an array of strings to the request with a given key name
   * @param self The initialized request
   * @param key The name of the key
   * @param values The array of string values to add
   */
  function addStringArray(
    Request memory self,
    string memory key,
    string[] memory values
  ) internal pure {
    self.buf.encodeString(key);
    self.buf.startArray();
    for (uint256 i = 0; i < values.length; i++) {
      self.buf.encodeString(values[i]);
    }
    self.buf.endSequence();
  }
}

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

interface ChainlinkRequestInterface {
  function oracleRequest(
    address sender,
    uint256 requestPrice,
    bytes32 serviceAgreementID,
    address callbackAddress,
    bytes4 callbackFunctionId,
    uint256 nonce,
    uint256 dataVersion,
    bytes calldata data
  ) external;

  function cancelOracleRequest(
    bytes32 requestId,
    uint256 payment,
    bytes4 callbackFunctionId,
    uint256 expiration
  ) external;
}

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

interface LinkTokenInterface {
  function allowance(address owner, address spender) external view returns (uint256 remaining);

  function approve(address spender, uint256 value) external returns (bool success);

  function balanceOf(address owner) external view returns (uint256 balance);

  function decimals() external view returns (uint8 decimalPlaces);

  function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);

  function increaseApproval(address spender, uint256 subtractedValue) external;

  function name() external view returns (string memory tokenName);

  function symbol() external view returns (string memory tokenSymbol);

  function totalSupply() external view returns (uint256 totalTokensIssued);

  function transfer(address to, uint256 value) external returns (bool success);

  function transferAndCall(
    address to,
    uint256 value,
    bytes calldata data
  ) external returns (bool success);

  function transferFrom(
    address from,
    address to,
    uint256 value
  ) external returns (bool success);
}

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

/**
 * @dev A library for working with mutable byte buffers in Solidity.
 *
 * Byte buffers are mutable and expandable, and provide a variety of primitives
 * for writing to them. At any time you can fetch a bytes object containing the
 * current contents of the buffer. The bytes object should not be stored between
 * operations, as it may change due to resizing of the buffer.
 */
library BufferChainlink {
  /**
   * @dev Represents a mutable buffer. Buffers have a current value (buf) and
   *      a capacity. The capacity may be longer than the current value, in
   *      which case it can be extended without the need to allocate more memory.
   */
  struct buffer {
    bytes buf;
    uint256 capacity;
  }

  /**
   * @dev Initializes a buffer with an initial capacity.
   * @param buf The buffer to initialize.
   * @param capacity The number of bytes of space to allocate the buffer.
   * @return The buffer, for chaining.
   */
  function init(buffer memory buf, uint256 capacity) internal pure returns (buffer memory) {
    if (capacity % 32 != 0) {
      capacity += 32 - (capacity % 32);
    }
    // Allocate space for the buffer data
    buf.capacity = capacity;
    assembly {
      let ptr := mload(0x40)
      mstore(buf, ptr)
      mstore(ptr, 0)
      mstore(0x40, add(32, add(ptr, capacity)))
    }
    return buf;
  }

  /**
   * @dev Initializes a new buffer from an existing bytes object.
   *      Changes to the buffer may mutate the original value.
   * @param b The bytes object to initialize the buffer with.
   * @return A new buffer.
   */
  function fromBytes(bytes memory b) internal pure returns (buffer memory) {
    buffer memory buf;
    buf.buf = b;
    buf.capacity = b.length;
    return buf;
  }

  function resize(buffer memory buf, uint256 capacity) private pure {
    bytes memory oldbuf = buf.buf;
    init(buf, capacity);
    append(buf, oldbuf);
  }

  function max(uint256 a, uint256 b) private pure returns (uint256) {
    if (a > b) {
      return a;
    }
    return b;
  }

  /**
   * @dev Sets buffer length to 0.
   * @param buf The buffer to truncate.
   * @return The original buffer, for chaining..
   */
  function truncate(buffer memory buf) internal pure returns (buffer memory) {
    assembly {
      let bufptr := mload(buf)
      mstore(bufptr, 0)
    }
    return buf;
  }

  /**
   * @dev Writes a byte string to a buffer. Resizes if doing so would exceed
   *      the capacity of the buffer.
   * @param buf The buffer to append to.
   * @param off The start offset to write to.
   * @param data The data to append.
   * @param len The number of bytes to copy.
   * @return The original buffer, for chaining.
   */
  function write(
    buffer memory buf,
    uint256 off,
    bytes memory data,
    uint256 len
  ) internal pure returns (buffer memory) {
    require(len <= data.length);

    if (off + len > buf.capacity) {
      resize(buf, max(buf.capacity, len + off) * 2);
    }

    uint256 dest;
    uint256 src;
    assembly {
      // Memory address of the buffer data
      let bufptr := mload(buf)
      // Length of existing buffer data
      let buflen := mload(bufptr)
      // Start address = buffer address + offset + sizeof(buffer length)
      dest := add(add(bufptr, 32), off)
      // Update buffer length if we're extending it
      if gt(add(len, off), buflen) {
        mstore(bufptr, add(len, off))
      }
      src := add(data, 32)
    }

    // Copy word-length chunks while possible
    for (; len >= 32; len -= 32) {
      assembly {
        mstore(dest, mload(src))
      }
      dest += 32;
      src += 32;
    }

    // Copy remaining bytes
    unchecked {
      uint256 mask = (256**(32 - len)) - 1;
      assembly {
        let srcpart := and(mload(src), not(mask))
        let destpart := and(mload(dest), mask)
        mstore(dest, or(destpart, srcpart))
      }
    }

    return buf;
  }

  /**
   * @dev Appends a byte string to a buffer. Resizes if doing so would exceed
   *      the capacity of the buffer.
   * @param buf The buffer to append to.
   * @param data The data to append.
   * @param len The number of bytes to copy.
   * @return The original buffer, for chaining.
   */
  function append(
    buffer memory buf,
    bytes memory data,
    uint256 len
  ) internal pure returns (buffer memory) {
    return write(buf, buf.buf.length, data, len);
  }

  /**
   * @dev Appends a byte string to a buffer. Resizes if doing so would exceed
   *      the capacity of the buffer.
   * @param buf The buffer to append to.
   * @param data The data to append.
   * @return The original buffer, for chaining.
   */
  function append(buffer memory buf, bytes memory data) internal pure returns (buffer memory) {
    return write(buf, buf.buf.length, data, data.length);
  }

  /**
   * @dev Writes a byte to the buffer. Resizes if doing so would exceed the
   *      capacity of the buffer.
   * @param buf The buffer to append to.
   * @param off The offset to write the byte at.
   * @param data The data to append.
   * @return The original buffer, for chaining.
   */
  function writeUint8(
    buffer memory buf,
    uint256 off,
    uint8 data
  ) internal pure returns (buffer memory) {
    if (off >= buf.capacity) {
      resize(buf, buf.capacity * 2);
    }

    assembly {
      // Memory address of the buffer data
      let bufptr := mload(buf)
      // Length of existing buffer data
      let buflen := mload(bufptr)
      // Address = buffer address + sizeof(buffer length) + off
      let dest := add(add(bufptr, off), 32)
      mstore8(dest, data)
      // Update buffer length if we extended it
      if eq(off, buflen) {
        mstore(bufptr, add(buflen, 1))
      }
    }
    return buf;
  }

  /**
   * @dev Appends a byte to the buffer. Resizes if doing so would exceed the
   *      capacity of the buffer.
   * @param buf The buffer to append to.
   * @param data The data to append.
   * @return The original buffer, for chaining.
   */
  function appendUint8(buffer memory buf, uint8 data) internal pure returns (buffer memory) {
    return writeUint8(buf, buf.buf.length, data);
  }

  /**
   * @dev Writes up to 32 bytes to the buffer. Resizes if doing so would
   *      exceed the capacity of the buffer.
   * @param buf The buffer to append to.
   * @param off The offset to write at.
   * @param data The data to append.
   * @param len The number of bytes to write (left-aligned).
   * @return The original buffer, for chaining.
   */
  function write(
    buffer memory buf,
    uint256 off,
    bytes32 data,
    uint256 len
  ) private pure returns (buffer memory) {
    if (len + off > buf.capacity) {
      resize(buf, (len + off) * 2);
    }

    unchecked {
      uint256 mask = (256**len) - 1;
      // Right-align data
      data = data >> (8 * (32 - len));
      assembly {
        // Memory address of the buffer data
        let bufptr := mload(buf)
        // Address = buffer address + sizeof(buffer length) + off + len
        let dest := add(add(bufptr, off), len)
        mstore(dest, or(and(mload(dest), not(mask)), data))
        // Update buffer length if we extended it
        if gt(add(off, len), mload(bufptr)) {
          mstore(bufptr, add(off, len))
        }
      }
    }
    return buf;
  }

  /**
   * @dev Writes a bytes20 to the buffer. Resizes if doing so would exceed the
   *      capacity of the buffer.
   * @param buf The buffer to append to.
   * @param off The offset to write at.
   * @param data The data to append.
   * @return The original buffer, for chaining.
   */
  function writeBytes20(
    buffer memory buf,
    uint256 off,
    bytes20 data
  ) internal pure returns (buffer memory) {
    return write(buf, off, bytes32(data), 20);
  }

  /**
   * @dev Appends a bytes20 to the buffer. Resizes if doing so would exceed
   *      the capacity of the buffer.
   * @param buf The buffer to append to.
   * @param data The data to append.
   * @return The original buffer, for chhaining.
   */
  function appendBytes20(buffer memory buf, bytes20 data) internal pure returns (buffer memory) {
    return write(buf, buf.buf.length, bytes32(data), 20);
  }

  /**
   * @dev Appends a bytes32 to the buffer. Resizes if doing so would exceed
   *      the capacity of the buffer.
   * @param buf The buffer to append to.
   * @param data The data to append.
   * @return The original buffer, for chaining.
   */
  function appendBytes32(buffer memory buf, bytes32 data) internal pure returns (buffer memory) {
    return write(buf, buf.buf.length, data, 32);
  }

  /**
   * @dev Writes an integer to the buffer. Resizes if doing so would exceed
   *      the capacity of the buffer.
   * @param buf The buffer to append to.
   * @param off The offset to write at.
   * @param data The data to append.
   * @param len The number of bytes to write (right-aligned).
   * @return The original buffer, for chaining.
   */
  function writeInt(
    buffer memory buf,
    uint256 off,
    uint256 data,
    uint256 len
  ) private pure returns (buffer memory) {
    if (len + off > buf.capacity) {
      resize(buf, (len + off) * 2);
    }

    uint256 mask = (256**len) - 1;
    assembly {
      // Memory address of the buffer data
      let bufptr := mload(buf)
      // Address = buffer address + off + sizeof(buffer length) + len
      let dest := add(add(bufptr, off), len)
      mstore(dest, or(and(mload(dest), not(mask)), data))
      // Update buffer length if we extended it
      if gt(add(off, len), mload(bufptr)) {
        mstore(bufptr, add(off, len))
      }
    }
    return buf;
  }

  /**
   * @dev Appends a byte to the end of the buffer. Resizes if doing so would
   * exceed the capacity of the buffer.
   * @param buf The buffer to append to.
   * @param data The data to append.
   * @return The original buffer.
   */
  function appendInt(
    buffer memory buf,
    uint256 data,
    uint256 len
  ) internal pure returns (buffer memory) {
    return writeInt(buf, buf.buf.length, data, len);
  }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.19;

import {BufferChainlink} from "./BufferChainlink.sol";

library CBORChainlink {
  using BufferChainlink for BufferChainlink.buffer;

  uint8 private constant MAJOR_TYPE_INT = 0;
  uint8 private constant MAJOR_TYPE_NEGATIVE_INT = 1;
  uint8 private constant MAJOR_TYPE_BYTES = 2;
  uint8 private constant MAJOR_TYPE_STRING = 3;
  uint8 private constant MAJOR_TYPE_ARRAY = 4;
  uint8 private constant MAJOR_TYPE_MAP = 5;
  uint8 private constant MAJOR_TYPE_TAG = 6;
  uint8 private constant MAJOR_TYPE_CONTENT_FREE = 7;

  uint8 private constant TAG_TYPE_BIGNUM = 2;
  uint8 private constant TAG_TYPE_NEGATIVE_BIGNUM = 3;

  function encodeFixedNumeric(BufferChainlink.buffer memory buf, uint8 major, uint64 value) private pure {
    if(value <= 23) {
      buf.appendUint8(uint8((major << 5) | value));
    } else if (value <= 0xFF) {
      buf.appendUint8(uint8((major << 5) | 24));
      buf.appendInt(value, 1);
    } else if (value <= 0xFFFF) {
      buf.appendUint8(uint8((major << 5) | 25));
      buf.appendInt(value, 2);
    } else if (value <= 0xFFFFFFFF) {
      buf.appendUint8(uint8((major << 5) | 26));
      buf.appendInt(value, 4);
    } else {
      buf.appendUint8(uint8((major << 5) | 27));
      buf.appendInt(value, 8);
    }
  }

  function encodeIndefiniteLengthType(BufferChainlink.buffer memory buf, uint8 major) private pure {
    buf.appendUint8(uint8((major << 5) | 31));
  }

  function encodeUInt(BufferChainlink.buffer memory buf, uint value) internal pure {
    if(value > 0xFFFFFFFFFFFFFFFF) {
      encodeBigNum(buf, value);
    } else {
      encodeFixedNumeric(buf, MAJOR_TYPE_INT, uint64(value));
    }
  }

  function encodeInt(BufferChainlink.buffer memory buf, int value) internal pure {
    if(value < -0x10000000000000000) {
      encodeSignedBigNum(buf, value);
    } else if(value > 0xFFFFFFFFFFFFFFFF) {
      encodeBigNum(buf, uint(value));
    } else if(value >= 0) {
      encodeFixedNumeric(buf, MAJOR_TYPE_INT, uint64(uint256(value)));
    } else {
      encodeFixedNumeric(buf, MAJOR_TYPE_NEGATIVE_INT, uint64(uint256(-1 - value)));
    }
  }

  function encodeBytes(BufferChainlink.buffer memory buf, bytes memory value) internal pure {
    encodeFixedNumeric(buf, MAJOR_TYPE_BYTES, uint64(value.length));
    buf.append(value);
  }

  function encodeBigNum(BufferChainlink.buffer memory buf, uint value) internal pure {
    buf.appendUint8(uint8((MAJOR_TYPE_TAG << 5) | TAG_TYPE_BIGNUM));
    encodeBytes(buf, abi.encode(value));
  }

  function encodeSignedBigNum(BufferChainlink.buffer memory buf, int input) internal pure {
    buf.appendUint8(uint8((MAJOR_TYPE_TAG << 5) | TAG_TYPE_NEGATIVE_BIGNUM));
    encodeBytes(buf, abi.encode(uint256(-1 - input)));
  }

  function encodeString(BufferChainlink.buffer memory buf, string memory value) internal pure {
    encodeFixedNumeric(buf, MAJOR_TYPE_STRING, uint64(bytes(value).length));
    buf.append(bytes(value));
  }

  function startArray(BufferChainlink.buffer memory buf) internal pure {
    encodeIndefiniteLengthType(buf, MAJOR_TYPE_ARRAY);
  }

  function startMap(BufferChainlink.buffer memory buf) internal pure {
    encodeIndefiniteLengthType(buf, MAJOR_TYPE_MAP);
  }

  function endSequence(BufferChainlink.buffer memory buf) internal pure {
    encodeIndefiniteLengthType(buf, MAJOR_TYPE_CONTENT_FREE);
  }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (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.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
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].
     *
     * CAUTION: See Security Considerations above.
     */
    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);
}

// 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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (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. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    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));
    }
}

// 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);
        }
    }
}

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

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.4.0;

/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Used in SqrtPriceMath.sol
library FixedPoint96 {
    uint8 internal constant RESOLUTION = 96;
    uint256 internal constant Q96 = 0x1000000000000000000000000;
}

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

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
    /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
    function mulDiv(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = a * b
            // Compute the product mod 2**256 and mod 2**256 - 1
            // then use the Chinese Remainder Theorem to reconstruct
            // the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2**256 + prod0
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(a, b, not(0))
                prod0 := mul(a, b)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division
            if (prod1 == 0) {
                require(denominator > 0);
                assembly {
                    result := div(prod0, denominator)
                }
                return result;
            }

            // Make sure the result is less than 2**256.
            // Also prevents denominator == 0
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0]
            // Compute remainder using mulmod
            uint256 remainder;
            assembly {
                remainder := mulmod(a, b, denominator)
            }
            // Subtract 256 bit number from 512 bit number
            assembly {
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator
            // Compute largest power of two divisor of denominator.
            // Always >= 1.
            uint256 twos = (0 - denominator) & denominator;
            // Divide denominator by power of two
            assembly {
                denominator := div(denominator, twos)
            }

            // Divide [prod1 prod0] by the factors of two
            assembly {
                prod0 := div(prod0, twos)
            }
            // Shift in bits from prod1 into prod0. For this we need
            // to flip `twos` such that it is 2**256 / twos.
            // If twos is zero, then it becomes one
            assembly {
                twos := add(div(sub(0, twos), twos), 1)
            }
            prod0 |= prod1 * twos;

            // Invert denominator mod 2**256
            // Now that denominator is an odd number, it has an inverse
            // modulo 2**256 such that denominator * inv = 1 mod 2**256.
            // Compute the inverse by starting with a seed that is correct
            // correct for four bits. That is, denominator * inv = 1 mod 2**4
            uint256 inv = (3 * denominator) ^ 2;
            // Now use Newton-Raphson iteration to improve the precision.
            // Thanks to Hensel's lifting lemma, this also works in modular
            // arithmetic, doubling the correct bits in each step.
            inv *= 2 - denominator * inv; // inverse mod 2**8
            inv *= 2 - denominator * inv; // inverse mod 2**16
            inv *= 2 - denominator * inv; // inverse mod 2**32
            inv *= 2 - denominator * inv; // inverse mod 2**64
            inv *= 2 - denominator * inv; // inverse mod 2**128
            inv *= 2 - denominator * inv; // inverse mod 2**256

            // Because the division is now exact we can divide by multiplying
            // with the modular inverse of denominator. This will give us the
            // correct result modulo 2**256. Since the precoditions guarantee
            // that the outcome is less than 2**256, this is the final result.
            // We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inv;
            return result;
        }
    }

    /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    function mulDivRoundingUp(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            result = mulDiv(a, b, denominator);
            if (mulmod(a, b, denominator) > 0) {
                require(result < type(uint256).max);
                result++;
            }
        }
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    error T();
    error R();

    /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
    int24 internal constant MAX_TICK = -MIN_TICK;

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
    /// at the given tick
    function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        unchecked {
            uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
            if (absTick > uint256(int256(MAX_TICK))) revert T();

            uint256 ratio = absTick & 0x1 != 0
                ? 0xfffcb933bd6fad37aa2d162d1a594001
                : 0x100000000000000000000000000000000;
            if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
            if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
            if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
            if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
            if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
            if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
            if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
            if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
            if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
            if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
            if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
            if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
            if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
            if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
            if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
            if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
            if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
            if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
            if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

            if (tick > 0) ratio = type(uint256).max / ratio;

            // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
            // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
            // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
            sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
        }
    }

    /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
    function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
        unchecked {
            // second inequality must be < because the price can never reach the price at the max tick
            if (!(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO)) revert R();
            uint256 ratio = uint256(sqrtPriceX96) << 32;

            uint256 r = ratio;
            uint256 msb = 0;

            assembly {
                let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(5, gt(r, 0xFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(4, gt(r, 0xFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(3, gt(r, 0xFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(2, gt(r, 0xF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(1, gt(r, 0x3))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := gt(r, 0x1)
                msb := or(msb, f)
            }

            if (msb >= 128) r = ratio >> (msb - 127);
            else r = ratio << (127 - msb);

            int256 log_2 = (int256(msb) - 128) << 64;

            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(63, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(62, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(61, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(60, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(59, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(58, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(57, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(56, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(55, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(54, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(53, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(52, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(51, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(50, f))
            }

            int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number

            int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
            int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

            tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Interface for Arbitrum special l2 functions
 */
interface IArbSys {
    function arbBlockNumber() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Interface for Chainlink feeds
 */
interface IChainlinkFeed {
    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80);

    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev Interface for ERC20 tokens
 */
interface IERC20 is IERC20Metadata {
    function burn(address, uint256) external;

    function mint(address, uint256) external;

    function hasRole(bytes32, address) external view returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Interface for errors potentially used in all libraries (general names)
 */
interface IGeneralErrors {
    error InitError();
    error InvalidAddresses();
    error InvalidAddress();
    error InvalidInputLength();
    error InvalidCollateralIndex();
    error WrongParams();
    error WrongLength();
    error WrongOrder();
    error WrongIndex();
    error BlockOrder();
    error Overflow();
    error ZeroAddress();
    error ZeroValue();
    error AlreadyExists();
    error DoesntExist();
    error Paused();
    error BelowMin();
    error AboveMax();
    error NotAuthorized();
    error WrongTradeType();
    error WrongOrderType();
    error InsufficientBalance();
    error UnsupportedChain();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "./types/IAddressStore.sol";
import "./IGeneralErrors.sol";

/**
 * @dev Interface for AddressStoreUtils library
 */
interface IGNSAddressStore is IAddressStore, IGeneralErrors {
    /**
     * @dev Initializes address store facet
     * @param _rolesManager roles manager address
     */
    function initialize(address _rolesManager) external;

    /**
     * @dev Returns addresses current values
     */
    function getAddresses() external view returns (Addresses memory);

    /**
     * @dev Returns whether an account has been granted a particular role
     * @param _account account address to check
     * @param _role role to check
     */
    function hasRole(address _account, Role _role) external view returns (bool);

    /**
     * @dev Returns whether an account has been granted at least one of two roles
     * @param _account address to check
     * @param _roleA first role to check
     * @param _roleB second role to check
     */
    function hasRoles(address _account, Role _roleA, Role _roleB) external view returns (bool);

    /**
     * @dev Updates access control for a list of accounts
     * @param _accounts accounts addresses to update
     * @param _roles corresponding roles to update
     * @param _values corresponding new values to set
     */
    function setRoles(address[] calldata _accounts, Role[] calldata _roles, bool[] calldata _values) external;

    /**
     * @dev Emitted when addresses are updated
     * @param addresses new addresses values
     */
    event AddressesUpdated(Addresses addresses);

    /**
     * @dev Emitted when access control is updated for an account
     * @param target account address to update
     * @param role role to update
     * @param access whether role is granted or revoked
     */
    event AccessControlUpdated(address target, Role role, bool access);

    error NotAllowed();
    error WrongAccess();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "./IGNSAddressStore.sol";
import "./IGNSDiamondCut.sol";
import "./IGNSDiamondLoupe.sol";
import "./types/ITypes.sol";

/**
 * @dev the non-expanded interface for multi-collat diamond, only contains types/structs/enums
 */

interface IGNSDiamond is IGNSAddressStore, IGNSDiamondCut, IGNSDiamondLoupe, ITypes {

}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "./types/IDiamondStorage.sol";

/**
 * @author Nick Mudge <[email protected]> (https://twitter.com/mudgen)
 * @author Gains Network
 * @dev Based on EIP-2535: Diamonds (https://eips.ethereum.org/EIPS/eip-2535)
 * @dev Follows diamond-3 implementation (https://github.com/mudgen/diamond-3-hardhat/)
 * @dev One of the diamond standard interfaces, used for diamond management.
 */
interface IGNSDiamondCut is IDiamondStorage {
    /**
     * @notice Add/replace/remove any number of functions and optionally execute a function with delegatecall
     * @param _diamondCut Contains the facet addresses and function selectors
     * @param _init The address of the contract or facet to execute _calldata
     * @param _calldata A function call, including function selector and arguments _calldata is executed with delegatecall on _init
     */
    function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external;

    /**
     * @dev Emitted when function selectors of a facet of the diamond is added, replaced, or removed
     * @param _diamondCut Contains the update data (facet addresses, action, function selectors)
     * @param _init The address of the contract or facet to execute _calldata
     * @param _calldata Function call to execute after the diamond cut
     */
    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);

    error InitializationFunctionReverted(address _initializationContractAddress, bytes _calldata);
    error InvalidFacetCutAction();
    error NotContract();
    error NotFound();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @author Nick Mudge <[email protected]> (https://twitter.com/mudgen)
 * @author Gains Network
 * @dev Based on EIP-2535: Diamonds (https://eips.ethereum.org/EIPS/eip-2535)
 * @dev Follows diamond-3 implementation (https://github.com/mudgen/diamond-3-hardhat/)
 * @dev One of the diamond standard interfaces, used to inspect the diamond like a magnifying glass.
 */
interface IGNSDiamondLoupe {
    /// These functions are expected to be called frequently
    /// by tools.

    struct Facet {
        address facetAddress;
        bytes4[] functionSelectors;
    }

    /// @notice Gets all facet addresses and their four byte function selectors.
    /// @return facets_ Facet
    function facets() external view returns (Facet[] memory facets_);

    /// @notice Gets all the function selectors supported by a specific facet.
    /// @param _facet The facet address.
    /// @return facetFunctionSelectors_
    function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);

    /// @notice Get all the facet addresses used by a diamond.
    /// @return facetAddresses_
    function facetAddresses() external view returns (address[] memory facetAddresses_);

    /// @notice Gets the facet that supports the given selector.
    /// @dev If facet is not found return address(0).
    /// @param _functionSelector The function selector.
    /// @return facetAddress_ The facet address.
    function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "./IGNSDiamond.sol";
import "./libraries/IPairsStorageUtils.sol";
import "./libraries/IReferralsUtils.sol";
import "./libraries/IFeeTiersUtils.sol";
import "./libraries/IPriceImpactUtils.sol";
import "./libraries/ITradingStorageUtils.sol";
import "./libraries/ITriggerRewardsUtils.sol";
import "./libraries/ITradingInteractionsUtils.sol";
import "./libraries/ITradingCallbacksUtils.sol";
import "./libraries/IBorrowingFeesUtils.sol";
import "./libraries/IPriceAggregatorUtils.sol";
import "./libraries/IOtcUtils.sol";
import "./IMulticall.sol";
import "./libraries/IChainConfigUtils.sol";

/**
 * @dev Expanded version of multi-collat diamond that includes events and function signatures
 * Technically this interface is virtual since the diamond doesn't directly implement these functions.
 * It only forwards the calls to the facet contracts using delegatecall.
 */
interface IGNSMultiCollatDiamond is
    IGNSDiamond,
    IPairsStorageUtils,
    IReferralsUtils,
    IFeeTiersUtils,
    IPriceImpactUtils,
    ITradingStorageUtils,
    ITriggerRewardsUtils,
    ITradingInteractionsUtils,
    ITradingCallbacksUtils,
    IBorrowingFeesUtils,
    IPriceAggregatorUtils,
    IOtcUtils,
    IMulticall,
    IChainConfigUtils
{

}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Interface for GNSStaking contract
 */
interface IGNSStaking {
    struct Staker {
        uint128 stakedGns; // 1e18
        uint128 debtDai; // 1e18
    }

    struct StakerInfo {
        uint48 lastDepositTs;
        uint208 __placeholder;
    }

    struct RewardState {
        uint128 accRewardPerGns; // 1e18
        uint128 precisionDelta;
    }

    struct RewardInfo {
        uint128 debtToken; // 1e18
        uint128 __placeholder;
    }

    struct UnlockSchedule {
        uint128 totalGns; // 1e18
        uint128 claimedGns; // 1e18
        uint128 debtDai; // 1e18
        uint48 start; // block.timestamp (seconds)
        uint48 duration; // in seconds
        bool revocable;
        UnlockType unlockType;
        uint16 __placeholder;
    }

    struct UnlockScheduleInput {
        uint128 totalGns; // 1e18
        uint48 start; // block.timestamp (seconds)
        uint48 duration; // in seconds
        bool revocable;
        UnlockType unlockType;
    }

    enum UnlockType {
        LINEAR,
        CLIFF
    }

    function owner() external view returns (address);

    function distributeReward(address _rewardToken, uint256 _amountToken) external;

    function createUnlockSchedule(UnlockScheduleInput calldata _schedule, address _staker) external;

    event UnlockManagerUpdated(address indexed manager, bool authorized);

    event DaiHarvested(address indexed staker, uint128 amountDai);

    event RewardHarvested(address indexed staker, address indexed token, uint128 amountToken);
    event RewardHarvestedFromUnlock(
        address indexed staker,
        address indexed token,
        bool isOldDai,
        uint256[] ids,
        uint128 amountToken
    );
    event RewardDistributed(address indexed token, uint256 amount);

    event GnsStaked(address indexed staker, uint128 amountGns);
    event GnsUnstaked(address indexed staker, uint128 amountGns);
    event GnsClaimed(address indexed staker, uint256[] ids, uint128 amountGns);

    event UnlockScheduled(address indexed staker, uint256 indexed index, UnlockSchedule schedule);
    event UnlockScheduleRevoked(address indexed staker, uint256 indexed index);

    event RewardTokenAdded(address token, uint256 index, uint128 precisionDelta);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Interface for GToken contract
 */
interface IGToken {
    struct GnsPriceProvider {
        address addr;
        bytes signature;
    }

    struct LockedDeposit {
        address owner;
        uint256 shares; // collateralConfig.precision
        uint256 assetsDeposited; // collateralConfig.precision
        uint256 assetsDiscount; // collateralConfig.precision
        uint256 atTimestamp; // timestamp
        uint256 lockDuration; // timestamp
    }

    struct ContractAddresses {
        address asset;
        address owner; // 2-week timelock contract
        address manager; // 3-day timelock contract
        address admin; // bypasses timelock, access to emergency functions
        address gnsToken;
        address lockedDepositNft;
        address pnlHandler;
        address openTradesPnlFeed;
        GnsPriceProvider gnsPriceProvider;
    }

    struct Meta {
        string name;
        string symbol;
    }

    function manager() external view returns (address);

    function admin() external view returns (address);

    function currentEpoch() external view returns (uint256);

    function currentEpochStart() external view returns (uint256);

    function currentEpochPositiveOpenPnl() external view returns (uint256);

    function updateAccPnlPerTokenUsed(
        uint256 prevPositiveOpenPnl,
        uint256 newPositiveOpenPnl
    ) external returns (uint256);

    function getLockedDeposit(uint256 depositId) external view returns (LockedDeposit memory);

    function sendAssets(uint256 assets, address receiver) external;

    function receiveAssets(uint256 assets, address user) external;

    function distributeReward(uint256 assets) external;

    function tvl() external view returns (uint256);

    function marketCap() external view returns (uint256);

    function shareToAssetsPrice() external view returns (uint256);

    function collateralConfig() external view returns (uint128, uint128);

    event ManagerUpdated(address newValue);
    event AdminUpdated(address newValue);
    event PnlHandlerUpdated(address newValue);
    event OpenTradesPnlFeedUpdated(address newValue);
    event GnsPriceProviderUpdated(GnsPriceProvider newValue);
    event WithdrawLockThresholdsPUpdated(uint256[2] newValue);
    event MaxAccOpenPnlDeltaUpdated(uint256 newValue);
    event MaxDailyAccPnlDeltaUpdated(uint256 newValue);
    event MaxSupplyIncreaseDailyPUpdated(uint256 newValue);
    event LossesBurnPUpdated(uint256 newValue);
    event MaxGnsSupplyMintDailyPUpdated(uint256 newValue);
    event MaxDiscountPUpdated(uint256 newValue);
    event MaxDiscountThresholdPUpdated(uint256 newValue);

    event CurrentMaxSupplyUpdated(uint256 newValue);
    event DailyAccPnlDeltaReset();
    event ShareToAssetsPriceUpdated(uint256 newValue);
    event OpenTradesPnlFeedCallFailed();

    event WithdrawRequested(
        address indexed sender,
        address indexed owner,
        uint256 shares,
        uint256 currEpoch,
        uint256 indexed unlockEpoch
    );
    event WithdrawCanceled(
        address indexed sender,
        address indexed owner,
        uint256 shares,
        uint256 currEpoch,
        uint256 indexed unlockEpoch
    );

    event DepositLocked(address indexed sender, address indexed owner, uint256 depositId, LockedDeposit d);
    event DepositUnlocked(
        address indexed sender,
        address indexed receiver,
        address indexed owner,
        uint256 depositId,
        LockedDeposit d
    );

    event RewardDistributed(address indexed sender, uint256 assets);

    event AssetsSent(address indexed sender, address indexed receiver, uint256 assets);
    event AssetsReceived(address indexed sender, address indexed user, uint256 assets, uint256 assetsLessDeplete);

    event Depleted(address indexed sender, uint256 assets, uint256 amountGns);
    event Refilled(address indexed sender, uint256 assets, uint256 amountGns);

    event AccPnlPerTokenUsedUpdated(
        address indexed sender,
        uint256 indexed newEpoch,
        uint256 prevPositiveOpenPnl,
        uint256 newPositiveOpenPnl,
        uint256 newEpochPositiveOpenPnl,
        int256 newAccPnlPerTokenUsed
    );

    error OnlyManager();
    error OnlyTradingPnlHandler();
    error OnlyPnlFeed();
    error AddressZero();
    error PriceZero();
    error ValueZero();
    error BytesZero();
    error NoActiveDiscount();
    error BelowMin();
    error AboveMax();
    error WrongValue();
    error WrongValues();
    error GnsPriceCallFailed();
    error GnsTokenPriceZero();
    error PendingWithdrawal();
    error EndOfEpoch();
    error NotAllowed();
    error NoDiscount();
    error NotUnlocked();
    error NotEnoughAssets();
    error MaxDailyPnl();
    error NotUnderCollateralized();
    error AboveInflationLimit();

    // Ownable
    error OwnableInvalidOwner(address owner);

    // ERC4626
    error ERC4626ExceededMaxDeposit();
    error ERC4626ExceededMaxMint();
    error ERC4626ExceededMaxWithdraw();
    error ERC4626ExceededMaxRedeem();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @custom:version 8
 * @dev Generic interface for liquidity pool methods for fetching observations (to calculate TWAP) and other basic information
 */
interface ILiquidityPool {
    /**
     * @dev AlgebraPool V1.9 equivalent of Uniswap V3 `observe` function
     * See https://github.com/cryptoalgebra/AlgebraV1.9/blob/main/src/core/contracts/interfaces/pool/IAlgebraPoolDerivedState.sol for more information
     */
    function getTimepoints(
        uint32[] calldata secondsAgos
    )
        external
        view
        returns (
            int56[] memory tickCumulatives,
            uint160[] memory secondsPerLiquidityCumulatives,
            uint112[] memory volatilityCumulatives,
            uint256[] memory volumePerAvgLiquiditys
        );

    /**
     * @dev Uniswap V3 `observe` function
     * See `https://github.com/Uniswap/v3-core/blob/main/contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol` for more information
     */
    function observe(
        uint32[] calldata secondsAgos
    ) external view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);

    /**
     * @notice The first of the two tokens of the pool, sorted by address
     * @return The token contract address
     */
    function token0() external view returns (address);

    /**
     * @notice The second of the two tokens of the pool, sorted by address
     * @return The token contract address
     */
    function token1() external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

interface IMulticall {
    /**
     * @dev Call multiple functions in a single call.
     * @param data The data for the calls.
     * @return results The results of the calls.
     */
    function multicall(bytes[] calldata data) external returns (bytes[] memory results);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Interface for WETH9 token
 */
interface IWETH9 {
    function approve(address spender, uint256 amount) external returns (bool);

    function transfer(address to, uint256 amount) external returns (bool);

    function deposit() external payable;

    function withdraw(uint256) external;

    function balanceOf(address account) external view returns (uint256);

    event Approval(address indexed src, address indexed guy, uint256 wad);
    event Transfer(address indexed src, address indexed dst, uint256 wad);
    event Deposit(address indexed dst, uint256 wad);
    event Withdrawal(address indexed src, uint256 wad);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IBorrowingFees.sol";

/**
 * @dev Interface for GNSBorrowingFees facet (inherits types and also contains functions, events, and custom errors)
 */
interface IBorrowingFeesUtils is IBorrowingFees {
    /**
     * @dev Updates borrowing pair params of a pair
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     * @param _value new value
     */
    function setBorrowingPairParams(
        uint8 _collateralIndex,
        uint16 _pairIndex,
        BorrowingPairParams calldata _value
    ) external;

    /**
     * @dev Updates borrowing pair params of multiple pairs
     * @param _collateralIndex index of the collateral
     * @param _indices indices of the pairs
     * @param _values new values
     */
    function setBorrowingPairParamsArray(
        uint8 _collateralIndex,
        uint16[] calldata _indices,
        BorrowingPairParams[] calldata _values
    ) external;

    /**
     * @dev Updates borrowing group params of a group
     * @param _collateralIndex index of the collateral
     * @param _groupIndex index of the borrowing group
     * @param _value new value
     */
    function setBorrowingGroupParams(
        uint8 _collateralIndex,
        uint16 _groupIndex,
        BorrowingGroupParams calldata _value
    ) external;

    /**
     * @dev Updates borrowing group params of multiple groups
     * @param _collateralIndex index of the collateral
     * @param _indices indices of the groups
     * @param _values new values
     */
    function setBorrowingGroupParamsArray(
        uint8 _collateralIndex,
        uint16[] calldata _indices,
        BorrowingGroupParams[] calldata _values
    ) external;

    /**
     * @dev Callback after a trade is opened/closed to store pending borrowing fees and adjust open interests
     * @param _collateralIndex index of the collateral
     * @param _trader address of the trader
     * @param _pairIndex index of the pair
     * @param _index index of the trade
     * @param _positionSizeCollateral position size of the trade in collateral tokens
     * @param _open true if trade has been opened, false if trade has been closed
     * @param _long true if trade is long, false if trade is short
     */
    function handleTradeBorrowingCallback(
        uint8 _collateralIndex,
        address _trader,
        uint16 _pairIndex,
        uint32 _index,
        uint256 _positionSizeCollateral,
        bool _open,
        bool _long
    ) external;

    /**
     * @dev Resets a trade borrowing fee to 0 (useful when new trade opened or when partial trade executed)
     * @param _collateralIndex index of the collateral
     * @param _trader address of the trader
     * @param _pairIndex index of the pair
     * @param _index index of the trade
     * @param _long true if trade is long, false if trade is short
     */
    function resetTradeBorrowingFees(
        uint8 _collateralIndex,
        address _trader,
        uint16 _pairIndex,
        uint32 _index,
        bool _long
    ) external;

    /**
     * @dev Returns the pending acc borrowing fees for a pair on both sides
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     * @param _currentBlock current block number
     * @return accFeeLong new pair acc borrowing fee on long side
     * @return accFeeShort new pair acc borrowing fee on short side
     * @return pairAccFeeDelta  pair acc borrowing fee delta (for side that changed)
     */
    function getBorrowingPairPendingAccFees(
        uint8 _collateralIndex,
        uint16 _pairIndex,
        uint256 _currentBlock
    ) external view returns (uint64 accFeeLong, uint64 accFeeShort, uint64 pairAccFeeDelta);

    /**
     * @dev Returns the pending acc borrowing fees for a borrowing group on both sides
     * @param _collateralIndex index of the collateral
     * @param _groupIndex index of the borrowing group
     * @param _currentBlock current block number
     * @return accFeeLong new group acc borrowing fee on long side
     * @return accFeeShort new group acc borrowing fee on short side
     * @return groupAccFeeDelta  group acc borrowing fee delta (for side that changed)
     */
    function getBorrowingGroupPendingAccFees(
        uint8 _collateralIndex,
        uint16 _groupIndex,
        uint256 _currentBlock
    ) external view returns (uint64 accFeeLong, uint64 accFeeShort, uint64 groupAccFeeDelta);

    /**
     * @dev Returns the borrowing fee for a trade
     * @param _input input data (collateralIndex, trader, pairIndex, index, long, collateral, leverage)
     * @return feeAmountCollateral borrowing fee (collateral precision)
     */
    function getTradeBorrowingFee(BorrowingFeeInput memory _input) external view returns (uint256 feeAmountCollateral);

    /**
     * @dev Returns the liquidation price for a trade
     * @param _input input data (collateralIndex, trader, pairIndex, index, openPrice, long, collateral, leverage)
     */
    function getTradeLiquidationPrice(LiqPriceInput calldata _input) external view returns (uint256);

    /**
     * @dev Returns the open interests for a pair
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     * @return longOi open interest on long side
     * @return shortOi open interest on short side
     */
    function getPairOisCollateral(
        uint8 _collateralIndex,
        uint16 _pairIndex
    ) external view returns (uint256 longOi, uint256 shortOi);

    /**
     * @dev Returns the borrowing group index for a pair
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     * @return groupIndex borrowing group index
     */
    function getBorrowingPairGroupIndex(
        uint8 _collateralIndex,
        uint16 _pairIndex
    ) external view returns (uint16 groupIndex);

    /**
     * @dev Returns the open interest in collateral tokens for a pair on one side
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     * @param _long true if long side
     */
    function getPairOiCollateral(uint8 _collateralIndex, uint16 _pairIndex, bool _long) external view returns (uint256);

    /**
     * @dev Returns whether a trade is within the max group borrowing open interest
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     * @param _long true if long side
     * @param _positionSizeCollateral position size of the trade in collateral tokens
     */
    function withinMaxBorrowingGroupOi(
        uint8 _collateralIndex,
        uint16 _pairIndex,
        bool _long,
        uint256 _positionSizeCollateral
    ) external view returns (bool);

    /**
     * @dev Returns a borrowing group's data
     * @param _collateralIndex index of the collateral
     * @param _groupIndex index of the borrowing group
     */
    function getBorrowingGroup(
        uint8 _collateralIndex,
        uint16 _groupIndex
    ) external view returns (BorrowingData memory group);

    /**
     * @dev Returns a borrowing group's oi data
     * @param _collateralIndex index of the collateral
     * @param _groupIndex index of the borrowing group
     */
    function getBorrowingGroupOi(
        uint8 _collateralIndex,
        uint16 _groupIndex
    ) external view returns (OpenInterest memory group);

    /**
     * @dev Returns a borrowing pair's data
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     */
    function getBorrowingPair(uint8 _collateralIndex, uint16 _pairIndex) external view returns (BorrowingData memory);

    /**
     * @dev Returns a borrowing pair's oi data
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     */
    function getBorrowingPairOi(uint8 _collateralIndex, uint16 _pairIndex) external view returns (OpenInterest memory);

    /**
     * @dev Returns a borrowing pair's oi data
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     */
    function getBorrowingPairGroups(
        uint8 _collateralIndex,
        uint16 _pairIndex
    ) external view returns (BorrowingPairGroup[] memory);

    /**
     * @dev Returns all borrowing pairs' borrowing data, oi data, and pair groups data
     * @param _collateralIndex index of the collateral
     */
    function getAllBorrowingPairs(
        uint8 _collateralIndex
    ) external view returns (BorrowingData[] memory, OpenInterest[] memory, BorrowingPairGroup[][] memory);

    /**
     * @dev Returns borrowing groups' data and oi data
     * @param _collateralIndex index of the collateral
     * @param _indices indices of the groups
     */
    function getBorrowingGroups(
        uint8 _collateralIndex,
        uint16[] calldata _indices
    ) external view returns (BorrowingData[] memory, OpenInterest[] memory);

    /**
     * @dev Returns borrowing groups' data
     * @param _collateralIndex index of the collateral
     * @param _trader address of trader
     * @param _index index of trade
     */
    function getBorrowingInitialAccFees(
        uint8 _collateralIndex,
        address _trader,
        uint32 _index
    ) external view returns (BorrowingInitialAccFees memory);

    /**
     * @dev Returns the max open interest for a pair
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     */
    function getPairMaxOi(uint8 _collateralIndex, uint16 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns the max open interest in collateral tokens for a pair
     * @param _collateralIndex index of the collateral
     * @param _pairIndex index of the pair
     */
    function getPairMaxOiCollateral(uint8 _collateralIndex, uint16 _pairIndex) external view returns (uint256);

    /**
     * @dev Emitted when a pair's borrowing params is updated
     * @param pairIndex index of the pair
     * @param groupIndex index of its new group
     * @param feePerBlock new fee per block
     * @param feeExponent new fee exponent
     * @param maxOi new max open interest
     */
    event BorrowingPairParamsUpdated(
        uint8 indexed collateralIndex,
        uint16 indexed pairIndex,
        uint16 indexed groupIndex,
        uint32 feePerBlock,
        uint48 feeExponent,
        uint72 maxOi
    );

    /**
     * @dev Emitted when a pair's borrowing group has been updated
     * @param pairIndex index of the pair
     * @param prevGroupIndex previous borrowing group index
     * @param newGroupIndex new borrowing group index
     */
    event BorrowingPairGroupUpdated(
        uint8 indexed collateralIndex,
        uint16 indexed pairIndex,
        uint16 prevGroupIndex,
        uint16 newGroupIndex
    );

    /**
     * @dev Emitted when a group's borrowing params is updated
     * @param groupIndex index of the group
     * @param feePerBlock new fee per block
     * @param maxOi new max open interest
     * @param feeExponent new fee exponent
     */
    event BorrowingGroupUpdated(
        uint8 indexed collateralIndex,
        uint16 indexed groupIndex,
        uint32 feePerBlock,
        uint72 maxOi,
        uint48 feeExponent
    );

    /**
     * @dev Emitted when a trade's initial acc borrowing fees are stored
     * @param trader address of the trader
     * @param pairIndex index of the pair
     * @param index index of the trade
     * @param initialPairAccFee initial pair acc fee (for the side of the trade)
     * @param initialGroupAccFee initial group acc fee (for the side of the trade)
     */
    event BorrowingInitialAccFeesStored(
        uint8 indexed collateralIndex,
        address indexed trader,
        uint16 indexed pairIndex,
        uint32 index,
        bool long,
        uint64 initialPairAccFee,
        uint64 initialGroupAccFee
    );

    /**
     * @dev Emitted when a trade is executed and borrowing callback is handled
     * @param trader address of the trader
     * @param pairIndex index of the pair
     * @param index index of the trade
     * @param open true if trade has been opened, false if trade has been closed
     * @param long true if trade is long, false if trade is short
     * @param positionSizeCollateral position size of the trade in collateral tokens
     */
    event TradeBorrowingCallbackHandled(
        uint8 indexed collateralIndex,
        address indexed trader,
        uint16 indexed pairIndex,
        uint32 index,
        bool open,
        bool long,
        uint256 positionSizeCollateral
    );

    /**
     * @dev Emitted when a pair's borrowing acc fees are updated
     * @param pairIndex index of the pair
     * @param currentBlock current block number
     * @param accFeeLong new pair acc borrowing fee on long side
     * @param accFeeShort new pair acc borrowing fee on short side
     */
    event BorrowingPairAccFeesUpdated(
        uint8 indexed collateralIndex,
        uint16 indexed pairIndex,
        uint256 currentBlock,
        uint64 accFeeLong,
        uint64 accFeeShort
    );

    /**
     * @dev Emitted when a group's borrowing acc fees are updated
     * @param groupIndex index of the borrowing group
     * @param currentBlock current block number
     * @param accFeeLong new group acc borrowing fee on long side
     * @param accFeeShort new group acc borrowing fee on short side
     */
    event BorrowingGroupAccFeesUpdated(
        uint8 indexed collateralIndex,
        uint16 indexed groupIndex,
        uint256 currentBlock,
        uint64 accFeeLong,
        uint64 accFeeShort
    );

    /**
     * @dev Emitted when a borrowing pair's open interests are updated
     * @param pairIndex index of the pair
     * @param long true if long side
     * @param increase true if open interest is increased, false if decreased
     * @param delta change in open interest in collateral tokens (1e10 precision)
     * @param newOiLong new open interest on long side
     * @param newOiShort new open interest on short side
     */
    event BorrowingPairOiUpdated(
        uint8 indexed collateralIndex,
        uint16 indexed pairIndex,
        bool long,
        bool increase,
        uint72 delta,
        uint72 newOiLong,
        uint72 newOiShort
    );

    /**
     * @dev Emitted when a borrowing group's open interests are updated
     * @param groupIndex index of the borrowing group
     * @param long true if long side
     * @param increase true if open interest is increased, false if decreased
     * @param delta change in open interest in collateral tokens (1e10 precision)
     * @param newOiLong new open interest on long side
     * @param newOiShort new open interest on short side
     */
    event BorrowingGroupOiUpdated(
        uint8 indexed collateralIndex,
        uint16 indexed groupIndex,
        bool long,
        bool increase,
        uint72 delta,
        uint72 newOiLong,
        uint72 newOiShort
    );

    error BorrowingZeroGroup();
    error BorrowingWrongExponent();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IChainConfig.sol";

/**
 * @dev Interface for GNSChainConfig facet (inherits types and also contains functions, events, and custom errors)
 */
interface IChainConfigUtils is IChainConfig {
    /**
     * @dev Initializer for ChainConfig facet
     * @param _nativeTransferGasLimit new native transfer gas limit
     * @param _nativeTransferEnabled whether native transfers should be enabled
     */
    function initializeChainConfig(uint16 _nativeTransferGasLimit, bool _nativeTransferEnabled) external;

    /**
     * @dev Updates native transfer gas limit
     * @param _nativeTransferGasLimit new native transfer gas limit. Must be greater or equal to MIN_NATIVE_TRANSFER_GAS_LIMIT.
     */
    function updateNativeTransferGasLimit(uint16 _nativeTransferGasLimit) external;

    /**
     * @dev Updates `nativeTransferEnabled`. When true, the diamond is allowed to unwrap native tokens on transfer-out.
     * @param _nativeTransferEnable the new value
     */
    function updateNativeTransferEnabled(bool _nativeTransferEnable) external;

    /**
     * @dev Returns gas limit to be used for native transfers, with a minimum of `MIN_NATIVE_TRANSFER_GAS_LIMIT` (21k gas)
     */
    function getNativeTransferGasLimit() external returns (uint16);

    /**
     * @dev Returns whether native transfers are enabled
     */
    function getNativeTransferEnabled() external returns (bool);

    /**
     * @dev Returns the current value for reentrancy lock
     */
    function getReentrancyLock() external returns (uint256);

    /**
     * @dev Emitted when `nativeTransferGasLimit` is updated
     * @param newLimit new gas limit
     */
    event NativeTransferGasLimitUpdated(uint16 newLimit);

    /**
     * @dev Emitted when `nativeTransferEnabled` is updated
     * @param enabled the new value
     */
    event NativeTransferEnabledUpdated(bool enabled);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IFeeTiers.sol";

/**
 * @dev Interface for GNSFeeTiers facet (inherits types and also contains functions, events, and custom errors)
 */
interface IFeeTiersUtils is IFeeTiers {
    /**
     *
     * @param _groupIndices group indices (pairs storage fee index) to initialize
     * @param _groupVolumeMultipliers corresponding group volume multipliers (1e3)
     * @param _feeTiersIndices fee tiers indices to initialize
     * @param _feeTiers fee tiers values to initialize (feeMultiplier, pointsThreshold)
     */
    function initializeFeeTiers(
        uint256[] calldata _groupIndices,
        uint256[] calldata _groupVolumeMultipliers,
        uint256[] calldata _feeTiersIndices,
        IFeeTiersUtils.FeeTier[] calldata _feeTiers
    ) external;

    /**
     * @dev Updates groups volume multipliers
     * @param _groupIndices indices of groups to update
     * @param _groupVolumeMultipliers corresponding new volume multipliers (1e3)
     */
    function setGroupVolumeMultipliers(
        uint256[] calldata _groupIndices,
        uint256[] calldata _groupVolumeMultipliers
    ) external;

    /**
     * @dev Updates fee tiers
     * @param _feeTiersIndices indices of fee tiers to update
     * @param _feeTiers new fee tiers values (feeMultiplier, pointsThreshold)
     */
    function setFeeTiers(uint256[] calldata _feeTiersIndices, IFeeTiersUtils.FeeTier[] calldata _feeTiers) external;

    /**
     * @dev Updates traders enrollment status in fee tiers
     * @param _traders group of traders
     * @param _values corresponding enrollment values
     */
    function setTradersFeeTiersEnrollment(
        address[] calldata _traders,
        IFeeTiersUtils.TraderEnrollment[] calldata _values
    ) external;

    /**
     * @dev Credits points to traders
     * @param _traders traders addresses
     * @param _creditTypes types of credit (IMMEDIATE, CLAIMABLE)
     * @param _points points to credit (1e18)
     */
    function addTradersUnclaimedPoints(
        address[] calldata _traders,
        IFeeTiersUtils.CreditType[] calldata _creditTypes,
        uint224[] calldata _points
    ) external;

    /**
     * @dev Increases daily points from a new trade, re-calculate trailing points, and cache daily fee tier for a trader.
     * @param _trader trader address
     * @param _volumeUsd trading volume in USD (1e18)
     * @param _pairIndex pair index
     */
    function updateTraderPoints(address _trader, uint256 _volumeUsd, uint256 _pairIndex) external;

    /**
     * @dev Returns fee amount after applying the trader's active fee tier multiplier
     * @param _trader address of trader
     * @param _normalFeeAmountCollateral base fee amount (collateral precision)
     */
    function calculateFeeAmount(address _trader, uint256 _normalFeeAmountCollateral) external view returns (uint256);

    /**
     * Returns the current number of active fee tiers
     */
    function getFeeTiersCount() external view returns (uint256);

    /**
     * @dev Returns a fee tier's details (feeMultiplier, pointsThreshold)
     * @param _feeTierIndex fee tier index
     */
    function getFeeTier(uint256 _feeTierIndex) external view returns (IFeeTiersUtils.FeeTier memory);

    /**
     * @dev Returns a group's volume multiplier
     * @param _groupIndex group index (pairs storage fee index)
     */
    function getGroupVolumeMultiplier(uint256 _groupIndex) external view returns (uint256);

    /**
     * @dev Returns a trader's info (lastDayUpdated, trailingPoints)
     * @param _trader trader address
     */
    function getFeeTiersTraderInfo(address _trader) external view returns (IFeeTiersUtils.TraderInfo memory);

    /**
     * @dev Returns a trader's daily fee tier info (feeMultiplierCache, points)
     * @param _trader trader address
     * @param _day day
     */
    function getFeeTiersTraderDailyInfo(
        address _trader,
        uint32 _day
    ) external view returns (IFeeTiersUtils.TraderDailyInfo memory);

    /**
     * @dev Returns a trader's fee tiers enrollment status
     * @param _trader trader address
     */
    function getTraderFeeTiersEnrollment(
        address _trader
    ) external view returns (IFeeTiersUtils.TraderEnrollment memory);

    /**
     * @dev Returns a trader's unclaimed points, credited by Governance
     * @param _trader trader address
     */
    function getTraderUnclaimedPoints(address _trader) external view returns (uint224);

    /**
     * @dev Emitted when group volume multipliers are updated
     * @param groupIndices indices of updated groups
     * @param groupVolumeMultipliers new corresponding volume multipliers (1e3)
     */
    event GroupVolumeMultipliersUpdated(uint256[] groupIndices, uint256[] groupVolumeMultipliers);

    /**
     * @dev Emitted when fee tiers are updated
     * @param feeTiersIndices indices of updated fee tiers
     * @param feeTiers new corresponding fee tiers values (feeMultiplier, pointsThreshold)
     */
    event FeeTiersUpdated(uint256[] feeTiersIndices, IFeeTiersUtils.FeeTier[] feeTiers);

    /**
     * @dev Emitted when a trader's daily points are updated
     * @param trader trader address
     * @param day day
     * @param points points added (1e18 precision)
     */
    event TraderDailyPointsIncreased(address indexed trader, uint32 indexed day, uint224 points);

    /**
     * @dev Emitted when a trader info is updated for the first time
     * @param trader address of trader
     * @param day day
     */
    event TraderInfoFirstUpdate(address indexed trader, uint32 day);

    /**
     * @dev Emitted when a trader's trailing points are updated
     * @param trader trader address
     * @param fromDay from day
     * @param toDay to day
     * @param expiredPoints expired points amount (1e18 precision)
     */
    event TraderTrailingPointsExpired(address indexed trader, uint32 fromDay, uint32 toDay, uint224 expiredPoints);

    /**
     * @dev Emitted when a trader's info is updated
     * @param trader address of trader
     * @param traderInfo new trader info value (lastDayUpdated, trailingPoints)
     */
    event TraderInfoUpdated(address indexed trader, IFeeTiersUtils.TraderInfo traderInfo);

    /**
     * @dev Emitted when a trader's cached fee multiplier is updated (this is the one used in fee calculations)
     * @param trader address of trader
     * @param day day
     * @param feeMultiplier new fee multiplier (1e3 precision)
     */
    event TraderFeeMultiplierCached(address indexed trader, uint32 indexed day, uint32 feeMultiplier);

    /**
     * @dev Emitted when a trader's enrollment status is updated
     * @param trader address of trader
     * @param enrollment trader's new enrollment status
     */
    event TraderEnrollmentUpdated(address indexed trader, IFeeTiersUtils.TraderEnrollment enrollment);

    /**
     * @dev Emitted when a trader is credited points by governance
     * @param trader trader address
     * @param day day the points were credited on, may be different from the day the points were claimed
     * @param creditType credit type (IMMEDIATE, CLAIMABLE)
     * @param points points added (1e18 precision)
     */
    event TraderPointsCredited(
        address indexed trader,
        uint32 indexed day,
        IFeeTiers.CreditType creditType,
        uint224 points
    );

    /**
     * @dev Emitted when a trader's unclaimed points are claimed
     * @param trader trader address
     * @param day day of claim
     * @param points points added (1e18 precision)
     */
    event TraderUnclaimedPointsClaimed(address indexed trader, uint32 indexed day, uint224 points);

    error WrongFeeTier();
    error PointsOverflow();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IOtc.sol";

/**
 * @dev Interface for GNSOtc facet (inherits types and also contains functions, events, and custom errors)
 */
interface IOtcUtils is IOtc {
    /**
     * @dev Initializer for OTC facet
     * @param _config new OTC Config
     */
    function initializeOtc(IOtcUtils.OtcConfig memory _config) external;

    /**
     * @dev Updates OTC config
     * @param _config new OTC Config. Sum of `treasuryShareP`, `stakingShareP`, `burnShareP` must equal 100 and `premiumP` must be less than or equal to MAX_PREMIUM_P
     */
    function updateOtcConfig(IOtcUtils.OtcConfig memory _config) external;

    /**
     * @dev Increases OTC balance for a collateral
     * @param _collateralIndex collateral index
     * @param _collateralAmount amount of collateral to increase (collateral precision)
     */
    function addOtcCollateralBalance(uint8 _collateralIndex, uint256 _collateralAmount) external;

    /**
     * @dev OTC Buys GNS from caller for `_amountCollateral` of `_collateralIndex`
     * @param _collateralIndex collateral index
     * @param _collateralAmount amount of collateral to trade (collateral precision)
     */
    function sellGnsForCollateral(uint8 _collateralIndex, uint256 _collateralAmount) external;

    /**
     * @dev Returns OTC Config
     */
    function getOtcConfig() external view returns (IOtcUtils.OtcConfig memory);

    /**
     * @dev Returns OTC balance for a collateral (collateral precision)
     * @param _collateralIndex collateral index
     */
    function getOtcBalance(uint8 _collateralIndex) external view returns (uint256);

    /**
     * @dev Returns OTC rate (price + premium) of GNS in collateral (1e10)
     * @param _collateralIndex collateral index
     */
    function getOtcRate(uint8 _collateralIndex) external view returns (uint256);

    /**
     * @dev Emitted when OTCConfig is updated
     * @param config new OTC config
     */
    event OtcConfigUpdated(IOtcUtils.OtcConfig config);

    /**
     * @dev Emitted when OTC balance is updated
     * @param collateralIndex collateral index
     * @param balanceCollateral new balance (collateral precision)
     */
    event OtcBalanceUpdated(uint8 indexed collateralIndex, uint256 balanceCollateral);

    /**
     * @dev Emitted when an OTC trade is executed
     * @param collateralIndex collateral index
     * @param collateralAmount amount of collateral traded (collateral precision)
     * @param gnsPriceCollateral effective gns/collateral price, including premium (1e10)
     * @param treasuryAmountGns amount of GNS sent to treasury (1e18)
     * @param stakingAmountGns amount of GNS sent to GNS Staking (1e18)
     * @param burnAmountGns amount of GNS burned (1e18)
     */
    event OtcExecuted(
        uint8 indexed collateralIndex,
        uint256 collateralAmount,
        uint256 gnsPriceCollateral,
        uint256 treasuryAmountGns,
        uint256 stakingAmountGns,
        uint256 burnAmountGns
    );

    error InvalidShareSum();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IPairsStorage.sol";

/**
 * @dev Interface for GNSPairsStorage facet (inherits types and also contains functions, events, and custom errors)
 */
interface IPairsStorageUtils is IPairsStorage {
    /**
     * @dev Initializes liquidation params for all existing groups
     * @param _groupLiquidationParams liquidation params for each group (index corresponds to group index)
     */
    function initializeGroupLiquidationParams(
        IPairsStorage.GroupLiquidationParams[] memory _groupLiquidationParams
    ) external;

    /**
     * @dev Copies all existing fee groups to new mapping, multiplies existing groups min/max lev by 1e3, initializes new global trade fee params
     * @param _tradeFeeParams global trade fee params
     */
    function initializeNewFees(IPairsStorage.GlobalTradeFeeParams memory _tradeFeeParams) external;

    /**
     * @dev Adds new trading pairs
     * @param _pairs pairs to add
     */
    function addPairs(Pair[] calldata _pairs) external;

    /**
     * @dev Updates trading pairs
     * @param _pairIndices indices of pairs
     * @param _pairs new pairs values
     */
    function updatePairs(uint256[] calldata _pairIndices, Pair[] calldata _pairs) external;

    /**
     * @dev Adds new pair groups
     * @param _groups groups to add
     */
    function addGroups(Group[] calldata _groups) external;

    /**
     * @dev Updates pair groups
     * @param _ids indices of groups
     * @param _groups new groups values
     */
    function updateGroups(uint256[] calldata _ids, Group[] calldata _groups) external;

    /**
     * @dev Adds new pair fees groups
     * @param _fees fees to add
     */
    function addFees(FeeGroup[] calldata _fees) external;

    /**
     * @dev Updates pair fees groups
     * @param _ids indices of fees
     * @param _fees new fees values
     */
    function updateFees(uint256[] calldata _ids, FeeGroup[] calldata _fees) external;

    /**
     * @dev Updates pair custom max leverages (if unset group default is used); useful to delist a pair if new value is below the pair's group minLeverage
     * @param _indices indices of pairs
     * @param _values new custom max leverages (1e3 precision)
     */
    function setPairCustomMaxLeverages(uint256[] calldata _indices, uint256[] calldata _values) external;

    /**
     * @dev Updates group liquidation params (will only apply for trades opened after the change)
     * @param _groupIndex index of group
     * @param _params new liquidation params
     */
    function setGroupLiquidationParams(
        uint256 _groupIndex,
        IPairsStorage.GroupLiquidationParams memory _params
    ) external;

    /**
     * @dev Updates global trade fee params
     * @param _feeParams new fee params
     */
    function setGlobalTradeFeeParams(IPairsStorage.GlobalTradeFeeParams memory _feeParams) external;

    /**
     * @dev Returns data needed by price aggregator when doing a new price request
     * @param _pairIndex index of pair
     * @return from pair from (eg. BTC)
     * @return to pair to (eg. USD)
     */
    function pairJob(uint256 _pairIndex) external view returns (string memory from, string memory to);

    /**
     * @dev Returns whether a pair is listed
     * @param _from pair from (eg. BTC)
     * @param _to pair to (eg. USD)
     */
    function isPairListed(string calldata _from, string calldata _to) external view returns (bool);

    /**
     * @dev Returns whether a pair index is listed
     * @param _pairIndex index of pair to check
     */
    function isPairIndexListed(uint256 _pairIndex) external view returns (bool);

    /**
     * @dev Returns a pair's details
     * @param _index index of pair
     */
    function pairs(uint256 _index) external view returns (Pair memory);

    /**
     * @dev Returns number of listed pairs
     */
    function pairsCount() external view returns (uint256);

    /**
     * @dev Returns a pair's spread % (1e10 precision)
     * @param _pairIndex index of pair
     */
    function pairSpreadP(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns a pair's min leverage (1e3 precision)
     * @param _pairIndex index of pair
     */
    function pairMinLeverage(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns a pair's total position size fee % (1e10 precision)
     * @param _pairIndex index of pair
     */
    function pairTotalPositionSizeFeeP(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns a pair's total liquidation collateral fee % (1e10 precision)
     * @param _pairIndex index of pair
     */
    function pairTotalLiqCollateralFeeP(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns a pair's oracle position size fee % (1e10 precision)
     * @param _pairIndex index of pair
     */
    function pairOraclePositionSizeFeeP(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns a pair's min position size in USD (1e18 precision)
     * @param _pairIndex index of pair
     */
    function pairMinPositionSizeUsd(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns global trade fee params
     */
    function getGlobalTradeFeeParams() external view returns (IPairsStorage.GlobalTradeFeeParams memory);

    /**
     * @dev Returns a pair's minimum trading fee in USD (1e18 precision)
     * @param _pairIndex index of pair
     */
    function pairMinFeeUsd(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns a group details
     * @param _index index of group
     */
    function groups(uint256 _index) external view returns (Group memory);

    /**
     * @dev Returns number of listed groups
     */
    function groupsCount() external view returns (uint256);

    /**
     * @dev Returns a fee group details
     * @param _index index of fee group
     */
    function fees(uint256 _index) external view returns (FeeGroup memory);

    /**
     * @dev Returns number of listed fee groups
     */
    function feesCount() external view returns (uint256);

    /**
     * @dev Returns a pair's active max leverage; custom if set, otherwise group default (1e3 precision)
     * @param _pairIndex index of pair
     */
    function pairMaxLeverage(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns a pair's custom max leverage; 0 if not set (1e3 precision)
     * @param _pairIndex index of pair
     */
    function pairCustomMaxLeverage(uint256 _pairIndex) external view returns (uint256);

    /**
     * @dev Returns all listed pairs custom max leverages (1e3 precision)
     */
    function getAllPairsRestrictedMaxLeverage() external view returns (uint256[] memory);

    /**
     * @dev Returns a group's liquidation params
     */
    function getGroupLiquidationParams(
        uint256 _groupIndex
    ) external view returns (IPairsStorage.GroupLiquidationParams memory);

    /**
     * @dev Returns a pair's group liquidation params
     */
    function getPairLiquidationParams(
        uint256 _pairIndex
    ) external view returns (IPairsStorage.GroupLiquidationParams memory);

    /**
     * @dev Emitted when a new pair is listed
     * @param index index of pair
     * @param from pair from (eg. BTC)
     * @param to pair to (eg. USD)
     */
    event PairAdded(uint256 index, string from, string to);

    /**
     * @dev Emitted when a pair is updated
     * @param index index of pair
     */
    event PairUpdated(uint256 index);

    /**
     * @dev Emitted when a pair's custom max leverage is updated
     * @param index index of pair
     * @param maxLeverage new max leverage (1e3 precision)
     */
    event PairCustomMaxLeverageUpdated(uint256 indexed index, uint256 maxLeverage);

    /**
     * @dev Emitted when a new group is added
     * @param index index of group
     * @param name name of group
     */
    event GroupAdded(uint256 index, string name);

    /**
     * @dev Emitted when a group is updated
     * @param index index of group
     */
    event GroupUpdated(uint256 index);

    /**
     * @dev Emitted when a new fee group is added
     * @param index index of fee group
     * @param feeGroup fee group
     */
    event FeeAdded(uint256 index, FeeGroup feeGroup);

    /**
     * @dev Emitted when a fee group is updated
     * @param index index of fee group
     * @param feeGroup updated fee group
     */
    event FeeUpdated(uint256 index, FeeGroup feeGroup);

    /**
     * @dev Emitted when a group liquidation params are updated
     * @param index index of group
     * @param params new group liquidation params
     */
    event GroupLiquidationParamsUpdated(uint256 index, IPairsStorage.GroupLiquidationParams params);

    /**
     * @dev Emitted when global trade fee params are updated
     * @param feeParams new fee params
     */
    event GlobalTradeFeeParamsUpdated(IPairsStorage.GlobalTradeFeeParams feeParams);

    error PairNotListed();
    error GroupNotListed();
    error FeeNotListed();
    error WrongLeverages();
    error WrongFees();
    error PairAlreadyListed();
    error MaxLiqSpreadPTooHigh();
    error WrongLiqParamsThresholds();
    error WrongLiqParamsLeverages();
    error StartLiqThresholdTooHigh();
    error EndLiqThresholdTooLow();
    error StartLeverageTooLow();
    error EndLeverageTooHigh();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {Chainlink} from "@chainlink/contracts/src/v0.8/Chainlink.sol";

import "../types/IPriceAggregator.sol";
import "../types/ITradingStorage.sol";
import "../types/ITradingCallbacks.sol";

/**
 * @dev Interface for GNSPriceAggregator facet (inherits types and also contains functions, events, and custom errors)
 */
interface IPriceAggregatorUtils is IPriceAggregator {
    /**
     * @dev Initializes price aggregator facet
     * @param _linkToken LINK token address
     * @param _linkUsdPriceFeed LINK/USD price feed address
     * @param _twapInterval TWAP interval (seconds)
     * @param _minAnswers answers count at which a trade is executed with median
     * @param _oracles chainlink oracle addresses
     * @param _jobIds chainlink job ids (market/lookback)
     * @param _collateralIndices collateral indices
     * @param _gnsCollateralLiquidityPools corresponding GNS/collateral liquidity pool values
     * @param _collateralUsdPriceFeeds corresponding collateral/USD chainlink price feeds
     */
    function initializePriceAggregator(
        address _linkToken,
        IChainlinkFeed _linkUsdPriceFeed,
        uint24 _twapInterval,
        uint8 _minAnswers,
        address[] memory _oracles,
        bytes32[2] memory _jobIds,
        uint8[] calldata _collateralIndices,
        LiquidityPoolInput[] memory _gnsCollateralLiquidityPools,
        IChainlinkFeed[] memory _collateralUsdPriceFeeds
    ) external;

    /**
     * @dev Updates LINK/USD chainlink price feed
     * @param _value new value
     */
    function updateLinkUsdPriceFeed(IChainlinkFeed _value) external;

    /**
     * @dev Updates collateral/USD chainlink price feed
     * @param _collateralIndex collateral index
     * @param _value new value
     */
    function updateCollateralUsdPriceFeed(uint8 _collateralIndex, IChainlinkFeed _value) external;

    /**
     * @dev Updates collateral/GNS liquidity pool
     * @param _collateralIndex collateral index
     * @param _liquidityPoolInput new values
     */
    function updateCollateralGnsLiquidityPool(
        uint8 _collateralIndex,
        LiquidityPoolInput calldata _liquidityPoolInput
    ) external;

    /**
     * @dev Updates TWAP interval
     * @param _twapInterval new value (seconds)
     */
    function updateTwapInterval(uint24 _twapInterval) external;

    /**
     * @dev Updates minimum answers count
     * @param _value new value
     */
    function updateMinAnswers(uint8 _value) external;

    /**
     * @dev Adds an oracle
     * @param _a new value
     */
    function addOracle(address _a) external;

    /**
     * @dev Replaces an oracle
     * @param _index oracle index
     * @param _a new value
     */
    function replaceOracle(uint256 _index, address _a) external;

    /**
     * @dev Removes an oracle
     * @param _index oracle index
     */
    function removeOracle(uint256 _index) external;

    /**
     * @dev Updates market job id
     * @param _jobId new value
     */
    function setMarketJobId(bytes32 _jobId) external;

    /**
     * @dev Updates lookback job id
     * @param _jobId new value
     */
    function setLimitJobId(bytes32 _jobId) external;

    /**
     * @dev Requests price from oracles
     * @param _collateralIndex collateral index
     * @param _pairIndex pair index
     * @param _tradeId trade id
     * @param _orderId order id
     * @param _orderType order type
     * @param _positionSizeCollateral position size (collateral precision)
     * @param _fromBlock block number from which to start fetching prices (for lookbacks)
     */
    function getPrice(
        uint8 _collateralIndex,
        uint16 _pairIndex,
        ITradingStorage.Id memory _tradeId,
        ITradingStorage.Id memory _orderId,
        ITradingStorage.PendingOrderType _orderType,
        uint256 _positionSizeCollateral,
        uint256 _fromBlock
    ) external;

    /**
     * @dev Fulfills price request, called by chainlink oracles
     * @param _requestId request id
     * @param _priceData price data
     */
    function fulfill(bytes32 _requestId, uint256 _priceData) external;

    /**
     * @dev Claims back LINK tokens, called by gov fund
     */
    function claimBackLink() external;

    /**
     * @dev Returns LINK fee for price request
     * @param _collateralIndex collateral index
     * @param _trader trader address
     * @param _pairIndex pair index
     * @param _positionSizeCollateral position size in collateral tokens (collateral precision)
     */
    function getLinkFee(
        uint8 _collateralIndex,
        address _trader,
        uint16 _pairIndex,
        uint256 _positionSizeCollateral
    ) external view returns (uint256);

    /**
     * @dev Returns collateral/USD price
     * @param _collateralIndex index of collateral
     */
    function getCollateralPriceUsd(uint8 _collateralIndex) external view returns (uint256);

    /**
     * @dev Returns USD normalized value from collateral value
     * @param _collateralIndex index of collateral
     * @param _collateralValue collateral value (collateral precision)
     */
    function getUsdNormalizedValue(uint8 _collateralIndex, uint256 _collateralValue) external view returns (uint256);

    /**
     * @dev Returns collateral value (collateral precision) from USD normalized value
     * @param _collateralIndex index of collateral
     * @param _normalizedValue normalized value (1e18 USD)
     */
    function getCollateralFromUsdNormalizedValue(
        uint8 _collateralIndex,
        uint256 _normalizedValue
    ) external view returns (uint256);

    /**
     * @dev Returns GNS/USD price based on GNS/collateral price
     * @param _collateralIndex index of collateral
     */
    function getGnsPriceUsd(uint8 _collateralIndex) external view returns (uint256);

    /**
     * @dev Returns GNS/USD price based on GNS/collateral price
     * @param _collateralIndex index of collateral
     * @param _gnsPriceCollateral GNS/collateral price (1e10)
     */
    function getGnsPriceUsd(uint8 _collateralIndex, uint256 _gnsPriceCollateral) external view returns (uint256);

    /**
     * @dev Returns GNS/collateral price
     * @param _collateralIndex index of collateral
     */
    function getGnsPriceCollateralIndex(uint8 _collateralIndex) external view returns (uint256);

    /**
     * @dev Returns GNS/collateral price
     * @param _collateral address of the collateral
     */
    function getGnsPriceCollateralAddress(address _collateral) external view returns (uint256);

    /**
     * @dev Returns the link/usd price feed address
     */
    function getLinkUsdPriceFeed() external view returns (IChainlinkFeed);

    /**
     * @dev Returns the twap interval in seconds
     */
    function getTwapInterval() external view returns (uint24);

    /**
     * @dev Returns the minimum answers to execute an order and take the median
     */
    function getMinAnswers() external view returns (uint8);

    /**
     * @dev Returns the market job id
     */
    function getMarketJobId() external view returns (bytes32);

    /**
     * @dev Returns the limit job id
     */
    function getLimitJobId() external view returns (bytes32);

    /**
     * @dev Returns a specific oracle
     * @param _index index of the oracle
     */
    function getOracle(uint256 _index) external view returns (address);

    /**
     * @dev Returns all oracles
     */
    function getOracles() external view returns (address[] memory);

    /**
     * @dev Returns collateral/gns liquidity pool info
     * @param _collateralIndex index of collateral
     */
    function getCollateralGnsLiquidityPool(uint8 _collateralIndex) external view returns (LiquidityPoolInfo memory);

    /**
     * @dev Returns collateral/usd chainlink price feed
     * @param _collateralIndex index of collateral
     */
    function getCollateralUsdPriceFeed(uint8 _collateralIndex) external view returns (IChainlinkFeed);

    /**
     * @dev Returns order data
     * @param _requestId index of collateral
     */
    function getPriceAggregatorOrder(bytes32 _requestId) external view returns (Order memory);

    /**
     * @dev Returns order data
     * @param _orderId order id
     */
    function getPriceAggregatorOrderAnswers(
        ITradingStorage.Id calldata _orderId
    ) external view returns (OrderAnswer[] memory);

    /**
     * @dev Returns chainlink token address
     */
    function getChainlinkToken() external view returns (address);

    /**
     * @dev Returns requestCount (used by ChainlinkClientUtils)
     */
    function getRequestCount() external view returns (uint256);

    /**
     * @dev Returns pendingRequests mapping entry (used by ChainlinkClientUtils)
     */
    function getPendingRequest(bytes32 _id) external view returns (address);

    /**
     * @dev Emitted when LINK/USD price feed is updated
     * @param value new value
     */
    event LinkUsdPriceFeedUpdated(address value);

    /**
     * @dev Emitted when collateral/USD price feed is updated
     * @param collateralIndex collateral index
     * @param value new value
     */
    event CollateralUsdPriceFeedUpdated(uint8 collateralIndex, address value);

    /**
     * @dev Emitted when collateral/GNS Uniswap V3 pool is updated
     * @param collateralIndex collateral index
     * @param newValue new value
     */
    event CollateralGnsLiquidityPoolUpdated(uint8 collateralIndex, LiquidityPoolInfo newValue);

    /**
     * @dev Emitted when TWAP interval is updated
     * @param newValue new value
     */
    event TwapIntervalUpdated(uint32 newValue);

    /**
     * @dev Emitted when minimum answers count is updated
     * @param value new value
     */
    event MinAnswersUpdated(uint8 value);

    /**
     * @dev Emitted when an oracle is added
     * @param index new oracle index
     * @param value value
     */
    event OracleAdded(uint256 index, address value);

    /**
     * @dev Emitted when an oracle is replaced
     * @param index oracle index
     * @param oldOracle old value
     * @param newOracle new value
     */
    event OracleReplaced(uint256 index, address oldOracle, address newOracle);

    /**
     * @dev Emitted when an oracle is removed
     * @param index oracle index
     * @param oldOracle old value
     */
    event OracleRemoved(uint256 index, address oldOracle);

    /**
     * @dev Emitted when market job id is updated
     * @param index index
     * @param jobId new value
     */
    event JobIdUpdated(uint256 index, bytes32 jobId);

    /**
     * @dev Emitted when a chainlink request is created
     * @param request link request details
     */
    event LinkRequestCreated(Chainlink.Request request);

    /**
     * @dev Emitted when a price is requested to the oracles
     * @param collateralIndex collateral index
     * @param pairIndex trading pair index
     * @param tradeId trader id
     * @param pendingOrderId pending order id
     * @param orderType order type (market open/market close/limit open/stop open/etc.)
     * @param fromBlock block number from which to start fetching prices (for lookbacks)
     * @param isLookback true if lookback
     * @param job chainlink job id (market/lookback)
     * @param linkFeePerNode link fee distributed per node (1e18 precision)
     * @param nodesCount amount of nodes to fetch prices from
     */
    event PriceRequested(
        uint8 indexed collateralIndex,
        uint256 indexed pairIndex,
        ITradingStorage.Id tradeId,
        ITradingStorage.Id pendingOrderId,
        ITradingStorage.PendingOrderType orderType,
        uint256 fromBlock,
        bool isLookback,
        bytes32 job,
        uint256 linkFeePerNode,
        uint256 nodesCount
    );

    /**
     * @dev Emitted when a trading callback is called from the price aggregator
     * @param a aggregator answer data
     * @param orderType order type
     */
    event TradingCallbackExecuted(ITradingCallbacks.AggregatorAnswer a, ITradingStorage.PendingOrderType orderType);

    /**
     * @dev Emitted when a price is received from the oracles
     * @param orderId pending order id
     * @param pairIndex trading pair index
     * @param request chainlink request id
     * @param priceData OrderAnswer compressed into uint256
     * @param isLookback true if lookback
     * @param usedInMedian false if order already executed because min answers count was already reached
     */
    event PriceReceived(
        ITradingStorage.Id orderId,
        uint16 indexed pairIndex,
        bytes32 request,
        uint256 priceData,
        bool isLookback,
        bool usedInMedian
    );

    /**
     * @dev Emitted when LINK tokens are claimed back by gov fund
     * @param amountLink amount of LINK tokens claimed back
     */
    event LinkClaimedBack(uint256 amountLink);

    error TransferAndCallToOracleFailed();
    error SourceNotOracleOfRequest();
    error RequestAlreadyPending();
    error OracleAlreadyListed();
    error InvalidCandle();
    error WrongCollateralUsdDecimals();
    error InvalidPoolType();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IPriceImpact.sol";
import "../types/ITradingStorage.sol";

/**
 * @dev Interface for GNSPriceImpact facet (inherits types and also contains functions, events, and custom errors)
 */
interface IPriceImpactUtils is IPriceImpact {
    /**
     * @dev Initializes price impact facet
     * @param _windowsDuration windows duration (seconds)
     * @param _windowsCount windows count
     */
    function initializePriceImpact(uint48 _windowsDuration, uint48 _windowsCount) external;

    /**
     * @dev Initializes negative pnl cumulative volume multiplier
     * @param _negPnlCumulVolMultiplier new value (1e10)
     */
    function initializeNegPnlCumulVolMultiplier(uint40 _negPnlCumulVolMultiplier) external;

    /**
     * @dev Initializes pair factors
     * @param _pairIndices pair indices to initialize
     * @param _protectionCloseFactors protection close factors (1e10)
     * @param _protectionCloseFactorBlocks protection close factor blocks
     * @param _cumulativeFactors cumulative factors (1e10)
     */
    function initializePairFactors(
        uint16[] calldata _pairIndices,
        uint40[] calldata _protectionCloseFactors,
        uint32[] calldata _protectionCloseFactorBlocks,
        uint40[] calldata _cumulativeFactors
    ) external;

    /**
     * @dev Updates price impact windows count
     * @param _newWindowsCount new windows count
     */
    function setPriceImpactWindowsCount(uint48 _newWindowsCount) external;

    /**
     * @dev Updates price impact windows duration
     * @param _newWindowsDuration new windows duration (seconds)
     */
    function setPriceImpactWindowsDuration(uint48 _newWindowsDuration) external;

    /**
     * @dev Updates negative pnl cumulative volume multiplier
     * @param _negPnlCumulVolMultiplier new value (1e10)
     */
    function setNegPnlCumulVolMultiplier(uint40 _negPnlCumulVolMultiplier) external;

    /**
     * @dev Whitelists/unwhitelists traders from protection close factor
     * @param _traders traders addresses
     * @param _whitelisted values
     */
    function setProtectionCloseFactorWhitelist(address[] calldata _traders, bool[] calldata _whitelisted) external;

    /**
     * @dev Updates pairs 1% depths above and below
     * @param _indices indices of pairs
     * @param _depthsAboveUsd depths above the price in USD
     * @param _depthsBelowUsd depths below the price in USD
     */
    function setPairDepths(
        uint256[] calldata _indices,
        uint128[] calldata _depthsAboveUsd,
        uint128[] calldata _depthsBelowUsd
    ) external;

    /**
     * @dev Sets protection close factors for pairs
     * @param _pairIndices pair indices to update
     * @param _protectionCloseFactors new protection close factors (1e10)
     */
    function setProtectionCloseFactors(
        uint16[] calldata _pairIndices,
        uint40[] calldata _protectionCloseFactors
    ) external;

    /**
     * @dev Sets protection close factor blocks duration for pairs
     * @param _pairIndices pair indices to update
     * @param _protectionCloseFactorBlocks new protection close factor blocks
     */
    function setProtectionCloseFactorBlocks(
        uint16[] calldata _pairIndices,
        uint32[] calldata _protectionCloseFactorBlocks
    ) external;

    /**
     * @dev Sets cumulative factors for pairs
     * @param _pairIndices pair indices to update
     * @param _cumulativeFactors new cumulative factors (1e10)
     */
    function setCumulativeFactors(uint16[] calldata _pairIndices, uint40[] calldata _cumulativeFactors) external;

    /**
     * @dev Sets whether pairs are exempt from price impact on open
     * @param _pairIndices pair indices to update
     * @param _exemptOnOpen new values
     */
    function setExemptOnOpen(uint16[] calldata _pairIndices, bool[] calldata _exemptOnOpen) external;

    /**
     * @dev Sets whether pairs are exempt from price impact on close once protection close factor has expired
     * @param _pairIndices pair indices to update
     * @param _exemptAfterProtectionCloseFactor new values
     */
    function setExemptAfterProtectionCloseFactor(
        uint16[] calldata _pairIndices,
        bool[] calldata _exemptAfterProtectionCloseFactor
    ) external;

    /**
     * @dev Adds open interest to current window
     * @param _trader trader address
     * @param _index trade index
     * @param _oiDeltaCollateral open interest to add (collateral precision)
     * @param _open whether it corresponds to opening or closing a trade
     * @param _isPnlPositive whether it corresponds to a positive pnl trade (only relevant when _open = false)
     */
    function addPriceImpactOpenInterest(
        address _trader,
        uint32 _index,
        uint256 _oiDeltaCollateral,
        bool _open,
        bool _isPnlPositive
    ) external;

    /**
     * @dev Returns active open interest used in price impact calculation for a pair and side (long/short)
     * @param _pairIndex index of pair
     * @param _long true for long, false for short
     */
    function getPriceImpactOi(uint256 _pairIndex, bool _long) external view returns (uint256 activeOi);

    /**
     * @dev Returns price impact % (1e10 precision) and price after impact (1e10 precision) for a trade
     * @param _trader trader address (to check if whitelisted from protection close factor)
     * @param _marketPrice market price (1e10 precision)
     * @param _pairIndex index of pair
     * @param _long true for long, false for short
     * @param _tradeOpenInterestUsd open interest of trade in USD (1e18 precision)
     * @param _isPnlPositive true if positive pnl, false if negative pnl (only relevant when _open = false)
     * @param _open true on open, false on close
     * @param _lastPosIncreaseBlock block when trade position size was last increased (only relevant when _open = false)
     * @param _contractsVersion trade contracts version
     */
    function getTradePriceImpact(
        address _trader,
        uint256 _marketPrice,
        uint256 _pairIndex,
        bool _long,
        uint256 _tradeOpenInterestUsd,
        bool _isPnlPositive,
        bool _open,
        uint256 _lastPosIncreaseBlock,
        ITradingStorage.ContractsVersion _contractsVersion
    ) external view returns (uint256 priceImpactP, uint256 priceAfterImpact);

    /**
     * @dev Returns a pair's depths above and below the price
     * @param _pairIndex index of pair
     */
    function getPairDepth(uint256 _pairIndex) external view returns (PairDepth memory);

    /**
     * @dev Returns current price impact windows settings
     */
    function getOiWindowsSettings() external view returns (OiWindowsSettings memory);

    /**
     * @dev Returns OI window details (long/short OI)
     * @param _windowsDuration windows duration (seconds)
     * @param _pairIndex index of pair
     * @param _windowId id of window
     */
    function getOiWindow(
        uint48 _windowsDuration,
        uint256 _pairIndex,
        uint256 _windowId
    ) external view returns (PairOi memory);

    /**
     * @dev Returns multiple OI windows details (long/short OI)
     * @param _windowsDuration windows duration (seconds)
     * @param _pairIndex index of pair
     * @param _windowIds ids of windows
     */
    function getOiWindows(
        uint48 _windowsDuration,
        uint256 _pairIndex,
        uint256[] calldata _windowIds
    ) external view returns (PairOi[] memory);

    /**
     * @dev Returns depths above and below the price for multiple pairs
     * @param _indices indices of pairs
     */
    function getPairDepths(uint256[] calldata _indices) external view returns (PairDepth[] memory);

    /**
     * @dev Returns factors for a set of pairs (1e10)
     * @param _indices indices of pairs
     */
    function getPairFactors(uint256[] calldata _indices) external view returns (IPriceImpact.PairFactors[] memory);

    /**
     * @dev Returns negative pnl cumulative volume multiplier
     */
    function getNegPnlCumulVolMultiplier() external view returns (uint48);

    /**
     * @dev Returns whether a trader is whitelisted from protection close factor
     */
    function getProtectionCloseFactorWhitelist(address _trader) external view returns (bool);

    /**
     * @dev Triggered when OiWindowsSettings is initialized (once)
     * @param windowsDuration duration of each window (seconds)
     * @param windowsCount number of windows
     */
    event OiWindowsSettingsInitialized(uint48 indexed windowsDuration, uint48 indexed windowsCount);

    /**
     * @dev Triggered when OiWindowsSettings.windowsCount is updated
     * @param windowsCount new number of windows
     */
    event PriceImpactWindowsCountUpdated(uint48 indexed windowsCount);

    /**
     * @dev Triggered when OiWindowsSettings.windowsDuration is updated
     * @param windowsDuration new duration of each window (seconds)
     */
    event PriceImpactWindowsDurationUpdated(uint48 indexed windowsDuration);

    /**
     * @dev Triggered when negPnlCumulVolMultiplier is updated
     * @param negPnlCumulVolMultiplier new value (1e10)
     */
    event NegPnlCumulVolMultiplierUpdated(uint40 indexed negPnlCumulVolMultiplier);

    /**
     * @dev Triggered when a trader is whitelisted/unwhitelisted from protection close factor
     * @param trader trader address
     * @param whitelisted true if whitelisted, false if unwhitelisted
     */
    event ProtectionCloseFactorWhitelistUpdated(address trader, bool whitelisted);

    /**
     * @dev Triggered when a pair's protection close factor is updated
     * @param pairIndex index of the pair
     * @param protectionCloseFactor new protection close factor (1e10)
     */
    event ProtectionCloseFactorUpdated(uint256 indexed pairIndex, uint40 protectionCloseFactor);

    /**
     * @dev Triggered when a pair's protection close factor duration is updated
     * @param pairIndex index of the pair
     * @param protectionCloseFactorBlocks new protection close factor blocks
     */
    event ProtectionCloseFactorBlocksUpdated(uint256 indexed pairIndex, uint32 protectionCloseFactorBlocks);

    /**
     * @dev Triggered when a pair's cumulative factor is updated
     * @param pairIndex index of the pair
     * @param cumulativeFactor new cumulative factor (1e10)
     */
    event CumulativeFactorUpdated(uint256 indexed pairIndex, uint40 cumulativeFactor);

    /**
     * @dev Triggered when a pair's exemptOnOpen value is updated
     * @param pairIndex index of the pair
     * @param exemptOnOpen whether the pair is exempt of price impact on open
     */
    event ExemptOnOpenUpdated(uint256 indexed pairIndex, bool exemptOnOpen);

    /**
     * @dev Triggered when a pair's exemptAfterProtectionCloseFactor value is updated
     * @param pairIndex index of the pair
     * @param exemptAfterProtectionCloseFactor whether the pair is exempt of price impact on close once protection close factor has expired
     */
    event ExemptAfterProtectionCloseFactorUpdated(uint256 indexed pairIndex, bool exemptAfterProtectionCloseFactor);

    /**
     * @dev Triggered when OI is added to a window.
     * @param oiWindowUpdate OI window update details (windowsDuration, pairIndex, windowId, etc.)
     */
    event PriceImpactOpenInterestAdded(IPriceImpact.OiWindowUpdate oiWindowUpdate);

    /**
     * @dev Triggered when multiple pairs' OI are transferred to a new window (when updating windows duration).
     * @param pairsCount number of pairs
     * @param prevCurrentWindowId previous current window ID corresponding to previous window duration
     * @param prevEarliestWindowId previous earliest window ID corresponding to previous window duration
     * @param newCurrentWindowId new current window ID corresponding to new window duration
     */
    event PriceImpactOiTransferredPairs(
        uint256 pairsCount,
        uint256 prevCurrentWindowId,
        uint256 prevEarliestWindowId,
        uint256 newCurrentWindowId
    );

    /**
     * @dev Triggered when a pair's OI is transferred to a new window.
     * @param pairIndex index of the pair
     * @param totalPairOi total USD long/short OI of the pair (1e18 precision)
     */
    event PriceImpactOiTransferredPair(uint256 indexed pairIndex, IPriceImpact.PairOi totalPairOi);

    /**
     * @dev Triggered when a pair's depth is updated.
     * @param pairIndex index of the pair
     * @param valueAboveUsd new USD depth above the price
     * @param valueBelowUsd new USD depth below the price
     */
    event OnePercentDepthUpdated(uint256 indexed pairIndex, uint128 valueAboveUsd, uint128 valueBelowUsd);

    error WrongWindowsDuration();
    error WrongWindowsCount();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IReferrals.sol";

/**
 * @dev Interface for GNSReferrals facet (inherits types and also contains functions, events, and custom errors)
 */
interface IReferralsUtils is IReferrals {
    /**
     *
     * @param _allyFeeP % of total referral fee going to ally
     * @param _startReferrerFeeP initial % of total referral fee earned when zero volume referred
     * @param _targetVolumeUsd usd opening volume to refer to reach 100% of referral fee
     */
    function initializeReferrals(uint256 _allyFeeP, uint256 _startReferrerFeeP, uint256 _targetVolumeUsd) external;

    /**
     * @dev Updates allyFeeP
     * @param _value new ally fee %
     */
    function updateAllyFeeP(uint256 _value) external;

    /**
     * @dev Updates startReferrerFeeP
     * @param _value new start referrer fee %
     */
    function updateStartReferrerFeeP(uint256 _value) external;

    /**
     * @dev Updates targetVolumeUsd
     * @param _value new target volume in usd
     */
    function updateReferralsTargetVolumeUsd(uint256 _value) external;

    /**
     * @dev Whitelists ally addresses
     * @param _allies array of ally addresses
     */
    function whitelistAllies(address[] calldata _allies) external;

    /**
     * @dev Unwhitelists ally addresses
     * @param _allies array of ally addresses
     */
    function unwhitelistAllies(address[] calldata _allies) external;

    /**
     * @dev Whitelists referrer addresses
     * @param _referrers array of referrer addresses
     * @param _allies array of corresponding ally addresses
     */
    function whitelistReferrers(address[] calldata _referrers, address[] calldata _allies) external;

    /**
     * @dev Unwhitelists referrer addresses
     * @param _referrers array of referrer addresses
     */
    function unwhitelistReferrers(address[] calldata _referrers) external;

    /**
     * @dev Registers potential referrer for trader (only works if trader wasn't referred yet by someone else)
     * @param _trader trader address
     * @param _referral referrer address
     */
    function registerPotentialReferrer(address _trader, address _referral) external;

    /**
     * @dev Distributes ally and referrer rewards
     * @param _trader trader address
     * @param _volumeUsd trading volume in usd (1e18 precision)
     * @param _referrerFeeUsd referrer fee in USD (1e18 precision)
     * @param _gnsPriceUsd token price in usd (1e10 precision)
     */
    function distributeReferralReward(
        address _trader,
        uint256 _volumeUsd, // 1e18
        uint256 _referrerFeeUsd,
        uint256 _gnsPriceUsd // 1e10
    ) external;

    /**
     * @dev Claims pending GNS ally rewards of caller
     */
    function claimAllyRewards() external;

    /**
     * @dev Claims pending GNS referrer rewards of caller
     */
    function claimReferrerRewards() external;

    /**
     * @dev Returns referrer fee % progress towards earning 100% based on his volume referred (1e10)
     * @param _referrer referrer address
     */
    function getReferrerFeeProgressP(address _referrer) external view returns (uint256);

    /**
     * @dev Returns last referrer of trader (whether referrer active or not)
     * @param _trader address of trader
     */
    function getTraderLastReferrer(address _trader) external view returns (address);

    /**
     * @dev Returns active referrer of trader
     * @param _trader address of trader
     */
    function getTraderActiveReferrer(address _trader) external view returns (address);

    /**
     * @dev Returns referrers referred by ally
     * @param _ally address of ally
     */
    function getReferrersReferred(address _ally) external view returns (address[] memory);

    /**
     * @dev Returns traders referred by referrer
     * @param _referrer address of referrer
     */
    function getTradersReferred(address _referrer) external view returns (address[] memory);

    /**
     * @dev Returns ally fee % of total referral fee
     */
    function getReferralsAllyFeeP() external view returns (uint256);

    /**
     * @dev Returns start referrer fee % of total referral fee when zero volume was referred
     */
    function getReferralsStartReferrerFeeP() external view returns (uint256);

    /**
     * @dev Returns target volume in usd to reach 100% of referral fee
     */
    function getReferralsTargetVolumeUsd() external view returns (uint256);

    /**
     * @dev Returns ally details
     * @param _ally address of ally
     */
    function getAllyDetails(address _ally) external view returns (AllyDetails memory);

    /**
     * @dev Returns referrer details
     * @param _referrer address of referrer
     */
    function getReferrerDetails(address _referrer) external view returns (ReferrerDetails memory);

    /**
     * @dev Emitted when allyFeeP is updated
     * @param value new ally fee %
     */
    event UpdatedAllyFeeP(uint256 value);

    /**
     * @dev Emitted when startReferrerFeeP is updated
     * @param value new start referrer fee %
     */
    event UpdatedStartReferrerFeeP(uint256 value);

    /**
     * @dev Emitted when openFeeP is updated
     * @param value new open fee %
     */
    event UpdatedOpenFeeP(uint256 value);

    /**
     * @dev Emitted when targetVolumeUsd is updated
     * @param value new target volume in usd
     */
    event UpdatedTargetVolumeUsd(uint256 value);

    /**
     * @dev Emitted when an ally is whitelisted
     * @param ally ally address
     */
    event AllyWhitelisted(address indexed ally);

    /**
     * @dev Emitted when an ally is unwhitelisted
     * @param ally ally address
     */
    event AllyUnwhitelisted(address indexed ally);

    /**
     * @dev Emitted when a referrer is whitelisted
     * @param referrer referrer address
     * @param ally ally address
     */
    event ReferrerWhitelisted(address indexed referrer, address indexed ally);

    /**
     * @dev Emitted when a referrer is unwhitelisted
     * @param referrer referrer address
     */
    event ReferrerUnwhitelisted(address indexed referrer);

    /**
     * @dev Emitted when a trader has a new active referrer
     */
    event ReferrerRegistered(address indexed trader, address indexed referrer);

    /**
     * @dev Emitted when ally rewards are distributed for a trade
     * @param ally address of ally
     * @param trader address of trader
     * @param volumeUsd trade volume in usd (1e18 precision)
     * @param amountGns amount of GNS reward (1e18 precision)
     * @param amountValueUsd USD value of GNS reward (1e18 precision)
     */
    event AllyRewardDistributed(
        address indexed ally,
        address indexed trader,
        uint256 volumeUsd,
        uint256 amountGns,
        uint256 amountValueUsd
    );

    /**
     * @dev Emitted when referrer rewards are distributed for a trade
     * @param referrer address of referrer
     * @param trader address of trader
     * @param volumeUsd trade volume in usd (1e18 precision)
     * @param amountGns amount of GNS reward (1e18 precision)
     * @param amountValueUsd USD value of GNS reward (1e18 precision)
     */
    event ReferrerRewardDistributed(
        address indexed referrer,
        address indexed trader,
        uint256 volumeUsd,
        uint256 amountGns,
        uint256 amountValueUsd
    );

    /**
     * @dev Emitted when an ally claims his pending rewards
     * @param ally address of ally
     * @param amountGns GNS pending rewards amount
     */
    event AllyRewardsClaimed(address indexed ally, uint256 amountGns);

    /**
     * @dev Emitted when a referrer claims his pending rewards
     * @param referrer address of referrer
     * @param amountGns GNS pending rewards amount
     */
    event ReferrerRewardsClaimed(address indexed referrer, uint256 amountGns);

    error NoPendingRewards();
    error AlreadyActive();
    error AlreadyInactive();
    error AllyNotActive();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/ITradingCallbacks.sol";
import "../libraries/IUpdateLeverageUtils.sol";
import "../libraries/IUpdatePositionSizeUtils.sol";
import "../libraries/ITradingCommonUtils.sol";

/**
 * @dev Interface for GNSTradingCallbacks facet (inherits types and also contains functions, events, and custom errors)
 */
interface ITradingCallbacksUtils is
    ITradingCallbacks,
    IUpdateLeverageUtils,
    IUpdatePositionSizeUtils,
    ITradingCommonUtils
{
    /**
     *
     * @param _vaultClosingFeeP the % of closing fee going to vault
     */
    function initializeCallbacks(uint8 _vaultClosingFeeP) external;

    /**
     * @dev Initialize the treasury address
     * @param _treasury the treasury address
     */
    function initializeTreasuryAddress(address _treasury) external;

    /**
     * @dev Update the % of closing fee going to vault
     * @param _valueP the % of closing fee going to vault
     */
    function updateVaultClosingFeeP(uint8 _valueP) external;

    /**
     * @dev Updates the treasury address
     * @param _treasury the new treasury address
     */
    function updateTreasuryAddress(address _treasury) external;

    /**
     * @dev Claim the pending gov fees for all collaterals
     */
    function claimPendingGovFees() external;

    /**
     * @dev Executes a pending open trade market order
     * @param _a the price aggregator answer (order id, price, etc.)
     */
    function openTradeMarketCallback(AggregatorAnswer memory _a) external;

    /**
     * @dev Executes a pending close trade market order
     * @param _a the price aggregator answer (order id, price, etc.)
     */
    function closeTradeMarketCallback(AggregatorAnswer memory _a) external;

    /**
     * @dev Executes a pending open trigger order (for limit/stop orders)
     * @param _a the price aggregator answer (order id, price, etc.)
     */
    function executeTriggerOpenOrderCallback(AggregatorAnswer memory _a) external;

    /**
     * @dev Executes a pending close trigger order (for tp/sl/liq orders)
     * @param _a the price aggregator answer (order id, price, etc.)
     */
    function executeTriggerCloseOrderCallback(AggregatorAnswer memory _a) external;

    /**
     * @dev Executes a pending update leverage order
     * @param _a the price aggregator answer (order id, price, etc.)
     */
    function updateLeverageCallback(AggregatorAnswer memory _a) external;

    /**
     * @dev Executes a pending increase position size market order
     * @param _a the price aggregator answer (order id, price, etc.)
     */
    function increasePositionSizeMarketCallback(AggregatorAnswer memory _a) external;

    /**
     * @dev Executes a pending decrease position size market order
     * @param _a the price aggregator answer (order id, price, etc.)
     */
    function decreasePositionSizeMarketCallback(AggregatorAnswer memory _a) external;

    /**
     * @dev Returns the current vaultClosingFeeP value (%)
     */
    function getVaultClosingFeeP() external view returns (uint8);

    /**
     * @dev Returns the current pending gov fees for a collateral index (collateral precision)
     */
    function getPendingGovFeesCollateral(uint8 _collateralIndex) external view returns (uint256);

    /**
     * @dev Makes open trigger (STOP/LIMIT) checks like slippage, price impact, missed targets and returns cancellation reason if any
     * @param _tradeId the id of the trade
     * @param _orderType the pending order type
     * @param _open the `open` value from an aggregator answer
     * @param _high the `high` value from an aggregator answer
     * @param _low the `low` value from an aggregator answer
     */
    function validateTriggerOpenOrderCallback(
        ITradingStorage.Id memory _tradeId,
        ITradingStorage.PendingOrderType _orderType,
        uint64 _open,
        uint64 _high,
        uint64 _low
    )
        external
        view
        returns (
            ITradingStorage.Trade memory t,
            ITradingCallbacks.CancelReason cancelReason,
            ITradingCallbacks.Values memory v
        );

    /**
     * @dev Makes close trigger (SL/TP/LIQ) checks like slippage and price impact and returns cancellation reason if any
     * @param _tradeId the id of the trade
     * @param _orderType the pending order type
     * @param _open the `open` value from an aggregator answer
     * @param _high the `high` value from an aggregator answer
     * @param _low the `low` value from an aggregator answer
     */
    function validateTriggerCloseOrderCallback(
        ITradingStorage.Id memory _tradeId,
        ITradingStorage.PendingOrderType _orderType,
        uint64 _open,
        uint64 _high,
        uint64 _low
    )
        external
        view
        returns (
            ITradingStorage.Trade memory t,
            ITradingCallbacks.CancelReason cancelReason,
            ITradingCallbacks.Values memory v
        );

    /**
     * @dev Emitted when vaultClosingFeeP is updated
     * @param valueP the % of closing fee going to vault
     */
    event VaultClosingFeePUpdated(uint8 valueP);

    /**
     * @dev Emitted when gov fees are claimed for a collateral
     * @param collateralIndex the collateral index
     * @param amountCollateral the amount of fees claimed (collateral precision)
     */
    event PendingGovFeesClaimed(uint8 collateralIndex, uint256 amountCollateral);

    /**
     * @dev Emitted when a market order is executed (open/close)
     * @param orderId the id of the corresponding pending market order
     * @param user trade user
     * @param index trade index
     * @param t the trade object
     * @param open true for a market open order, false for a market close order
     * @param oraclePrice the oracle price without spread/impact (1e10 precision)
     * @param marketPrice the price at which the trade was executed (1e10 precision)
     * @param liqPrice trade liquidation price (1e10 precision)
     * @param priceImpactP the price impact in percentage (1e10 precision)
     * @param percentProfit the profit in percentage (1e10 precision)
     * @param amountSentToTrader the final amount of collateral sent to the trader
     * @param collateralPriceUsd the price of the collateral in USD (1e8 precision)
     */
    event MarketExecuted(
        ITradingStorage.Id orderId,
        address indexed user,
        uint32 indexed index,
        ITradingStorage.Trade t,
        bool open,
        uint256 oraclePrice,
        uint256 marketPrice,
        uint256 liqPrice,
        uint256 priceImpactP,
        int256 percentProfit, // before fees
        uint256 amountSentToTrader,
        uint256 collateralPriceUsd // 1e8
    );

    /**
     * @dev Emitted when a limit/stop order is executed
     * @param orderId the id of the corresponding pending trigger order
     * @param user trade user
     * @param index trade index
     * @param limitIndex limit index
     * @param t the trade object
     * @param triggerCaller the address that triggered the limit order
     * @param orderType the type of the pending order
     * @param oraclePrice the oracle price without spread/impact (1e10 precision)
     * @param marketPrice the price at which the trade was executed (1e10 precision)
     * @param liqPrice trade liquidation price (1e10 precision)
     * @param priceImpactP the price impact in percentage (1e10 precision)
     * @param percentProfit the profit in percentage (1e10 precision)
     * @param amountSentToTrader the final amount of collateral sent to the trader
     * @param collateralPriceUsd the price of the collateral in USD (1e8 precision)
     * @param exactExecution true if guaranteed execution was used
     */
    event LimitExecuted(
        ITradingStorage.Id orderId,
        address indexed user,
        uint32 indexed index,
        uint32 indexed limitIndex,
        ITradingStorage.Trade t,
        address triggerCaller,
        ITradingStorage.PendingOrderType orderType,
        uint256 oraclePrice,
        uint256 marketPrice,
        uint256 liqPrice,
        uint256 priceImpactP,
        int256 percentProfit,
        uint256 amountSentToTrader,
        uint256 collateralPriceUsd, // 1e8
        bool exactExecution
    );

    /**
     * @dev Emitted when a pending market open order is canceled
     * @param orderId order id of the pending market open order
     * @param trader address of the trader
     * @param pairIndex index of the trading pair
     * @param cancelReason reason for the cancellation
     */
    event MarketOpenCanceled(
        ITradingStorage.Id orderId,
        address indexed trader,
        uint256 indexed pairIndex,
        CancelReason cancelReason
    );

    /**
     * @dev Emitted when a pending market close order is canceled
     * @param orderId order id of the pending market close order
     * @param trader address of the trader
     * @param pairIndex index of the trading pair
     * @param index index of the trade for trader
     * @param cancelReason reason for the cancellation
     */
    event MarketCloseCanceled(
        ITradingStorage.Id orderId,
        address indexed trader,
        uint256 indexed pairIndex,
        uint256 indexed index,
        CancelReason cancelReason
    );

    /**
     * @dev Emitted when a pending trigger order is canceled
     * @param orderId order id of the pending trigger order
     * @param triggerCaller address of the trigger caller
     * @param orderType type of the pending trigger order
     * @param cancelReason reason for the cancellation
     */
    event TriggerOrderCanceled(
        ITradingStorage.Id orderId,
        address indexed triggerCaller,
        ITradingStorage.PendingOrderType orderType,
        CancelReason cancelReason
    );

    /**
     *
     * @param trader address of the trader
     * @param index index of the trade
     * @param collateralIndex index of the collateral
     * @param amountCollateral amount charged (collateral precision)
     */
    event BorrowingFeeCharged(
        address indexed trader,
        uint32 indexed index,
        uint8 indexed collateralIndex,
        uint256 amountCollateral
    );
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/ITradingStorage.sol";

/**
 * @dev Interface for TradingCommonUtils library
 */
interface ITradingCommonUtils {
    struct TradePriceImpactInput {
        ITradingStorage.Trade trade;
        uint256 oraclePrice;
        uint256 spreadP;
        uint256 positionSizeCollateral;
    }

    /**
     * @dev Emitted when gov fee is charged
     * @param trader address of the trader
     * @param collateralIndex index of the collateral
     * @param amountCollateral amount charged (collateral precision)
     */
    event GovFeeCharged(address indexed trader, uint8 indexed collateralIndex, uint256 amountCollateral);

    /**
     * @dev Emitted when referral fee is charged
     * @param trader address of the trader
     * @param collateralIndex index of the collateral
     * @param amountCollateral amount charged (collateral precision)
     */
    event ReferralFeeCharged(address indexed trader, uint8 indexed collateralIndex, uint256 amountCollateral);

    /**
     * @dev Emitted when GNS otc fee is charged
     * @param trader address of the trader
     * @param collateralIndex index of the collateral
     * @param amountCollateral amount charged (collateral precision)
     */
    event GnsOtcFeeCharged(address indexed trader, uint8 indexed collateralIndex, uint256 amountCollateral);

    /**
     * @dev Emitted when trigger fee is charged
     * @param trader address of the trader
     * @param collateralIndex index of the collateral
     * @param amountCollateral amount charged (collateral precision)
     */
    event TriggerFeeCharged(address indexed trader, uint8 indexed collateralIndex, uint256 amountCollateral);

    /**
     * @dev Emitted when gToken fee is charged
     * @param trader address of the trader
     * @param collateralIndex index of the collateral
     * @param amountCollateral amount charged (collateral precision)
     */
    event GTokenFeeCharged(address indexed trader, uint8 indexed collateralIndex, uint256 amountCollateral);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/ITradingInteractions.sol";
import "../types/ITradingStorage.sol";
import "../libraries/IUpdateLeverageUtils.sol";
import "../libraries/IUpdatePositionSizeUtils.sol";

/**
 * @dev Interface for GNSTradingInteractions facet (inherits types and also contains functions, events, and custom errors)
 */
interface ITradingInteractionsUtils is ITradingInteractions, IUpdateLeverageUtils, IUpdatePositionSizeUtils {
    /**
     * @dev Initializes the trading facet
     * @param _marketOrdersTimeoutBlocks The number of blocks after which a market order is considered timed out
     */
    function initializeTrading(uint16 _marketOrdersTimeoutBlocks, address[] memory _usersByPassTriggerLink) external;

    /**
     * @dev Updates marketOrdersTimeoutBlocks
     * @param _valueBlocks blocks after which a market order times out
     */
    function updateMarketOrdersTimeoutBlocks(uint16 _valueBlocks) external;

    /**
     * @dev Updates the users that can bypass the link cost of triggerOrder
     * @param _users array of addresses that can bypass the link cost of triggerOrder
     * @param _shouldByPass whether each user should bypass the link cost
     */
    function updateByPassTriggerLink(address[] memory _users, bool[] memory _shouldByPass) external;

    /**
     * @dev Sets _delegate as the new delegate of caller (can call delegatedAction)
     * @param _delegate the new delegate address
     */
    function setTradingDelegate(address _delegate) external;

    /**
     * @dev Removes the delegate of caller (can't call delegatedAction)
     */
    function removeTradingDelegate() external;

    /**
     * @dev Caller executes a trading action on behalf of _trader using delegatecall
     * @param _trader the trader address to execute the trading action for
     * @param _callData the data to be executed (open trade/close trade, etc.)
     */
    function delegatedTradingAction(address _trader, bytes calldata _callData) external returns (bytes memory);

    /**
     * @dev Opens a new trade/limit order/stop order
     * @param _trade the trade to be opened
     * @param _maxSlippageP the maximum allowed slippage % when open the trade (1e3 precision)
     * @param _referrer the address of the referrer (can only be set once for a trader)
     */
    function openTrade(ITradingStorage.Trade memory _trade, uint16 _maxSlippageP, address _referrer) external;

    /**
     * @dev Wraps native token and opens a new trade/limit order/stop order
     * @param _trade the trade to be opened
     * @param _maxSlippageP the maximum allowed slippage % when open the trade (1e3 precision)
     * @param _referrer the address of the referrer (can only be set once for a trader)
     */
    function openTradeNative(
        ITradingStorage.Trade memory _trade,
        uint16 _maxSlippageP,
        address _referrer
    ) external payable;

    /**
     * @dev Updates existing trade's max closing slippage % for caller
     * @param _index index of trade
     * @param _maxSlippageP new max closing slippage % (1e3 precision)
     */
    function updateMaxClosingSlippageP(uint32 _index, uint16 _maxSlippageP) external;

    /**
     * @dev Closes an open trade (market order) for caller
     * @param _index the index of the trade of caller
     * @param _expectedPrice expected closing price, used to check max slippage (1e10 precision)
     */
    function closeTradeMarket(uint32 _index, uint64 _expectedPrice) external;

    /**
     * @dev Updates an existing limit/stop order for caller
     * @param _index index of limit/stop order of caller
     * @param _triggerPrice new trigger price of limit/stop order (1e10 precision)
     * @param _tp new tp of limit/stop order (1e10 precision)
     * @param _sl new sl of limit/stop order (1e10 precision)
     * @param _maxSlippageP new max slippage % of limit/stop order (1e3 precision)
     */
    function updateOpenOrder(
        uint32 _index,
        uint64 _triggerPrice,
        uint64 _tp,
        uint64 _sl,
        uint16 _maxSlippageP
    ) external;

    /**
     * @dev Cancels an open limit/stop order for caller
     * @param _index index of limit/stop order of caller
     */
    function cancelOpenOrder(uint32 _index) external;

    /**
     * @dev Updates the tp of an open trade for caller
     * @param _index index of open trade of caller
     * @param _newTp new tp of open trade (1e10 precision)
     */
    function updateTp(uint32 _index, uint64 _newTp) external;

    /**
     * @dev Updates the sl of an open trade for caller
     * @param _index index of open trade of caller
     * @param _newSl new sl of open trade (1e10 precision)
     */
    function updateSl(uint32 _index, uint64 _newSl) external;

    /**
     * @dev Initiates a new trigger order (for tp/sl/liq/limit/stop orders)
     * @param _packed the packed data of the trigger order (orderType, trader, index)
     */
    function triggerOrder(uint256 _packed) external;

    /**
     * @dev Safety function in case oracles don't answer in time, allows caller to cancel a pending order and if relevant claim back any stuck collateral
     * @dev Only allowed for MARKET_OPEN, MARKET_CLOSE, UPDATE_LEVERAGE, MARKET_PARTIAL_OPEN, and MARKET_PARTIAL_CLOSE orders
     * @param _orderIndex the id of the pending order to cancel
     */
    function cancelOrderAfterTimeout(uint32 _orderIndex) external;

    /**
     * @dev Update trade leverage
     * @param _index index of trade
     * @param _newLeverage new leverage (1e3)
     */
    function updateLeverage(uint32 _index, uint24 _newLeverage) external;

    /**
     * @dev Wraps native tokens and updates trade leverage
     * @param _index index of trade
     * @param _newLeverage new leverage (1e3)
     */
    function updateLeverageNative(uint32 _index, uint24 _newLeverage) external payable;

    /**
     * @dev Increase trade position size
     * @param _index index of trade
     * @param _collateralDelta collateral to add (collateral precision)
     * @param _leverageDelta partial trade leverage (1e3)
     * @param _expectedPrice expected price of execution (1e10 precision)
     * @param _maxSlippageP max slippage % (1e3)
     */
    function increasePositionSize(
        uint32 _index,
        uint120 _collateralDelta,
        uint24 _leverageDelta,
        uint64 _expectedPrice,
        uint16 _maxSlippageP
    ) external;

    /**
     * @dev Wraps native token and increase trade position size
     * @param _index index of trade
     * @param _collateralDelta collateral to add (collateral precision)
     * @param _leverageDelta partial trade leverage (1e3)
     * @param _expectedPrice expected price of execution (1e10 precision)
     * @param _maxSlippageP max slippage % (1e3)
     */
    function increasePositionSizeNative(
        uint32 _index,
        uint120 _collateralDelta,
        uint24 _leverageDelta,
        uint64 _expectedPrice,
        uint16 _maxSlippageP
    ) external payable;

    /**
     * @dev Decrease trade position size
     * @param _index index of trade
     * @param _collateralDelta collateral to remove (collateral precision)
     * @param _leverageDelta leverage to reduce by (1e3)
     * @param _expectedPrice expected closing price, used to check max slippage (1e10 precision)
     */
    function decreasePositionSize(
        uint32 _index,
        uint120 _collateralDelta,
        uint24 _leverageDelta,
        uint64 _expectedPrice
    ) external;

    /**
     * @dev Returns the wrapped native token or address(0) if the current chain, or the wrapped token, is not supported.
     */
    function getWrappedNativeToken() external view returns (address);

    /**
     * @dev Returns true if the token is the wrapped native token for the current chain, where supported.
     * @param _token token address
     */
    function isWrappedNativeToken(address _token) external view returns (bool);

    /**
     * @dev Returns the address a trader delegates his trading actions to
     * @param _trader address of the trader
     */
    function getTradingDelegate(address _trader) external view returns (address);

    /**
     * @dev Returns the current marketOrdersTimeoutBlocks value
     */
    function getMarketOrdersTimeoutBlocks() external view returns (uint16);

    /**
     * @dev Returns whether a user bypasses trigger link costs
     * @param _user address of the user
     */
    function getByPassTriggerLink(address _user) external view returns (bool);

    /**
     * @dev Emitted when marketOrdersTimeoutBlocks is updated
     * @param newValueBlocks the new value of marketOrdersTimeoutBlocks
     */
    event MarketOrdersTimeoutBlocksUpdated(uint256 newValueBlocks);

    /**
     * @dev Emitted when a user is allowed/disallowed to bypass the link cost of triggerOrder
     * @param user address of the user
     * @param bypass whether the user can bypass the link cost of triggerOrder
     */
    event ByPassTriggerLinkUpdated(address indexed user, bool bypass);

    /**
     * @dev Emitted when a market order is initiated
     * @param orderId price aggregator order id of the pending market order
     * @param trader address of the trader
     * @param pairIndex index of the trading pair
     * @param open whether the market order is for opening or closing a trade
     */
    event MarketOrderInitiated(ITradingStorage.Id orderId, address indexed trader, uint16 indexed pairIndex, bool open);

    /**
     * @dev Emitted when a new limit/stop order is placed
     * @param trader address of the trader
     * @param pairIndex index of the trading pair
     * @param index index of the open limit order for caller
     */
    event OpenOrderPlaced(address indexed trader, uint16 indexed pairIndex, uint32 indexed index);

    /**
     *
     * @param trader address of the trader
     * @param pairIndex index of the trading pair
     * @param index index of the open limit/stop order for caller
     * @param newPrice new trigger price (1e10 precision)
     * @param newTp new tp (1e10 precision)
     * @param newSl new sl (1e10 precision)
     * @param maxSlippageP new max slippage % (1e3 precision)
     */
    event OpenLimitUpdated(
        address indexed trader,
        uint16 indexed pairIndex,
        uint32 indexed index,
        uint64 newPrice,
        uint64 newTp,
        uint64 newSl,
        uint64 maxSlippageP
    );

    /**
     * @dev Emitted when a limit/stop order is canceled (collateral sent back to trader)
     * @param trader address of the trader
     * @param pairIndex index of the trading pair
     * @param index index of the open limit/stop order for caller
     */
    event OpenLimitCanceled(address indexed trader, uint16 indexed pairIndex, uint32 indexed index);

    /**
     * @dev Emitted when a trigger order is initiated (tp/sl/liq/limit/stop orders)
     * @param orderId price aggregator order id of the pending trigger order
     * @param trader address of the trader
     * @param pairIndex index of the trading pair
     * @param byPassesLinkCost whether the caller bypasses the link cost
     */
    event TriggerOrderInitiated(
        ITradingStorage.Id orderId,
        address indexed trader,
        uint16 indexed pairIndex,
        bool byPassesLinkCost
    );

    /**
     * @dev Emitted when a pending market order is canceled due to timeout
     * @param pendingOrderId id of the pending order
     * @param pairIndex index of the trading pair
     */
    event ChainlinkCallbackTimeout(ITradingStorage.Id pendingOrderId, uint256 indexed pairIndex);

    /**
     * @dev Emitted when a pending market order is canceled due to timeout and new closeTradeMarket() call failed
     * @param trader address of the trader
     * @param pairIndex index of the trading pair
     * @param index index of the open trade for caller
     */
    event CouldNotCloseTrade(address indexed trader, uint16 indexed pairIndex, uint32 indexed index);

    /**
     * @dev Emitted when a native token is wrapped
     * @param trader address of the trader
     * @param nativeTokenAmount amount of native token wrapped
     */
    event NativeTokenWrapped(address indexed trader, uint256 nativeTokenAmount);

    error NotWrappedNativeToken();
    error DelegateNotApproved();
    error PriceZero();
    error AboveExposureLimits();
    error CollateralNotActive();
    error PriceImpactTooHigh();
    error NoTrade();
    error NoOrder();
    error AlreadyBeingMarketClosed();
    error ConflictingPendingOrder(ITradingStorage.PendingOrderType);
    error WrongLeverage();
    error WrongTp();
    error WrongSl();
    error WaitTimeout();
    error PendingTrigger();
    error NoSl();
    error NoTp();
    error NotYourOrder();
    error DelegatedActionNotAllowed();
    error InsufficientCollateral();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/ITradingStorage.sol";

/**
 * @dev Interface for GNSTradingStorage facet (inherits types and also contains functions, events, and custom errors)
 */
interface ITradingStorageUtils is ITradingStorage {
    /**
     * @dev Initializes the trading storage facet
     * @param _gns address of the gns token
     * @param _gnsStaking address of the gns staking contract
     */
    function initializeTradingStorage(
        address _gns,
        address _gnsStaking,
        address[] memory _collaterals,
        address[] memory _gTokens
    ) external;

    /**
     * @dev Updates the trading activated state
     * @param _activated the new trading activated state
     */
    function updateTradingActivated(TradingActivated _activated) external;

    /**
     * @dev Adds a new supported collateral
     * @param _collateral the address of the collateral
     * @param _gToken the gToken contract of the collateral
     */
    function addCollateral(address _collateral, address _gToken) external;

    /**
     * @dev Toggles the active state of a supported collateral
     * @param _collateralIndex index of the collateral
     */
    function toggleCollateralActiveState(uint8 _collateralIndex) external;

    /**
     * @dev Updates the contracts of a supported collateral trading stack
     * @param _collateral address of the collateral
     * @param _gToken the gToken contract of the collateral
     */
    function updateGToken(address _collateral, address _gToken) external;

    /**
     * @dev Stores a new trade (trade/limit/stop)
     * @param _trade trade to be stored
     * @param _tradeInfo trade info to be stored
     */
    function storeTrade(Trade memory _trade, TradeInfo memory _tradeInfo) external returns (Trade memory);

    /**
     * @dev Updates an existing trade max closing slippage %
     * @param _tradeId id of the trade
     * @param _maxSlippageP new max slippage % (1e3 precision)
     */
    function updateTradeMaxClosingSlippageP(ITradingStorage.Id memory _tradeId, uint16 _maxSlippageP) external;

    /**
     * @dev Updates an open trade collateral
     * @param _tradeId id of updated trade
     * @param _collateralAmount new collateral amount value (collateral precision)
     */
    function updateTradeCollateralAmount(Id memory _tradeId, uint120 _collateralAmount) external;

    /**
     * @dev Updates an open trade collateral
     * @param _tradeId id of updated trade
     * @param _collateralAmount new collateral amount value (collateral precision)
     * @param _leverage new leverage value
     * @param _openPrice new open price value
     * @param _isPartialIncrease refreshes trade liquidation params if true
     * @param _isPnlPositive whether the pnl is positive (only relevant when closing)
     */
    function updateTradePosition(
        Id memory _tradeId,
        uint120 _collateralAmount,
        uint24 _leverage,
        uint64 _openPrice,
        bool _isPartialIncrease,
        bool _isPnlPositive
    ) external;

    /**
     * @dev Updates an open order details (limit/stop)
     * @param _tradeId id of updated trade
     * @param _openPrice new open price (1e10)
     * @param _tp new take profit price (1e10)
     * @param _sl new stop loss price (1e10)
     * @param _maxSlippageP new max slippage % value (1e3)
     */
    function updateOpenOrderDetails(
        Id memory _tradeId,
        uint64 _openPrice,
        uint64 _tp,
        uint64 _sl,
        uint16 _maxSlippageP
    ) external;

    /**
     * @dev Updates the take profit of an open trade
     * @param _tradeId the trade id
     * @param _newTp the new take profit (1e10 precision)
     */
    function updateTradeTp(Id memory _tradeId, uint64 _newTp) external;

    /**
     * @dev Updates the stop loss of an open trade
     * @param _tradeId the trade id
     * @param _newSl the new sl (1e10 precision)
     */
    function updateTradeSl(Id memory _tradeId, uint64 _newSl) external;

    /**
     * @dev Marks an open trade/limit/stop as closed
     * @param _tradeId the trade id
     * @param _isPnlPositive whether the pnl is positive
     */
    function closeTrade(Id memory _tradeId, bool _isPnlPositive) external;

    /**
     * @dev Stores a new pending order
     * @param _pendingOrder the pending order to be stored
     */
    function storePendingOrder(PendingOrder memory _pendingOrder) external returns (PendingOrder memory);

    /**
     * @dev Closes a pending order
     * @param _orderId the id of the pending order to be closed
     */
    function closePendingOrder(Id memory _orderId) external;

    /**
     * @dev Returns collateral data by index
     * @param _index the index of the supported collateral
     */
    function getCollateral(uint8 _index) external view returns (Collateral memory);

    /**
     * @dev Returns whether can open new trades with a collateral
     * @param _index the index of the collateral to check
     */
    function isCollateralActive(uint8 _index) external view returns (bool);

    /**
     * @dev Returns whether a collateral has been listed
     * @param _index the index of the collateral to check
     */
    function isCollateralListed(uint8 _index) external view returns (bool);

    /**
     * @dev Returns the number of supported collaterals
     */
    function getCollateralsCount() external view returns (uint8);

    /**
     * @dev Returns the supported collaterals
     */
    function getCollaterals() external view returns (Collateral[] memory);

    /**
     * @dev Returns the index of a supported collateral
     * @param _collateral the address of the collateral
     */
    function getCollateralIndex(address _collateral) external view returns (uint8);

    /**
     * @dev Returns the trading activated state
     */
    function getTradingActivated() external view returns (TradingActivated);

    /**
     * @dev Returns whether a trader is stored in the traders array
     * @param _trader trader to check
     */
    function getTraderStored(address _trader) external view returns (bool);

    /**
     * @dev Returns the length of the traders array
     */
    function getTradersCount() external view returns (uint256);

    /**
     * @dev Returns all traders that have open trades using a pagination system
     * @param _offset start index in the traders array
     * @param _limit end index in the traders array
     */
    function getTraders(uint32 _offset, uint32 _limit) external view returns (address[] memory);

    /**
     * @dev Returns open trade/limit/stop order
     * @param _trader address of the trader
     * @param _index index of the trade for trader
     */
    function getTrade(address _trader, uint32 _index) external view returns (Trade memory);

    /**
     * @dev Returns all open trades/limit/stop orders for a trader
     * @param _trader address of the trader
     */
    function getTrades(address _trader) external view returns (Trade[] memory);

    /**
     * @dev Returns all trade/limit/stop orders using a pagination system
     * @param _traders list of traders to return trades for
     * @param _offset index of first trade to return
     * @param _limit index of last trade to return
     */
    function getAllTradesForTraders(
        address[] memory _traders,
        uint256 _offset,
        uint256 _limit
    ) external view returns (Trade[] memory);

    /**
     * @dev Returns all trade/limit/stop orders using a pagination system.
     * @dev Calls `getAllTradesForTraders` internally with all traders.
     * @param _offset index of first trade to return
     * @param _limit index of last trade to return
     */
    function getAllTrades(uint256 _offset, uint256 _limit) external view returns (Trade[] memory);

    /**
     * @dev Returns trade info of an open trade/limit/stop order
     * @param _trader address of the trader
     * @param _index index of the trade for trader
     */
    function getTradeInfo(address _trader, uint32 _index) external view returns (TradeInfo memory);

    /**
     * @dev Returns all trade infos of open trade/limit/stop orders for a trader
     * @param _trader address of the trader
     */
    function getTradeInfos(address _trader) external view returns (TradeInfo[] memory);

    /**
     * @dev Returns all trade infos of open trade/limit/stop orders using a pagination system
     * @param _traders list of traders to return tradeInfo for
     * @param _offset index of first tradeInfo to return
     * @param _limit index of last tradeInfo to return
     */
    function getAllTradeInfosForTraders(
        address[] memory _traders,
        uint256 _offset,
        uint256 _limit
    ) external view returns (TradeInfo[] memory);

    /**
     * @dev Returns all trade infos of open trade/limit/stop orders using a pagination system.
     * @dev Calls `getAllTradeInfosForTraders` internally with all traders.
     * @param _offset index of first tradeInfo to return
     * @param _limit index of last tradeInfo to return
     */
    function getAllTradeInfos(uint256 _offset, uint256 _limit) external view returns (TradeInfo[] memory);

    /**
     * @dev Returns a pending ordeer
     * @param _orderId id of the pending order
     */
    function getPendingOrder(Id memory _orderId) external view returns (PendingOrder memory);

    /**
     * @dev Returns all pending orders for a trader
     * @param _user address of the trader
     */
    function getPendingOrders(address _user) external view returns (PendingOrder[] memory);

    /**
     * @dev Returns all pending orders using a pagination system
     * @param _traders list of traders to return pendingOrder for
     * @param _offset index of first pendingOrder to return
     * @param _limit index of last pendingOrder to return
     */
    function getAllPendingOrdersForTraders(
        address[] memory _traders,
        uint256 _offset,
        uint256 _limit
    ) external view returns (PendingOrder[] memory);

    /**
     * @dev Returns all pending orders using a pagination system
     * @dev Calls `getAllPendingOrdersForTraders` internally with all traders.
     * @param _offset index of first pendingOrder to return
     * @param _limit index of last pendingOrder to return
     */
    function getAllPendingOrders(uint256 _offset, uint256 _limit) external view returns (PendingOrder[] memory);

    /**
     * @dev Returns the block number of the pending order for a trade (0 = doesn't exist)
     * @param _tradeId id of the trade
     * @param _orderType pending order type to check
     */
    function getTradePendingOrderBlock(Id memory _tradeId, PendingOrderType _orderType) external view returns (uint256);

    /**
     * @dev Returns the counters of a trader (currentIndex / open count for trades/tradeInfos and pendingOrders mappings)
     * @param _trader address of the trader
     * @param _type the counter type (trade/pending order)
     */
    function getCounters(address _trader, CounterType _type) external view returns (Counter memory);

    /**
     * @dev Returns the counters for a list of traders
     * @param _traders the list of traders
     * @param _type the counter type (trade/pending order)
     */
    function getCountersForTraders(
        address[] calldata _traders,
        CounterType _type
    ) external view returns (Counter[] memory);

    /**
     * @dev Returns the address of the gToken for a collateral stack
     * @param _collateralIndex the index of the supported collateral
     */
    function getGToken(uint8 _collateralIndex) external view returns (address);

    /**
     * @dev Returns the liquidation params for a trade
     * @param _trader address of the trader
     * @param _index index of the trade for trader
     */
    function getTradeLiquidationParams(
        address _trader,
        uint32 _index
    ) external view returns (IPairsStorage.GroupLiquidationParams memory);

    /**
     * @dev Returns all trade liquidation params of open trade/limit/stop orders for a trader
     * @param _trader address of the trader
     */
    function getTradesLiquidationParams(
        address _trader
    ) external view returns (IPairsStorage.GroupLiquidationParams[] memory);

    /**
     * @dev Returns all trade liquidation params of open trade/limit/stop orders using a pagination system
     * @param _traders list of traders to return liq params for
     * @param _offset index of first liq param to return
     * @param _limit index of last liq param to return
     */
    function getAllTradesLiquidationParamsForTraders(
        address[] memory _traders,
        uint256 _offset,
        uint256 _limit
    ) external view returns (IPairsStorage.GroupLiquidationParams[] memory);

    /**
     * @dev Returns all trade liquidation params of open trade/limit/stop orders using a pagination system
     * @dev Calls `getAllTradesLiquidationParamsForTraders` internally with all traders.
     * @param _offset index of first liq param to return
     * @param _limit index of last liq param to return
     */
    function getAllTradesLiquidationParams(
        uint256 _offset,
        uint256 _limit
    ) external view returns (IPairsStorage.GroupLiquidationParams[] memory);

    /**
     * @dev Returns the current contracts version
     */
    function getCurrentContractsVersion() external pure returns (ITradingStorage.ContractsVersion);

    /**
     * @dev Emitted when the trading activated state is updated
     * @param activated the new trading activated state
     */
    event TradingActivatedUpdated(TradingActivated activated);

    /**
     * @dev Emitted when a new supported collateral is added
     * @param collateral the address of the collateral
     * @param index the index of the supported collateral
     * @param gToken the gToken contract of the collateral
     */
    event CollateralAdded(address collateral, uint8 index, address gToken);

    /**
     * @dev Emitted when an existing supported collateral active state is updated
     * @param index the index of the supported collateral
     * @param isActive the new active state
     */
    event CollateralUpdated(uint8 indexed index, bool isActive);

    /**
     * @dev Emitted when an existing supported collateral is disabled (can still close trades but not open new ones)
     * @param index the index of the supported collateral
     */
    event CollateralDisabled(uint8 index);

    /**
     * @dev Emitted when the contracts of a supported collateral trading stack are updated
     * @param collateral the address of the collateral
     * @param index the index of the supported collateral
     * @param gToken the gToken contract of the collateral
     */
    event GTokenUpdated(address collateral, uint8 index, address gToken);

    /**
     * @dev Emitted when a new trade is stored
     * @param user trade user
     * @param index trade index
     * @param trade the trade stored
     * @param tradeInfo the trade info stored
     * @param liquidationParams the trade liquidation params stored
     */
    event TradeStored(
        address indexed user,
        uint32 indexed index,
        Trade trade,
        TradeInfo tradeInfo,
        IPairsStorage.GroupLiquidationParams liquidationParams
    );

    /**
     * @dev Emitted when the max closing slippage % of an open trade is updated
     * @param user trade user
     * @param index trade index
     * @param maxClosingSlippageP new max closing slippage % value (1e3 precision)
     */
    event TradeMaxClosingSlippagePUpdated(address indexed user, uint32 indexed index, uint16 maxClosingSlippageP);

    /**
     * @dev Emitted when an open trade collateral is updated
     * @param user trade user
     * @param index trade index
     * @param collateralAmount new collateral value (collateral precision)
     */
    event TradeCollateralUpdated(address indexed user, uint32 indexed index, uint120 collateralAmount);

    /**
     * @dev Emitted when an open trade collateral is updated
     * @param user trade user
     * @param index trade index
     * @param collateralAmount new collateral value (collateral precision)
     * @param leverage new leverage value if present
     * @param openPrice new open price value if present
     * @param isPartialIncrease true if trade liquidation params were refreshed
     * @param isPnlPositive true if trade pnl is positive (only relevant when closing)
     */
    event TradePositionUpdated(
        address indexed user,
        uint32 indexed index,
        uint120 collateralAmount,
        uint24 leverage,
        uint64 openPrice,
        uint64 newTp,
        uint64 newSl,
        bool isPartialIncrease,
        bool isPnlPositive
    );

    /**
     * @dev Emitted when an existing trade/limit order/stop order is updated
     * @param user trade user
     * @param index trade index
     * @param openPrice new open price value (1e10)
     * @param tp new take profit value (1e10)
     * @param sl new stop loss value (1e10)
     * @param maxSlippageP new max slippage % value (1e3)
     */
    event OpenOrderDetailsUpdated(
        address indexed user,
        uint32 indexed index,
        uint64 openPrice,
        uint64 tp,
        uint64 sl,
        uint16 maxSlippageP
    );

    /**
     * @dev Emitted when the take profit of an open trade is updated
     * @param user trade user
     * @param index trade index
     * @param newTp the new take profit (1e10 precision)
     */
    event TradeTpUpdated(address indexed user, uint32 indexed index, uint64 newTp);

    /**
     * @dev Emitted when the stop loss of an open trade is updated
     * @param user trade user
     * @param index trade index
     * @param newSl the new sl (1e10 precision)
     */
    event TradeSlUpdated(address indexed user, uint32 indexed index, uint64 newSl);

    /**
     * @dev Emitted when an open trade is closed
     * @param user trade user
     * @param index trade index
     * @param isPnlPositive true if trade pnl is positive
     */
    event TradeClosed(address indexed user, uint32 indexed index, bool isPnlPositive);

    /**
     * @dev Emitted when a new pending order is stored
     * @param pendingOrder the pending order stored
     */
    event PendingOrderStored(PendingOrder pendingOrder);

    /**
     * @dev Emitted when a pending order is closed
     * @param orderId the id of the pending order closed
     */
    event PendingOrderClosed(Id orderId);

    error MissingCollaterals();
    error CollateralAlreadyActive();
    error CollateralAlreadyDisabled();
    error TradePositionSizeZero();
    error TradeOpenPriceZero();
    error TradePairNotListed();
    error TradeTpInvalid();
    error TradeSlInvalid();
    error MaxSlippageZero();
    error TradeInfoCollateralPriceUsdZero();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/ITriggerRewards.sol";

/**
 * @dev Interface for GNSTriggerRewards facet (inherits types and also contains functions, events, and custom errors)
 */
interface ITriggerRewardsUtils is ITriggerRewards {
    /**
     *
     * @dev Initializes parameters for trigger rewards facet
     * @param _timeoutBlocks blocks after which a trigger times out
     */
    function initializeTriggerRewards(uint16 _timeoutBlocks) external;

    /**
     *
     * @dev Updates the blocks after which a trigger times out
     * @param _timeoutBlocks blocks after which a trigger times out
     */
    function updateTriggerTimeoutBlocks(uint16 _timeoutBlocks) external;

    /**
     *
     * @dev Distributes GNS rewards to oracles for a specific trigger
     * @param _rewardGns total GNS reward to be distributed among oracles
     */
    function distributeTriggerReward(uint256 _rewardGns) external;

    /**
     * @dev Claims pending GNS trigger rewards for the caller
     * @param _oracle address of the oracle
     */
    function claimPendingTriggerRewards(address _oracle) external;

    /**
     *
     * @dev Returns current triggerTimeoutBlocks value
     */
    function getTriggerTimeoutBlocks() external view returns (uint16);

    /**
     *
     * @dev Checks if an order is active (exists and has not timed out)
     * @param _orderBlock block number of the order
     */
    function hasActiveOrder(uint256 _orderBlock) external view returns (bool);

    /**
     *
     * @dev Returns the pending GNS trigger rewards for an oracle
     * @param _oracle address of the oracle
     */
    function getTriggerPendingRewardsGns(address _oracle) external view returns (uint256);

    /**
     *
     * @dev Emitted when timeoutBlocks is updated
     * @param timeoutBlocks blocks after which a trigger times out
     */
    event TriggerTimeoutBlocksUpdated(uint16 timeoutBlocks);

    /**
     *
     * @dev Emitted when trigger rewards are distributed for a specific order
     * @param rewardsPerOracleGns reward in GNS distributed per oracle
     * @param oraclesCount number of oracles rewarded
     */
    event TriggerRewarded(uint256 rewardsPerOracleGns, uint256 oraclesCount);

    /**
     *
     * @dev Emitted when pending GNS trigger rewards are claimed by an oracle
     * @param oracle address of the oracle
     * @param rewardsGns GNS rewards claimed
     */
    event TriggerRewardsClaimed(address oracle, uint256 rewardsGns);

    error TimeoutBlocksZero();
    error NoPendingTriggerRewards();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IUpdateLeverage.sol";
import "../types/ITradingStorage.sol";
import "../types/ITradingCallbacks.sol";

/**
 * @dev Interface for leverage updates
 */
interface IUpdateLeverageUtils is IUpdateLeverage {
    /**
     * @param orderId request order id
     * @param trader address of trader
     * @param pairIndex index of pair
     * @param index index of trade
     * @param isIncrease true if increase leverage, false if decrease
     * @param newLeverage new leverage value (1e3)
     */
    event LeverageUpdateInitiated(
        ITradingStorage.Id orderId,
        address indexed trader,
        uint256 indexed pairIndex,
        uint256 indexed index,
        bool isIncrease,
        uint256 newLeverage
    );

    /**
     * @param orderId request order id
     * @param isIncrease true if leverage increased, false if decreased
     * @param cancelReason cancel reason (executed if none)
     * @param collateralIndex collateral index
     * @param trader address of trader
     * @param pairIndex index of pair
     * @param index index of trade
     * @param oraclePrice current oracle price (1e10)
     * @param collateralDelta collateral delta (collateral precision)
     * @param values useful values (new collateral, new leverage, liq price, gov fee collateral)
     */
    event LeverageUpdateExecuted(
        ITradingStorage.Id orderId,
        bool isIncrease,
        ITradingCallbacks.CancelReason cancelReason,
        uint8 indexed collateralIndex,
        address indexed trader,
        uint256 pairIndex,
        uint256 indexed index,
        uint256 oraclePrice,
        uint256 collateralDelta,
        IUpdateLeverage.UpdateLeverageValues values
    );
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/IUpdatePositionSize.sol";
import "../types/ITradingStorage.sol";
import "../types/ITradingCallbacks.sol";

/**
 * @dev Interface for position size updates
 */
interface IUpdatePositionSizeUtils is IUpdatePositionSize {
    /**
     * @param orderId request order id
     * @param trader address of the trader
     * @param pairIndex index of the pair
     * @param index index of user trades
     * @param isIncrease true if increase position size, false if decrease
     * @param collateralDelta collateral delta (collateral precision)
     * @param leverageDelta leverage delta (1e3)
     */
    event PositionSizeUpdateInitiated(
        ITradingStorage.Id orderId,
        address indexed trader,
        uint256 indexed pairIndex,
        uint256 indexed index,
        bool isIncrease,
        uint256 collateralDelta,
        uint256 leverageDelta
    );

    /**
     * @param orderId request order id
     * @param cancelReason cancel reason if canceled or none if executed
     * @param collateralIndex collateral index
     * @param trader address of trader
     * @param pairIndex index of pair
     * @param index index of trade
     * @param long true for long, false for short
     * @param oraclePrice oracle price (1e10)
     * @param collateralPriceUsd collateral price in USD (1e8)
     * @param collateralDelta collateral delta (collateral precision)
     * @param leverageDelta leverage delta (1e3)
     * @param values important values (new open price, new leverage, new collateral, etc.)
     */
    event PositionSizeIncreaseExecuted(
        ITradingStorage.Id orderId,
        ITradingCallbacks.CancelReason cancelReason,
        uint8 indexed collateralIndex,
        address indexed trader,
        uint256 pairIndex,
        uint256 indexed index,
        bool long,
        uint256 oraclePrice,
        uint256 collateralPriceUsd,
        uint256 collateralDelta,
        uint256 leverageDelta,
        IUpdatePositionSize.IncreasePositionSizeValues values
    );

    /**
     * @param orderId request order id
     * @param cancelReason cancel reason if canceled or none if executed
     * @param collateralIndex collateral index
     * @param trader address of trader
     * @param pairIndex index of pair
     * @param index index of trade
     * @param long true for long, false for short
     * @param oraclePrice oracle price (1e10)
     * @param collateralPriceUsd collateral price in USD (1e8)
     * @param collateralDelta collateral delta (collateral precision)
     * @param leverageDelta leverage delta (1e3)
     * @param values important values (pnl, new leverage, new collateral, etc.)
     */
    event PositionSizeDecreaseExecuted(
        ITradingStorage.Id orderId,
        ITradingCallbacks.CancelReason cancelReason,
        uint8 indexed collateralIndex,
        address indexed trader,
        uint256 pairIndex,
        uint256 indexed index,
        bool long,
        uint256 oraclePrice,
        uint256 collateralPriceUsd,
        uint256 collateralDelta,
        uint256 leverageDelta,
        IUpdatePositionSize.DecreasePositionSizeValues values
    );

    error InvalidIncreasePositionSizeInput();
    error InvalidDecreasePositionSizeInput();
    error NewPositionSizeSmaller();
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Interface for BlockManager_Mock contract (test helper)
 */
interface IBlockManager_Mock {
    function getBlockNumber() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSAddressStore facet
 */
interface IAddressStore {
    enum Role {
        GOV_TIMELOCK,
        GOV,
        MANAGER,
        GOV_EMERGENCY
    }

    struct Addresses {
        address gns;
        address gnsStaking;
        address treasury;
    }

    struct AddressStore {
        uint256 __deprecated; // previously globalAddresses (gns token only, 1 slot)
        mapping(address => mapping(Role => bool)) accessControl;
        Addresses globalAddresses;
        uint256[7] __gap1; // gap for global addresses
        // insert new storage here
        uint256[38] __gap2; // gap for rest of diamond storage
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "./IPairsStorage.sol";

/**
 * @dev Contains the types for the GNSBorrowingFees facet
 */
interface IBorrowingFees {
    struct BorrowingFeesStorage {
        mapping(uint8 => mapping(uint16 => BorrowingData)) pairs;
        mapping(uint8 => mapping(uint16 => BorrowingPairGroup[])) pairGroups;
        mapping(uint8 => mapping(uint16 => OpenInterest)) pairOis;
        mapping(uint8 => mapping(uint16 => BorrowingData)) groups;
        mapping(uint8 => mapping(uint16 => OpenInterest)) groupOis;
        mapping(uint8 => mapping(address => mapping(uint32 => BorrowingInitialAccFees))) initialAccFees;
        uint256[44] __gap;
    }

    struct BorrowingData {
        uint32 feePerBlock; // 1e10 (%)
        uint64 accFeeLong; // 1e10 (%)
        uint64 accFeeShort; // 1e10 (%)
        uint48 accLastUpdatedBlock;
        uint48 feeExponent;
    }

    struct BorrowingPairGroup {
        uint16 groupIndex;
        uint48 block;
        uint64 initialAccFeeLong; // 1e10 (%)
        uint64 initialAccFeeShort; // 1e10 (%)
        uint64 prevGroupAccFeeLong; // 1e10 (%)
        uint64 prevGroupAccFeeShort; // 1e10 (%)
        uint64 pairAccFeeLong; // 1e10 (%)
        uint64 pairAccFeeShort; // 1e10 (%)
        uint64 __placeholder; // might be useful later
    }

    struct OpenInterest {
        uint72 long; // 1e10 (collateral)
        uint72 short; // 1e10 (collateral)
        uint72 max; // 1e10 (collateral)
        uint40 __placeholder; // might be useful later
    }

    struct BorrowingInitialAccFees {
        uint64 accPairFee; // 1e10 (%)
        uint64 accGroupFee; // 1e10 (%)
        uint48 block;
        uint80 __placeholder; // might be useful later
    }

    struct BorrowingPairParams {
        uint16 groupIndex;
        uint32 feePerBlock; // 1e10 (%)
        uint48 feeExponent;
        uint72 maxOi;
    }

    struct BorrowingGroupParams {
        uint32 feePerBlock; // 1e10 (%)
        uint72 maxOi; // 1e10
        uint48 feeExponent;
    }

    struct BorrowingFeeInput {
        uint8 collateralIndex;
        address trader;
        uint16 pairIndex;
        uint32 index;
        bool long;
        uint256 collateral; // 1e18 | 1e6 (collateral)
        uint256 leverage; // 1e3
    }

    struct LiqPriceInput {
        uint8 collateralIndex;
        address trader;
        uint16 pairIndex;
        uint32 index;
        uint64 openPrice; // 1e10
        bool long;
        uint256 collateral; // 1e18 | 1e6 (collateral)
        uint256 leverage; // 1e3
        bool useBorrowingFees;
        IPairsStorage.GroupLiquidationParams liquidationParams;
    }

    struct PendingBorrowingAccFeesInput {
        uint64 accFeeLong; // 1e10 (%)
        uint64 accFeeShort; // 1e10 (%)
        uint256 oiLong; // 1e18 | 1e6
        uint256 oiShort; // 1e18 | 1e6
        uint32 feePerBlock; // 1e10
        uint256 currentBlock;
        uint256 accLastUpdatedBlock;
        uint72 maxOi; // 1e10
        uint48 feeExponent;
        uint128 collateralPrecision;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSChainConfig facet
 */
interface IChainConfig {
    struct ChainConfigStorage {
        uint256 reentrancyLock; // HAS TO BE FIRST AND TAKE A FULL SLOT (GNSReentrancyGuard expects it)
        uint16 nativeTransferGasLimit; // 16 bits. 64,535 max value
        bool nativeTransferEnabled; // When true, the diamond is allowed to unwrap native tokens on transfer out
        uint232 __placeholder;
        uint256[48] __gap;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @author Nick Mudge <[email protected]> (https://twitter.com/mudgen)
 * @author Gains Network
 * @dev Based on EIP-2535: Diamonds (https://eips.ethereum.org/EIPS/eip-2535)
 * @dev Follows diamond-3 implementation (https://github.com/mudgen/diamond-3-hardhat/)
 * @dev Contains the types used in the diamond management contracts.
 */
interface IDiamondStorage {
    struct DiamondStorage {
        // maps function selector to the facet address and
        // the position of the selector in the facetFunctionSelectors.selectors array
        mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
        // maps facet addresses to function selectors
        mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
        // facet addresses
        address[] facetAddresses;
        address[47] __gap;
    }

    struct FacetAddressAndPosition {
        address facetAddress;
        uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array
    }

    struct FacetFunctionSelectors {
        bytes4[] functionSelectors;
        uint256 facetAddressPosition; // position of facetAddress in facetAddresses array
    }

    enum FacetCutAction {
        ADD,
        REPLACE,
        REMOVE,
        NOP
    }

    struct FacetCut {
        address facetAddress;
        FacetCutAction action;
        bytes4[] functionSelectors;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSFeeTiers facet
 */
interface IFeeTiers {
    struct FeeTiersStorage {
        FeeTier[8] feeTiers;
        mapping(uint256 => uint256) groupVolumeMultipliers; // groupIndex (pairs storage) => multiplier (1e3)
        mapping(address => TraderInfo) traderInfos; // trader => TraderInfo
        mapping(address => mapping(uint32 => TraderDailyInfo)) traderDailyInfos; // trader => day => TraderDailyInfo
        mapping(address => TraderEnrollment) traderEnrollments; // trader => TraderEnrollment
        mapping(address => uint224) unclaimedPoints; // trader => points (1e18)
        uint256[37] __gap;
    }

    enum TraderEnrollmentStatus {
        ENROLLED,
        EXCLUDED
    }

    enum CreditType {
        IMMEDIATE,
        CLAIMABLE
    }

    struct FeeTier {
        uint32 feeMultiplier; // 1e3
        uint32 pointsThreshold;
    }

    struct TraderInfo {
        uint32 lastDayUpdated;
        uint224 trailingPoints; // 1e18
    }

    struct TraderDailyInfo {
        uint32 feeMultiplierCache; // 1e3
        uint224 points; // 1e18
    }

    struct TraderEnrollment {
        TraderEnrollmentStatus status;
        uint248 __placeholder;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSPairsStorage facet
 */
interface IOtc {
    struct OtcStorage {
        mapping(uint8 => uint256) collateralBalances; // collateralIndex => available OTC value (collateral precision)
        OtcConfig otcConfig;
        uint256[47] __gap;
    }

    struct OtcConfig {
        address gnsTreasury; /// @custom:deprecated Use `AddressStore.globalAddresses.treasury` instead
        uint64 treasuryShareP; // %, 1e10 precision
        uint64 stakingShareP; // %, 1e10 precision
        uint64 burnShareP; // %, 1e10 precision
        uint64 premiumP; // %, 1e10 precision
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSPairsStorage facet
 */
interface IPairsStorage {
    struct PairsStorage {
        mapping(uint256 => Pair) pairs;
        mapping(uint256 => Group) groups;
        mapping(uint256 => Fee) fees; /// @custom:deprecated
        mapping(string => mapping(string => bool)) isPairListed;
        mapping(uint256 => uint256) pairCustomMaxLeverage; // 1e3 precision
        uint256 currentOrderId; /// @custom:deprecated
        uint256 pairsCount;
        uint256 groupsCount;
        uint256 feesCount;
        mapping(uint256 => GroupLiquidationParams) groupLiquidationParams;
        mapping(uint256 => FeeGroup) feeGroups;
        GlobalTradeFeeParams globalTradeFeeParams;
        uint256[38] __gap;
    }

    struct Pair {
        string from;
        string to;
        Feed feed; /// @custom:deprecated
        uint256 spreadP; // 1e10
        uint256 groupIndex;
        uint256 feeIndex;
    }

    struct Group {
        string name;
        bytes32 job; /// @custom:deprecated
        uint256 minLeverage; // 1e3 precision
        uint256 maxLeverage; // 1e3 precision
    }

    struct GlobalTradeFeeParams {
        uint24 referralFeeP; // 1e3 (%)
        uint24 govFeeP; // 1e3 (%)
        uint24 triggerOrderFeeP; // 1e3 (%)
        uint24 gnsOtcFeeP; // 1e3 (%)
        uint24 gTokenFeeP; // 1e3 (%)
        uint136 __placeholder;
    }

    struct FeeGroup {
        uint40 totalPositionSizeFeeP; // 1e10 (%)
        uint40 totalLiqCollateralFeeP; // 1e10 (%)
        uint40 oraclePositionSizeFeeP; // 1e10 (%)
        uint32 minPositionSizeUsd; // 1e3
        uint104 __placeholder;
    }

    struct TradeFees {
        uint256 totalFeeCollateral; // collateral precision
        uint256 referralFeeCollateral; // collateral precision
        uint256 govFeeCollateral; // collateral precision
        uint256 triggerOrderFeeCollateral; // collateral precision
        uint256 gnsOtcFeeCollateral; // collateral precision
        uint256 gTokenFeeCollateral; // collateral precision
    }

    struct GroupLiquidationParams {
        uint40 maxLiqSpreadP; // 1e10 (%)
        uint40 startLiqThresholdP; // 1e10 (%)
        uint40 endLiqThresholdP; // 1e10 (%)
        uint24 startLeverage; // 1e3
        uint24 endLeverage; // 1e3
    }

    // Deprecated structs
    enum FeedCalculation {
        DEFAULT,
        INVERT,
        COMBINE
    } /// @custom:deprecated
    struct Feed {
        address feed1;
        address feed2;
        FeedCalculation feedCalculation;
        uint256 maxDeviationP;
    } /// @custom:deprecated
    struct Fee {
        string name;
        uint256 openFeeP; // 1e10 (% of position size)
        uint256 closeFeeP; // 1e10 (% of position size)
        uint256 oracleFeeP; // 1e10 (% of position size)
        uint256 triggerOrderFeeP; // 1e10 (% of position size)
        uint256 minPositionSizeUsd; // 1e18 (collateral x leverage, useful for min fee)
    } /// @custom:deprecated
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

import "./ITradingStorage.sol";
import "../IChainlinkFeed.sol";
import "../ILiquidityPool.sol";

/**
 * @dev Contains the types for the GNSPriceAggregator facet
 */
interface IPriceAggregator {
    struct PriceAggregatorStorage {
        // slot 1
        IChainlinkFeed linkUsdPriceFeed; // 160 bits
        uint24 twapInterval; // 24 bits
        uint8 minAnswers; // 8 bits
        // slot 2, 3, 4, 5, 6, 7, 8
        bytes32[2] jobIds; // 2 slots
        address[] oracles;
        mapping(uint8 => LiquidityPoolInfo) collateralGnsLiquidityPools;
        mapping(uint8 => IChainlinkFeed) collateralUsdPriceFeed;
        mapping(bytes32 => Order) orders;
        mapping(address => mapping(uint32 => OrderAnswer[])) orderAnswers;
        // Chainlink Client (slots 9, 10, 11)
        LinkTokenInterface linkErc677; // 160 bits
        uint96 __placeholder; // 96 bits
        uint256 requestCount; // 256 bits
        mapping(bytes32 => address) pendingRequests;
        uint256[39] __gap;
    }

    struct LiquidityPoolInfo {
        ILiquidityPool pool; // 160 bits
        bool isGnsToken0InLp; // 8 bits
        PoolType poolType; // 8 bits
        uint80 __placeholder; // 80 bits
    }

    struct Order {
        address user; // 160 bits
        uint32 index; // 32 bits
        ITradingStorage.PendingOrderType orderType; // 8 bits
        uint16 pairIndex; // 16 bits
        bool isLookback; // 8 bits
        uint32 __placeholder; // 32 bits
    }

    struct OrderAnswer {
        uint64 open;
        uint64 high;
        uint64 low;
        uint64 ts;
    }

    struct LiquidityPoolInput {
        ILiquidityPool pool;
        PoolType poolType;
    }

    enum PoolType {
        UNISWAP_V3,
        ALGEBRA_v1_9
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSPriceImpact facet
 */
interface IPriceImpact {
    struct PriceImpactStorage {
        OiWindowsSettings oiWindowsSettings;
        mapping(uint48 => mapping(uint256 => mapping(uint256 => PairOi))) windows; // duration => pairIndex => windowId => Oi
        mapping(uint256 => PairDepth) pairDepths; // pairIndex => depth (USD)
        mapping(address => mapping(uint32 => TradePriceImpactInfo)) tradePriceImpactInfos; // deprecated
        mapping(uint256 => PairFactors) pairFactors;
        uint40 negPnlCumulVolMultiplier;
        uint216 __placeholder;
        mapping(address => bool) protectionCloseFactorWhitelist;
        uint256[43] __gap;
    }

    struct OiWindowsSettings {
        uint48 startTs;
        uint48 windowsDuration;
        uint48 windowsCount;
    }

    struct PairOi {
        uint128 oiLongUsd; // 1e18 USD
        uint128 oiShortUsd; // 1e18 USD
    }

    struct OiWindowUpdate {
        address trader;
        uint32 index;
        uint48 windowsDuration;
        uint256 pairIndex;
        uint256 windowId;
        bool long;
        bool open;
        bool isPnlPositive;
        uint128 openInterestUsd; // 1e18 USD
    }

    struct PairDepth {
        uint128 onePercentDepthAboveUsd; // USD
        uint128 onePercentDepthBelowUsd; // USD
    }

    struct PairFactors {
        uint40 protectionCloseFactor; // 1e10; max 109.95x
        uint32 protectionCloseFactorBlocks;
        uint40 cumulativeFactor; // 1e10; max 109.95x
        bool exemptOnOpen;
        bool exemptAfterProtectionCloseFactor;
        uint128 __placeholder;
    }

    // Deprecated
    struct TradePriceImpactInfo {
        uint128 lastWindowOiUsd;
        uint128 __placeholder;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSReferrals facet
 */
interface IReferrals {
    struct ReferralsStorage {
        mapping(address => AllyDetails) allyDetails;
        mapping(address => ReferrerDetails) referrerDetails;
        mapping(address => address) referrerByTrader;
        uint256 allyFeeP; // % (of referrer fees going to allies, eg. 10)
        uint256 startReferrerFeeP; // % (of referrer fee when 0 volume referred, eg. 75)
        uint256 openFeeP; /// @custom:deprecated
        uint256 targetVolumeUsd; // USD (to reach maximum referral system fee, eg. 1e8)
        uint256[43] __gap;
    }

    struct AllyDetails {
        address[] referrersReferred;
        uint256 volumeReferredUsd; // 1e18
        uint256 pendingRewardsGns; // 1e18
        uint256 totalRewardsGns; // 1e18
        uint256 totalRewardsValueUsd; // 1e18
        bool active;
    }

    struct ReferrerDetails {
        address ally;
        address[] tradersReferred;
        uint256 volumeReferredUsd; // 1e18
        uint256 pendingRewardsGns; // 1e18
        uint256 totalRewardsGns; // 1e18
        uint256 totalRewardsValueUsd; // 1e18
        bool active;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../types/ITradingStorage.sol";

/**
 * @dev Contains the types for the GNSTradingCallbacks facet
 */
interface ITradingCallbacks {
    struct TradingCallbacksStorage {
        uint8 vaultClosingFeeP;
        uint248 __placeholder;
        mapping(uint8 => uint256) pendingGovFees; // collateralIndex => pending gov fee (collateral)
        uint256[48] __gap;
    }

    enum CancelReason {
        NONE,
        PAUSED, // deprecated
        MARKET_CLOSED,
        SLIPPAGE,
        TP_REACHED,
        SL_REACHED,
        EXPOSURE_LIMITS,
        PRICE_IMPACT,
        MAX_LEVERAGE,
        NO_TRADE,
        WRONG_TRADE, // deprecated
        NOT_HIT,
        LIQ_REACHED
    }

    struct AggregatorAnswer {
        ITradingStorage.Id orderId;
        uint256 spreadP;
        uint64 price;
        uint64 open;
        uint64 high;
        uint64 low;
    }

    // Useful to avoid stack too deep errors
    struct Values {
        int256 profitP;
        uint256 executionPrice;
        uint256 liqPrice;
        uint256 amountSentToTrader;
        uint256 collateralPriceUsd;
        bool exactExecution;
        uint256 collateralLeftInStorage;
        uint256 oraclePrice;
        uint32 limitIndex;
        uint256 priceImpactP;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSTradingInteractions facet
 */
interface ITradingInteractions {
    struct TradingInteractionsStorage {
        address senderOverride; // 160 bits
        uint16 marketOrdersTimeoutBlocks; // 16 bits
        uint80 __placeholder;
        mapping(address => address) delegations;
        mapping(address => bool) byPassTriggerLink;
        uint256[47] __gap;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "./IPairsStorage.sol";

/**
 * @dev Contains the types for the GNSTradingStorage facet
 */
interface ITradingStorage {
    struct TradingStorage {
        TradingActivated tradingActivated; // 8 bits
        uint8 lastCollateralIndex; // 8 bits
        uint240 __placeholder; // 240 bits
        mapping(uint8 => Collateral) collaterals;
        mapping(uint8 => address) gTokens;
        mapping(address => uint8) collateralIndex;
        mapping(address => mapping(uint32 => Trade)) trades;
        mapping(address => mapping(uint32 => TradeInfo)) tradeInfos;
        mapping(address => mapping(uint32 => mapping(PendingOrderType => uint256))) tradePendingOrderBlock;
        mapping(address => mapping(uint32 => PendingOrder)) pendingOrders;
        mapping(address => mapping(CounterType => Counter)) userCounters;
        address[] traders;
        mapping(address => bool) traderStored;
        mapping(address => mapping(uint32 => IPairsStorage.GroupLiquidationParams)) tradeLiquidationParams;
        uint256[38] __gap;
    }

    enum PendingOrderType {
        MARKET_OPEN,
        MARKET_CLOSE,
        LIMIT_OPEN,
        STOP_OPEN,
        TP_CLOSE,
        SL_CLOSE,
        LIQ_CLOSE,
        UPDATE_LEVERAGE,
        MARKET_PARTIAL_OPEN,
        MARKET_PARTIAL_CLOSE
    }

    enum CounterType {
        TRADE,
        PENDING_ORDER
    }

    enum TradeType {
        TRADE,
        LIMIT,
        STOP
    }

    enum TradingActivated {
        ACTIVATED,
        CLOSE_ONLY,
        PAUSED
    }

    enum ContractsVersion {
        BEFORE_V9_2,
        V9_2
    }

    struct Collateral {
        // slot 1
        address collateral; // 160 bits
        bool isActive; // 8 bits
        uint88 __placeholder; // 88 bits
        // slot 2
        uint128 precision;
        uint128 precisionDelta;
    }

    struct Id {
        address user; // 160 bits
        uint32 index; // max: 4,294,967,295
    }

    struct Trade {
        // slot 1
        address user; // 160 bits
        uint32 index; // max: 4,294,967,295
        uint16 pairIndex; // max: 65,535
        uint24 leverage; // 1e3; max: 16,777.215
        bool long; // 8 bits
        bool isOpen; // 8 bits
        uint8 collateralIndex; // max: 255
        // slot 2
        TradeType tradeType; // 8 bits
        uint120 collateralAmount; // 1e18; max: 3.402e+38
        uint64 openPrice; // 1e10; max: 1.8e19
        uint64 tp; // 1e10; max: 1.8e19
        // slot 3 (192 bits left)
        uint64 sl; // 1e10; max: 1.8e19
        uint192 __placeholder;
    }

    struct TradeInfo {
        uint32 createdBlock; // for lookbacks
        uint32 tpLastUpdatedBlock; // for lookbacks
        uint32 slLastUpdatedBlock; // for lookbacks
        uint16 maxSlippageP; // 1e3 (%)
        uint48 lastOiUpdateTs; // deprecated
        uint48 collateralPriceUsd; // 1e8 collateral price at trade open
        ContractsVersion contractsVersion;
        uint32 lastPosIncreaseBlock; // for protection close factor
        uint8 __placeholder;
    }

    struct PendingOrder {
        // slots 1-3
        Trade trade;
        // slot 4
        address user; // 160 bits
        uint32 index; // max: 4,294,967,295
        bool isOpen; // 8 bits
        PendingOrderType orderType; // 8 bits
        uint32 createdBlock; // max: 4,294,967,295
        uint16 maxSlippageP; // 1e3 (%), max: 65.535%
    }

    struct Counter {
        uint32 currentIndex;
        uint32 openCount;
        uint192 __placeholder;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Contains the types for the GNSTriggerRewards facet
 */
interface ITriggerRewards {
    struct TriggerRewardsStorage {
        uint16 triggerTimeoutBlocks; // 16 bits
        uint240 __placeholder; // 240 bits
        mapping(address => uint256) pendingRewardsGns;
        uint256[48] __gap;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "./IDiamondStorage.sol";
import "./IPairsStorage.sol";
import "./IReferrals.sol";
import "./IFeeTiers.sol";
import "./IPriceImpact.sol";
import "./ITradingStorage.sol";
import "./ITriggerRewards.sol";
import "./ITradingInteractions.sol";
import "./ITradingCallbacks.sol";
import "./IBorrowingFees.sol";
import "./IPriceAggregator.sol";
import "./IOtc.sol";
import "./IChainConfig.sol";

/**
 * @dev Contains the types of all diamond facets
 */
interface ITypes is
    IDiamondStorage,
    IPairsStorage,
    IReferrals,
    IFeeTiers,
    IPriceImpact,
    ITradingStorage,
    ITriggerRewards,
    ITradingInteractions,
    ITradingCallbacks,
    IBorrowingFees,
    IPriceAggregator,
    IOtc,
    IChainConfig
{}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 *
 * @dev Interface for leverage updates types
 */
interface IUpdateLeverage {
    /// @dev Update leverage input values
    struct UpdateLeverageInput {
        address user;
        uint32 index;
        uint24 newLeverage;
    }

    /// @dev Useful values for increase leverage callback
    struct UpdateLeverageValues {
        uint256 newLeverage;
        uint256 newCollateralAmount;
        uint256 liqPrice;
        uint256 govFeeCollateral;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 *
 * @dev Interface for position size updates types
 */
interface IUpdatePositionSize {
    /// @dev Request decrease position input values
    struct DecreasePositionSizeInput {
        address user;
        uint32 index;
        uint120 collateralDelta;
        uint24 leverageDelta;
        uint64 expectedPrice;
    }

    /// @dev Request increase position input values
    struct IncreasePositionSizeInput {
        address user;
        uint32 index;
        uint120 collateralDelta;
        uint24 leverageDelta;
        uint64 expectedPrice;
        uint16 maxSlippageP;
    }

    /// @dev Useful values for decrease position size callback
    struct DecreasePositionSizeValues {
        uint256 positionSizeCollateralDelta;
        uint256 existingPositionSizeCollateral;
        uint256 existingLiqPrice;
        uint256 priceAfterImpact;
        int256 existingPnlCollateral;
        uint256 borrowingFeeCollateral;
        uint256 closingFeeCollateral;
        int256 availableCollateralInDiamond;
        int256 collateralSentToTrader;
        uint120 newCollateralAmount;
        uint24 newLeverage;
    }

    /// @dev Useful values for increase position size callback
    struct IncreasePositionSizeValues {
        uint256 positionSizeCollateralDelta;
        uint256 existingPositionSizeCollateral;
        uint256 newPositionSizeCollateral;
        uint256 newCollateralAmount;
        uint256 newLeverage;
        uint256 priceAfterImpact;
        int256 existingPnlCollateral;
        uint256 oldPosSizePlusPnlCollateral;
        uint256 newOpenPrice;
        uint256 borrowingFeeCollateral;
        uint256 openingFeesCollateral;
        uint256 existingLiqPrice;
        uint256 newLiqPrice;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../interfaces/types/IAddressStore.sol";

import "./StorageUtils.sol";

/**
 *
 * @dev GNSAddressStore facet internal library
 */
library AddressStoreUtils {
    /**
     * @dev Returns storage slot to use when fetching addresses
     */
    function _getSlot() internal pure returns (uint256) {
        return StorageUtils.GLOBAL_ADDRESSES_SLOT;
    }

    /**
     * @dev Returns storage pointer for Addresses struct in global diamond contract, at defined slot
     */
    function getAddresses() internal pure returns (IAddressStore.Addresses storage s) {
        uint256 storageSlot = _getSlot();
        assembly {
            s.slot := storageSlot
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../interfaces/IGeneralErrors.sol";
import "../interfaces/libraries/IChainConfigUtils.sol";

import "./StorageUtils.sol";

/**
 * @dev ChainConfig facet internal library
 */
library ChainConfigUtils {
    uint16 internal constant MIN_NATIVE_TRANSFER_GAS_LIMIT = 21_000;

    /**
     * @dev Check IChainConfig interface for documentation
     */
    function initializeChainConfig(uint16 _nativeTransferGasLimit, bool _nativeTransferEnabled) internal {
        updateNativeTransferGasLimit(_nativeTransferGasLimit);
        updateNativeTransferEnabled(_nativeTransferEnabled);
    }

    /**
     * @dev Check IChainConfigUtils interface for documentation
     */
    function updateNativeTransferGasLimit(uint16 _nativeTransferGasLimit) internal {
        if (_nativeTransferGasLimit < MIN_NATIVE_TRANSFER_GAS_LIMIT) revert IGeneralErrors.BelowMin();

        _getStorage().nativeTransferGasLimit = _nativeTransferGasLimit;

        emit IChainConfigUtils.NativeTransferGasLimitUpdated(_nativeTransferGasLimit);
    }

    /**
     * @dev Check IChainConfigUtils interface for documentation
     */
    function updateNativeTransferEnabled(bool _nativeTransferEnabled) internal {
        _getStorage().nativeTransferEnabled = _nativeTransferEnabled;

        emit IChainConfigUtils.NativeTransferEnabledUpdated(_nativeTransferEnabled);
    }

    /**
     * @dev Check IChainConfigUtils interface for documentation
     */
    function getNativeTransferGasLimit() internal view returns (uint16) {
        uint16 gasLimit = _getStorage().nativeTransferGasLimit;

        // If `nativeTransferGasLimit` is 0 (not yet initialized) then return `MIN_NATIVE_TRANSFER_GAS_LIMIT
        return gasLimit == 0 ? MIN_NATIVE_TRANSFER_GAS_LIMIT : gasLimit;
    }

    /**
     * @dev Check IChainConfigUtils interface for documentation
     */
    function getNativeTransferEnabled() internal view returns (bool) {
        return _getStorage().nativeTransferEnabled;
    }

    /**
     * @dev Check IChainConfigUtils interface for documentation
     */
    function getReentrancyLock() internal view returns (uint256) {
        return _getStorage().reentrancyLock;
    }

    /**
     * @dev Returns storage slot to use when fetching storage relevant to library
     */
    function _getSlot() internal pure returns (uint256) {
        return StorageUtils.GLOBAL_CHAIN_CONFIG_SLOT;
    }

    /**
     * @dev Returns storage pointer for storage struct in diamond contract, at defined slot
     */
    function _getStorage() internal pure returns (IChainConfig.ChainConfigStorage storage s) {
        uint256 storageSlot = _getSlot();
        assembly {
            s.slot := storageSlot
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {Chainlink} from "@chainlink/contracts/src/v0.8/Chainlink.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";
import {ChainlinkRequestInterface} from "@chainlink/contracts/src/v0.8/interfaces/ChainlinkRequestInterface.sol";

import "../interfaces/libraries/IPriceAggregatorUtils.sol";
import "../interfaces/IGeneralErrors.sol";

import "./StorageUtils.sol";

/**
 *
 * @dev Chainlink client refactored into library and all unused functions removed
 * Uses price aggregator facet of multi collat diamond for storage.
 *
 * Copy of https://github.com/smartcontractkit/chainlink/blob/contracts-v0.5.1/contracts/src/v0.8/ChainlinkClient.sol
 * with only `requestCount` changed to unset so as to be inherited by a proxy implementation.
 */
library ChainlinkClientUtils {
    using Chainlink for Chainlink.Request;

    uint256 private constant AMOUNT_OVERRIDE = 0;
    address private constant SENDER_OVERRIDE = address(0);
    uint256 private constant ORACLE_ARGVERSION = 1;

    event ChainlinkRequested(bytes32 indexed id);
    event ChainlinkFulfilled(bytes32 indexed id);

    /**
     * @dev Returns storage slot to use when fetching storage relevant to library
     */
    function _getSlot() internal pure returns (uint256) {
        return StorageUtils.GLOBAL_PRICE_AGGREGATOR_SLOT;
    }

    /**
     * @dev Returns storage pointer for storage struct in diamond contract, at defined slot
     */
    function _getStorage() internal pure returns (IPriceAggregator.PriceAggregatorStorage storage s) {
        uint256 storageSlot = _getSlot();
        assembly {
            s.slot := storageSlot
        }
    }

    /**
     * @notice Creates a request that can hold additional parameters
     * @param specId The Job Specification ID that the request will be created for
     * @param callbackAddr address to operate the callback on
     * @param callbackFunctionSignature function signature to use for the callback
     * @return A Chainlink Request struct in memory
     */
    function buildChainlinkRequest(
        bytes32 specId,
        address callbackAddr,
        bytes4 callbackFunctionSignature
    ) internal pure returns (Chainlink.Request memory) {
        Chainlink.Request memory req;
        return req.initialize(specId, callbackAddr, callbackFunctionSignature);
    }

    /**
     * @notice Creates a Chainlink request to the specified oracle address
     * @dev Generates and stores a request ID, increments the local nonce, and uses `transferAndCall` to
     * send LINK which creates a request on the target oracle contract.
     * Emits ChainlinkRequested event.
     * @param oracleAddress The address of the oracle for the request
     * @param req The initialized Chainlink Request
     * @param payment The amount of LINK to send for the request
     * @return requestId The request ID
     */
    function sendChainlinkRequestTo(
        address oracleAddress,
        Chainlink.Request memory req,
        uint256 payment
    ) internal returns (bytes32 requestId) {
        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();
        uint256 nonce = s.requestCount;
        s.requestCount = nonce + 1;
        bytes memory encodedRequest = abi.encodeWithSelector(
            ChainlinkRequestInterface.oracleRequest.selector,
            SENDER_OVERRIDE, // Sender value - overridden by onTokenTransfer by the requesting contract's address
            AMOUNT_OVERRIDE, // Amount value - overridden by onTokenTransfer by the actual amount of LINK sent
            req.id,
            address(this),
            req.callbackFunctionId,
            nonce,
            ORACLE_ARGVERSION,
            req.buf.buf
        );
        return _rawRequest(oracleAddress, nonce, payment, encodedRequest);
    }

    /**
     * @notice Make a request to an oracle
     * @param oracleAddress The address of the oracle for the request
     * @param nonce used to generate the request ID
     * @param payment The amount of LINK to send for the request
     * @param encodedRequest data encoded for request type specific format
     * @return requestId The request ID
     */
    function _rawRequest(
        address oracleAddress,
        uint256 nonce,
        uint256 payment,
        bytes memory encodedRequest
    ) private returns (bytes32 requestId) {
        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();
        requestId = keccak256(abi.encodePacked(this, nonce));
        s.pendingRequests[requestId] = oracleAddress;
        emit ChainlinkRequested(requestId);
        if (!s.linkErc677.transferAndCall(oracleAddress, payment, encodedRequest))
            revert IPriceAggregatorUtils.TransferAndCallToOracleFailed();
    }

    /**
     * @notice Sets the LINK token address
     * @param _linkErc677 The address of the LINK token contract
     */
    function setChainlinkToken(address _linkErc677) internal {
        if (_linkErc677 == address(0)) revert IGeneralErrors.ZeroAddress();
        _getStorage().linkErc677 = LinkTokenInterface(_linkErc677);
    }

    /**
     * @notice Ensures that the fulfillment is valid for this contract
     * @dev Use if the contract developer prefers methods instead of modifiers for validation
     * @param requestId The request ID for fulfillment
     */
    function validateChainlinkCallback(
        bytes32 requestId
    )
        internal
        recordChainlinkFulfillment(requestId) // solhint-disable-next-line no-empty-blocks
    {}

    /**
     * @dev Reverts if the sender is not the oracle of the request.
     * Emits ChainlinkFulfilled event.
     * @param requestId The request ID for fulfillment
     */
    modifier recordChainlinkFulfillment(bytes32 requestId) {
        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();
        if (msg.sender != s.pendingRequests[requestId]) revert IPriceAggregatorUtils.SourceNotOracleOfRequest();
        delete s.pendingRequests[requestId];
        emit ChainlinkFulfilled(requestId);
        _;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

import "../interfaces/IArbSys.sol";
import "../interfaces/IGeneralErrors.sol";
import "../interfaces/mock/IBlockManager_Mock.sol";

/**
 * @dev Chain helpers internal library
 */
library ChainUtils {
    // Supported chains
    uint256 internal constant ARBITRUM_MAINNET = 42161;
    uint256 internal constant ARBITRUM_SEPOLIA = 421614;
    uint256 internal constant POLYGON_MAINNET = 137;
    uint256 internal constant BASE_MAINNET = 8453;
    uint256 internal constant APECHAIN_MAINNET = 33139;
    uint256 internal constant TESTNET = 31337;

    // Wrapped native tokens
    address private constant ARBITRUM_MAINNET_WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1;
    address private constant ARBITRUM_SEPOLIA_WETH = 0x980B62Da83eFf3D4576C647993b0c1D7faf17c73;
    address private constant POLYGON_MAINNET_WMATIC = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270;
    address private constant BASE_MAINNET_WETH = 0x4200000000000000000000000000000000000006;
    address private constant APECHAIN_MAINNET_WAPE = 0x00000000000f7e000644657dC9417b185962645a; // Custom non-rebasing WAPE

    IArbSys private constant ARB_SYS = IArbSys(address(100));

    error Overflow();
    /**
     * @dev Returns the current block number (l2 block for arbitrum)
     */
    function getBlockNumber() internal view returns (uint256) {
        if (block.chainid == ARBITRUM_MAINNET || block.chainid == ARBITRUM_SEPOLIA) {
            return ARB_SYS.arbBlockNumber();
        }

        if (block.chainid == TESTNET) {
            return IBlockManager_Mock(address(420)).getBlockNumber();
        }

        return block.number;
    }

    /**
     * @dev Returns blockNumber converted to uint48
     * @param blockNumber block number to convert
     */
    function getUint48BlockNumber(uint256 blockNumber) internal pure returns (uint48) {
        if (blockNumber > type(uint48).max) revert Overflow();
        return uint48(blockNumber);
    }

    /**
     * @dev Returns the wrapped native token address for the current chain
     */
    function getWrappedNativeToken() internal view returns (address) {
        if (block.chainid == ARBITRUM_MAINNET) {
            return ARBITRUM_MAINNET_WETH;
        }

        if (block.chainid == BASE_MAINNET) {
            return BASE_MAINNET_WETH;
        }

        if (block.chainid == APECHAIN_MAINNET) {
            return APECHAIN_MAINNET_WAPE;
        }

        if (block.chainid == POLYGON_MAINNET) {
            return POLYGON_MAINNET_WMATIC;
        }

        if (block.chainid == ARBITRUM_SEPOLIA) {
            return ARBITRUM_SEPOLIA_WETH;
        }

        if (block.chainid == TESTNET) {
            return address(421);
        }

        return address(0);
    }

    /**
     * @dev Returns whether a token is the wrapped native token for the current chain
     * @param _token token address to check
     */
    function isWrappedNativeToken(address _token) internal view returns (bool) {
        return _token != address(0) && _token == getWrappedNativeToken();
    }

    /**
     * @dev Converts blocks to seconds for the current chain.
     * @dev Important: the result is an estimation and may not be accurate. Use with caution.
     * @param _blocks block count to convert to seconds
     */
    function convertBlocksToSeconds(uint256 _blocks) internal view returns (uint256) {
        uint256 millisecondsPerBlock;

        if (block.chainid == ARBITRUM_MAINNET || block.chainid == ARBITRUM_SEPOLIA) {
            millisecondsPerBlock = 300; // 0.3 seconds per block
        } else if (block.chainid == BASE_MAINNET) {
            millisecondsPerBlock = 2000; // 2 seconds per block
        } else if (block.chainid == POLYGON_MAINNET) {
            millisecondsPerBlock = 2200; // 2.2 seconds per block
        } else if (block.chainid == APECHAIN_MAINNET) {
            millisecondsPerBlock = 12000; // for apescan we use L1 blocktime (12s)
        } else if (block.chainid == TESTNET) {
            millisecondsPerBlock = 1000; // 1 second per block
        } else {
            revert IGeneralErrors.UnsupportedChain();
        }

        return Math.mulDiv(_blocks, millisecondsPerBlock, 1000, Math.Rounding.Up);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../interfaces/types/ITradingStorage.sol";

/**
 *
 * @dev Internal library for important constants commonly used in many places
 */
library ConstantsUtils {
    uint256 internal constant P_10 = 1e10; // 10 decimals (DO NOT UPDATE)
    uint256 internal constant MAX_PNL_P = 900; // 900% PnL (10x)
    uint256 internal constant SL_LIQ_BUFFER_P = 10 * P_10; // SL has to be 10% closer than liq price
    uint256 internal constant LEGACY_LIQ_THRESHOLD_P = 90 * P_10; // -90% pnl
    uint256 internal constant MIN_LIQ_THRESHOLD_P = 50 * P_10; // -50% pnl
    uint256 internal constant MAX_OPEN_NEGATIVE_PNL_P = 40 * P_10; // -40% pnl
    uint256 internal constant MAX_LIQ_SPREAD_P = (5 * P_10) / 100; // 0.05%
    uint16 internal constant DEFAULT_MAX_CLOSING_SLIPPAGE_P = 1 * 1e3; // 1%

    function getMarketOrderTypes() internal pure returns (ITradingStorage.PendingOrderType[5] memory) {
        return [
            ITradingStorage.PendingOrderType.MARKET_OPEN,
            ITradingStorage.PendingOrderType.MARKET_CLOSE,
            ITradingStorage.PendingOrderType.UPDATE_LEVERAGE,
            ITradingStorage.PendingOrderType.MARKET_PARTIAL_OPEN,
            ITradingStorage.PendingOrderType.MARKET_PARTIAL_CLOSE
        ];
    }

    /**
     * @dev Returns pending order type (market open/limit open/stop open) for a trade type (trade/limit/stop)
     * @param _tradeType the trade type
     */
    function getPendingOpenOrderType(
        ITradingStorage.TradeType _tradeType
    ) internal pure returns (ITradingStorage.PendingOrderType) {
        return
            _tradeType == ITradingStorage.TradeType.TRADE
                ? ITradingStorage.PendingOrderType.MARKET_OPEN
                : _tradeType == ITradingStorage.TradeType.LIMIT
                ? ITradingStorage.PendingOrderType.LIMIT_OPEN
                : ITradingStorage.PendingOrderType.STOP_OPEN;
    }

    /**
     * @dev Returns true if order type is market
     * @param _orderType order type
     */
    function isOrderTypeMarket(ITradingStorage.PendingOrderType _orderType) internal pure returns (bool) {
        ITradingStorage.PendingOrderType[5] memory marketOrderTypes = ConstantsUtils.getMarketOrderTypes();
        for (uint256 i; i < marketOrderTypes.length; ++i) {
            if (_orderType == marketOrderTypes[i]) return true;
        }
        return false;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import {FixedPoint96} from "@uniswap/v3-core/contracts/libraries/FixedPoint96.sol";
import {FullMath} from "@uniswap/v3-core/contracts/libraries/FullMath.sol";

import "../interfaces/IGNSMultiCollatDiamond.sol";

import "./AddressStoreUtils.sol";
import "./ConstantsUtils.sol";

/**
 * @custom:version 8.0.1
 * @dev Library to abstract liquidity pool operations such as fetching observations, calculating TWAP, etc.
 * Currently supports Uniswap V3 and Algebra V1.9 liquidity pools
 */
library LiquidityPoolUtils {
    /**
     * @dev Returns a `LiquidityPoolInfo` struct for LiquidityPoolInput `_input`
     * @param _input LiquidityPoolInput struct with pool address and type
     */
    function getLiquidityPoolInfo(
        IPriceAggregator.LiquidityPoolInput calldata _input
    ) internal view returns (IPriceAggregator.LiquidityPoolInfo memory) {
        return
            IPriceAggregator.LiquidityPoolInfo({
                poolType: _input.poolType,
                pool: _input.pool,
                isGnsToken0InLp: _input.pool.token0() == AddressStoreUtils.getAddresses().gns,
                __placeholder: 0
            });
    }

    /**
     * @dev Calculates the time-weighted average price of a liquidity pool over a given interval
     * @param _poolInfo Liquidity pool info
     * @param _twapInterval TWAP interval in seconds
     * @param _precisionDelta precision delta of collateral
     */
    function getTimeWeightedAveragePrice(
        IPriceAggregator.LiquidityPoolInfo memory _poolInfo,
        uint32 _twapInterval,
        uint256 _precisionDelta
    ) internal view returns (uint256) {
        int56[] memory tickCumulatives = _getPoolTickCumulatives(_poolInfo, _twapInterval);
        return _tickCumulativesToTokenPrice(tickCumulatives, _twapInterval, _precisionDelta, _poolInfo.isGnsToken0InLp);
    }

    /**
     * @dev Fetches tickCumulatives data from the pool. Calls the appropriate oracle function based on the pool type
     * @param _poolInfo Liquidity pool info
     * @param _twapInterval TWAP interval
     */
    function _getPoolTickCumulatives(
        IPriceAggregator.LiquidityPoolInfo memory _poolInfo,
        uint32 _twapInterval
    ) internal view returns (int56[] memory) {
        int56[] memory tickCumulatives;
        uint32[] memory secondsAgos = new uint32[](2);

        secondsAgos[0] = _twapInterval;
        secondsAgos[1] = 0;

        // If pool is Uniswap V3 call `observe` function
        if (_poolInfo.poolType == IPriceAggregator.PoolType.UNISWAP_V3) {
            (tickCumulatives, ) = _poolInfo.pool.observe(secondsAgos);
        }
        // If pool is Algebra V1.9 call `getTimepoints` function
        else if (_poolInfo.poolType == IPriceAggregator.PoolType.ALGEBRA_v1_9) {
            (tickCumulatives, , , ) = _poolInfo.pool.getTimepoints(secondsAgos);
        }
        // If pool is anything else, revert with InvalidPoolType error (this should never happen)
        else {
            revert IPriceAggregatorUtils.InvalidPoolType();
        }

        return tickCumulatives;
    }

    /**
     * @dev Returns TWAP price (1e10 precision) from tickCumulatives data
     * @param _tickCumulatives array of tickCumulatives
     * @param _twapInterval TWAP interval
     * @param _precisionDelta precision delta of collateral
     * @param _isGnsToken0InLp true if GNS is token0 in LP
     *
     * Inspired from https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/OracleLibrary.sol
     */
    function _tickCumulativesToTokenPrice(
        int56[] memory _tickCumulatives,
        uint32 _twapInterval,
        uint256 _precisionDelta,
        bool _isGnsToken0InLp
    ) internal pure returns (uint256) {
        if (_tickCumulatives.length != 2) revert IGeneralErrors.WrongLength();

        int56 tickCumulativesDelta = _tickCumulatives[1] - _tickCumulatives[0];
        int56 twapIntervalInt = int56(int32(_twapInterval));

        int24 arithmeticMeanTick = int24(tickCumulativesDelta / twapIntervalInt);
        // Always round to negative infinity
        if (tickCumulativesDelta < 0 && (tickCumulativesDelta % twapIntervalInt != 0)) arithmeticMeanTick--;

        uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick); // sqrt(token1/token0*2^96)
        uint256 priceX96 = (FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, FixedPoint96.Q96) * ConstantsUtils.P_10); // token1/token0*2^96*1e10

        return
            _isGnsToken0InLp
                ? (priceX96 * _precisionDelta) / 2 ** 96 // 1e6/1e18*2^96*1e10 * 1e12 / 2^96 = 1e18/1e18*1e10
                : (ConstantsUtils.P_10 ** 2) / (priceX96 / _precisionDelta / 2 ** 96); // 1e10^2 / (1e18/1e6*2^96*1e10 / 1e12 / 2^96) = 1e10^2 / (1e18/1e18*1e10) = 1e18/1e18*1e10
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev External library used to pack and unpack values
 */
library PackingUtils {
    /**
     * @dev Packs values array into a single uint256
     * @param _values values to pack
     * @param _bitLengths corresponding bit lengths for each value
     */
    function pack(uint256[] memory _values, uint256[] memory _bitLengths) external pure returns (uint256 packed) {
        require(_values.length == _bitLengths.length, "Mismatch in the lengths of values and bitLengths arrays");

        uint256 currentShift;

        for (uint256 i; i < _values.length; ++i) {
            require(currentShift + _bitLengths[i] <= 256, "Packed value exceeds 256 bits");

            uint256 maxValue = (1 << _bitLengths[i]) - 1;
            require(_values[i] <= maxValue, "Value too large for specified bit length");

            uint256 maskedValue = _values[i] & maxValue;
            packed |= maskedValue << currentShift;
            currentShift += _bitLengths[i];
        }
    }

    /**
     * @dev Unpacks a single uint256 into an array of values
     * @param _packed packed value
     * @param _bitLengths corresponding bit lengths for each value
     */
    function unpack(uint256 _packed, uint256[] memory _bitLengths) external pure returns (uint256[] memory values) {
        values = new uint256[](_bitLengths.length);

        uint256 currentShift;
        for (uint256 i; i < _bitLengths.length; ++i) {
            require(currentShift + _bitLengths[i] <= 256, "Unpacked value exceeds 256 bits");

            uint256 maxValue = (1 << _bitLengths[i]) - 1;
            uint256 mask = maxValue << currentShift;
            values[i] = (_packed & mask) >> currentShift;

            currentShift += _bitLengths[i];
        }
    }

    /**
     * @dev Unpacks a single uint256 into 4 uint64 values
     * @param _packed packed value
     * @return a returned value 1
     * @return b returned value 2
     * @return c returned value 3
     * @return d returned value 4
     */
    function unpack256To64(uint256 _packed) external pure returns (uint64 a, uint64 b, uint64 c, uint64 d) {
        a = uint64(_packed);
        b = uint64(_packed >> 64);
        c = uint64(_packed >> 128);
        d = uint64(_packed >> 192);
    }

    /**
     * @dev Unpacks trigger order calldata into 3 values
     * @param _packed packed value
     * @return orderType order type
     * @return trader trader address
     * @return index trade index
     */
    function unpackTriggerOrder(uint256 _packed) external pure returns (uint8 orderType, address trader, uint32 index) {
        orderType = uint8(_packed & 0xFF); // 8 bits
        trader = address(uint160(_packed >> 8)); // 160 bits
        index = uint32((_packed >> 168)); // 32 bits
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Chainlink} from "@chainlink/contracts/src/v0.8/Chainlink.sol";

import "../interfaces/IGNSMultiCollatDiamond.sol";
import "../interfaces/IChainlinkFeed.sol";
import "../interfaces/IERC20.sol";
import "../interfaces/ILiquidityPool.sol";

import "./AddressStoreUtils.sol";
import "./StorageUtils.sol";
import "./ChainlinkClientUtils.sol";
import "./PackingUtils.sol";
import "./ConstantsUtils.sol";
import "./TradingCommonUtils.sol";
import "./LiquidityPoolUtils.sol";

/**
 * @dev GNSPriceAggregator facet external library
 */
library PriceAggregatorUtils {
    using PackingUtils for uint256;
    using SafeERC20 for IERC20;
    using Chainlink for Chainlink.Request;
    using LiquidityPoolUtils for IPriceAggregator.LiquidityPoolInput;
    using LiquidityPoolUtils for IPriceAggregator.LiquidityPoolInfo;

    uint256 private constant MAX_ORACLE_NODES = 20;
    uint256 private constant MIN_ANSWERS = 2;
    uint32 private constant MIN_TWAP_PERIOD = 1 hours / 2;
    uint32 private constant MAX_TWAP_PERIOD = 4 hours;

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function initializePriceAggregator(
        address _linkErc677,
        IChainlinkFeed _linkUsdPriceFeed,
        uint24 _twapInterval,
        uint8 _minAnswers,
        address[] memory _oracles,
        bytes32[2] memory _jobIds,
        uint8[] calldata _collateralIndices,
        IPriceAggregator.LiquidityPoolInput[] calldata _gnsCollateralLiquidityPools,
        IChainlinkFeed[] memory _collateralUsdPriceFeeds
    ) external {
        if (
            _collateralIndices.length != _gnsCollateralLiquidityPools.length ||
            _collateralIndices.length != _collateralUsdPriceFeeds.length
        ) revert IGeneralErrors.WrongLength();

        ChainlinkClientUtils.setChainlinkToken(_linkErc677);
        updateLinkUsdPriceFeed(_linkUsdPriceFeed);
        updateTwapInterval(_twapInterval);
        updateMinAnswers(_minAnswers);

        for (uint256 i = 0; i < _oracles.length; ++i) {
            addOracle(_oracles[i]);
        }

        setMarketJobId(_jobIds[0]);
        setLimitJobId(_jobIds[1]);

        for (uint8 i = 0; i < _collateralIndices.length; ++i) {
            updateCollateralGnsLiquidityPool(_collateralIndices[i], _gnsCollateralLiquidityPools[i]);
            updateCollateralUsdPriceFeed(_collateralIndices[i], _collateralUsdPriceFeeds[i]);
        }
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function updateLinkUsdPriceFeed(IChainlinkFeed _value) public {
        if (address(_value) == address(0)) revert IGeneralErrors.ZeroValue();

        _getStorage().linkUsdPriceFeed = _value;

        emit IPriceAggregatorUtils.LinkUsdPriceFeedUpdated(address(_value));
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function updateCollateralUsdPriceFeed(
        uint8 _collateralIndex,
        IChainlinkFeed _value
    ) public validCollateralIndex(_collateralIndex) {
        if (address(_value) == address(0)) revert IGeneralErrors.ZeroValue();
        if (_value.decimals() != 8) revert IPriceAggregatorUtils.WrongCollateralUsdDecimals();

        _getStorage().collateralUsdPriceFeed[_collateralIndex] = _value;

        emit IPriceAggregatorUtils.CollateralUsdPriceFeedUpdated(_collateralIndex, address(_value));
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function updateCollateralGnsLiquidityPool(
        uint8 _collateralIndex,
        IPriceAggregator.LiquidityPoolInput calldata _liquidityPoolInput
    ) public validCollateralIndex(_collateralIndex) {
        if (address(_liquidityPoolInput.pool) == address(0)) revert IGeneralErrors.ZeroValue();

        // Fetch LiquidityPoolInfo from LP utils library
        IPriceAggregator.LiquidityPoolInfo memory poolInfo = _liquidityPoolInput.getLiquidityPoolInfo();

        // Update liquidity pool storage for collateral
        _getStorage().collateralGnsLiquidityPools[_collateralIndex] = poolInfo;

        emit IPriceAggregatorUtils.CollateralGnsLiquidityPoolUpdated(_collateralIndex, poolInfo);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function updateTwapInterval(uint24 _twapInterval) public {
        if (_twapInterval < MIN_TWAP_PERIOD || _twapInterval > MAX_TWAP_PERIOD) revert IGeneralErrors.WrongParams();

        _getStorage().twapInterval = _twapInterval;

        emit IPriceAggregatorUtils.TwapIntervalUpdated(_twapInterval);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function updateMinAnswers(uint8 _value) public {
        if (_value < MIN_ANSWERS) revert IGeneralErrors.BelowMin();

        _getStorage().minAnswers = _value;

        emit IPriceAggregatorUtils.MinAnswersUpdated(_value);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function addOracle(address _a) public {
        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();

        if (_a == address(0)) revert IGeneralErrors.ZeroValue();
        if (s.oracles.length >= MAX_ORACLE_NODES) revert IGeneralErrors.AboveMax();

        for (uint256 i; i < s.oracles.length; ++i) {
            if (s.oracles[i] == _a) revert IPriceAggregatorUtils.OracleAlreadyListed();
        }

        s.oracles.push(_a);

        emit IPriceAggregatorUtils.OracleAdded(s.oracles.length - 1, _a);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function replaceOracle(uint256 _index, address _a) external {
        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();

        if (_index >= s.oracles.length) revert IGeneralErrors.WrongIndex();
        if (_a == address(0)) revert IGeneralErrors.ZeroValue();

        address oldNode = s.oracles[_index];
        s.oracles[_index] = _a;

        emit IPriceAggregatorUtils.OracleReplaced(_index, oldNode, _a);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function removeOracle(uint256 _index) external {
        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();

        if (_index >= s.oracles.length) revert IGeneralErrors.WrongIndex();

        address oldNode = s.oracles[_index];

        s.oracles[_index] = s.oracles[s.oracles.length - 1];
        s.oracles.pop();

        emit IPriceAggregatorUtils.OracleRemoved(_index, oldNode);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function setMarketJobId(bytes32 _jobId) public {
        if (_jobId == bytes32(0)) revert IGeneralErrors.ZeroValue();

        _getStorage().jobIds[0] = _jobId;

        emit IPriceAggregatorUtils.JobIdUpdated(0, _jobId);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function setLimitJobId(bytes32 _jobId) public {
        if (_jobId == bytes32(0)) revert IGeneralErrors.ZeroValue();

        _getStorage().jobIds[1] = _jobId;

        emit IPriceAggregatorUtils.JobIdUpdated(1, _jobId);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getPrice(
        uint8 _collateralIndex,
        uint16 _pairIndex,
        ITradingStorage.Id memory _tradeId,
        ITradingStorage.Id memory _orderId,
        ITradingStorage.PendingOrderType _orderType,
        uint256 _positionSizeCollateral,
        uint256 _fromBlock
    ) external validCollateralIndex(_collateralIndex) {
        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();

        // 1. Build chainlink request
        bool isLookback = !ConstantsUtils.isOrderTypeMarket(_orderType);

        Chainlink.Request memory linkRequest = ChainlinkClientUtils.buildChainlinkRequest(
            s.jobIds[isLookback ? 1 : 0],
            address(this),
            IPriceAggregatorUtils.fulfill.selector
        );

        {
            (string memory from, string memory to) = _getMultiCollatDiamond().pairJob(_pairIndex);

            linkRequest.add("from", from);
            linkRequest.add("to", to);

            if (isLookback) {
                linkRequest.addUint("fromBlock", _fromBlock);
                linkRequest.addBytes("trader", abi.encodePacked(_tradeId.user));
                linkRequest.addUint("index", uint256(_tradeId.index));
                linkRequest.addUint("orderType", uint256(_orderType));
            }

            emit IPriceAggregatorUtils.LinkRequestCreated(linkRequest);
        }

        // 2. Calculate link fee for each oracle
        TradingCommonUtils.updateFeeTierPoints(_collateralIndex, _tradeId.user, _pairIndex, 0);
        uint256 linkFeePerNode = getLinkFee(_collateralIndex, _tradeId.user, _pairIndex, _positionSizeCollateral) /
            s.oracles.length;

        // 3. Send request to all oracles
        {
            IPriceAggregator.Order memory order = IPriceAggregator.Order({
                user: _orderId.user,
                index: _orderId.index,
                orderType: _orderType,
                pairIndex: uint16(_pairIndex),
                isLookback: isLookback,
                __placeholder: 0
            });

            for (uint256 i; i < s.oracles.length; ++i) {
                bytes32 requestId = ChainlinkClientUtils.sendChainlinkRequestTo(
                    s.oracles[i],
                    linkRequest,
                    linkFeePerNode
                );
                s.orders[requestId] = order;
            }
        }

        emit IPriceAggregatorUtils.PriceRequested(
            _collateralIndex,
            _pairIndex,
            _tradeId,
            _orderId,
            _orderType,
            _fromBlock,
            isLookback,
            linkRequest.id,
            linkFeePerNode,
            s.oracles.length
        );
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function fulfill(bytes32 _requestId, uint256 _priceData) external {
        ChainlinkClientUtils.validateChainlinkCallback(_requestId);

        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();

        IPriceAggregator.Order memory order = s.orders[_requestId];
        ITradingStorage.Id memory orderId = ITradingStorage.Id({user: order.user, index: order.index});

        IPriceAggregator.OrderAnswer[] storage orderAnswers = s.orderAnswers[orderId.user][orderId.index];
        uint8 minAnswers = s.minAnswers;
        bool usedInMedian = orderAnswers.length < minAnswers;

        if (usedInMedian) {
            IPriceAggregator.OrderAnswer memory newAnswer;
            (newAnswer.open, newAnswer.high, newAnswer.low, newAnswer.ts) = _priceData.unpack256To64();
            if (
                order.isLookback &&
                ((newAnswer.high > 0 || newAnswer.low > 0) &&
                    (newAnswer.high < newAnswer.open || newAnswer.low > newAnswer.open || newAnswer.low == 0))
            ) revert IPriceAggregatorUtils.InvalidCandle();

            orderAnswers.push(newAnswer);

            if (orderAnswers.length == minAnswers) {
                ITradingCallbacks.AggregatorAnswer memory finalAnswer;

                finalAnswer.orderId = orderId;
                finalAnswer.spreadP = _getMultiCollatDiamond().pairSpreadP(order.pairIndex);

                if (order.isLookback) {
                    (finalAnswer.open, finalAnswer.high, finalAnswer.low) = _medianLookbacks(orderAnswers);
                } else {
                    finalAnswer.price = _median(orderAnswers);
                }

                if (order.orderType == ITradingStorage.PendingOrderType.MARKET_OPEN) {
                    _getMultiCollatDiamond().openTradeMarketCallback(finalAnswer);
                } else if (order.orderType == ITradingStorage.PendingOrderType.MARKET_CLOSE) {
                    _getMultiCollatDiamond().closeTradeMarketCallback(finalAnswer);
                } else if (
                    order.orderType == ITradingStorage.PendingOrderType.LIMIT_OPEN ||
                    order.orderType == ITradingStorage.PendingOrderType.STOP_OPEN
                ) {
                    _getMultiCollatDiamond().executeTriggerOpenOrderCallback(finalAnswer);
                } else if (
                    order.orderType == ITradingStorage.PendingOrderType.TP_CLOSE ||
                    order.orderType == ITradingStorage.PendingOrderType.SL_CLOSE ||
                    order.orderType == ITradingStorage.PendingOrderType.LIQ_CLOSE
                ) {
                    _getMultiCollatDiamond().executeTriggerCloseOrderCallback(finalAnswer);
                } else if (order.orderType == ITradingStorage.PendingOrderType.UPDATE_LEVERAGE) {
                    _getMultiCollatDiamond().updateLeverageCallback(finalAnswer);
                } else if (order.orderType == ITradingStorage.PendingOrderType.MARKET_PARTIAL_OPEN) {
                    _getMultiCollatDiamond().increasePositionSizeMarketCallback(finalAnswer);
                } else if (order.orderType == ITradingStorage.PendingOrderType.MARKET_PARTIAL_CLOSE) {
                    _getMultiCollatDiamond().decreasePositionSizeMarketCallback(finalAnswer);
                }

                emit IPriceAggregatorUtils.TradingCallbackExecuted(finalAnswer, order.orderType);
            }
        }

        emit IPriceAggregatorUtils.PriceReceived(
            orderId,
            order.pairIndex,
            _requestId,
            _priceData,
            order.isLookback,
            usedInMedian
        );
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function claimBackLink() external {
        address treasury = AddressStoreUtils.getAddresses().treasury;

        if (treasury == address(0)) revert IGeneralErrors.ZeroAddress();

        IERC20 link = IERC20(getChainlinkToken());
        uint256 linkAmount = link.balanceOf(address(this));

        link.safeTransfer(treasury, linkAmount);

        emit IPriceAggregatorUtils.LinkClaimedBack(linkAmount);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getLinkFee(
        uint8 _collateralIndex,
        address _trader,
        uint16 _pairIndex,
        uint256 _positionSizeCollateral // collateral precision
    ) public view returns (uint256) {
        (, int256 linkPriceUsd, , , ) = _getStorage().linkUsdPriceFeed.latestRoundData(); // 1e8

        uint256 normalFeeAmount = (getUsdNormalizedValue(
            _collateralIndex,
            _getMultiCollatDiamond().pairOraclePositionSizeFeeP(_pairIndex) *
                (
                    _positionSizeCollateral > 0
                        ? TradingCommonUtils.getPositionSizeCollateralBasis(
                            _collateralIndex,
                            _pairIndex,
                            _positionSizeCollateral
                        )
                        : 0
                )
        ) * 1e8) /
            uint256(linkPriceUsd) /
            ConstantsUtils.P_10 /
            100;

        return _getMultiCollatDiamond().calculateFeeAmount(_trader, normalFeeAmount);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getCollateralPriceUsd(uint8 _collateralIndex) public view returns (uint256) {
        (, int256 collateralPriceUsd, , , ) = _getStorage().collateralUsdPriceFeed[_collateralIndex].latestRoundData();

        return uint256(collateralPriceUsd);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getUsdNormalizedValue(uint8 _collateralIndex, uint256 _collateralValue) public view returns (uint256) {
        return
            (_collateralValue *
                _getCollateralPrecisionDelta(_collateralIndex) *
                getCollateralPriceUsd(_collateralIndex)) / 1e8;
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getCollateralFromUsdNormalizedValue(
        uint8 _collateralIndex,
        uint256 _normalizedValue
    ) external view returns (uint256) {
        return
            (_normalizedValue * 1e8) /
            getCollateralPriceUsd(_collateralIndex) /
            _getCollateralPrecisionDelta(_collateralIndex);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getGnsPriceUsd(uint8 _collateralIndex) external view returns (uint256) {
        return getGnsPriceUsd(_collateralIndex, getGnsPriceCollateralIndex(_collateralIndex));
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getGnsPriceUsd(uint8 _collateralIndex, uint256 _gnsPriceCollateral) public view returns (uint256) {
        return (_gnsPriceCollateral * getCollateralPriceUsd(_collateralIndex)) / 1e8;
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getGnsPriceCollateralAddress(address _collateral) external view returns (uint256 _price) {
        return getGnsPriceCollateralIndex(_getMultiCollatDiamond().getCollateralIndex(_collateral));
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getGnsPriceCollateralIndex(uint8 _collateralIndex) public view returns (uint256 _price) {
        IPriceAggregator.PriceAggregatorStorage storage s = _getStorage();
        uint32 twapInterval = uint32(s.twapInterval);

        return
            s.collateralGnsLiquidityPools[_collateralIndex].getTimeWeightedAveragePrice(
                twapInterval,
                _getCollateralPrecisionDelta(_collateralIndex)
            );
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getLinkUsdPriceFeed() external view returns (IChainlinkFeed) {
        return _getStorage().linkUsdPriceFeed;
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getTwapInterval() external view returns (uint24) {
        return _getStorage().twapInterval;
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getMinAnswers() external view returns (uint8) {
        return _getStorage().minAnswers;
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getMarketJobId() external view returns (bytes32) {
        return _getStorage().jobIds[0];
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getLimitJobId() external view returns (bytes32) {
        return _getStorage().jobIds[1];
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getOracle(uint256 _index) external view returns (address) {
        return _getStorage().oracles[_index];
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getOracles() external view returns (address[] memory) {
        return _getStorage().oracles;
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getCollateralGnsLiquidityPool(
        uint8 _collateralIndex
    ) external view returns (IPriceAggregator.LiquidityPoolInfo memory) {
        return _getStorage().collateralGnsLiquidityPools[_collateralIndex];
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getCollateralUsdPriceFeed(uint8 _collateralIndex) external view returns (IChainlinkFeed) {
        return _getStorage().collateralUsdPriceFeed[_collateralIndex];
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getPriceAggregatorOrder(bytes32 _requestId) external view returns (IPriceAggregator.Order memory) {
        return _getStorage().orders[_requestId];
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getPriceAggregatorOrderAnswers(
        ITradingStorage.Id calldata _orderId
    ) external view returns (IPriceAggregator.OrderAnswer[] memory) {
        return _getStorage().orderAnswers[_orderId.user][_orderId.index];
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getChainlinkToken() public view returns (address) {
        return address(_getStorage().linkErc677);
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getRequestCount() external view returns (uint256) {
        return _getStorage().requestCount;
    }

    /**
     * @dev Check IPriceAggregatorUtils interface for documentation
     */
    function getPendingRequest(bytes32 _id) external view returns (address) {
        return _getStorage().pendingRequests[_id];
    }

    /**
     * @dev Returns storage slot to use when fetching storage relevant to library
     */
    function _getSlot() internal pure returns (uint256) {
        return StorageUtils.GLOBAL_PRICE_AGGREGATOR_SLOT;
    }

    /**
     * @dev Returns storage pointer for storage struct in diamond contract, at defined slot
     */
    function _getStorage() internal pure returns (IPriceAggregator.PriceAggregatorStorage storage s) {
        uint256 storageSlot = _getSlot();
        assembly {
            s.slot := storageSlot
        }
    }

    /**
     * @dev Returns current address as multi-collateral diamond interface to call other facets functions.
     */
    function _getMultiCollatDiamond() internal view returns (IGNSMultiCollatDiamond) {
        return IGNSMultiCollatDiamond(address(this));
    }

    /**
     * @dev Reverts if collateral index is not valid
     */
    modifier validCollateralIndex(uint8 _collateralIndex) {
        if (!_getMultiCollatDiamond().isCollateralListed(_collateralIndex)) {
            revert IGeneralErrors.InvalidCollateralIndex();
        }
        _;
    }

    /**
     * @dev returns median price of array (1 price only)
     * @param _array array of values
     */
    function _median(IPriceAggregator.OrderAnswer[] memory _array) internal pure returns (uint64) {
        uint256 length = _array.length;

        uint256[] memory prices = new uint256[](length);

        for (uint256 i; i < length; ++i) {
            prices[i] = _array[i].open;
        }

        _sort(prices, 0, length);

        return uint64(length % 2 == 0 ? (prices[length / 2 - 1] + prices[length / 2]) / 2 : prices[length / 2]);
    }

    /**
     * @dev returns median prices of array (open, high, low)
     * @param _array array of values
     */
    function _medianLookbacks(
        IPriceAggregator.OrderAnswer[] memory _array
    ) internal pure returns (uint64 open, uint64 high, uint64 low) {
        uint256 length = _array.length;

        uint256[] memory opens = new uint256[](length);
        uint256[] memory highs = new uint256[](length);
        uint256[] memory lows = new uint256[](length);

        for (uint256 i; i < length; ++i) {
            opens[i] = _array[i].open;
            highs[i] = _array[i].high;
            lows[i] = _array[i].low;
        }

        _sort(opens, 0, length);
        _sort(highs, 0, length);
        _sort(lows, 0, length);

        bool isLengthEven = length % 2 == 0;
        uint256 halfLength = length / 2;

        open = uint64(isLengthEven ? (opens[halfLength - 1] + opens[halfLength]) / 2 : opens[halfLength]);
        high = uint64(isLengthEven ? (highs[halfLength - 1] + highs[halfLength]) / 2 : highs[halfLength]);
        low = uint64(isLengthEven ? (lows[halfLength - 1] + lows[halfLength]) / 2 : lows[halfLength]);
    }

    /**
     * @dev swaps two elements in array
     * @param _array array of values
     * @param _i index of first element
     * @param _j index of second element
     */
    function _swap(uint256[] memory _array, uint256 _i, uint256 _j) internal pure {
        (_array[_i], _array[_j]) = (_array[_j], _array[_i]);
    }

    /**
     * @dev sorts array of uint256 values
     * @param _array array of values
     * @param begin start index
     * @param end end index
     */
    function _sort(uint256[] memory _array, uint256 begin, uint256 end) internal pure {
        if (begin >= end) {
            return;
        }

        uint256 j = begin;
        uint256 pivot = _array[j];

        for (uint256 i = begin + 1; i < end; ++i) {
            if (_array[i] < pivot) {
                _swap(_array, i, ++j);
            }
        }

        _swap(_array, begin, j);
        _sort(_array, begin, j);
        _sort(_array, j + 1, end);
    }

    /**
     * @dev Returns precision delta of collateral as uint256
     * @param _collateralIndex index of collateral
     */
    function _getCollateralPrecisionDelta(uint8 _collateralIndex) internal view returns (uint256) {
        return uint256(_getMultiCollatDiamond().getCollateral(_collateralIndex).precisionDelta);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 *
 * @dev Internal library to manage storage slots of GNSMultiCollatDiamond contract diamond storage structs.
 *
 * BE EXTREMELY CAREFUL, DO NOT EDIT THIS WITHOUT A GOOD REASON
 *
 */
library StorageUtils {
    uint256 internal constant GLOBAL_ADDRESSES_SLOT = 3;
    uint256 internal constant GLOBAL_PAIRS_STORAGE_SLOT = 51;
    uint256 internal constant GLOBAL_REFERRALS_SLOT = 101;
    uint256 internal constant GLOBAL_FEE_TIERS_SLOT = 151;
    uint256 internal constant GLOBAL_PRICE_IMPACT_SLOT = 201;
    uint256 internal constant GLOBAL_DIAMOND_SLOT = 251;
    uint256 internal constant GLOBAL_TRADING_STORAGE_SLOT = 301;
    uint256 internal constant GLOBAL_TRIGGER_REWARDS_SLOT = 351;
    uint256 internal constant GLOBAL_TRADING_SLOT = 401;
    uint256 internal constant GLOBAL_TRADING_CALLBACKS_SLOT = 451;
    uint256 internal constant GLOBAL_BORROWING_FEES_SLOT = 501;
    uint256 internal constant GLOBAL_PRICE_AGGREGATOR_SLOT = 551;
    uint256 internal constant GLOBAL_OTC_SLOT = 601;
    uint256 internal constant GLOBAL_CHAIN_CONFIG_SLOT = 651;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../interfaces/IWETH9.sol";
import "../interfaces/IERC20.sol";

/**
 * @dev Library to handle transfers of tokens, including native tokens.
 */
library TokenTransferUtils {
    using SafeERC20 for IERC20;

    /**
     * @dev Unwraps and transfers `_amount` of native tokens to a recipient, `_to`.
     *
     * IMPORTANT:
     * If the recipient does not accept the native transfer then the tokens are re-wrapped and transferred as ERC20.
     * Always ensure CEI pattern is followed or reentrancy guards are in place before performing native transfers.
     *
     * @param _token the wrapped native token address
     * @param _to the recipient
     * @param _amount the amount of tokens to transfer
     * @param _gasLimit how much gas to forward.
     */
    function unwrapAndTransferNative(address _token, address _to, uint256 _amount, uint256 _gasLimit) internal {
        // 1. Unwrap `_amount` of `_token`
        IWETH9(_token).withdraw(_amount);

        // 2. Attempt to transfer native tokens
        // Uses low-level call and loads no return data into memory to prevent `returnbomb` attacks
        // See https://gist.github.com/pcaversaccio/3b487a24922c839df22f925babd3c809 for an example
        bool success;
        assembly {
            // call(gas, address, value, argsOffset, argsSize, retOffset, retSize)
            success := call(_gasLimit, _to, _amount, 0, 0, 0, 0)
        }

        // 3. If the native transfer was successful, return
        if (success) return;

        // 4. Otherwise re-wrap `_amount` of `_token`
        IWETH9(_token).deposit{value: _amount}();

        // 5. Send with an ERC20 transfer
        transfer(_token, _to, _amount);
    }

    /**
     * @dev Transfers `_amount` of `_token` to a recipient, `to`
     * @param _token the token address
     * @param _to the recipient
     * @param _amount amount of tokens to transfer
     */
    function transfer(address _token, address _to, uint256 _amount) internal {
        IERC20(_token).safeTransfer(_to, _amount);
    }

    /**
     * @dev Transfers `_amount` of `_token` from a sender, `_from`, to a recipient, `to`.
     * @param _token the token address
     * @param _from the sender
     * @param _to the recipient
     * @param _amount amount of tokens to transfer
     */
    function transferFrom(address _token, address _from, address _to, uint256 _amount) internal {
        IERC20(_token).safeTransferFrom(_from, _to, _amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../interfaces/IGNSMultiCollatDiamond.sol";
import "../interfaces/IGToken.sol";
import "../interfaces/IGNSStaking.sol";
import "../interfaces/IERC20.sol";

import "./StorageUtils.sol";
import "./AddressStoreUtils.sol";
import "./TradingCommonUtils.sol";
import "./updateLeverage/UpdateLeverageLifecycles.sol";
import "./updatePositionSize/UpdatePositionSizeLifecycles.sol";

/**
 * @dev GNSTradingCallbacks facet internal library
 */
library TradingCallbacksUtils {
    /**
     * @dev Modifier to only allow trading action when trading is activated (= revert if not activated)
     */
    modifier tradingActivated() {
        if (_getMultiCollatDiamond().getTradingActivated() != ITradingStorage.TradingActivated.ACTIVATED)
            revert IGeneralErrors.Paused();
        _;
    }

    /**
     * @dev Modifier to only allow trading action when trading is activated or close only (= revert if paused)
     */
    modifier tradingActivatedOrCloseOnly() {
        if (_getMultiCollatDiamond().getTradingActivated() == ITradingStorage.TradingActivated.PAUSED)
            revert IGeneralErrors.Paused();
        _;
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function initializeCallbacks(uint8 _vaultClosingFeeP) internal {
        updateVaultClosingFeeP(_vaultClosingFeeP);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function updateVaultClosingFeeP(uint8 _valueP) internal {
        if (_valueP > 100) revert IGeneralErrors.AboveMax();

        _getStorage().vaultClosingFeeP = _valueP;

        emit ITradingCallbacksUtils.VaultClosingFeePUpdated(_valueP);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function updateTreasuryAddress(address _treasury) internal {
        if (_treasury == address(0)) revert IGeneralErrors.ZeroAddress();

        // Set treasury address
        IGNSAddressStore.Addresses storage addresses = AddressStoreUtils.getAddresses();
        addresses.treasury = _treasury;

        emit IGNSAddressStore.AddressesUpdated(addresses);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function claimPendingGovFees() internal {
        address treasury = AddressStoreUtils.getAddresses().treasury;

        if (treasury == address(0)) revert IGeneralErrors.ZeroAddress();

        uint8 collateralsCount = _getMultiCollatDiamond().getCollateralsCount();
        for (uint8 i = 1; i <= collateralsCount; ++i) {
            uint256 feesAmountCollateral = _getStorage().pendingGovFees[i];

            if (feesAmountCollateral > 0) {
                _getStorage().pendingGovFees[i] = 0;

                TradingCommonUtils.transferCollateralTo(i, treasury, feesAmountCollateral, false);

                emit ITradingCallbacksUtils.PendingGovFeesClaimed(i, feesAmountCollateral);
            }
        }
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function openTradeMarketCallback(ITradingCallbacks.AggregatorAnswer memory _a) internal tradingActivated {
        ITradingStorage.PendingOrder memory o = _getPendingOrder(_a.orderId);

        if (!o.isOpen) return;

        ITradingStorage.Trade memory t = o.trade;

        (uint256 priceImpactP, uint256 priceAfterImpact, ITradingCallbacks.CancelReason cancelReason) = _openTradePrep(
            t,
            _a.price,
            _a.price,
            _a.spreadP,
            o.maxSlippageP
        );

        t.openPrice = uint64(priceAfterImpact);

        if (cancelReason == ITradingCallbacks.CancelReason.NONE) {
            t = _registerTrade(t, o);

            uint256 collateralPriceUsd = _getCollateralPriceUsd(t.collateralIndex);
            emit ITradingCallbacksUtils.MarketExecuted(
                _a.orderId,
                t.user,
                t.index,
                t,
                true,
                _a.price,
                t.openPrice,
                TradingCommonUtils.getTradeLiquidationPrice(t, true),
                priceImpactP,
                0,
                0,
                collateralPriceUsd
            );
        } else {
            // Gov fee to pay for oracle cost
            TradingCommonUtils.updateFeeTierPoints(t.collateralIndex, t.user, t.pairIndex, 0);
            uint256 govFeeCollateral = TradingCommonUtils.getMinGovFeeCollateral(
                t.collateralIndex,
                t.user,
                t.pairIndex
            );
            TradingCommonUtils.distributeExactGovFeeCollateral(t.collateralIndex, t.user, govFeeCollateral);
            TradingCommonUtils.transferCollateralTo(t.collateralIndex, t.user, t.collateralAmount - govFeeCollateral);

            emit ITradingCallbacksUtils.MarketOpenCanceled(_a.orderId, t.user, t.pairIndex, cancelReason);
        }

        _getMultiCollatDiamond().closePendingOrder(_a.orderId);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function closeTradeMarketCallback(
        ITradingCallbacks.AggregatorAnswer memory _a
    ) internal tradingActivatedOrCloseOnly {
        ITradingStorage.PendingOrder memory o = _getPendingOrder(_a.orderId);

        if (!o.isOpen) return;

        ITradingStorage.Trade memory t = _getTrade(o.trade.user, o.trade.index);
        ITradingStorage.TradeInfo memory i = _getTradeInfo(o.trade.user, o.trade.index);

        (uint256 priceImpactP, uint256 priceAfterImpact, ) = TradingCommonUtils.getTradeClosingPriceImpact(
            ITradingCommonUtils.TradePriceImpactInput(
                t,
                _a.price,
                _a.spreadP,
                TradingCommonUtils.getPositionSizeCollateral(t.collateralAmount, t.leverage)
            )
        );

        ITradingCallbacks.CancelReason cancelReason;
        {
            uint256 expectedPrice = o.trade.openPrice;
            uint256 maxSlippage = (expectedPrice *
                (i.maxSlippageP > 0 ? i.maxSlippageP : ConstantsUtils.DEFAULT_MAX_CLOSING_SLIPPAGE_P)) /
                100 /
                1e3;

            // prettier-ignore
            cancelReason = !t.isOpen ? ITradingCallbacks.CancelReason.NO_TRADE : _a.price == 0
                ? ITradingCallbacks.CancelReason.MARKET_CLOSED
                : (
                    t.long
                        ? priceAfterImpact < expectedPrice - maxSlippage
                        : priceAfterImpact > expectedPrice + maxSlippage
                )
                ? ITradingCallbacks.CancelReason.SLIPPAGE
                : ITradingCallbacks.CancelReason.NONE;
        }

        if (cancelReason != ITradingCallbacks.CancelReason.NO_TRADE) {
            ITradingCallbacks.Values memory v;

            if (cancelReason == ITradingCallbacks.CancelReason.NONE) {
                v.profitP = TradingCommonUtils.getPnlPercent(t.openPrice, uint64(priceAfterImpact), t.long, t.leverage);
                v.liqPrice = TradingCommonUtils.getTradeLiquidationPrice(t, true);
                v.amountSentToTrader = _unregisterTrade(t, v.profitP, o.orderType, _a.price, v.liqPrice);
                v.collateralPriceUsd = _getCollateralPriceUsd(t.collateralIndex);

                emit ITradingCallbacksUtils.MarketExecuted(
                    _a.orderId,
                    t.user,
                    t.index,
                    t,
                    false,
                    _a.price,
                    priceAfterImpact,
                    v.liqPrice,
                    priceImpactP,
                    v.profitP,
                    v.amountSentToTrader,
                    v.collateralPriceUsd
                );
            } else {
                // Charge gov fee
                TradingCommonUtils.updateFeeTierPoints(t.collateralIndex, t.user, t.pairIndex, 0);
                uint256 govFeeCollateral = TradingCommonUtils.getMinGovFeeCollateral(
                    t.collateralIndex,
                    t.user,
                    t.pairIndex
                );
                TradingCommonUtils.distributeExactGovFeeCollateral(t.collateralIndex, t.user, govFeeCollateral);

                // Deduct from trade collateral
                _getMultiCollatDiamond().updateTradeCollateralAmount(
                    ITradingStorage.Id({user: t.user, index: t.index}),
                    t.collateralAmount - uint120(govFeeCollateral)
                );
            }
        }

        if (cancelReason != ITradingCallbacks.CancelReason.NONE) {
            emit ITradingCallbacksUtils.MarketCloseCanceled(_a.orderId, t.user, t.pairIndex, t.index, cancelReason);
        }

        _getMultiCollatDiamond().closePendingOrder(_a.orderId);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function executeTriggerOpenOrderCallback(ITradingCallbacks.AggregatorAnswer memory _a) internal tradingActivated {
        ITradingStorage.PendingOrder memory o = _getPendingOrder(_a.orderId);

        if (!o.isOpen) return;

        // Ensure state conditions for executing open order trigger are met
        (
            ITradingStorage.Trade memory t,
            ITradingCallbacks.CancelReason cancelReason,
            ITradingCallbacks.Values memory v
        ) = validateTriggerOpenOrderCallback(
                ITradingStorage.Id({user: o.trade.user, index: o.trade.index}),
                o.orderType,
                _a.open,
                _a.high,
                _a.low
            );

        if (cancelReason == ITradingCallbacks.CancelReason.NONE) {
            // Unregister open order
            v.limitIndex = t.index;
            _getMultiCollatDiamond().closeTrade(ITradingStorage.Id({user: t.user, index: v.limitIndex}), false);

            // Store trade
            t.openPrice = uint64(v.executionPrice);
            t.tradeType = ITradingStorage.TradeType.TRADE;
            t = _registerTrade(t, o);

            v.liqPrice = TradingCommonUtils.getTradeLiquidationPrice(t, true);
            v.collateralPriceUsd = _getCollateralPriceUsd(t.collateralIndex);

            emit ITradingCallbacksUtils.LimitExecuted(
                _a.orderId,
                t.user,
                t.index,
                v.limitIndex,
                t,
                o.user,
                o.orderType,
                v.oraclePrice,
                t.openPrice,
                v.liqPrice,
                v.priceImpactP,
                0,
                0,
                v.collateralPriceUsd,
                v.exactExecution
            );
        } else {
            emit ITradingCallbacksUtils.TriggerOrderCanceled(_a.orderId, o.user, o.orderType, cancelReason);
        }

        _getMultiCollatDiamond().closePendingOrder(_a.orderId);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function executeTriggerCloseOrderCallback(
        ITradingCallbacks.AggregatorAnswer memory _a
    ) internal tradingActivatedOrCloseOnly {
        ITradingStorage.PendingOrder memory o = _getPendingOrder(_a.orderId);

        if (!o.isOpen) return;

        // Ensure state conditions for executing close order trigger are met
        (
            ITradingStorage.Trade memory t,
            ITradingCallbacks.CancelReason cancelReason,
            ITradingCallbacks.Values memory v
        ) = validateTriggerCloseOrderCallback(
                ITradingStorage.Id({user: o.trade.user, index: o.trade.index}),
                o.orderType,
                _a.open,
                _a.high,
                _a.low
            );

        if (cancelReason == ITradingCallbacks.CancelReason.NONE) {
            v.profitP = TradingCommonUtils.getPnlPercent(t.openPrice, uint64(v.executionPrice), t.long, t.leverage);
            v.amountSentToTrader = _unregisterTrade(t, v.profitP, o.orderType, v.oraclePrice, v.liqPrice);
            v.collateralPriceUsd = _getCollateralPriceUsd(t.collateralIndex);

            emit ITradingCallbacksUtils.LimitExecuted(
                _a.orderId,
                t.user,
                t.index,
                0,
                t,
                o.user,
                o.orderType,
                v.oraclePrice,
                v.executionPrice,
                v.liqPrice,
                v.priceImpactP,
                v.profitP,
                v.amountSentToTrader,
                v.collateralPriceUsd,
                v.exactExecution
            );
        } else {
            emit ITradingCallbacksUtils.TriggerOrderCanceled(_a.orderId, o.user, o.orderType, cancelReason);
        }

        _getMultiCollatDiamond().closePendingOrder(_a.orderId);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function updateLeverageCallback(ITradingCallbacks.AggregatorAnswer memory _a) internal tradingActivated {
        ITradingStorage.PendingOrder memory order = _getMultiCollatDiamond().getPendingOrder(_a.orderId);

        if (!order.isOpen) return;

        UpdateLeverageLifecycles.executeUpdateLeverage(order, _a);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function increasePositionSizeMarketCallback(
        ITradingCallbacks.AggregatorAnswer memory _a
    ) internal tradingActivated {
        ITradingStorage.PendingOrder memory order = _getMultiCollatDiamond().getPendingOrder(_a.orderId);

        if (!order.isOpen) return;

        UpdatePositionSizeLifecycles.executeIncreasePositionSizeMarket(order, _a);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function decreasePositionSizeMarketCallback(
        ITradingCallbacks.AggregatorAnswer memory _a
    ) internal tradingActivatedOrCloseOnly {
        ITradingStorage.PendingOrder memory order = _getMultiCollatDiamond().getPendingOrder(_a.orderId);

        if (!order.isOpen) return;

        UpdatePositionSizeLifecycles.executeDecreasePositionSizeMarket(order, _a);
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function getVaultClosingFeeP() internal view returns (uint8) {
        return _getStorage().vaultClosingFeeP;
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function getPendingGovFeesCollateral(uint8 _collateralIndex) internal view returns (uint256) {
        return _getStorage().pendingGovFees[_collateralIndex];
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function validateTriggerOpenOrderCallback(
        ITradingStorage.Id memory _tradeId,
        ITradingStorage.PendingOrderType _orderType,
        uint64 _open,
        uint64 _high,
        uint64 _low
    )
        internal
        view
        returns (
            ITradingStorage.Trade memory t,
            ITradingCallbacks.CancelReason cancelReason,
            ITradingCallbacks.Values memory v
        )
    {
        if (
            _orderType != ITradingStorage.PendingOrderType.LIMIT_OPEN &&
            _orderType != ITradingStorage.PendingOrderType.STOP_OPEN
        ) {
            revert IGeneralErrors.WrongOrderType();
        }

        t = _getTrade(_tradeId.user, _tradeId.index);

        // Return early if trade is not open
        if (!t.isOpen) {
            cancelReason = ITradingCallbacks.CancelReason.NO_TRADE;
            return (t, cancelReason, v);
        }

        v.exactExecution = (_high >= t.openPrice && _low <= t.openPrice);
        v.oraclePrice = v.exactExecution ? t.openPrice : _open;

        (v.priceImpactP, v.executionPrice, cancelReason) = _openTradePrep(
            t,
            v.oraclePrice,
            _open,
            _getMultiCollatDiamond().pairSpreadP(t.pairIndex),
            _getTradeInfo(t.user, t.index).maxSlippageP
        );

        if (
            !v.exactExecution &&
            (
                t.tradeType == ITradingStorage.TradeType.STOP
                    ? (t.long ? _open < t.openPrice : _open > t.openPrice)
                    : (t.long ? _open > t.openPrice : _open < t.openPrice)
            )
        ) cancelReason = ITradingCallbacks.CancelReason.NOT_HIT;
    }

    /**
     * @dev Check ITradingCallbacksUtils interface for documentation
     */
    function validateTriggerCloseOrderCallback(
        ITradingStorage.Id memory _tradeId,
        ITradingStorage.PendingOrderType _orderType,
        uint64 _open,
        uint64 _high,
        uint64 _low
    )
        internal
        view
        returns (
            ITradingStorage.Trade memory t,
            ITradingCallbacks.CancelReason cancelReason,
            ITradingCallbacks.Values memory v
        )
    {
        if (
            _orderType != ITradingStorage.PendingOrderType.TP_CLOSE &&
            _orderType != ITradingStorage.PendingOrderType.SL_CLOSE &&
            _orderType != ITradingStorage.PendingOrderType.LIQ_CLOSE
        ) {
            revert IGeneralErrors.WrongOrderType();
        }

        t = _getTrade(_tradeId.user, _tradeId.index);
        ITradingStorage.TradeInfo memory i = _getTradeInfo(_tradeId.user, _tradeId.index);

        cancelReason = _open == 0
            ? ITradingCallbacks.CancelReason.MARKET_CLOSED
            : (!t.isOpen ? ITradingCallbacks.CancelReason.NO_TRADE : ITradingCallbacks.CancelReason.NONE);

        // Return early if trade is not open or market is closed
        if (cancelReason != ITradingCallbacks.CancelReason.NONE) return (t, cancelReason, v);

        v.liqPrice = TradingCommonUtils.getTradeLiquidationPrice(t, true);
        uint256 triggerPrice = _orderType == ITradingStorage.PendingOrderType.TP_CLOSE
            ? t.tp
            : (_orderType == ITradingStorage.PendingOrderType.SL_CLOSE ? t.sl : v.liqPrice);

        v.exactExecution = triggerPrice > 0 && _low <= triggerPrice && _high >= triggerPrice;
        v.oraclePrice = v.exactExecution ? triggerPrice : _open;

        // Apply closing spread and price impact for TPs and SLs, not liquidations (because trade value is 0 already)
        if (_orderType != ITradingStorage.PendingOrderType.LIQ_CLOSE) {
            (v.priceImpactP, v.executionPrice, ) = TradingCommonUtils.getTradeClosingPriceImpact(
                ITradingCommonUtils.TradePriceImpactInput(
                    t,
                    v.oraclePrice,
                    _getMultiCollatDiamond().pairSpreadP(t.pairIndex),
                    TradingCommonUtils.getPositionSizeCollateral(t.collateralAmount, t.leverage)
                )
            );
        } else {
            v.executionPrice = v.oraclePrice;
        }

        uint256 maxSlippage = (triggerPrice *
            (i.maxSlippageP > 0 ? i.maxSlippageP : ConstantsUtils.DEFAULT_MAX_CLOSING_SLIPPAGE_P)) /
            100 /
            1e3;

        cancelReason = (v.exactExecution ||
            (_orderType == ITradingStorage.PendingOrderType.LIQ_CLOSE &&
                (t.long ? _open <= v.liqPrice : _open >= v.liqPrice)) ||
            (_orderType == ITradingStorage.PendingOrderType.TP_CLOSE &&
                t.tp > 0 &&
                (t.long ? _open >= t.tp : _open <= t.tp)) ||
            (_orderType == ITradingStorage.PendingOrderType.SL_CLOSE &&
                t.sl > 0 &&
                (t.long ? _open <= t.sl : _open >= t.sl)))
            ? (
                _orderType != ITradingStorage.PendingOrderType.LIQ_CLOSE &&
                    (
                        t.long
                            ? v.executionPrice < triggerPrice - maxSlippage
                            : v.executionPrice > triggerPrice + maxSlippage
                    )
                    ? ITradingCallbacks.CancelReason.SLIPPAGE
                    : ITradingCallbacks.CancelReason.NONE
            )
            : ITradingCallbacks.CancelReason.NOT_HIT;
    }

    /**
     * @dev Returns storage slot to use when fetching storage relevant to library
     */
    function _getSlot() internal pure returns (uint256) {
        return StorageUtils.GLOBAL_TRADING_CALLBACKS_SLOT;
    }

    /**
     * @dev Returns storage pointer for storage struct in diamond contract, at defined slot
     */
    function _getStorage() internal pure returns (ITradingCallbacks.TradingCallbacksStorage storage s) {
        uint256 storageSlot = _getSlot();
        assembly {
            s.slot := storageSlot
        }
    }

    /**
     * @dev Returns current address as multi-collateral diamond interface to call other facets functions.
     */
    function _getMultiCollatDiamond() internal view returns (IGNSMultiCollatDiamond) {
        return IGNSMultiCollatDiamond(address(this));
    }

    /**
     * @dev Registers a trade in storage, and handles all fees and rewards
     * @param _trade Trade to register
     * @param _pendingOrder Corresponding pending order
     * @return Final registered trade
     */
    function _registerTrade(
        ITradingStorage.Trade memory _trade,
        ITradingStorage.PendingOrder memory _pendingOrder
    ) internal returns (ITradingStorage.Trade memory) {
        // 1. Deduct gov fee, GNS staking fee (previously dev fee), Market/Limit fee
        _trade.collateralAmount -= uint120(
            TradingCommonUtils.processFees(
                _trade,
                TradingCommonUtils.getPositionSizeCollateral(_trade.collateralAmount, _trade.leverage),
                _pendingOrder.orderType
            )
        );

        // 2. Store final trade in storage contract
        ITradingStorage.TradeInfo memory tradeInfo;
        _trade = _getMultiCollatDiamond().storeTrade(_trade, tradeInfo);

        return _trade;
    }

    /**
     * @dev Unregisters a trade from storage, and handles all fees and rewards
     * @param _trade Trade to unregister
     * @param _profitP Profit percentage (1e10)
     * @param _orderType pending order type
     * @param _oraclePrice oracle price without closing spread/impact (1e10)
     * @param _liqPrice trade liquidation price (1e10)
     * @return tradeValueCollateral Amount of collateral sent to trader, collateral + pnl (collateral precision)
     */
    function _unregisterTrade(
        ITradingStorage.Trade memory _trade,
        int256 _profitP,
        ITradingStorage.PendingOrderType _orderType,
        uint256 _oraclePrice,
        uint256 _liqPrice
    ) internal returns (uint256 tradeValueCollateral) {
        // 1. Process closing fees, fill 'v' with closing/trigger fees and collateral left in storage, to avoid stack too deep
        uint256 totalFeesCollateral = TradingCommonUtils.processFees(
            _trade,
            TradingCommonUtils.getPositionSizeCollateral(_trade.collateralAmount, _trade.leverage),
            _orderType
        );

        // 2.1 Calculate borrowing fee and net trade value (with pnl and after all closing/holding fees)
        uint256 borrowingFeeCollateral;
        (tradeValueCollateral, borrowingFeeCollateral) = TradingCommonUtils.getTradeValueCollateral(
            _trade,
            _profitP,
            totalFeesCollateral,
            _getMultiCollatDiamond().getCollateral(_trade.collateralIndex).precisionDelta
        );

        // 2.2 If trade is liquidated, set trade value to 0
        tradeValueCollateral = (_trade.long ? _oraclePrice <= _liqPrice : _oraclePrice >= _liqPrice)
            ? 0
            : tradeValueCollateral;

        // 3. Take collateral from vault if winning trade or send collateral to vault if losing trade
        TradingCommonUtils.handleTradePnl(
            _trade,
            int256(tradeValueCollateral),
            int256(
                _trade.collateralAmount >= totalFeesCollateral
                    ? _trade.collateralAmount - totalFeesCollateral
                    : _trade.collateralAmount // fees only charged when collateral enough to pay (due to min fee)
            ),
            borrowingFeeCollateral
        );

        // 4. Unregister trade from storage
        _getMultiCollatDiamond().closeTrade(ITradingStorage.Id({user: _trade.user, index: _trade.index}), _profitP > 0);
    }

    /**
     * @dev Makes pre-trade checks: price impact, if trade should be cancelled based on parameters like: PnL, leverage, slippage, etc.
     * @param _trade trade input
     * @param _executionPrice execution price (1e10 precision)
     * @param _oraclePrice oracle price (1e10 precision)
     * @param _spreadP spread % (1e10 precision)
     * @param _maxSlippageP max slippage % (1e3 precision)
     */
    function _openTradePrep(
        ITradingStorage.Trade memory _trade,
        uint256 _executionPrice,
        uint256 _oraclePrice,
        uint256 _spreadP,
        uint256 _maxSlippageP
    )
        internal
        view
        returns (uint256 priceImpactP, uint256 priceAfterImpact, ITradingCallbacks.CancelReason cancelReason)
    {
        uint256 positionSizeCollateral = TradingCommonUtils.getPositionSizeCollateral(
            _trade.collateralAmount,
            _trade.leverage
        );

        (priceImpactP, priceAfterImpact) = TradingCommonUtils.getTradeOpeningPriceImpact(
            ITradingCommonUtils.TradePriceImpactInput(_trade, _executionPrice, _spreadP, positionSizeCollateral),
            _getMultiCollatDiamond().getCurrentContractsVersion()
        );

        uint256 maxSlippage = (uint256(_trade.openPrice) * _maxSlippageP) / 100 / 1e3;

        // prettier-ignore
        cancelReason = _oraclePrice == 0
            ? ITradingCallbacks.CancelReason.MARKET_CLOSED
            : (
                (
                    _trade.long
                        ? priceAfterImpact > _trade.openPrice + maxSlippage
                        : priceAfterImpact < _trade.openPrice - maxSlippage
                )
                    ? ITradingCallbacks.CancelReason.SLIPPAGE
                    : (_trade.tp > 0 && (_trade.long ? priceAfterImpact >= _trade.tp : priceAfterImpact <= _trade.tp))
                    ? ITradingCallbacks.CancelReason.TP_REACHED
                    : (_trade.sl > 0 && (_trade.long ? _executionPrice <= _trade.sl : _executionPrice >= _trade.sl))
                    ? ITradingCallbacks.CancelReason.SL_REACHED
                    : !TradingCommonUtils.isWithinExposureLimits(
                        _trade.collateralIndex,
                        _trade.pairIndex,
                        _trade.long,
                        positionSizeCollateral
                    )
                    ? ITradingCallbacks.CancelReason.EXPOSURE_LIMITS
                    : (priceImpactP * _trade.leverage) / 1e3 > ConstantsUtils.MAX_OPEN_NEGATIVE_PNL_P
                    ? ITradingCallbacks.CancelReason.PRICE_IMPACT
                    : _trade.leverage > _getMultiCollatDiamond().pairMaxLeverage(_trade.pairIndex)
                    ? ITradingCallbacks.CancelReason.MAX_LEVERAGE
                    : ITradingCallbacks.CancelReason.NONE
            );
    }

    /**
     * @dev Returns pending order from storage
     * @param _orderId Order ID
     * @return Pending order
     */
    function _getPendingOrder(
        ITradingStorage.Id memory _orderId
    ) internal view returns (ITradingStorage.PendingOrder memory) {
        return _getMultiCollatDiamond().getPendingOrder(_orderId);
    }

    /**
     * @dev Returns collateral price in USD
     * @param _collateralIndex Collateral index
     * @return Collateral price in USD
     */
    function _getCollateralPriceUsd(uint8 _collateralIndex) internal view returns (uint256) {
        return _getMultiCollatDiamond().getCollateralPriceUsd(_collateralIndex);
    }

    /**
     * @dev Returns trade from storage
     * @param _trader Trader address
     * @param _index Trade index
     * @return Trade
     */
    function _getTrade(address _trader, uint32 _index) internal view returns (ITradingStorage.Trade memory) {
        return _getMultiCollatDiamond().getTrade(_trader, _index);
    }

    /**
     * @dev Returns trade info from storage
     * @param _trader Trader address
     * @param _index Trade index
     * @return TradeInfo
     */
    function _getTradeInfo(address _trader, uint32 _index) internal view returns (ITradingStorage.TradeInfo memory) {
        return _getMultiCollatDiamond().getTradeInfo(_trader, _index);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../interfaces/IGToken.sol";
import "../interfaces/IGNSMultiCollatDiamond.sol";
import "../interfaces/IGNSStaking.sol";
import "../interfaces/IERC20.sol";

import "./ConstantsUtils.sol";
import "./AddressStoreUtils.sol";
import "./ChainUtils.sol";
import "./ChainConfigUtils.sol";
import "./TradingCallbacksUtils.sol";
import "./TokenTransferUtils.sol";

/**
 * @dev External library for helper functions commonly used in many places
 */
library TradingCommonUtils {
    using TokenTransferUtils for address;

    // Pure functions

    /**
     * @dev Returns the current percent profit of a trade (1e10 precision)
     * @param _openPrice trade open price (1e10 precision)
     * @param _currentPrice trade current price (1e10 precision)
     * @param _long true for long, false for short
     * @param _leverage trade leverage (1e3 precision)
     */
    function getPnlPercent(
        uint64 _openPrice,
        uint64 _currentPrice,
        bool _long,
        uint24 _leverage
    ) public pure returns (int256 p) {
        int256 pricePrecision = int256(ConstantsUtils.P_10);
        int256 maxPnlP = int256(ConstantsUtils.MAX_PNL_P) * pricePrecision;
        int256 minPnlP = -100 * int256(ConstantsUtils.P_10);

        int256 openPrice = int256(uint256(_openPrice));
        int256 currentPrice = int256(uint256(_currentPrice));
        int256 leverage = int256(uint256(_leverage));

        p = _openPrice > 0
            ? ((_long ? currentPrice - openPrice : openPrice - currentPrice) * 100 * pricePrecision * leverage) /
                openPrice /
                1e3
            : int256(0);

        // prettier-ignore
        p = p > maxPnlP ? maxPnlP : p < minPnlP ? minPnlP : p;
    }

    /**
     * @dev Returns position size of trade in collateral tokens (avoids overflow from uint120 collateralAmount)
     * @param _collateralAmount collateral of trade
     * @param _leverage leverage of trade (1e3)
     */
    function getPositionSizeCollateral(uint120 _collateralAmount, uint24 _leverage) public pure returns (uint256) {
        return (uint256(_collateralAmount) * _leverage) / 1e3;
    }

    /**
     * @dev Calculates market execution price for a trade (1e10 precision)
     * @param _price price of the asset (1e10)
     * @param _spreadP spread percentage (1e10)
     * @param _long true if long, false if short
     */
    function getMarketExecutionPrice(
        uint256 _price,
        uint256 _spreadP,
        bool _long,
        bool _open,
        ITradingStorage.ContractsVersion _contractsVersion
    ) public pure returns (uint256) {
        // No closing spread for trades opened before v9.2
        if (!_open && _contractsVersion == ITradingStorage.ContractsVersion.BEFORE_V9_2) {
            return _price;
        }

        // For trades opened after v9.2 use half spread (open + close),
        // for trades opened before v9.2 use full spread (open)
        if (_contractsVersion >= ITradingStorage.ContractsVersion.V9_2) {
            _spreadP = _spreadP / 2;
        }

        uint256 priceDiff = (_price * _spreadP) / 100 / ConstantsUtils.P_10;
        if (!_open) _long = !_long; // reverse spread direction on close
        return _long ? _price + priceDiff : _price - priceDiff;
    }

    /**
     * @dev Converts collateral value to USD (1e18 precision)
     * @param _collateralAmount amount of collateral (collateral precision)
     * @param _collateralPrecisionDelta precision delta of collateral (10^18/10^decimals)
     * @param _collateralPriceUsd price of collateral in USD (1e8)
     */
    function convertCollateralToUsd(
        uint256 _collateralAmount,
        uint128 _collateralPrecisionDelta,
        uint256 _collateralPriceUsd
    ) public pure returns (uint256) {
        return (_collateralAmount * _collateralPrecisionDelta * _collateralPriceUsd) / 1e8;
    }

    /**
     * @dev Converts collateral value to GNS (1e18 precision)
     * @param _collateralAmount amount of collateral (collateral precision)
     * @param _collateralPrecisionDelta precision delta of collateral (10^18/10^decimals)
     * @param _gnsPriceCollateral price of GNS in collateral (1e10)
     */
    function convertCollateralToGns(
        uint256 _collateralAmount,
        uint128 _collateralPrecisionDelta,
        uint256 _gnsPriceCollateral
    ) internal pure returns (uint256) {
        return ((_collateralAmount * _collateralPrecisionDelta * ConstantsUtils.P_10) / _gnsPriceCollateral);
    }

    /**
     * @dev Calculates trade value (useful when closing a trade)
     * @dev Important: does not calculate if trade can be liquidated or not, has to be done by calling function
     * @param _collateral amount of collateral (collateral precision)
     * @param _percentProfit profit percentage (1e10)
     * @param _feesCollateral borrowing fee + closing fee in collateral tokens (collateral precision)
     * @param _collateralPrecisionDelta precision delta of collateral (10^18/10^decimals)
     */
    function getTradeValuePure(
        uint256 _collateral,
        int256 _percentProfit,
        uint256 _feesCollateral,
        uint128 _collateralPrecisionDelta
    ) public pure returns (uint256) {
        int256 precisionDelta = int256(uint256(_collateralPrecisionDelta));

        // Multiply collateral by precisionDelta so we don't lose precision for low decimals
        int256 value = (int256(_collateral) *
            precisionDelta +
            (int256(_collateral) * precisionDelta * _percentProfit) /
            int256(ConstantsUtils.P_10) /
            100) /
            precisionDelta -
            int256(_feesCollateral);

        return value > 0 ? uint256(value) : uint256(0);
    }

    /**
     * @dev Pure function that returns the liquidation pnl % threshold for a trade (1e10)
     * @param _params trade liquidation params
     * @param _leverage trade leverage (1e3 precision)
     */
    function getLiqPnlThresholdP(
        IPairsStorage.GroupLiquidationParams memory _params,
        uint256 _leverage
    ) public pure returns (uint256) {
        // By default use legacy threshold if liquidation params not set (trades opened before v9.2)
        if (_params.maxLiqSpreadP == 0) return ConstantsUtils.LEGACY_LIQ_THRESHOLD_P;

        if (_leverage <= _params.startLeverage) return _params.startLiqThresholdP;
        if (_leverage >= _params.endLeverage) return _params.endLiqThresholdP;

        return
            _params.startLiqThresholdP -
            ((_leverage - _params.startLeverage) * (_params.startLiqThresholdP - _params.endLiqThresholdP)) /
            (_params.endLeverage - _params.startLeverage);
    }

    // View functions

    /**
     * @dev Returns minimum position size in collateral tokens for a pair (collateral precision)
     * @param _collateralIndex collateral index
     * @param _pairIndex pair index
     */
    function getMinPositionSizeCollateral(uint8 _collateralIndex, uint256 _pairIndex) public view returns (uint256) {
        return
            _getMultiCollatDiamond().getCollateralFromUsdNormalizedValue(
                _collateralIndex,
                _getMultiCollatDiamond().pairMinPositionSizeUsd(_pairIndex)
            );
    }

    /**
     * @dev Returns position size to use when charging fees
     * @param _collateralIndex collateral index
     * @param _pairIndex pair index
     * @param _positionSizeCollateral trade position size in collateral tokens (collateral precision)
     */
    function getPositionSizeCollateralBasis(
        uint8 _collateralIndex,
        uint256 _pairIndex,
        uint256 _positionSizeCollateral
    ) public view returns (uint256) {
        uint256 minPositionSizeCollateral = getMinPositionSizeCollateral(_collateralIndex, _pairIndex);
        return
            _positionSizeCollateral > minPositionSizeCollateral ? _positionSizeCollateral : minPositionSizeCollateral;
    }

    /**
     * @dev Checks if total position size is not higher than maximum allowed open interest for a pair
     * @param _collateralIndex index of collateral
     * @param _pairIndex index of pair
     * @param _long true if long, false if short
     * @param _positionSizeCollateralDelta position size delta in collateral tokens (collateral precision)
     */
    function isWithinExposureLimits(
        uint8 _collateralIndex,
        uint16 _pairIndex,
        bool _long,
        uint256 _positionSizeCollateralDelta
    ) external view returns (bool) {
        return
            _getMultiCollatDiamond().getPairOiCollateral(_collateralIndex, _pairIndex, _long) +
                _positionSizeCollateralDelta <=
            _getMultiCollatDiamond().getPairMaxOiCollateral(_collateralIndex, _pairIndex) &&
            _getMultiCollatDiamond().withinMaxBorrowingGroupOi(
                _collateralIndex,
                _pairIndex,
                _long,
                _positionSizeCollateralDelta
            );
    }

    /**
     * @dev Convenient wrapper to return trade borrowing fee in collateral tokens (collateral precision)
     * @param _trade trade input
     */
    function getTradeBorrowingFeeCollateral(ITradingStorage.Trade memory _trade) public view returns (uint256) {
        return
            _getMultiCollatDiamond().getTradeBorrowingFee(
                IBorrowingFees.BorrowingFeeInput(
                    _trade.collateralIndex,
                    _trade.user,
                    _trade.pairIndex,
                    _trade.index,
                    _trade.long,
                    _trade.collateralAmount,
                    _trade.leverage
                )
            );
    }

    /**
     * @dev Convenient wrapper to return trade liquidation price (1e10)
     * @param _trade trade input
     */
    function getTradeLiquidationPrice(
        ITradingStorage.Trade memory _trade,
        bool _useBorrowingFees
    ) public view returns (uint256) {
        return
            _getMultiCollatDiamond().getTradeLiquidationPrice(
                IBorrowingFees.LiqPriceInput(
                    _trade.collateralIndex,
                    _trade.user,
                    _trade.pairIndex,
                    _trade.index,
                    _trade.openPrice,
                    _trade.long,
                    _trade.collateralAmount,
                    _trade.leverage,
                    _useBorrowingFees,
                    _getMultiCollatDiamond().getTradeLiquidationParams(_trade.user, _trade.index)
                )
            );
    }

    /**
     * @dev Returns trade value and borrowing fee in collateral tokens
     * @param _trade trade data
     * @param _percentProfit profit percentage (1e10)
     * @param _closingFeesCollateral closing fees in collateral tokens (collateral precision)
     * @param _collateralPrecisionDelta precision delta of collateral (10^18/10^decimals)
     */
    function getTradeValueCollateral(
        ITradingStorage.Trade memory _trade,
        int256 _percentProfit,
        uint256 _closingFeesCollateral,
        uint128 _collateralPrecisionDelta
    ) public view returns (uint256 valueCollateral, uint256 borrowingFeesCollateral) {
        borrowingFeesCollateral = getTradeBorrowingFeeCollateral(_trade);

        valueCollateral = getTradeValuePure(
            _trade.collateralAmount,
            _percentProfit,
            borrowingFeesCollateral + _closingFeesCollateral,
            _collateralPrecisionDelta
        );
    }

    /**
     * @dev Returns price impact % (1e10), price after spread and impact (1e10)
     * @param _input input data
     * @param _contractsVersion contracts version
     */
    function getTradeOpeningPriceImpact(
        ITradingCommonUtils.TradePriceImpactInput memory _input,
        ITradingStorage.ContractsVersion _contractsVersion
    ) external view returns (uint256 priceImpactP, uint256 priceAfterImpact) {
        ITradingStorage.Trade memory trade = _input.trade;

        (priceImpactP, priceAfterImpact) = _getMultiCollatDiamond().getTradePriceImpact(
            trade.user,
            getMarketExecutionPrice(_input.oraclePrice, _input.spreadP, trade.long, true, _contractsVersion),
            trade.pairIndex,
            trade.long,
            _getMultiCollatDiamond().getUsdNormalizedValue(trade.collateralIndex, _input.positionSizeCollateral),
            false,
            true,
            0,
            _contractsVersion
        );
    }

    /**
     * @dev Returns price impact % (1e10), price after spread and impact (1e10), and trade value used to know if pnl is positive (collateral precision)
     * @param _input input data
     */
    function getTradeClosingPriceImpact(
        ITradingCommonUtils.TradePriceImpactInput memory _input
    ) external view returns (uint256 priceImpactP, uint256 priceAfterImpact, uint256 tradeValueCollateralNoFactor) {
        ITradingStorage.Trade memory trade = _input.trade;
        ITradingStorage.TradeInfo memory tradeInfo = _getMultiCollatDiamond().getTradeInfo(trade.user, trade.index);

        // 0. If trade opened before v9.2, return market price (no closing spread or price impact)
        if (tradeInfo.contractsVersion == ITradingStorage.ContractsVersion.BEFORE_V9_2) {
            return (0, _input.oraclePrice, 0);
        }

        // 1. Prepare vars
        bool open = false;
        uint256 priceAfterSpread = getMarketExecutionPrice(
            _input.oraclePrice,
            _input.spreadP,
            trade.long,
            open,
            tradeInfo.contractsVersion
        );
        uint256 positionSizeUsd = _getMultiCollatDiamond().getUsdNormalizedValue(
            trade.collateralIndex,
            _input.positionSizeCollateral
        );

        // 2. Calculate PnL after fees, spread, and price impact without protection factor
        (, uint256 priceNoProtectionFactor) = _getMultiCollatDiamond().getTradePriceImpact(
            trade.user,
            priceAfterSpread,
            trade.pairIndex,
            trade.long,
            positionSizeUsd,
            false, // assume pnl negative, so it doesn't use protection factor
            open,
            tradeInfo.lastPosIncreaseBlock,
            tradeInfo.contractsVersion
        );
        int256 pnlPercentNoProtectionFactor = getPnlPercent(
            trade.openPrice,
            uint64(priceNoProtectionFactor),
            trade.long,
            trade.leverage
        );
        (tradeValueCollateralNoFactor, ) = getTradeValueCollateral(
            trade,
            pnlPercentNoProtectionFactor,
            getTotalTradeFeesCollateral(
                trade.collateralIndex,
                trade.user,
                trade.pairIndex,
                getPositionSizeCollateral(trade.collateralAmount, trade.leverage)
            ),
            _getMultiCollatDiamond().getCollateral(trade.collateralIndex).precisionDelta
        );

        (priceImpactP, priceAfterImpact) = _getMultiCollatDiamond().getTradePriceImpact(
            trade.user,
            priceAfterSpread,
            trade.pairIndex,
            trade.long,
            positionSizeUsd,
            tradeValueCollateralNoFactor > trade.collateralAmount, // _isPnlPositive = true when net pnl after fees is positive
            open,
            tradeInfo.lastPosIncreaseBlock,
            tradeInfo.contractsVersion
        );
    }

    /**
     * @dev Returns a trade's liquidation threshold % (1e10)
     * @param _trade trade struct
     */
    function getTradeLiqPnlThresholdP(ITradingStorage.Trade memory _trade) public view returns (uint256) {
        return
            getLiqPnlThresholdP(
                _getMultiCollatDiamond().getTradeLiquidationParams(_trade.user, _trade.index),
                _trade.leverage
            );
    }

    /**
     * @dev Returns all fees for a trade in collateral tokens
     * @param _collateralIndex collateral index
     * @param _trader address of trader
     * @param _pairIndex index of pair
     * @param _positionSizeCollateral position size in collateral tokens (collateral precision)
     */
    function getTotalTradeFeesCollateral(
        uint8 _collateralIndex,
        address _trader,
        uint16 _pairIndex,
        uint256 _positionSizeCollateral
    ) public view returns (uint256) {
        return
            _getMultiCollatDiamond().calculateFeeAmount(
                _trader,
                (getPositionSizeCollateralBasis(_collateralIndex, _pairIndex, _positionSizeCollateral) *
                    _getMultiCollatDiamond().pairTotalPositionSizeFeeP(_pairIndex)) /
                    ConstantsUtils.P_10 /
                    100
            );
    }

    /**
     * @dev Returns all fees for a trade in collateral tokens
     * @param _collateralIndex collateral index
     * @param _trader address of trader
     * @param _pairIndex index of pair
     * @param _collateralAmount trade collateral amount (collateral precision)
     * @param _positionSizeCollateral trade position size in collateral tokens (collateral precision)
     * @param _orderType corresponding order type
     */
    function getTradeFeesCollateral(
        uint8 _collateralIndex,
        address _trader,
        uint16 _pairIndex,
        uint256 _collateralAmount,
        uint256 _positionSizeCollateral,
        ITradingStorage.PendingOrderType _orderType
    ) public view returns (IPairsStorage.TradeFees memory tradeFees) {
        IPairsStorage.GlobalTradeFeeParams memory feeParams = _getMultiCollatDiamond().getGlobalTradeFeeParams();

        if (_orderType == ITradingStorage.PendingOrderType.LIQ_CLOSE) {
            uint256 totalLiqCollateralFeeP = _getMultiCollatDiamond().pairTotalLiqCollateralFeeP(_pairIndex);
            tradeFees.totalFeeCollateral = (_collateralAmount * totalLiqCollateralFeeP) / ConstantsUtils.P_10 / 100;
        } else {
            tradeFees.totalFeeCollateral = getTotalTradeFeesCollateral(
                _collateralIndex,
                _trader,
                _pairIndex,
                _positionSizeCollateral
            );
        }

        address referrer = _getMultiCollatDiamond().getTraderActiveReferrer(_trader);
        uint256 referralFeeCollateral = (tradeFees.totalFeeCollateral * feeParams.referralFeeP) / 1e3 / 100;
        tradeFees.referralFeeCollateral = referrer != address(0)
            ? (referralFeeCollateral * _getMultiCollatDiamond().getReferrerFeeProgressP(referrer)) /
                ConstantsUtils.P_10 /
                100
            : 0;
        uint256 missingReferralFeeCollateral = referralFeeCollateral - tradeFees.referralFeeCollateral;

        tradeFees.govFeeCollateral =
            ((tradeFees.totalFeeCollateral * feeParams.govFeeP) / 1e3 / 100) +
            (missingReferralFeeCollateral / 2);

        uint256 triggerOrderFeeCollateral = (tradeFees.totalFeeCollateral * feeParams.triggerOrderFeeP) / 1e3 / 100;
        tradeFees.triggerOrderFeeCollateral = ConstantsUtils.isOrderTypeMarket(_orderType)
            ? 0
            : triggerOrderFeeCollateral;
        uint256 missingTriggerOrderFeeCollateral = triggerOrderFeeCollateral - tradeFees.triggerOrderFeeCollateral;

        tradeFees.gnsOtcFeeCollateral =
            ((tradeFees.totalFeeCollateral * feeParams.gnsOtcFeeP) / 1e3 / 100) +
            (missingReferralFeeCollateral / 2) +
            missingTriggerOrderFeeCollateral;

        tradeFees.gTokenFeeCollateral = (tradeFees.totalFeeCollateral * feeParams.gTokenFeeP) / 1e3 / 100;
    }

    function getMinGovFeeCollateral(
        uint8 _collateralIndex,
        address _trader,
        uint16 _pairIndex
    ) public view returns (uint256) {
        uint256 totalFeeCollateral = getTotalTradeFeesCollateral(
            _collateralIndex,
            _trader,
            _pairIndex,
            0 // position size is 0 so it will use the minimum pos
        ) / 2; // charge fee on min pos / 2
        return (totalFeeCollateral * _getMultiCollatDiamond().getGlobalTradeFeeParams().govFeeP) / 1e3 / 100;
    }

    /**
     * @dev Reverts if user initiated any kind of pending market order on his trade
     * @param _user trade user
     * @param _index trade index
     */
    function revertIfTradeHasPendingMarketOrder(address _user, uint32 _index) public view {
        ITradingStorage.PendingOrderType[5] memory pendingOrderTypes = ConstantsUtils.getMarketOrderTypes();
        ITradingStorage.Id memory tradeId = ITradingStorage.Id(_user, _index);

        for (uint256 i; i < pendingOrderTypes.length; ++i) {
            ITradingStorage.PendingOrderType orderType = pendingOrderTypes[i];

            if (_getMultiCollatDiamond().getTradePendingOrderBlock(tradeId, orderType) > 0)
                revert ITradingInteractionsUtils.ConflictingPendingOrder(orderType);
        }
    }

    /**
     * @dev Returns gToken contract for a collateral index
     * @param _collateralIndex collateral index
     */
    function getGToken(uint8 _collateralIndex) public view returns (IGToken) {
        return IGToken(_getMultiCollatDiamond().getGToken(_collateralIndex));
    }

    // Transfers

    /**
     * @dev Transfers collateral from trader
     * @param _collateralIndex index of the collateral
     * @param _from sending address
     * @param _amountCollateral amount of collateral to receive (collateral precision)
     */
    function transferCollateralFrom(uint8 _collateralIndex, address _from, uint256 _amountCollateral) public {
        if (_amountCollateral > 0) {
            _getMultiCollatDiamond().getCollateral(_collateralIndex).collateral.transferFrom(
                _from,
                address(this),
                _amountCollateral
            );
        }
    }

    /**
     * @dev Transfers collateral to trader
     * @param _collateralIndex index of the collateral
     * @param _to receiving address
     * @param _amountCollateral amount of collateral to transfer (collateral precision)
     */
    function transferCollateralTo(uint8 _collateralIndex, address _to, uint256 _amountCollateral) internal {
        transferCollateralTo(_collateralIndex, _to, _amountCollateral, true);
    }

    /**
     * @dev Transfers collateral to trader
     * @param _collateralIndex index of the collateral
     * @param _to receiving address
     * @param _amountCollateral amount of collateral to transfer (collateral precision)
     * @param _unwrapNativeToken whether to try and unwrap native token before sending
     */
    function transferCollateralTo(
        uint8 _collateralIndex,
        address _to,
        uint256 _amountCollateral,
        bool _unwrapNativeToken
    ) internal {
        if (_amountCollateral > 0) {
            address collateral = _getMultiCollatDiamond().getCollateral(_collateralIndex).collateral;

            if (
                _unwrapNativeToken &&
                ChainUtils.isWrappedNativeToken(collateral) &&
                ChainConfigUtils.getNativeTransferEnabled()
            ) {
                collateral.unwrapAndTransferNative(
                    _to,
                    _amountCollateral,
                    uint256(ChainConfigUtils.getNativeTransferGasLimit())
                );
            } else {
                collateral.transfer(_to, _amountCollateral);
            }
        }
    }

    /**
     * @dev Transfers GNS to address
     * @param _to receiving address
     * @param _amountGns amount of GNS to transfer (1e18)
     */
    function transferGnsTo(address _to, uint256 _amountGns) internal {
        if (_amountGns > 0) {
            AddressStoreUtils.getAddresses().gns.transfer(_to, _amountGns);
        }
    }

    /**
     * @dev Transfers GNS from address
     * @param _from sending address
     * @param _amountGns amount of GNS to receive (1e18)
     */
    function transferGnsFrom(address _from, uint256 _amountGns) internal {
        if (_amountGns > 0) {
            AddressStoreUtils.getAddresses().gns.transferFrom(_from, address(this), _amountGns);
        }
    }

    /**
     * @dev Sends collateral to gToken vault for negative pnl
     * @param _collateralIndex collateral index
     * @param _amountCollateral amount of collateral to send to vault (collateral precision)
     * @param _trader trader address
     */
    function sendCollateralToVault(uint8 _collateralIndex, uint256 _amountCollateral, address _trader) public {
        getGToken(_collateralIndex).receiveAssets(_amountCollateral, _trader);
    }

    /**
     * @dev Handles pnl transfers when (fully or partially) closing a trade
     * @param _trade trade struct
     * @param _collateralSentToTrader total amount to send to trader (collateral precision)
     * @param _availableCollateralInDiamond part of _collateralSentToTrader available in diamond balance (collateral precision)
     */
    function handleTradePnl(
        ITradingStorage.Trade memory _trade,
        int256 _collateralSentToTrader,
        int256 _availableCollateralInDiamond,
        uint256 _borrowingFeeCollateral
    ) external returns (uint256 traderDebt) {
        if (_collateralSentToTrader > _availableCollateralInDiamond) {
            // Calculate amount to be received from gToken
            int256 collateralFromGToken = _collateralSentToTrader - _availableCollateralInDiamond;

            // Receive PNL from gToken; This is sent to the trader at a later point
            getGToken(_trade.collateralIndex).sendAssets(uint256(collateralFromGToken), address(this));

            if (_availableCollateralInDiamond < 0) {
                traderDebt = uint256(-_availableCollateralInDiamond);

                // When `_availableCollateralInDiamond` is negative, update `_collateralSentToTrader` value received from gToken
                _collateralSentToTrader = collateralFromGToken;
            }
        } else {
            // Send loss to gToken
            getGToken(_trade.collateralIndex).receiveAssets(
                uint256(_availableCollateralInDiamond - _collateralSentToTrader),
                _trade.user
            );

            // There is no collateral left
            if (_collateralSentToTrader < 0) {
                traderDebt = uint256(-_collateralSentToTrader);
            }
        }

        // Send collateral to trader, if any
        if (_collateralSentToTrader > 0) {
            transferCollateralTo(_trade.collateralIndex, _trade.user, uint256(_collateralSentToTrader));
        }

        emit ITradingCallbacksUtils.BorrowingFeeCharged(
            _trade.user,
            _trade.index,
            _trade.collateralIndex,
            _borrowingFeeCollateral
        );
    }

    // Fees

    /**
     * @dev Updates a trader's fee tiers points based on his trade size
     * @param _collateralIndex collateral index
     * @param _trader address of trader
     * @param _positionSizeCollateral position size in collateral tokens (collateral precision)
     * @param _pairIndex index of pair
     */
    function updateFeeTierPoints(
        uint8 _collateralIndex,
        address _trader,
        uint256 _pairIndex,
        uint256 _positionSizeCollateral
    ) public {
        uint256 usdNormalizedPositionSize = _getMultiCollatDiamond().getUsdNormalizedValue(
            _collateralIndex,
            _positionSizeCollateral
        );
        _getMultiCollatDiamond().updateTraderPoints(_trader, usdNormalizedPositionSize, _pairIndex);
    }

    /**
     * @dev Distributes fee to gToken vault
     * @param _collateralIndex index of collateral
     * @param _trader address of trader
     * @param _valueCollateral fee in collateral tokens (collateral precision)
     */
    function distributeVaultFeeCollateral(uint8 _collateralIndex, address _trader, uint256 _valueCollateral) public {
        getGToken(_collateralIndex).distributeReward(_valueCollateral);
        emit ITradingCommonUtils.GTokenFeeCharged(_trader, _collateralIndex, _valueCollateral);
    }

    /**
     * @dev Distributes gov fees exact amount
     * @param _collateralIndex index of collateral
     * @param _trader address of trader
     * @param _govFeeCollateral position size in collateral tokens (collateral precision)
     */
    function distributeExactGovFeeCollateral(
        uint8 _collateralIndex,
        address _trader,
        uint256 _govFeeCollateral
    ) public {
        TradingCallbacksUtils._getStorage().pendingGovFees[_collateralIndex] += _govFeeCollateral;
        emit ITradingCommonUtils.GovFeeCharged(_trader, _collateralIndex, _govFeeCollateral);
    }

    /**
     * @dev Increases OTC balance to be distributed once OTC is executed
     * @param _collateralIndex collateral index
     * @param _trader trader address
     * @param _amountCollateral amount of collateral tokens to distribute (collateral precision)
     */
    function distributeGnsOtcFeeCollateral(uint8 _collateralIndex, address _trader, uint256 _amountCollateral) public {
        _getMultiCollatDiamond().addOtcCollateralBalance(_collateralIndex, _amountCollateral);
        emit ITradingCommonUtils.GnsOtcFeeCharged(_trader, _collateralIndex, _amountCollateral);
    }

    /**
     * @dev Distributes trigger fee in GNS tokens
     * @param _trader address of trader
     * @param _collateralIndex index of collateral
     * @param _triggerFeeCollateral trigger fee in collateral tokens (collateral precision)
     * @param _gnsPriceCollateral gns/collateral price (1e10 precision)
     * @param _collateralPrecisionDelta collateral precision delta (10^18/10^decimals)
     */
    function distributeTriggerFeeGns(
        address _trader,
        uint8 _collateralIndex,
        uint256 _triggerFeeCollateral,
        uint256 _gnsPriceCollateral,
        uint128 _collateralPrecisionDelta
    ) public {
        sendCollateralToVault(_collateralIndex, _triggerFeeCollateral, _trader);

        uint256 triggerFeeGns = convertCollateralToGns(
            _triggerFeeCollateral,
            _collateralPrecisionDelta,
            _gnsPriceCollateral
        );
        _getMultiCollatDiamond().distributeTriggerReward(triggerFeeGns);

        emit ITradingCommonUtils.TriggerFeeCharged(_trader, _collateralIndex, _triggerFeeCollateral);
    }

    /**
     * @dev Distributes opening fees for trade and returns the trade fees charged in collateral tokens
     * @param _trade trade struct
     * @param _positionSizeCollateral position size in collateral tokens (collateral precision)
     * @param _orderType trade order type
     */
    function processFees(
        ITradingStorage.Trade memory _trade,
        uint256 _positionSizeCollateral,
        ITradingStorage.PendingOrderType _orderType
    ) external returns (uint256) {
        uint128 collateralPrecisionDelta = _getMultiCollatDiamond()
            .getCollateral(_trade.collateralIndex)
            .precisionDelta;
        uint256 gnsPriceCollateral = _getMultiCollatDiamond().getGnsPriceCollateralIndex(_trade.collateralIndex);

        // 1. Before charging any fee, re-calculate current trader fee tier cache
        updateFeeTierPoints(_trade.collateralIndex, _trade.user, _trade.pairIndex, _positionSizeCollateral);

        // 2. Calculate all fees
        IPairsStorage.TradeFees memory tradeFees = getTradeFeesCollateral(
            _trade.collateralIndex,
            _trade.user,
            _trade.pairIndex,
            _trade.collateralAmount,
            _positionSizeCollateral,
            _orderType
        );

        // 3. Distribute fees only if trade collateral is enough to pay (due to min fee)
        if (_trade.collateralAmount >= tradeFees.totalFeeCollateral) {
            // 3.1 Distribute referral fee
            if (tradeFees.referralFeeCollateral > 0) {
                distributeReferralFeeCollateral(
                    _trade.collateralIndex,
                    _trade.user,
                    _positionSizeCollateral,
                    tradeFees.referralFeeCollateral,
                    gnsPriceCollateral
                );
            }

            // 3.2 Distribute gov fee
            distributeExactGovFeeCollateral(_trade.collateralIndex, _trade.user, tradeFees.govFeeCollateral);

            // 3.3 Distribute trigger fee
            if (tradeFees.triggerOrderFeeCollateral > 0) {
                distributeTriggerFeeGns(
                    _trade.user,
                    _trade.collateralIndex,
                    tradeFees.triggerOrderFeeCollateral,
                    gnsPriceCollateral,
                    collateralPrecisionDelta
                );
            }

            // 3.4 Distribute GNS OTC fee
            distributeGnsOtcFeeCollateral(_trade.collateralIndex, _trade.user, tradeFees.gnsOtcFeeCollateral);

            // 3.5 Distribute GToken fees
            distributeVaultFeeCollateral(_trade.collateralIndex, _trade.user, tradeFees.gTokenFeeCollateral);
        }

        return tradeFees.totalFeeCollateral;
    }

    /**
     * @dev Distributes referral rewards and returns the amount charged in collateral tokens
     * @param _collateralIndex collateral index
     * @param _trader address of trader
     * @param _positionSizeCollateral position size in collateral tokens (collateral precision)
     * @param _referralFeeCollateral referral fee in collateral tokens (collateral precision)
     * @param _gnsPriceCollateral gns/collateral price (1e10 precision)
     */
    function distributeReferralFeeCollateral(
        uint8 _collateralIndex,
        address _trader,
        uint256 _positionSizeCollateral,
        uint256 _referralFeeCollateral,
        uint256 _gnsPriceCollateral
    ) public {
        _getMultiCollatDiamond().distributeReferralReward(
            _trader,
            _getMultiCollatDiamond().getUsdNormalizedValue(_collateralIndex, _positionSizeCollateral),
            _getMultiCollatDiamond().getUsdNormalizedValue(_collateralIndex, _referralFeeCollateral),
            _getMultiCollatDiamond().getGnsPriceUsd(_collateralIndex, _gnsPriceCollateral)
        );

        sendCollateralToVault(_collateralIndex, _referralFeeCollateral, _trader);

        emit ITradingCommonUtils.ReferralFeeCharged(_trader, _collateralIndex, _referralFeeCollateral);
    }

    // Open interests

    /**
     * @dev Update protocol open interest (any amount)
     * @dev CAREFUL: this will reset the trade's borrowing fees to 0 when _open = true
     * @param _trade trade struct
     * @param _positionSizeCollateral position size in collateral tokens (collateral precision)
     * @param _open whether it corresponds to a trade opening or closing
     * @param _isPnlPositive whether it corresponds to a positive pnl trade (only relevant when _open = false)
     */
    function updateOi(
        ITradingStorage.Trade memory _trade,
        uint256 _positionSizeCollateral,
        bool _open,
        bool _isPnlPositive
    ) public {
        _getMultiCollatDiamond().handleTradeBorrowingCallback(
            _trade.collateralIndex,
            _trade.user,
            _trade.pairIndex,
            _trade.index,
            _positionSizeCollateral,
            _open,
            _trade.long
        );
        _getMultiCollatDiamond().addPriceImpactOpenInterest(
            _trade.user,
            _trade.index,
            _positionSizeCollateral,
            _open,
            _isPnlPositive
        );
    }

    /**
     * @dev Update protocol open interest (trade position size)
     * @dev CAREFUL: this will reset the trade's borrowing fees to 0 when _open = true
     * @param _trade trade struct
     * @param _open whether it corresponds to a trade opening or closing
     * @param _isPnlPositive whether it corresponds to a positive pnl trade (only relevant when _open = false)
     */
    function updateOiTrade(ITradingStorage.Trade memory _trade, bool _open, bool _isPnlPositive) external {
        updateOi(_trade, getPositionSizeCollateral(_trade.collateralAmount, _trade.leverage), _open, _isPnlPositive);
    }

    /**
     * @dev Handles OI delta for an existing trade (for trade updates)
     * @param _trade trade struct
     * @param _newPositionSizeCollateral new position size in collateral tokens (collateral precision)
     * @param _isPnlPositive whether it corresponds to a positive pnl trade (only relevant when closing)
     */
    function handleOiDelta(
        ITradingStorage.Trade memory _trade,
        uint256 _newPositionSizeCollateral,
        bool _isPnlPositive
    ) external {
        uint256 existingPositionSizeCollateral = getPositionSizeCollateral(_trade.collateralAmount, _trade.leverage);

        if (_newPositionSizeCollateral > existingPositionSizeCollateral) {
            updateOi(_trade, _newPositionSizeCollateral - existingPositionSizeCollateral, true, _isPnlPositive);
        } else if (_newPositionSizeCollateral < existingPositionSizeCollateral) {
            updateOi(_trade, existingPositionSizeCollateral - _newPositionSizeCollateral, false, _isPnlPositive);
        }
    }

    /**
     * @dev Returns current address as multi-collateral diamond interface to call other facets functions.
     */
    function _getMultiCollatDiamond() internal view returns (IGNSMultiCollatDiamond) {
        return IGNSMultiCollatDiamond(address(this));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../../interfaces/IGNSMultiCollatDiamond.sol";

import "../TradingCommonUtils.sol";

/**
 *
 * @dev This is an external library for leverage update lifecycles
 * @dev Used by GNSTrading and GNSTradingCallbacks facets
 */
library UpdateLeverageLifecycles {
    /**
     * @dev Initiate update leverage order, done in 2 steps because need to cancel if liquidation price reached
     * @param _input request decrease leverage input
     * @param _isNative whether this request is using native tokens
     * @param _nativeBalance amount of native tokens available. Always 0 when `_isNative` is false, and >0 when true.
     */
    function requestUpdateLeverage(
        IUpdateLeverageUtils.UpdateLeverageInput memory _input,
        bool _isNative,
        uint120 _nativeBalance
    ) external returns (uint120) {
        // 1. Request validation
        (ITradingStorage.Trade memory trade, bool isIncrease, uint256 collateralDelta) = _validateRequest(_input);

        // 2. If decrease leverage, transfer collateral delta to diamond
        if (!isIncrease) {
            // If it's native, we have already transferred the balance
            if (_isNative) {
                // If the request uses more balance (`collateralDelta`) than is available (`_nativeBalance`) then revert
                if (collateralDelta > _nativeBalance) revert ITradingInteractionsUtils.InsufficientCollateral();

                // Deduct `collateralDelta` from native collateral
                _nativeBalance -= uint120(collateralDelta);
            } else {
                // Transfer in collateral
                TradingCommonUtils.transferCollateralFrom(trade.collateralIndex, trade.user, collateralDelta);
            }
        }

        // 3. Create pending order and make price aggregator request
        ITradingStorage.Id memory orderId = _initiateRequest(trade, _input.newLeverage, collateralDelta);

        emit IUpdateLeverageUtils.LeverageUpdateInitiated(
            orderId,
            _input.user,
            trade.pairIndex,
            _input.index,
            isIncrease,
            _input.newLeverage
        );

        // Return the amount of native collateral remaining
        return _nativeBalance;
    }

    /**
     * @dev Execute update leverage callback
     * @param _order pending order struct
     * @param _answer price aggregator request answer
     */
    function executeUpdateLeverage(
        ITradingStorage.PendingOrder memory _order,
        ITradingCallbacks.AggregatorAnswer memory _answer
    ) external {
        // 1. Prepare values
        ITradingStorage.Trade memory pendingTrade = _order.trade;
        ITradingStorage.Trade memory existingTrade = _getMultiCollatDiamond().getTrade(
            pendingTrade.user,
            pendingTrade.index
        );
        bool isIncrease = pendingTrade.leverage > existingTrade.leverage;

        // 2. Refresh trader fee tier cache
        TradingCommonUtils.updateFeeTierPoints(
            existingTrade.collateralIndex,
            existingTrade.user,
            existingTrade.pairIndex,
            0
        );

        // 3. Prepare useful values
        IUpdateLeverageUtils.UpdateLeverageValues memory values = _prepareCallbackValues(
            existingTrade,
            pendingTrade,
            isIncrease
        );

        // 4. Callback validation
        ITradingCallbacks.CancelReason cancelReason = _validateCallback(existingTrade, values, _answer);

        // 5. Handle callback (update trade in storage, remove gov fee OI, handle collateral delta transfers)
        _handleCallback(existingTrade, pendingTrade, values, cancelReason, isIncrease);

        // 6. Close pending update leverage order
        _getMultiCollatDiamond().closePendingOrder(_answer.orderId);

        emit IUpdateLeverageUtils.LeverageUpdateExecuted(
            _answer.orderId,
            isIncrease,
            cancelReason,
            existingTrade.collateralIndex,
            existingTrade.user,
            existingTrade.pairIndex,
            existingTrade.index,
            _answer.price,
            pendingTrade.collateralAmount,
            values
        );
    }

    /**
     * @dev Returns current address as multi-collateral diamond interface to call other facets functions.
     */
    function _getMultiCollatDiamond() internal view returns (IGNSMultiCollatDiamond) {
        return IGNSMultiCollatDiamond(address(this));
    }

    /**
     * @dev Returns new trade collateral amount based on new leverage (collateral precision)
     * @param _existingCollateralAmount existing trade collateral amount (collateral precision)
     * @param _existingLeverage existing trade leverage (1e3)
     * @param _newLeverage new trade leverage (1e3)
     */
    function _getNewCollateralAmount(
        uint256 _existingCollateralAmount,
        uint256 _existingLeverage,
        uint256 _newLeverage
    ) internal pure returns (uint120) {
        return uint120((_existingCollateralAmount * _existingLeverage) / _newLeverage);
    }

    /**
     * @dev Fetches trade, does validation for update leverage request, and returns useful data
     * @param _input request input struct
     */
    function _validateRequest(
        IUpdateLeverageUtils.UpdateLeverageInput memory _input
    ) internal view returns (ITradingStorage.Trade memory trade, bool isIncrease, uint256 collateralDelta) {
        trade = _getMultiCollatDiamond().getTrade(_input.user, _input.index);
        isIncrease = _input.newLeverage > trade.leverage;

        // 1. Check trade exists
        if (!trade.isOpen) revert IGeneralErrors.DoesntExist();

        // 2. Revert if any market order (market close, increase leverage, partial open, partial close) already exists for trade
        TradingCommonUtils.revertIfTradeHasPendingMarketOrder(_input.user, _input.index);

        // 3. Revert if collateral not active
        if (!_getMultiCollatDiamond().isCollateralActive(trade.collateralIndex))
            revert IGeneralErrors.InvalidCollateralIndex();

        // 4. Validate leverage update
        if (
            _input.newLeverage == trade.leverage ||
            (
                isIncrease
                    ? _input.newLeverage > _getMultiCollatDiamond().pairMaxLeverage(trade.pairIndex)
                    : _input.newLeverage < _getMultiCollatDiamond().pairMinLeverage(trade.pairIndex)
            )
        ) revert ITradingInteractionsUtils.WrongLeverage();

        // 5. Check trade remaining collateral is enough to pay gov fee
        uint256 govFeeCollateral = TradingCommonUtils.getMinGovFeeCollateral(
            trade.collateralIndex,
            trade.user,
            trade.pairIndex
        );
        uint256 newCollateralAmount = _getNewCollateralAmount(
            trade.collateralAmount,
            trade.leverage,
            _input.newLeverage
        );
        collateralDelta = isIncrease
            ? trade.collateralAmount - newCollateralAmount
            : newCollateralAmount - trade.collateralAmount;

        if (newCollateralAmount <= govFeeCollateral) revert ITradingInteractionsUtils.InsufficientCollateral();
    }

    /**
     * @dev Stores pending update leverage order and makes price aggregator request
     * @param _trade trade struct
     * @param _newLeverage new leverage (1e3)
     * @param _collateralDelta trade collateral delta (collateral precision)
     */
    function _initiateRequest(
        ITradingStorage.Trade memory _trade,
        uint24 _newLeverage,
        uint256 _collateralDelta
    ) internal returns (ITradingStorage.Id memory orderId) {
        // 1. Store pending order
        ITradingStorage.PendingOrder memory pendingOrder;
        {
            ITradingStorage.Trade memory pendingOrderTrade;
            pendingOrderTrade.user = _trade.user;
            pendingOrderTrade.index = _trade.index;
            pendingOrderTrade.leverage = _newLeverage;
            pendingOrderTrade.collateralAmount = uint120(_collateralDelta);

            pendingOrder.trade = pendingOrderTrade;
            pendingOrder.user = _trade.user;
            pendingOrder.orderType = ITradingStorage.PendingOrderType.UPDATE_LEVERAGE;
        }

        pendingOrder = _getMultiCollatDiamond().storePendingOrder(pendingOrder);
        orderId = ITradingStorage.Id(pendingOrder.user, pendingOrder.index);

        // 2. Request price
        _getMultiCollatDiamond().getPrice(
            _trade.collateralIndex,
            _trade.pairIndex,
            ITradingStorage.Id(_trade.user, _trade.index),
            orderId,
            pendingOrder.orderType,
            TradingCommonUtils.getMinPositionSizeCollateral(_trade.collateralIndex, _trade.pairIndex) / 2,
            0
        );
    }

    /**
     * @dev Calculates values for callback
     * @param _existingTrade existing trade struct
     * @param _pendingTrade pending trade struct
     * @param _isIncrease true if increase leverage, false if decrease leverage
     */
    function _prepareCallbackValues(
        ITradingStorage.Trade memory _existingTrade,
        ITradingStorage.Trade memory _pendingTrade,
        bool _isIncrease
    ) internal view returns (IUpdateLeverageUtils.UpdateLeverageValues memory values) {
        if (_existingTrade.isOpen == false) return values;

        values.newLeverage = _pendingTrade.leverage;
        values.govFeeCollateral = TradingCommonUtils.getMinGovFeeCollateral(
            _existingTrade.collateralIndex,
            _existingTrade.user,
            _existingTrade.pairIndex
        );
        values.newCollateralAmount =
            (
                _isIncrease
                    ? _existingTrade.collateralAmount - _pendingTrade.collateralAmount
                    : _existingTrade.collateralAmount + _pendingTrade.collateralAmount
            ) -
            values.govFeeCollateral;
        values.liqPrice = _getMultiCollatDiamond().getTradeLiquidationPrice(
            IBorrowingFees.LiqPriceInput(
                _existingTrade.collateralIndex,
                _existingTrade.user,
                _existingTrade.pairIndex,
                _existingTrade.index,
                _existingTrade.openPrice,
                _existingTrade.long,
                _isIncrease ? values.newCollateralAmount : _existingTrade.collateralAmount,
                _isIncrease ? values.newLeverage : _existingTrade.leverage,
                true,
                _getMultiCollatDiamond().getTradeLiquidationParams(_existingTrade.user, _existingTrade.index)
            )
        ); // for increase leverage we calculate new trade liquidation price and for decrease leverage we calculate existing trade liquidation price
    }

    /**
     * @dev Validates callback, and returns corresponding cancel reason
     * @param _existingTrade existing trade struct
     * @param _values pre-calculated useful values
     * @param _answer price aggregator answer
     */
    function _validateCallback(
        ITradingStorage.Trade memory _existingTrade,
        IUpdateLeverage.UpdateLeverageValues memory _values,
        ITradingCallbacks.AggregatorAnswer memory _answer
    ) internal view returns (ITradingCallbacks.CancelReason) {
        // prettier-ignore
        return
            !_existingTrade.isOpen ? ITradingCallbacks.CancelReason.NO_TRADE : _answer.price == 0
                ? ITradingCallbacks.CancelReason.MARKET_CLOSED
                : (_existingTrade.long ? _answer.price <= _values.liqPrice : _answer.price >= _values.liqPrice)
                ? ITradingCallbacks.CancelReason.LIQ_REACHED
                : _values.newLeverage > _getMultiCollatDiamond().pairMaxLeverage(_existingTrade.pairIndex)
                ? ITradingCallbacks.CancelReason.MAX_LEVERAGE
                : ITradingCallbacks.CancelReason.NONE;
    }

    /**
     * @dev Handles trade update, removes gov fee OI, and transfers collateral delta (for both successful and failed requests)
     * @param _trade trade struct
     * @param _pendingTrade pending trade struct
     * @param _values pre-calculated useful values
     * @param _cancelReason cancel reason
     * @param _isIncrease true if increase leverage, false if decrease leverage
     */
    function _handleCallback(
        ITradingStorage.Trade memory _trade,
        ITradingStorage.Trade memory _pendingTrade,
        IUpdateLeverageUtils.UpdateLeverageValues memory _values,
        ITradingCallbacks.CancelReason _cancelReason,
        bool _isIncrease
    ) internal {
        // 1. If trade exists, distribute gov fee
        bool tradeExists = _cancelReason != ITradingCallbacks.CancelReason.NO_TRADE;
        if (tradeExists) {
            TradingCommonUtils.distributeExactGovFeeCollateral(
                _trade.collateralIndex,
                _trade.user,
                _values.govFeeCollateral
            );
        }

        // 2. Request successful
        if (_cancelReason == ITradingCallbacks.CancelReason.NONE) {
            // 2.1 Update trade collateral (- gov fee) and leverage, openPrice stays the same
            _getMultiCollatDiamond().updateTradePosition(
                ITradingStorage.Id(_trade.user, _trade.index),
                uint120(_values.newCollateralAmount),
                uint24(_values.newLeverage),
                _trade.openPrice,
                false,
                false
            );

            // 2.2 If leverage increase, transfer collateral delta to trader
            if (_isIncrease)
                TradingCommonUtils.transferCollateralTo(
                    _trade.collateralIndex,
                    _trade.user,
                    _pendingTrade.collateralAmount
                );
        } else {
            // 3. Request canceled

            // 3.1 Remove gov fee from trade collateral (if trade exists)
            if (tradeExists) {
                _getMultiCollatDiamond().updateTradeCollateralAmount(
                    ITradingStorage.Id(_trade.user, _trade.index),
                    _trade.collateralAmount - uint120(_values.govFeeCollateral)
                );
            }

            // 3.2 If leverage decrease, send back collateral delta to trader
            if (!_isIncrease) {
                TradingCommonUtils.transferCollateralTo(
                    _trade.collateralIndex,
                    _trade.user,
                    _pendingTrade.collateralAmount
                );
            }
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../../interfaces/IGNSMultiCollatDiamond.sol";
import "../../interfaces/IERC20.sol";

import "../ConstantsUtils.sol";
import "../TradingCommonUtils.sol";

/**
 *
 * @dev This is an internal utils library for position size decreases
 * @dev Used by UpdatePositionSizeLifecycles internal library
 */
library DecreasePositionSizeUtils {
    /**
     * @dev Validates decrease position size request
     *
     * @dev Possible inputs: collateral delta > 0 and leverage delta = 0 (decrease collateral by collateral delta)
     *                       collateral delta = 0 and leverage delta > 0 (decrease leverage by leverage delta)
     *
     *  @param _trade trade of request
     *  @param _input input values
     */
    function validateRequest(
        ITradingStorage.Trade memory _trade,
        IUpdatePositionSizeUtils.DecreasePositionSizeInput memory _input
    ) internal view returns (uint256 positionSizeCollateralDelta) {
        // 1. Revert if both collateral and leverage are zero or if both are non-zero
        if (
            (_input.collateralDelta == 0 && _input.leverageDelta == 0) ||
            (_input.collateralDelta > 0 && _input.leverageDelta > 0)
        ) revert IUpdatePositionSizeUtils.InvalidDecreasePositionSizeInput();

        // 2. If we update the leverage, check new leverage is above the minimum
        bool isLeverageUpdate = _input.leverageDelta > 0;
        if (
            isLeverageUpdate &&
            _trade.leverage - _input.leverageDelta < _getMultiCollatDiamond().pairMinLeverage(_trade.pairIndex)
        ) revert ITradingInteractionsUtils.WrongLeverage();

        // 3. Make sure new trade collateral is enough to pay borrowing fees and closing fees
        positionSizeCollateralDelta = TradingCommonUtils.getPositionSizeCollateral(
            isLeverageUpdate ? _trade.collateralAmount : _input.collateralDelta,
            isLeverageUpdate ? _input.leverageDelta : _trade.leverage
        );

        uint256 newCollateralAmount = _trade.collateralAmount - _input.collateralDelta;
        uint256 borrowingFeeCollateral = TradingCommonUtils.getTradeBorrowingFeeCollateral(_trade);
        uint256 closingFeesCollateral = TradingCommonUtils.getTotalTradeFeesCollateral(
            _trade.collateralIndex,
            _trade.user,
            _trade.pairIndex,
            positionSizeCollateralDelta
        );

        if (newCollateralAmount <= borrowingFeeCollateral + closingFeesCollateral)
            revert ITradingInteractionsUtils.InsufficientCollateral();

        // 4. Revert if expected price is zero
        if (_input.expectedPrice == 0) revert IGeneralErrors.ZeroValue();
    }

    /**
     * @dev Calculates values for callback
     * @param _existingTrade existing trade data
     * @param _partialTrade partial trade data
     * @param _answer price aggregator answer
     */
    function prepareCallbackValues(
        ITradingStorage.Trade memory _existingTrade,
        ITradingStorage.Trade memory _partialTrade,
        ITradingCallbacks.AggregatorAnswer memory _answer
    ) internal view returns (IUpdatePositionSizeUtils.DecreasePositionSizeValues memory values) {
        // 1. Calculate position size delta and existing position size
        bool isLeverageUpdate = _partialTrade.leverage > 0;
        values.positionSizeCollateralDelta = TradingCommonUtils.getPositionSizeCollateral(
            isLeverageUpdate ? _existingTrade.collateralAmount : _partialTrade.collateralAmount,
            isLeverageUpdate ? _partialTrade.leverage : _existingTrade.leverage
        );
        values.existingPositionSizeCollateral = TradingCommonUtils.getPositionSizeCollateral(
            _existingTrade.collateralAmount,
            _existingTrade.leverage
        );

        // 2. Calculate existing trade liquidation price
        values.existingLiqPrice = TradingCommonUtils.getTradeLiquidationPrice(_existingTrade, true);

        // 2.1 Apply spread and price impact to answer.price
        (, values.priceAfterImpact, ) = TradingCommonUtils.getTradeClosingPriceImpact(
            ITradingCommonUtils.TradePriceImpactInput(
                _existingTrade,
                _answer.price,
                _answer.spreadP,
                values.positionSizeCollateralDelta
            )
        );

        // 3. Calculate existing trade pnl
        values.existingPnlCollateral =
            (TradingCommonUtils.getPnlPercent(
                _existingTrade.openPrice,
                uint64(values.priceAfterImpact),
                _existingTrade.long,
                _existingTrade.leverage
            ) * int256(uint256(_existingTrade.collateralAmount))) /
            100 /
            int256(ConstantsUtils.P_10);

        // 4. Calculate existing trade borrowing fee
        values.borrowingFeeCollateral = TradingCommonUtils.getTradeBorrowingFeeCollateral(_existingTrade);

        // 5. Calculate partial trade closing fees
        values.closingFeeCollateral = TradingCommonUtils.getTotalTradeFeesCollateral(
            _existingTrade.collateralIndex,
            _existingTrade.user,
            _existingTrade.pairIndex,
            values.positionSizeCollateralDelta
        );

        // 5. Calculate final collateral delta
        // Collateral delta = value to send to trader after position size is decreased
        int256 partialTradePnlCollateral = (values.existingPnlCollateral * int256(values.positionSizeCollateralDelta)) /
            int256(values.existingPositionSizeCollateral);

        values.availableCollateralInDiamond =
            int256(uint256(_partialTrade.collateralAmount)) -
            int256(values.closingFeeCollateral);

        values.collateralSentToTrader =
            values.availableCollateralInDiamond +
            partialTradePnlCollateral -
            int256(values.borrowingFeeCollateral);

        // 7. Calculate new collateral amount and leverage
        values.newCollateralAmount = _existingTrade.collateralAmount - _partialTrade.collateralAmount;
        values.newLeverage = _existingTrade.leverage - _partialTrade.leverage;
    }

    /**
     * @dev Validates callback, and returns corresponding cancel reason
     * @param _values pre-calculated useful values
     */
    function validateCallback(
        ITradingStorage.Trade memory _existingTrade,
        ITradingStorage.PendingOrder memory _pendingOrder,
        IUpdatePositionSizeUtils.DecreasePositionSizeValues memory _values,
        ITradingCallbacks.AggregatorAnswer memory _answer
    ) internal view returns (ITradingCallbacks.CancelReason) {
        uint256 expectedPrice = _pendingOrder.trade.openPrice;
        uint256 maxSlippageP = _getMultiCollatDiamond()
            .getTradeInfo(_existingTrade.user, _existingTrade.index)
            .maxSlippageP;
        uint256 maxSlippage = (expectedPrice *
            (maxSlippageP > 0 ? maxSlippageP : ConstantsUtils.DEFAULT_MAX_CLOSING_SLIPPAGE_P)) /
            100 /
            1e3;

        return
            (
                _existingTrade.long
                    ? _answer.price <= _values.existingLiqPrice
                    : _answer.price >= _values.existingLiqPrice
            )
                ? ITradingCallbacks.CancelReason.LIQ_REACHED
                : (
                    _existingTrade.long
                        ? _values.priceAfterImpact < expectedPrice - maxSlippage
                        : _values.priceAfterImpact > expectedPrice + maxSlippage
                )
                ? ITradingCallbacks.CancelReason.SLIPPAGE
                : ITradingCallbacks.CancelReason.NONE;
    }

    /**
     * @dev Updates trade (for successful request)
     * @param _existingTrade existing trade data
     * @param _values pre-calculated useful values
     */
    function updateTradeSuccess(
        ITradingStorage.Trade memory _existingTrade,
        IUpdatePositionSizeUtils.DecreasePositionSizeValues memory _values
    ) internal {
        // 1. Handle collateral/pnl transfers
        uint256 traderDebt = TradingCommonUtils.handleTradePnl(
            _existingTrade,
            _values.collateralSentToTrader,
            _values.availableCollateralInDiamond,
            _values.borrowingFeeCollateral
        );
        _values.newCollateralAmount -= uint120(traderDebt); // eg. when fees > partial collateral

        // 2. Update trade in storage
        _getMultiCollatDiamond().updateTradePosition(
            ITradingStorage.Id(_existingTrade.user, _existingTrade.index),
            _values.newCollateralAmount,
            _values.newLeverage,
            _existingTrade.openPrice, // open price stays the same
            false, // don't refresh liquidation params
            _values.existingPnlCollateral > 0
        );

        // 3. Reset trade borrowing fee to zero
        _getMultiCollatDiamond().resetTradeBorrowingFees(
            _existingTrade.collateralIndex,
            _existingTrade.user,
            _existingTrade.pairIndex,
            _existingTrade.index,
            _existingTrade.long
        );
    }

    /**
     * @dev Handles callback canceled case (for failed request)
     * @param _existingTrade trade to update
     * @param _cancelReason cancel reason
     */
    function handleCanceled(
        ITradingStorage.Trade memory _existingTrade,
        ITradingCallbacks.CancelReason _cancelReason
    ) internal {
        if (_cancelReason != ITradingCallbacks.CancelReason.NO_TRADE) {
            // 1. Distribute gov fee
            uint256 govFeeCollateral = TradingCommonUtils.getMinGovFeeCollateral(
                _existingTrade.collateralIndex,
                _existingTrade.user,
                _existingTrade.pairIndex
            );
            TradingCommonUtils.distributeExactGovFeeCollateral(
                _existingTrade.collateralIndex,
                _existingTrade.user,
                govFeeCollateral
            );

            // 2. Charge gov fee to trade
            _getMultiCollatDiamond().updateTradeCollateralAmount(
                ITradingStorage.Id(_existingTrade.user, _existingTrade.index),
                _existingTrade.collateralAmount - uint120(govFeeCollateral)
            );
        }
    }

    /**
     * @dev Returns current address as multi-collateral diamond interface to call other facets functions.
     */
    function _getMultiCollatDiamond() internal view returns (IGNSMultiCollatDiamond) {
        return IGNSMultiCollatDiamond(address(this));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../../interfaces/IGNSMultiCollatDiamond.sol";
import "../../interfaces/IERC20.sol";

import "../ConstantsUtils.sol";
import "../TradingCommonUtils.sol";

/**
 *
 * @dev This is an internal utils library for position size increases
 * @dev Used by UpdatePositionSizeLifecycles internal library
 */
library IncreasePositionSizeUtils {
    /**
     * @dev Validates increase position request.
     *
     * @dev Possible inputs: collateral delta > 0 and leverage delta > 0 (increase position size by collateral delta * leverage delta)
     *                       collateral delta = 0 and leverage delta > 0 (increase trade leverage by leverage delta)
     *
     * @param _trade trade of request
     * @param _input input values
     */
    function validateRequest(
        ITradingStorage.Trade memory _trade,
        IUpdatePositionSizeUtils.IncreasePositionSizeInput memory _input
    ) internal view returns (uint256 positionSizeCollateralDelta) {
        // 1. Zero values checks
        if (_input.leverageDelta == 0 || _input.expectedPrice == 0 || _input.maxSlippageP == 0)
            revert IUpdatePositionSizeUtils.InvalidIncreasePositionSizeInput();

        // 2. Revert if new leverage is below min leverage or above max leverage
        bool isLeverageUpdate = _input.collateralDelta == 0;
        {
            uint24 leverageToValidate = isLeverageUpdate
                ? _trade.leverage + _input.leverageDelta
                : _input.leverageDelta;
            if (
                leverageToValidate > _getMultiCollatDiamond().pairMaxLeverage(_trade.pairIndex) ||
                leverageToValidate < _getMultiCollatDiamond().pairMinLeverage(_trade.pairIndex)
            ) revert ITradingInteractionsUtils.WrongLeverage();
        }

        // 3. Make sure new position size is bigger than existing one after paying borrowing and opening fees
        positionSizeCollateralDelta = TradingCommonUtils.getPositionSizeCollateral(
            isLeverageUpdate ? _trade.collateralAmount : _input.collateralDelta,
            _input.leverageDelta
        );
        uint256 existingPositionSizeCollateral = TradingCommonUtils.getPositionSizeCollateral(
            _trade.collateralAmount,
            _trade.leverage
        );
        uint256 newCollateralAmount = _trade.collateralAmount + _input.collateralDelta;
        uint256 newLeverage = isLeverageUpdate
            ? _trade.leverage + _input.leverageDelta
            : ((existingPositionSizeCollateral + positionSizeCollateralDelta) * 1e3) / newCollateralAmount;
        {
            uint256 borrowingFeeCollateral = TradingCommonUtils.getTradeBorrowingFeeCollateral(_trade);
            uint256 openingFeesCollateral = TradingCommonUtils.getTotalTradeFeesCollateral(
                _trade.collateralIndex,
                _trade.user,
                _trade.pairIndex,
                positionSizeCollateralDelta
            );

            uint256 newPositionSizeCollateral = existingPositionSizeCollateral +
                positionSizeCollateralDelta -
                ((borrowingFeeCollateral + openingFeesCollateral) * newLeverage) /
                1e3;

            if (newPositionSizeCollateral <= existingPositionSizeCollateral)
                revert IUpdatePositionSizeUtils.NewPositionSizeSmaller();
        }

        // 4. Make sure trade stays within exposure limits
        if (
            !TradingCommonUtils.isWithinExposureLimits(
                _trade.collateralIndex,
                _trade.pairIndex,
                _trade.long,
                positionSizeCollateralDelta
            )
        ) revert ITradingInteractionsUtils.AboveExposureLimits();
    }

    /**
     * @dev Calculates values for callback
     * @param _existingTrade existing trade data
     * @param _partialTrade partial trade data
     * @param _answer price aggregator answer
     */
    function prepareCallbackValues(
        ITradingStorage.Trade memory _existingTrade,
        ITradingStorage.Trade memory _partialTrade,
        ITradingCallbacks.AggregatorAnswer memory _answer
    ) internal view returns (IUpdatePositionSizeUtils.IncreasePositionSizeValues memory values) {
        bool isLeverageUpdate = _partialTrade.collateralAmount == 0;

        // 1. Calculate position size values
        values.positionSizeCollateralDelta = TradingCommonUtils.getPositionSizeCollateral(
            isLeverageUpdate ? _existingTrade.collateralAmount : _partialTrade.collateralAmount,
            _partialTrade.leverage
        );
        values.existingPositionSizeCollateral = TradingCommonUtils.getPositionSizeCollateral(
            _existingTrade.collateralAmount,
            _existingTrade.leverage
        );
        values.newPositionSizeCollateral = values.existingPositionSizeCollateral + values.positionSizeCollateralDelta;

        // 2. Calculate new collateral amount and leverage
        values.newCollateralAmount = _existingTrade.collateralAmount + _partialTrade.collateralAmount;
        values.newLeverage = isLeverageUpdate
            ? _existingTrade.leverage + _partialTrade.leverage
            : (values.newPositionSizeCollateral * 1e3) / values.newCollateralAmount;

        // 3. Calculate price impact values
        (, values.priceAfterImpact) = TradingCommonUtils.getTradeOpeningPriceImpact(
            ITradingCommonUtils.TradePriceImpactInput(
                _existingTrade,
                _answer.price,
                _answer.spreadP,
                values.positionSizeCollateralDelta
            ),
            _getMultiCollatDiamond().getTradeInfo(_existingTrade.user, _existingTrade.index).contractsVersion
        );

        // 4. Calculate existing trade pnl
        values.existingPnlCollateral =
            (TradingCommonUtils.getPnlPercent(
                _existingTrade.openPrice,
                _answer.price,
                _existingTrade.long,
                _existingTrade.leverage
            ) * int256(uint256(_existingTrade.collateralAmount))) /
            100 /
            int256(ConstantsUtils.P_10);

        // 5. Calculate existing trade borrowing fee
        values.borrowingFeeCollateral = TradingCommonUtils.getTradeBorrowingFeeCollateral(_existingTrade);

        // 6. Calculate partial trade opening fees
        values.openingFeesCollateral = TradingCommonUtils.getTotalTradeFeesCollateral(
            _existingTrade.collateralIndex,
            _existingTrade.user,
            _existingTrade.pairIndex,
            values.positionSizeCollateralDelta
        );

        // 7. Charge opening fees and borrowing fees on new trade collateral amount
        values.newCollateralAmount -= values.borrowingFeeCollateral + values.openingFeesCollateral;

        // 8. Calculate new open price

        // existingPositionSizeCollateral + existingPnlCollateral can never be negative
        // Because minimum value for existingPnlCollateral is -100% of trade collateral
        // If trade is long, add pnl collateral to position size collateral
        if (_existingTrade.long) {
            values.oldPosSizePlusPnlCollateral = values.existingPnlCollateral < 0
                ? values.existingPositionSizeCollateral - uint256(values.existingPnlCollateral * -1)
                : values.existingPositionSizeCollateral + uint256(values.existingPnlCollateral);
        } else {
            // If trade is short, subtract pnl collateral from position size collateral
            values.oldPosSizePlusPnlCollateral = values.existingPnlCollateral < 0
                ? values.existingPositionSizeCollateral + uint256(values.existingPnlCollateral * -1)
                : values.existingPositionSizeCollateral - uint256(values.existingPnlCollateral);
        }

        values.newOpenPrice =
            (values.oldPosSizePlusPnlCollateral *
                uint256(_existingTrade.openPrice) +
                values.positionSizeCollateralDelta *
                values.priceAfterImpact) /
            (values.oldPosSizePlusPnlCollateral + values.positionSizeCollateralDelta);

        // 8. Calculate existing and new liq price
        values.existingLiqPrice = TradingCommonUtils.getTradeLiquidationPrice(_existingTrade, true);
        values.newLiqPrice = _getMultiCollatDiamond().getTradeLiquidationPrice(
            IBorrowingFees.LiqPriceInput(
                _existingTrade.collateralIndex,
                _existingTrade.user,
                _existingTrade.pairIndex,
                _existingTrade.index,
                uint64(values.newOpenPrice),
                _existingTrade.long,
                values.newCollateralAmount,
                values.newLeverage,
                false,
                _getMultiCollatDiamond().getPairLiquidationParams(_existingTrade.pairIndex) // new liquidation params
            )
        );
    }

    /**
     * @dev Validates callback, and returns corresponding cancel reason
     * @param _existingTrade existing trade data
     * @param _values pre-calculated useful values
     * @param _expectedPrice user expected price before callback (1e10)
     * @param _maxSlippageP maximum slippage percentage from expected price (1e3)
     */
    function validateCallback(
        ITradingStorage.Trade memory _existingTrade,
        IUpdatePositionSizeUtils.IncreasePositionSizeValues memory _values,
        ITradingCallbacks.AggregatorAnswer memory _answer,
        uint256 _expectedPrice,
        uint256 _maxSlippageP
    ) internal view returns (ITradingCallbacks.CancelReason cancelReason) {
        uint256 maxSlippage = (uint256(_expectedPrice) * _maxSlippageP) / 100 / 1e3;

        cancelReason = (
            _existingTrade.long
                ? _values.priceAfterImpact > _expectedPrice + maxSlippage
                : _values.priceAfterImpact < _expectedPrice - maxSlippage
        )
            ? ITradingCallbacks.CancelReason.SLIPPAGE // 1. Check price after impact is within slippage limits
            : _existingTrade.tp > 0 &&
                (_existingTrade.long ? _answer.price >= _existingTrade.tp : _answer.price <= _existingTrade.tp)
            ? ITradingCallbacks.CancelReason.TP_REACHED // 2. Check TP has not been reached
            : _existingTrade.sl > 0 &&
                (_existingTrade.long ? _answer.price <= _existingTrade.sl : _answer.price >= _existingTrade.sl)
            ? ITradingCallbacks.CancelReason.SL_REACHED // 3. Check SL has not been reached
            : (
                _existingTrade.long
                    ? (_answer.price <= _values.existingLiqPrice || _answer.price <= _values.newLiqPrice)
                    : (_answer.price >= _values.existingLiqPrice || _answer.price >= _values.newLiqPrice)
            )
            ? ITradingCallbacks.CancelReason.LIQ_REACHED // 4. Check current and new LIQ price not reached
            : !TradingCommonUtils.isWithinExposureLimits(
                _existingTrade.collateralIndex,
                _existingTrade.pairIndex,
                _existingTrade.long,
                _values.positionSizeCollateralDelta
            )
            ? ITradingCallbacks.CancelReason.EXPOSURE_LIMITS // 5. Check trade still within exposure limits
            : _values.newLeverage > _getMultiCollatDiamond().pairMaxLeverage(_existingTrade.pairIndex)
            ? ITradingCallbacks.CancelReason.MAX_LEVERAGE
            : ITradingCallbacks.CancelReason.NONE;
    }

    /**
     * @dev Updates trade (for successful request)
     * @param _existingTrade existing trade data
     * @param _values pre-calculated useful values
     */
    function updateTradeSuccess(
        ITradingStorage.Trade memory _existingTrade,
        IUpdatePositionSizeUtils.IncreasePositionSizeValues memory _values
    ) internal {
        // 1. Send borrowing fee to vault
        TradingCommonUtils.handleTradePnl(
            _existingTrade,
            0, // collateralSentToTrader = 0
            int256(_values.borrowingFeeCollateral),
            _values.borrowingFeeCollateral
        );

        // 2. Update trade in storage
        _getMultiCollatDiamond().updateTradePosition(
            ITradingStorage.Id(_existingTrade.user, _existingTrade.index),
            uint120(_values.newCollateralAmount),
            uint24(_values.newLeverage),
            uint64(_values.newOpenPrice),
            true, // refresh liquidation params
            false
        );

        // 3. Reset trade borrowing fees to zero
        _getMultiCollatDiamond().resetTradeBorrowingFees(
            _existingTrade.collateralIndex,
            _existingTrade.user,
            _existingTrade.pairIndex,
            _existingTrade.index,
            _existingTrade.long
        );
    }

    /**
     * @dev Handles callback canceled case (for failed request)
     * @param _existingTrade existing trade data
     * @param _partialTrade partial trade data
     * @param _cancelReason cancel reason
     */
    function handleCanceled(
        ITradingStorage.Trade memory _existingTrade,
        ITradingStorage.Trade memory _partialTrade,
        ITradingCallbacks.CancelReason _cancelReason
    ) internal {
        // 1. Charge gov fee on trade (if trade exists)
        if (_cancelReason != ITradingCallbacks.CancelReason.NO_TRADE) {
            // 1.1 Distribute gov fee
            uint256 govFeeCollateral = TradingCommonUtils.getMinGovFeeCollateral(
                _existingTrade.collateralIndex,
                _existingTrade.user,
                _existingTrade.pairIndex
            );
            TradingCommonUtils.distributeExactGovFeeCollateral(
                _existingTrade.collateralIndex,
                _existingTrade.user,
                govFeeCollateral
            );

            // 1.3 Charge gov fee to trade
            _getMultiCollatDiamond().updateTradeCollateralAmount(
                ITradingStorage.Id(_existingTrade.user, _existingTrade.index),
                _existingTrade.collateralAmount - uint120(govFeeCollateral)
            );
        }

        // 2. Send back partial collateral to trader
        TradingCommonUtils.transferCollateralTo(
            _existingTrade.collateralIndex,
            _existingTrade.user,
            _partialTrade.collateralAmount
        );
    }

    /**
     * @dev Returns current address as multi-collateral diamond interface to call other facets functions.
     */
    function _getMultiCollatDiamond() internal view returns (IGNSMultiCollatDiamond) {
        return IGNSMultiCollatDiamond(address(this));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "../../interfaces/IGNSMultiCollatDiamond.sol";

import "./IncreasePositionSizeUtils.sol";
import "./DecreasePositionSizeUtils.sol";

import "../ChainUtils.sol";
import "../ConstantsUtils.sol";
import "../TradingCommonUtils.sol";

/**
 *
 * @dev This is an external library for position size updates lifecycles
 * @dev Used by GNSTrading and GNSTradingCallbacks facets
 */
library UpdatePositionSizeLifecycles {
    /**
     * @dev Initiate increase position size order, done in 2 steps because position size changes
     * @param _input request increase position size input struct
     */
    function requestIncreasePositionSize(
        IUpdatePositionSizeUtils.IncreasePositionSizeInput memory _input,
        bool _isNative
    ) external {
        // 1. Base validation
        ITradingStorage.Trade memory trade = _baseValidateRequest(_input.user, _input.index);

        // 2. Increase position size validation
        uint256 positionSizeCollateralDelta = IncreasePositionSizeUtils.validateRequest(trade, _input);

        // 3. Transfer collateral delta from trader to diamond contract (nothing transferred for leverage update)
        // When `_isNative` is true, then payment has already been transferred in as native tokens and wrapped
        if (!_isNative)
            TradingCommonUtils.transferCollateralFrom(trade.collateralIndex, _input.user, _input.collateralDelta);

        // 4. Create pending order and make price aggregator request
        ITradingStorage.Id memory orderId = _initiateRequest(
            trade,
            true,
            _input.collateralDelta,
            _input.leverageDelta,
            positionSizeCollateralDelta,
            _input.expectedPrice,
            _input.maxSlippageP
        );

        emit IUpdatePositionSizeUtils.PositionSizeUpdateInitiated(
            orderId,
            trade.user,
            trade.pairIndex,
            trade.index,
            true,
            _input.collateralDelta,
            _input.leverageDelta
        );
    }

    /**
     * @dev Initiate decrease position size order, done in 2 steps because position size changes
     * @param _input request decrease position size input struct
     */
    function requestDecreasePositionSize(IUpdatePositionSizeUtils.DecreasePositionSizeInput memory _input) external {
        // 1. Base validation
        ITradingStorage.Trade memory trade = _baseValidateRequest(_input.user, _input.index);

        // 2. Decrease position size validation
        uint256 positionSizeCollateralDelta = DecreasePositionSizeUtils.validateRequest(trade, _input);

        // 3. Store pending order and make price aggregator request
        ITradingStorage.Id memory orderId = _initiateRequest(
            trade,
            false,
            _input.collateralDelta,
            _input.leverageDelta,
            positionSizeCollateralDelta,
            _input.expectedPrice,
            0
        );

        emit IUpdatePositionSizeUtils.PositionSizeUpdateInitiated(
            orderId,
            trade.user,
            trade.pairIndex,
            trade.index,
            false,
            _input.collateralDelta,
            _input.leverageDelta
        );
    }

    /**
     * @dev Execute increase position size market callback
     * @param _order corresponding pending order
     * @param _answer price aggregator answer
     */
    function executeIncreasePositionSizeMarket(
        ITradingStorage.PendingOrder memory _order,
        ITradingCallbacks.AggregatorAnswer memory _answer
    ) external {
        // 1. Prepare vars
        ITradingStorage.Trade memory partialTrade = _order.trade;
        ITradingStorage.Trade memory existingTrade = _getMultiCollatDiamond().getTrade(
            partialTrade.user,
            partialTrade.index
        );
        IUpdatePositionSizeUtils.IncreasePositionSizeValues memory values;

        // 2. Refresh trader fee tier cache
        TradingCommonUtils.updateFeeTierPoints(
            existingTrade.collateralIndex,
            existingTrade.user,
            existingTrade.pairIndex,
            0
        );

        // 3. Base validation (trade open, market open)
        ITradingCallbacks.CancelReason cancelReason = _validateBaseFulfillment(existingTrade, _answer);

        // 4. If passes base validation, validate further
        if (cancelReason == ITradingCallbacks.CancelReason.NONE) {
            // 4.1 Prepare useful values (position size delta, pnl, fees, new open price, etc.)
            values = IncreasePositionSizeUtils.prepareCallbackValues(existingTrade, partialTrade, _answer);

            // 4.2 Further validation
            cancelReason = IncreasePositionSizeUtils.validateCallback(
                existingTrade,
                values,
                _answer,
                partialTrade.openPrice,
                _order.maxSlippageP
            );

            // 5. If passes further validation, execute callback
            if (cancelReason == ITradingCallbacks.CancelReason.NONE) {
                // 5.1 Update trade collateral / leverage / open price in storage, and reset trade borrowing fees
                IncreasePositionSizeUtils.updateTradeSuccess(existingTrade, values);

                // 5.2 Distribute opening fees and store fee tier points for position size delta
                TradingCommonUtils.processFees(existingTrade, values.positionSizeCollateralDelta, _order.orderType);
            }
        }

        // 6. If didn't pass validation, charge gov fee (if trade exists) and return partial collateral (if any)
        if (cancelReason != ITradingCallbacks.CancelReason.NONE)
            IncreasePositionSizeUtils.handleCanceled(existingTrade, partialTrade, cancelReason);

        // 7. Close pending increase position size order
        _getMultiCollatDiamond().closePendingOrder(_answer.orderId);

        emit IUpdatePositionSizeUtils.PositionSizeIncreaseExecuted(
            _answer.orderId,
            cancelReason,
            existingTrade.collateralIndex,
            existingTrade.user,
            existingTrade.pairIndex,
            existingTrade.index,
            existingTrade.long,
            _answer.price,
            _getMultiCollatDiamond().getCollateralPriceUsd(existingTrade.collateralIndex),
            partialTrade.collateralAmount,
            partialTrade.leverage,
            values
        );
    }

    /**
     * @dev Execute decrease position size market callback
     * @param _order corresponding pending order
     * @param _answer price aggregator answer
     */
    function executeDecreasePositionSizeMarket(
        ITradingStorage.PendingOrder memory _order,
        ITradingCallbacks.AggregatorAnswer memory _answer
    ) external {
        // 1. Prepare vars
        ITradingStorage.Trade memory partialTrade = _order.trade;
        ITradingStorage.Trade memory existingTrade = _getMultiCollatDiamond().getTrade(
            partialTrade.user,
            partialTrade.index
        );
        IUpdatePositionSizeUtils.DecreasePositionSizeValues memory values;

        // 2. Refresh trader fee tier cache
        TradingCommonUtils.updateFeeTierPoints(
            existingTrade.collateralIndex,
            existingTrade.user,
            existingTrade.pairIndex,
            0
        );

        // 3. Base validation (trade open, market open)
        ITradingCallbacks.CancelReason cancelReason = _validateBaseFulfillment(existingTrade, _answer);

        // 4. If passes base validation, validate further
        if (cancelReason == ITradingCallbacks.CancelReason.NONE) {
            // 4.1 Prepare useful values (position size delta, closing fees, borrowing fees, etc.)
            values = DecreasePositionSizeUtils.prepareCallbackValues(existingTrade, partialTrade, _answer);

            // 4.2 Further validation
            cancelReason = DecreasePositionSizeUtils.validateCallback(existingTrade, _order, values, _answer);

            // 5. If passes further validation, execute callback
            if (cancelReason == ITradingCallbacks.CancelReason.NONE) {
                // 5.1 Send collateral delta (partial trade value - fees) if positive or remove from trade collateral if negative
                // Then update trade collateral / leverage in storage, and reset trade borrowing fees
                DecreasePositionSizeUtils.updateTradeSuccess(existingTrade, values);

                // 5.2 Distribute closing fees
                TradingCommonUtils.processFees(existingTrade, values.positionSizeCollateralDelta, _order.orderType);
            }
        }

        // 6. If didn't pass validation and trade exists, charge gov fee and remove corresponding OI
        if (cancelReason != ITradingCallbacks.CancelReason.NONE)
            DecreasePositionSizeUtils.handleCanceled(existingTrade, cancelReason);

        // 7. Close pending decrease position size order
        _getMultiCollatDiamond().closePendingOrder(_answer.orderId);

        emit IUpdatePositionSizeUtils.PositionSizeDecreaseExecuted(
            _answer.orderId,
            cancelReason,
            existingTrade.collateralIndex,
            existingTrade.user,
            existingTrade.pairIndex,
            existingTrade.index,
            existingTrade.long,
            _answer.price,
            _getMultiCollatDiamond().getCollateralPriceUsd(existingTrade.collateralIndex),
            partialTrade.collateralAmount,
            partialTrade.leverage,
            values
        );
    }

    /**
     * @dev Returns current address as multi-collateral diamond interface to call other facets functions.
     */
    function _getMultiCollatDiamond() internal view returns (IGNSMultiCollatDiamond) {
        return IGNSMultiCollatDiamond(address(this));
    }

    /**
     * @dev Basic validation for increase/decrease position size request
     * @param _trader trader address
     * @param _index trade index
     */
    function _baseValidateRequest(
        address _trader,
        uint32 _index
    ) internal view returns (ITradingStorage.Trade memory trade) {
        trade = _getMultiCollatDiamond().getTrade(_trader, _index);

        // 1. Check trade exists
        if (!trade.isOpen) revert IGeneralErrors.DoesntExist();

        // 2. Revert if any market order (market close, increase leverage, partial open, partial close) already exists for trade
        TradingCommonUtils.revertIfTradeHasPendingMarketOrder(_trader, _index);

        // 3. Revert if collateral not active
        if (!_getMultiCollatDiamond().isCollateralActive(trade.collateralIndex))
            revert IGeneralErrors.InvalidCollateralIndex();
    }

    /**
     * @dev Creates pending order, makes price aggregator request, and returns corresponding pending order id
     * @param _trade trade to update
     * @param _isIncrease whether is increase or decrease position size order
     * @param _collateralAmount partial trade collateral amount (collateral precision)
     * @param _leverage partial trade leverage (1e3)
     * @param _positionSizeCollateralDelta position size delta in collateral tokens (collateral precision)
     * @param _expectedPrice reference price for max slippage check (1e10), only useful for increase position size
     * @param _maxSlippageP max slippage % (1e3), only useful for increase position size
     */
    function _initiateRequest(
        ITradingStorage.Trade memory _trade,
        bool _isIncrease,
        uint120 _collateralAmount,
        uint24 _leverage,
        uint256 _positionSizeCollateralDelta,
        uint64 _expectedPrice,
        uint16 _maxSlippageP
    ) internal returns (ITradingStorage.Id memory orderId) {
        // 1. Initialize partial trade
        ITradingStorage.Trade memory pendingOrderTrade;
        pendingOrderTrade.user = _trade.user;
        pendingOrderTrade.index = _trade.index;
        pendingOrderTrade.collateralAmount = _collateralAmount;
        pendingOrderTrade.leverage = _leverage;
        pendingOrderTrade.openPrice = _expectedPrice; // useful for max slippage checks

        // 2. Store pending order
        ITradingStorage.PendingOrder memory pendingOrder;
        pendingOrder.trade = pendingOrderTrade;
        pendingOrder.user = _trade.user;
        pendingOrder.orderType = _isIncrease
            ? ITradingStorage.PendingOrderType.MARKET_PARTIAL_OPEN
            : ITradingStorage.PendingOrderType.MARKET_PARTIAL_CLOSE;
        pendingOrder.maxSlippageP = _maxSlippageP;

        pendingOrder = _getMultiCollatDiamond().storePendingOrder(pendingOrder);
        orderId = ITradingStorage.Id(pendingOrder.user, pendingOrder.index);

        // 3. Make price aggregator request
        _getMultiCollatDiamond().getPrice(
            _trade.collateralIndex,
            _trade.pairIndex,
            ITradingStorage.Id(_trade.user, _trade.index),
            orderId,
            pendingOrder.orderType,
            _positionSizeCollateralDelta,
            0
        );
    }

    /**
     * @dev Basic validation for callbacks, returns corresponding cancel reason
     * @param _trade trade struct
     * @param _answer price aggegator answer
     */
    function _validateBaseFulfillment(
        ITradingStorage.Trade memory _trade,
        ITradingCallbacks.AggregatorAnswer memory _answer
    ) internal pure returns (ITradingCallbacks.CancelReason) {
        // prettier-ignore
        return
            !_trade.isOpen ? ITradingCallbacks.CancelReason.NO_TRADE : _answer.price == 0
                ? ITradingCallbacks.CancelReason.MARKET_CLOSED
                : ITradingCallbacks.CancelReason.NONE;
    }
}

Context size (optional):