S Price: $0.779774 (-0.23%)

Contract Diff Checker

Contract Name:
TradingFloorV1

Contract Source Code:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @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 v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}

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

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../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 An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, 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);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @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.encodeCall(token.approve, (spender, value));

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

    /**
     * @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);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @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(token).code.length > 0;
    }
}

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

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @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 or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * 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.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @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`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

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

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

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) 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 FailedInnerCall();
        }
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "./AcceptableImplementationClaimableAdminStorage.sol";

/**
 * @title SafeUpgradeableClaimableAdmin
 * @dev based on Compound's Unitroller
 * https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Unitroller.sol
 */
contract AcceptableImplementationClaimableAdmin is
  AcceptableImplementationClaimableAdminStorage
{
  /**
   * @notice Emitted when pendingImplementation is changed
   */
  event NewPendingImplementation(
    address oldPendingImplementation,
    address newPendingImplementation
  );

  /**
   * @notice Emitted when pendingImplementation is accepted, which means delegation implementation is updated
   */
  event NewImplementation(address oldImplementation, address newImplementation);

  /**
   * @notice Emitted when pendingAdmin is changed
   */
  event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);

  /**
   * @notice Emitted when pendingAdmin is accepted, which means admin is updated
   */
  event NewAdmin(address oldAdmin, address newAdmin);

  /*** Admin Functions ***/
  function _setPendingImplementation(address newPendingImplementation) public {
    require(msg.sender == admin, "not admin");
    require(
      approvePendingImplementationInternal(newPendingImplementation),
      "INVALID_IMPLEMENTATION"
    );

    address oldPendingImplementation = pendingImplementation;

    pendingImplementation = newPendingImplementation;

    emit NewPendingImplementation(
      oldPendingImplementation,
      pendingImplementation
    );
  }

  /**
   * @notice Accepts new implementation. msg.sender must be pendingImplementation
   * @dev Admin function for new implementation to accept it's role as implementation
   */
  function _acceptImplementation() public returns (uint) {
    // Check caller is pendingImplementation and pendingImplementation ≠ address(0)
    require(
      msg.sender == pendingImplementation &&
        pendingImplementation != address(0),
      "Not the EXISTING pending implementation"
    );

    // Save current values for inclusion in log
    address oldImplementation = implementation;
    address oldPendingImplementation = pendingImplementation;

    implementation = pendingImplementation;

    pendingImplementation = address(0);

    emit NewImplementation(oldImplementation, implementation);
    emit NewPendingImplementation(
      oldPendingImplementation,
      pendingImplementation
    );

    return 0;
  }

  /**
   * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
   * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
   * @param newPendingAdmin New pending admin.
   */
  function _setPendingAdmin(address newPendingAdmin) public {
    // Check caller = admin
    require(msg.sender == admin, "Not Admin");

    // Save current value, if any, for inclusion in log
    address oldPendingAdmin = pendingAdmin;

    // Store pendingAdmin with value newPendingAdmin
    pendingAdmin = newPendingAdmin;

    // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)
    emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);
  }

  /**
   * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin
   * @dev Admin function for pending admin to accept role and update admin
   */
  function _acceptAdmin() public {
    // Check caller is pendingAdmin and pendingAdmin ≠ address(0)
    require(
      msg.sender == pendingAdmin && pendingAdmin != address(0),
      "Not the EXISTING pending admin"
    );

    // Save current values for inclusion in log
    address oldAdmin = admin;
    address oldPendingAdmin = pendingAdmin;

    // Store admin with value pendingAdmin
    admin = pendingAdmin;

    // Clear the pending value
    pendingAdmin = address(0);

    emit NewAdmin(oldAdmin, admin);
    emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
  }

  constructor(address _initialAdmin) {
    admin = _initialAdmin;
    emit NewAdmin(address(0), _initialAdmin);
  }

  /**
   * @dev Delegates execution to an implementation contract.
   * It returns to the external caller whatever the implementation returns
   * or forwards reverts.
   */
  fallback() external payable {
    // delegate all other functions to current implementation
    (bool success, ) = implementation.delegatecall(msg.data);

    assembly {
      let free_mem_ptr := mload(0x40)
      returndatacopy(free_mem_ptr, 0, returndatasize())

      switch success
      case 0 {
        revert(free_mem_ptr, returndatasize())
      }
      default {
        return(free_mem_ptr, returndatasize())
      }
    }
  }

  receive() external payable {}

  function approvePendingImplementationInternal(
    address // _implementation
  ) internal virtual returns (bool) {
    return true;
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

contract ClaimableAdminStorage {
  /**
   * @notice Administrator for this contract
   */
  address public admin;

  /**
   * @notice Pending administrator for this contract
   */
  address public pendingAdmin;

  /*** Modifiers ***/

  modifier onlyAdmin() {
    require(msg.sender == admin, "ONLY_ADMIN");
    _;
  }

  /*** Constructor ***/

  constructor() {
    // Set admin to caller
    admin = msg.sender;
  }
}

contract AcceptableImplementationClaimableAdminStorage is
  ClaimableAdminStorage
{
  /**
   * @notice Active logic
   */
  address public implementation;

  /**
   * @notice Pending logic
   */
  address public pendingImplementation;
}

contract AcceptableRegistryImplementationClaimableAdminStorage is
  AcceptableImplementationClaimableAdminStorage
{
  /**
   * @notice System Registry
   */
  address public registry;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "./AcceptableImplementationClaimableAdmin.sol";
import "./IContractRegistryBase.sol";

/**
 * @title AcceptableRegistryImplementationClaimableAdmin
 */
contract AcceptableRegistryImplementationClaimableAdmin is
  AcceptableImplementationClaimableAdmin,
  AcceptableRegistryImplementationClaimableAdminStorage
{
  bytes32 public immutable CONTRACT_NAME_HASH;

  constructor(
    address _registry,
    string memory proxyName,
    address _initialAdmin
  ) AcceptableImplementationClaimableAdmin(_initialAdmin) {
    registry = _registry;
    CONTRACT_NAME_HASH = keccak256(abi.encodePacked(proxyName));
  }

  function approvePendingImplementationInternal(
    address _implementation
  ) internal view override returns (bool) {
    return
      IContractRegistryBase(registry).isImplementationValidForProxy(
        CONTRACT_NAME_HASH,
        _implementation
      );
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface IContractRegistryBase {
  function isImplementationValidForProxy(
    bytes32 proxyNameHash,
    address _implementation
  ) external view returns (bool);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

/**
 * @dev only use immutables and constants in this contract
 */
contract CommonScales {
  uint256 public constant PRECISION = 1e18; // 18 decimals

  uint256 public constant LEVERAGE_SCALE = 100; // 2 decimal points

  uint256 public constant FRACTION_SCALE = 100000; // 5 decimal points

  uint256 public constant ACCURACY_IMPROVEMENT_SCALE = 1e9;

  function calculateLeveragedPosition(
    uint256 collateral,
    uint256 leverage
  ) internal pure returns (uint256) {
    return (collateral * leverage) / LEVERAGE_SCALE;
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface IFundingRateModel {
  // return value is the "funding paid by heavier side" in PRECISION per OI (heavier side) per second
  // e.g : (0.01 * PRECISION) = Paying (heavier) side (as a whole) pays 1% of funding per second for each OI unit
  function getFundingRate(
    uint256 pairId,
    uint256 openInterestLong,
    uint256 openInterestShort,
    uint256 pairMaxOpenInterest
  ) external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface IGlobalLock {
  function lock() external;
  function freeLock() external;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface IInterestRateModel {
  // Returns asset/second of interest per borrowed unit
  // e.g : (0.01 * PRECISION) = 1% of interest per second
  function getBorrowRate(uint256 utilization) external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "./LexErrors.sol";
import "./LexPoolAdminEnums.sol";
import "./IPoolAccountantV1.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface LexPoolStructs {
  struct PendingDeposit {
    uint256 amount;
    uint256 minAmountOut;
  }

  struct PendingRedeem {
    uint256 amount;
    uint256 minAmountOut;
    uint256 maxAmountOut;
  }
}

interface LexPoolEvents is LexPoolAdminEnums {
  event NewEpoch(
    uint256 epochId,
    int256 reportedUnrealizedPricePnL,
    uint256 exchangeRate,
    uint256 virtualUnderlyingBalance,
    uint256 totalSupply
  );

  event AddressUpdated(LexPoolAddressesEnum indexed enumCode, address a);
  event NumberUpdated(LexPoolNumbersEnum indexed enumCode, uint value);
  event DepositRequest(
    address indexed user,
    uint256 amount,
    uint256 minAmountOut,
    uint256 processingEpoch
  );
  event RedeemRequest(
    address indexed user,
    uint256 amount,
    uint256 minAmountOut,
    uint256 processingEpoch
  );
  event ProcessedDeposit(
    address indexed user,
    bool deposited,
    uint256 depositedAmount
  );
  event ProcessedRedeem(
    address indexed user,
    bool redeemed,
    uint256 withdrawnAmount // Underlying amount
  );
  event CanceledDeposit(
    address indexed user,
    uint256 epoch,
    uint256 cancelledAmount
  );
  event CanceledRedeem(
    address indexed user,
    uint256 epoch,
    uint256 cancelledAmount
  );
  event ImmediateDepositAllowedToggled(bool indexed value);
  event ImmediateDeposit(
    address indexed depositor,
    uint256 depositAmount,
    uint256 mintAmount
  );
  event ReservesWithdrawn(
    address _to,
    uint256 interestShare,
    uint256 totalFundingShare
  );
}

interface ILexPoolFunctionality is
  IERC20,
  LexPoolStructs,
  LexPoolEvents,
  LexErrors
{
  function setPoolAccountant(
    IPoolAccountantFunctionality _poolAccountant
  ) external;

  function setPnlRole(address pnl) external;

  function setMaxExtraWithdrawalAmountF(uint256 maxExtra) external;

  function setEpochsDelayDeposit(uint256 delay) external;

  function setEpochsDelayRedeem(uint256 delay) external;

  function setEpochDuration(uint256 duration) external;

  function setMinDepositAmount(uint256 amount) external;

  function toggleImmediateDepositAllowed() external;

  function reduceReserves(
    address _to
  ) external returns (uint256 interestShare, uint256 totalFundingShare);

  function requestDeposit(
    uint256 amount,
    uint256 minAmountOut,
    bytes32 domain,
    bytes32 referralCode
  ) external;

  function requestDepositViaIntent(
    address user,
    uint256 amount,
    uint256 minAmountOut,
    bytes32 domain,
    bytes32 referralCode
  ) external;

  function requestRedeem(uint256 amount, uint256 minAmountOut) external;

  function requestRedeemViaIntent(
    address user,
    uint256 amount,
    uint256 minAmountOut
  ) external;

  function processDeposit(
    address[] memory users
  )
    external
    returns (
      uint256 amountDeposited,
      uint256 amountCancelled,
      uint256 counterDeposited,
      uint256 counterCancelled
    );

  function cancelDeposits(
    address[] memory users,
    uint256[] memory epochs
  ) external;

  function processRedeems(
    address[] memory users
  )
    external
    returns (
      uint256 amountRedeemed,
      uint256 amountCancelled,
      uint256 counterDeposited,
      uint256 counterCancelled
    );

  function cancelRedeems(
    address[] memory users,
    uint256[] memory epochs
  ) external;

  function nextEpoch(
    int256 totalUnrealizedPricePnL
  ) external returns (uint256 newExchangeRate);

  function currentVirtualUtilization() external view returns (uint256);

  function currentVirtualUtilization(
    uint256 totalBorrows,
    uint256 totalReserves,
    int256 unrealizedFunding
  ) external view returns (uint256);

  function virtualBalanceForUtilization() external view returns (uint256);

  function virtualBalanceForUtilization(
    uint256 extraAmount,
    int256 unrealizedFunding
  ) external view returns (uint256);

  function underlyingBalanceForExchangeRate() external view returns (uint256);

  function sendAssetToTrader(address to, uint256 amount) external;

  function isUtilizationForLPsValid() external view returns (bool);
}

interface ILexPoolV1 is ILexPoolFunctionality {
  function name() external view returns (string memory);

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

  function SELF_UNIT_SCALE() external view returns (uint);

  function underlyingDecimals() external view returns (uint256);

  function poolAccountant() external view returns (address);

  function underlying() external view returns (IERC20);

  function tradingFloor() external view returns (address);

  function currentEpoch() external view returns (uint256);

  function currentExchangeRate() external view returns (uint256);

  function nextEpochStartMin() external view returns (uint256);

  function epochDuration() external view returns (uint256);

  function minDepositAmount() external view returns (uint256);

  function epochsDelayDeposit() external view returns (uint256);

  function epochsDelayRedeem() external view returns (uint256);

  function immediateDepositAllowed() external view returns (bool);

  function pendingDeposits(
    uint epoch,
    address account
  ) external view returns (PendingDeposit memory);

  function pendingRedeems(
    uint epoch,
    address account
  ) external view returns (PendingRedeem memory);

  function pendingDepositAmount() external view returns (uint256);

  function pendingWithdrawalAmount() external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "./LexErrors.sol";
import "./ILexPoolV1.sol";
import "./IInterestRateModel.sol";
import "./IFundingRateModel.sol";
import "./TradingEnumsV1.sol";

interface PoolAccountantStructs {
  // @note To be used for passing information in function calls
  struct PositionRegistrationParams {
    uint256 collateral;
    uint32 leverage;
    bool long;
    uint64 openPrice;
    uint64 tp;
  }

  struct PairFunding {
    // Slot 0
    int256 accPerOiLong; // 32 bytes -- Underlying Decimals
    // Slot 1
    int256 accPerOiShort; // 32 bytes -- Underlying Decimals
    // Slot 2
    uint256 lastUpdateTimestamp; // 32 bytes
  }

  struct TradeInitialAccFees {
    // Slot 0
    uint256 borrowIndex; // 32 bytes
    // Slot 1
    int256 funding; // 32 bytes -- underlying units -- Underlying Decimals
  }

  struct PairOpenInterest {
    // Slot 0
    uint256 long; // 32 bytes -- underlying units -- Dynamic open interest for long positions
    // Slot 1
    uint256 short; // 32 bytes -- underlying units -- Dynamic open interest for short positions
  }

  // This struct is not kept in storage
  struct PairFromTo {
    string from;
    string to;
  }

  struct Pair {
    // Slot 0
    uint16 id; // 02 bytes
    uint16 groupId; // 02 bytes
    uint16 feeId; // 02 bytes
    uint32 minLeverage; // 04 bytes
    uint32 maxLeverage; // 04 bytes
    uint32 maxBorrowF; // 04 bytes -- FRACTION_SCALE (5)
    // Slot 1
    uint256 maxPositionSize; // 32 bytes -- underlying units
    // Slot 2
    uint256 maxGain; // 32 bytes -- underlying units
    // Slot 3
    uint256 maxOpenInterest; // 32 bytes -- Underlying units
    // Slot 4
    uint256 maxSkew; // 32 bytes -- underlying units
    // Slot 5
    uint256 minOpenFee; // 32 bytes -- underlying units. MAX_UINT means use the default group level value
    // Slot 6
    uint256 minPerformanceFee; // 32 bytes -- underlying units
  }

  struct Group {
    // Slot 0
    uint16 id; // 02 bytes
    uint32 minLeverage; // 04 bytes
    uint32 maxLeverage; // 04 bytes
    uint32 maxBorrowF; // 04 bytes -- FRACTION_SCALE (5)
    // Slot 1
    uint256 maxPositionSize; // 32 bytes (Underlying units)
    // Slot 2
    uint256 minOpenFee; // 32 bytes (Underlying uints). MAX_UINT means use the default global level value
  }

  struct Fee {
    // Slot 0
    uint16 id; // 02 bytes
    uint32 openFeeF; // 04 bytes -- FRACTION_SCALE (5) (Fraction of leveraged pos)
    uint32 closeFeeF; // 04 bytes -- FRACTION_SCALE (5) (Fraction of leveraged pos)
    uint32 performanceFeeF; // 04 bytes -- FRACTION_SCALE (5) (Fraction of performance)
  }
}

interface PoolAccountantEvents is PoolAccountantStructs {
  event PairAdded(
    uint256 indexed id,
    string indexed from,
    string indexed to,
    Pair pair
  );
  event PairUpdated(uint256 indexed id, Pair pair);

  event GroupAdded(uint256 indexed id, string indexed groupName, Group group);
  event GroupUpdated(uint256 indexed id, Group group);

  event FeeAdded(uint256 indexed id, string indexed name, Fee fee);
  event FeeUpdated(uint256 indexed id, Fee fee);

  event TradeInitialAccFeesStored(
    bytes32 indexed positionId,
    uint256 borrowIndex,
    // uint256 rollover,
    int256 funding
  );

  event AccrueFunding(
    uint256 indexed pairId,
    int256 valueLong,
    int256 valueShort
  );

  event ProtocolFundingShareAccrued(
    uint16 indexed pairId,
    uint256 protocolFundingShare
  );
  // event AccRolloverFeesStored(uint256 pairIndex, uint256 value);

  event FeesCharged(
    bytes32 indexed positionId,
    address indexed trader,
    uint16 indexed pairId,
    PositionRegistrationParams positionRegistrationParams,
    //        bool long,
    //        uint256 collateral, // Underlying Decimals
    //        uint256 leverage,
    int256 profitPrecision, // PRECISION
    uint256 interest,
    int256 funding, // Underlying Decimals
    uint256 closingFee,
    uint256 tradeValue
  );

  event PerformanceFeeCharging(
    bytes32 indexed positionId,
    uint256 performanceFee
  );

  event MaxOpenInterestUpdated(uint256 pairIndex, uint256 maxOpenInterest);

  event AccrueInterest(
    uint256 cash,
    uint256 totalInterestNew,
    uint256 borrowIndexNew,
    uint256 interestShareNew
  );

  event Borrow(
    uint256 indexed pairId,
    uint256 borrowAmount,
    uint256 newTotalBorrows
  );

  event Repay(
    uint256 indexed pairId,
    uint256 repayAmount,
    uint256 newTotalBorrows
  );
}

interface IPoolAccountantFunctionality is
  PoolAccountantStructs,
  PoolAccountantEvents,
  LexErrors,
  TradingEnumsV1
{
  function setTradeIncentivizer(address _tradeIncentivizer) external;

  function setMaxGainF(uint256 _maxGainF) external;

  function setFrm(IFundingRateModel _frm) external;

  function setMinOpenFee(uint256 min) external;

  function setLexPartF(uint256 partF) external;

  function setFundingRateMax(uint256 maxValue) external;

  function setLiquidationThresholdF(uint256 threshold) external;

  function setLiquidationFeeF(uint256 fee) external;

  function setIrm(IInterestRateModel _irm) external;

  function setIrmHard(IInterestRateModel _irm) external;

  function setInterestShareFactor(uint256 factor) external;
  
  function setFundingShareFactor(uint256 factor) external;

  function setBorrowRateMax(uint256 rate) external;

  function setMaxTotalBorrows(uint256 maxBorrows) external;

  function setMaxVirtualUtilization(uint256 _maxVirtualUtilization) external;

  function resetTradersPairGains(uint256 pairId) external;

  function addGroup(Group calldata _group) external;

  function updateGroup(Group calldata _group) external;

  function addFee(Fee calldata _fee) external;

  function updateFee(Fee calldata _fee) external;

  function addPair(Pair calldata _pair) external;

  function addPairs(Pair[] calldata _pairs) external;

  function updatePair(Pair calldata _pair) external;

  function readAndZeroReserves()
    external
    returns (uint256 accumulatedInterestShare,
             uint256 accFundingShare);

  function registerOpenTrade(
    bytes32 positionId,
    address trader,
    uint16 pairId,
    uint256 collateral,
    uint32 leverage,
    bool long,
    uint256 tp,
    uint256 openPrice
  ) external returns (uint256 fee, uint256 lexPartFee);

  function registerCloseTrade(
    bytes32 positionId,
    address trader,
    uint16 pairId,
    PositionRegistrationParams calldata positionRegistrationParams,
    uint256 closePrice,
    PositionCloseType positionCloseType
  )
    external
    returns (
      uint256 closingFee,
      uint256 tradeValue,
      int256 profitPrecision,
      uint finalClosePrice
    );

  function registerUpdateTp(
    bytes32 positionId,
    address trader,
    uint16 pairId,
    uint256 collateral,
    uint32 leverage,
    bool long,
    uint256 openPrice,
    uint256 oldTriggerPrice,
    uint256 triggerPrice
  ) external;

  // function registerUpdateSl(
  //     address trader,
  //     uint256 pairIndex,
  //     uint256 index,
  //     uint256 collateral,
  //     uint256 leverage,
  //     bool long,
  //     uint256 openPrice,
  //     uint256 triggerPrice
  // ) external returns (uint256 fee);

  function accrueInterest()
    external
    returns (
      uint256 totalInterestNew,
      uint256 interestShareNew,
      uint256 borrowIndexNew
    );

  // Limited only for the LexPool
  function accrueInterest(
    uint256 availableCash
  )
    external
    returns (
      uint256 totalInterestNew,
      uint256 interestShareNew,
      uint256 borrowIndexNew
    );

  function getTradeClosingValues(
    bytes32 positionId,
    uint16 pairId,
    PositionRegistrationParams calldata positionRegistrationParams,
    uint256 closePrice,
    bool isLiquidation
  )
    external
    returns (
      uint256 tradeValue, // Underlying Decimals
      uint256 safeClosingFee,
      int256 profitPrecision,
      uint256 interest,
      int256 funding
    );

  function getTradeLiquidationPrice(
    bytes32 positionId,
    uint16 pairId,
    uint256 openPrice, // PRICE_SCALE (8)
    uint256 tp,
    bool long,
    uint256 collateral, // Underlying Decimals
    uint32 leverage
  )
    external
    returns (
      uint256 // PRICE_SCALE (8)
    );

  function calcTradeDynamicFees(
    bytes32 positionId,
    uint16 pairId,
    bool long,
    uint256 collateral,
    uint32 leverage,
    uint256 openPrice,
    uint256 tp
  ) external returns (uint256 interest, int256 funding);

  function unrealizedFunding() external view returns (int256);

  function totalBorrows() external view returns (uint256);

  function interestShare() external view returns (uint256);
  
  function fundingShare() external view returns (uint256);

  function totalReservesView() external view returns (uint256);

  function borrowsAndInterestShare()
    external
    view
    returns (uint256 totalBorrows, uint256 totalInterestShare);

  function pairTotalOpenInterest(
    uint256 pairIndex
  ) external view returns (int256);

  function pricePnL(
    uint256 pairId,
    uint256 price
  ) external view returns (int256);

  function getAllSupportedPairIds() external view returns (uint16[] memory);

  function getAllSupportedGroupsIds() external view returns (uint16[] memory);

  function getAllSupportedFeeIds() external view returns (uint16[] memory);
}

interface IPoolAccountantV1 is IPoolAccountantFunctionality {
  function totalBorrows() external view returns (uint256);

  function maxTotalBorrows() external view returns (uint256);

  function pairBorrows(uint256 pairId) external view returns (uint256);

  function groupBorrows(uint256 groupId) external view returns (uint256);

  function pairMaxBorrow(uint16 pairId) external view returns (uint256);

  function groupMaxBorrow(uint16 groupId) external view returns (uint256);

  function lexPool() external view returns (ILexPoolV1);

  function maxGainF() external view returns (uint256);

  function interestShareFactor() external view returns (uint256);
  
  function fundingShareFactor() external view returns (uint256);

  function frm() external view returns (IFundingRateModel);

  function irm() external view returns (IInterestRateModel);

  function pairs(uint16 pairId) external view returns (Pair memory);

  function groups(uint16 groupId) external view returns (Group memory);

  function fees(uint16 feeId) external view returns (Fee memory);

  function openInterestInPair(
    uint pairId
  ) external view returns (PairOpenInterest memory);

  function minOpenFee() external view returns (uint256);

  function liquidationThresholdF() external view returns (uint256);

  function liquidationFeeF() external view returns (uint256);

  function lexPartF() external view returns (uint256);

  function tradersPairGains(uint256 pairId) external view returns (int256);

  function calcBorrowAmount(
    uint256 collateral,
    uint256 leverage,
    bool long,
    uint256 openPrice,
    uint256 tp
  ) external pure returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "../../AdministrationContracts/IContractRegistryBase.sol";
import "./IGlobalLock.sol";

interface IRegistryV1Functionality is IContractRegistryBase, IGlobalLock {
  // **** Locking mechanism ****

  function isTradersPortalAndLocker(
    address _address
  ) external view returns (bool);

  function isTriggersAndLocker(address _address) external view returns (bool);

  function isTradersPortalOrTriggersAndLocker(
    address _address
  ) external view returns (bool);
}

interface IRegistryV1 is IRegistryV1Functionality {
  // **** Public Storage params ****

  function feesManagers(address asset) external view returns (address);

  function orderBook() external view returns (address);

  function tradersPortal() external view returns (address);

  function triggers() external view returns (address);

  function tradeIntentsVerifier() external view returns (address);

  function liquidityIntentsVerifier() external view returns (address);

  function chipsIntentsVerifier() external view returns (address);

  function lexProxiesFactory() external view returns (address);

  function chipsFactory() external view returns (address);

  /**
   * @return An array of all supported trading floors
   */
  function getAllSupportedTradingFloors()
    external
    view
    returns (address[] memory);

  /**
   * @return An array of all supported settlement assets
   */
  function getSettlementAssetsForTradingFloor(
    address _tradingFloor
  ) external view returns (address[] memory);

  /**
   * @return The spender role address that is set for this chip
   */
  function getValidSpenderTargetForChipByRole(
    address chip,
    string calldata role
  ) external view returns (address);

  /**
   * @return the address of the valid 'burnHandler' for the chip
   */
  function validBurnHandlerForChip(
    address chip
  ) external view returns (address);

  /**
   * @return The address matching for the given role
   */
  function getDynamicRoleAddress(
    string calldata _role
  ) external view returns (address);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "./TradingFloorStructsV1.sol";
import "./IPoolAccountantV1.sol";
import "./ILexPoolV1.sol";

interface ITradingFloorV1Functionality is TradingFloorStructsV1 {
  function supportNewSettlementAsset(
    address _asset,
    address _lexPool,
    address _poolAccountant
  ) external;

  function getPositionTriggerInfo(
    bytes32 _positionId
  )
    external
    view
    returns (
      PositionPhase positionPhase,
      uint64 timestamp,
      uint16 pairId,
      bool long,
      uint32 spreadReductionF
    );

  function getPositionPortalInfo(
    bytes32 _positionId
  )
    external
    view
    returns (
      PositionPhase positionPhase,
      uint64 inPhaseSince,
      address positionTrader
    );

  function storePendingPosition(
    OpenOrderType _orderType,
    PositionRequestIdentifiers memory _requestIdentifiers,
    PositionRequestParams memory _requestParams,
    uint32 _spreadReductionF
  ) external returns (bytes32 positionId);

  function setOpenedPositionToMarketClose(
    bytes32 _positionId,
    uint64 _minPrice,
    uint64 _maxPrice
  ) external;

  function cancelPendingPosition(
    bytes32 _positionId,
    OpenOrderType _orderType,
    uint feeFraction
  ) external;

  function cancelMarketCloseForPosition(
    bytes32 _positionId,
    CloseOrderType _orderType,
    uint feeFraction
  ) external;

  function updatePendingPosition_openLimit(
    bytes32 _positionId,
    uint64 _minPrice,
    uint64 _maxPrice,
    uint64 _tp,
    uint64 _sl
  ) external;

  function openNewPosition_market(
    bytes32 _positionId,
    uint64 assetEffectivePrice,
    uint256 feeForCancellation
  ) external;

  function openNewPosition_limit(
    bytes32 _positionId,
    uint64 assetEffectivePrice,
    uint256 feeForCancellation
  ) external;

  function closeExistingPosition_Market(
    bytes32 _positionId,
    uint64 assetPrice,
    uint64 effectivePrice
  ) external;

  function closeExistingPosition_Limit(
    bytes32 _positionId,
    LimitTrigger limitTrigger,
    uint64 assetPrice,
    uint64 effectivePrice
  ) external;

  // Manage open trade
  function updateOpenedPosition(
    bytes32 _positionId,
    PositionField updateField,
    uint64 fieldValue,
    uint64 effectivePrice
  ) external;

  // Fees
  function collectFee(address _asset, FeeType _feeType, address _to) external;
}

interface ITradingFloorV1 is ITradingFloorV1Functionality {
  function PRECISION() external pure returns (uint);

  // *** Views ***

  function pairTradersArray(
    address _asset,
    uint _pairIndex
  ) external view returns (address[] memory);

  function generatePositionHashId(
    address settlementAsset,
    address trader,
    uint16 pairId,
    uint32 index
  ) external pure returns (bytes32 hashId);

  // *** Public Storage addresses ***

  function lexPoolForAsset(address asset) external view returns (ILexPoolV1);

  function poolAccountantForAsset(
    address asset
  ) external view returns (IPoolAccountantV1);

  function registry() external view returns (address);

  // *** Public Storage params ***

  function positionsById(bytes32 id) external view returns (Position memory);

  function positionIdentifiersById(
    bytes32 id
  ) external view returns (PositionIdentifiers memory);

  function positionLimitsInfoById(
    bytes32 id
  ) external view returns (PositionLimitsInfo memory);

  function triggerPricesById(
    bytes32 id
  ) external view returns (PositionTriggerPrices memory);

  function pairTradersInfo(
    address settlementAsset,
    address trader,
    uint pairId
  ) external view returns (PairTraderInfo memory);

  function spreadReductionsP(uint) external view returns (uint);

  function maxSlF() external view returns (uint);

  function maxTradesPerPair() external view returns (uint);

  function maxSanityProfitF() external view returns (uint);

  function feesMap(
    address settlementAsset,
    FeeType feeType
  ) external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface LexErrors {
  enum CapType {
    NONE, // 0
    MIN_OPEN_FEE, // 1
    MAX_POS_SIZE_PAIR, // 2
    MAX_POS_SIZE_GROUP, // 3
    MAX_LEVERAGE, // 4
    MIN_LEVERAGE, // 5
    MAX_VIRTUAL_UTILIZATION, // 6
    MAX_OPEN_INTEREST, // 7
    MAX_ABS_SKEW, // 8
    MAX_BORROW_PAIR, // 9
    MAX_BORROW_GROUP, // 10
    MIN_DEPOSIT_AMOUNT, // 11
    MAX_ACCUMULATED_GAINS, // 12
    BORROW_RATE_MAX, // 13
    FUNDING_RATE_MAX, // 14
    MAX_POTENTIAL_GAIN, // 15
    MAX_TOTAL_BORROW, // 16
    MIN_PERFORMANCE_FEE // 17
    //...
  }
  error CapError(CapType, uint256 value);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

interface LexPoolAdminEnums {
  enum LexPoolAddressesEnum {
    none,
    poolAccountant,
    pnlRole
  }

  enum LexPoolNumbersEnum {
    none,
    maxExtraWithdrawalAmountF,
    epochsDelayDeposit,
    epochsDelayRedeem,
    epochDuration,
    minDepositAmount
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

interface TradingEnumsV1 {
  enum PositionPhase {
    NONE,
    OPEN_MARKET,
    OPEN_LIMIT,
    OPENED,
    CLOSE_MARKET,
    CLOSED
  }

  enum OpenOrderType {
    NONE,
    MARKET,
    LIMIT
  }
  enum CloseOrderType {
    NONE,
    MARKET
  }
  enum FeeType {
    NONE,
    OPEN_FEE,
    CLOSE_FEE,
    TRIGGER_FEE
  }
  enum LimitTrigger {
    NONE,
    TP,
    SL,
    LIQ
  }
  enum PositionField {
    NONE,
    TP,
    SL
  }

  enum PositionCloseType {
    NONE,
    TP,
    SL,
    LIQ,
    MARKET
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "./TradingEnumsV1.sol";

interface TradingFloorStructsV1 is TradingEnumsV1 {
  enum AdminNumericParam {
    NONE,
    MAX_TRADES_PER_PAIR,
    MAX_SL_F,
    MAX_SANITY_PROFIT_F
  }

  /**
   * @dev Memory struct for identifiers
   */
  struct PositionRequestIdentifiers {
    address trader;
    uint16 pairId;
    address settlementAsset;
    uint32 positionIndex;
  }

  struct PositionRequestParams {
    bool long;
    uint256 collateral; // Settlement Asset Decimals
    uint32 leverage;
    uint64 minPrice; // PRICE_SCALE
    uint64 maxPrice; // PRICE_SCALE
    uint64 tp; // PRICE_SCALE
    uint64 sl; // PRICE_SCALE
    uint64 tpByFraction; // FRACTION_SCALE
    uint64 slByFraction; // FRACTION_SCALE
  }

  /**
   * @dev Storage struct for identifiers
   */
  struct PositionIdentifiers {
    // Slot 0
    address settlementAsset; // 20 bytes
    uint16 pairId; // 02 bytes
    uint32 index; // 04 bytes
    // Slot 1
    address trader; // 20 bytes
  }

  struct Position {
    // Slot 0
    uint collateral; // 32 bytes -- Settlement Asset Decimals
    // Slot 1
    PositionPhase phase; // 01 bytes
    uint64 inPhaseSince; // 08 bytes
    uint32 leverage; // 04 bytes
    bool long; // 01 bytes
    uint64 openPrice; // 08 bytes -- PRICE_SCALE (8)
    uint32 spreadReductionF; // 04 bytes -- FRACTION_SCALE (5)
  }

  /**
   * Holds the non liquidation limits for the position
   */
  struct PositionLimitsInfo {
    uint64 tpLastUpdated; // 08 bytes -- timestamp
    uint64 slLastUpdated; // 08 bytes -- timestamp
    uint64 tp; // 08 bytes -- PRICE_SCALE (8)
    uint64 sl; // 08 bytes -- PRICE_SCALE (8)
  }

  /**
   * Holds the prices for opening (and market closing) of a position
   */
  struct PositionTriggerPrices {
    uint64 minPrice; // 08 bytes -- PRICE_SCALE
    uint64 maxPrice; // 08 bytes -- PRICE_SCALE
    uint64 tpByFraction; // 04 bytes -- FRACTION_SCALE
    uint64 slByFraction; // 04 bytes -- FRACTION_SCALE
  }

  /**
   * @dev administration struct, used to keep tracks on the 'PairTraders' list and
   *      to limit the amount of positions a trader can have
   */
  struct PairTraderInfo {
    uint32 positionsCounter; // 04 bytes
    uint32 positionInArray; // 04 bytes (the index + 1)
    // Note : Can add more fields here
  }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "../../AdministrationContracts/AcceptableRegistryImplementationClaimableAdmin.sol";

/**
 * @title TradingFloorProxy
 * @dev Used as the upgradable brain of the Lynx platform
 */
contract TradingFloorProxy is AcceptableRegistryImplementationClaimableAdmin {
  constructor(
    address _registry
  )
    AcceptableRegistryImplementationClaimableAdmin(
      _registry,
      "TradingFloor",
      msg.sender
    )
  {}
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "../../AdministrationContracts/AcceptableImplementationClaimableAdminStorage.sol";
import "../interfaces/ITradingFloorV1.sol";
import "../Common/CommonScales.sol";

/**
 * @title TradingFloorStorageV1
 * @dev Storage contract for the TradingFloor
 */
contract TradingFloorV1Storage is
  AcceptableRegistryImplementationClaimableAdminStorage,
  TradingFloorStructsV1,
  CommonScales
{
  // ***** Trading variables *****

  uint public maxTradesPerPair;

  uint public maxSlF; // FRACTION_SCALE (5)

  uint public maxSanityProfitF; // FRACTION_SCALE (5)

  // ***** Pair pausing *****

  mapping(uint => bool) public pausedPairs;

  // ***** Fees *****

  // Token => Fee Type => Amount
  mapping(address => mapping(FeeType => uint)) public feesMap;

  // ***** Lex&Accountant *****

  // settlement asset => lex pool
  mapping(address => address) public lexPoolForAsset;
  // settlement asset => pool accountant
  mapping(address => address) public poolAccountantForAsset;

  // ***** Position Identifiers *****

  // position id => PositionIdentifiers struct
  mapping(bytes32 => PositionIdentifiers) public positionIdentifiersById;

  // ***** Positions *****

  // position id => Position Struct
  mapping(bytes32 => Position) public positionsById;

  // position id => OpenTradeInfo struct
  mapping(bytes32 => PositionLimitsInfo) public positionLimitsInfoById;

  // position id => trigger prices
  mapping(bytes32 => PositionTriggerPrices) public triggerPricesById;

  // Position id => initial collateral
  mapping(bytes32 => uint) public initialCollateralByPositionId;

  // asset => pair id => traders list
  mapping(address => mapping(uint => address[])) public pairTraders;
  // asset => trader => pair id => index in traders list
  mapping(address => mapping(address => mapping(uint => PairTraderInfo)))
    public pairTradersInfo;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../interfaces/ITradingFloorV1.sol";
import "../interfaces/IRegistryV1.sol";
import "../interfaces/ILexPoolV1.sol";
import "../interfaces/IPoolAccountantV1.sol";

import "./TradingFloorStorage.sol";
import "./TradingFloorProxy.sol";

/**
 * @title TradingFloorV1
 * @dev The TradingFloor contract is the main contract for the Lynx trading platform.
 *      It handles the opening, updating and closing of positions.
 */
contract TradingFloorV1 is
  TradingFloorV1Storage,
  ITradingFloorV1Functionality,
  LexErrors
{
  using SafeERC20 for IERC20;

  // ***** Constants *****

  uint public constant MAX_FEE_FRACTION_FOR_CANCEL = FRACTION_SCALE / 100; // FRACTION_SCALE

  // ***** Events *****

  event SettlementAssetAdded(
    address indexed asset,
    address indexed lexPool,
    address indexed poolAccountant
  );

  event NumberUpdated(string indexed name, uint value);
  event PairPausedChange(uint indexed pairId, bool indexed isPaused);

  event PositionIdentifiersStored(
    bytes32 indexed positionId,
    PositionIdentifiers identifiers
  );

  event PendingPositionStored(
    bytes32 indexed positionId,
    PositionPhase phase,
    PositionRequestIdentifiers requestIdentifiers,
    PositionRequestParams requestParams,
    uint32 _spreadReductionF
  );

  event PositionOpenCancelledByMarketPriceRange(
    bytes32 indexed positionId,
    uint64 triggerPrice
  );

  event PositionOpenCancelledByCap(
    bytes32 indexed positionId,
    CapType capType,
    uint256 value
  );

  event PositionOpened(
    bytes32 indexed positionId,
    uint64 openPrice,
    uint64 tp,
    uint64 sl,
    uint totalOpenFee,
    uint lexFeePart
  );

  event PositionSetForMarketClose(
    bytes32 indexed positionId,
    uint64 _minPrice,
    uint64 _maxPrice
  );

  event PositionClosedMarket(
    bytes32 indexed positionId,
    uint triggerPrice,
    uint tradeValue,
    int profitPrecision
  );
  event PositionClosedLimit(
    bytes32 indexed positionId,
    LimitTrigger indexed limitTrigger,
    uint triggerPrice,
    uint effectiveClosePrice,
    uint tradeValue,
    int profitPrecision
  );

  event FeeRegistered(
    bytes32 indexed positionId,
    address indexed token,
    FeeType indexed feeType,
    uint amount
  );

  event FeeCollected(
    address indexed token,
    FeeType indexed feeType,
    address indexed receiver,
    uint amount
  );

  event PendingPositionCancelled(
    bytes32 indexed positionId,
    address indexed source,
    uint fee
  );
  event PositionMarketCloseCancelled(
    bytes32 indexed positionId,
    address indexed source,
    uint fee
  );

  event PendingPositionUpdated(
    bytes32 indexed positionId,
    uint64 tp,
    uint64 sl,
    uint64 minPrice,
    uint64 maxPrice
  );

  event OpenedPositionUpdated(
    bytes32 indexed positionId,
    uint16 indexed pairId,
    bool buy,
    PositionField indexed updatedField,
    uint64 fieldValue
  );

  // ***** Modifiers *****

  modifier onlyTradersPortal() {
    require(
      IRegistryV1(registry).isTradersPortalAndLocker(msg.sender),
      "!TradersPortal"
    );
    _;
  }
  modifier onlyTriggers() {
    require(IRegistryV1(registry).isTriggersAndLocker(msg.sender), "!Triggers");
    _;
  }
  modifier onlyTradersPortalOrTriggers() {
    require(
      IRegistryV1(registry).isTradersPortalOrTriggersAndLocker(msg.sender),
      "!(TradersPortal||Triggers)"
    );
    _;
  }

  // ***** Views *****

  /**
   * Generates the hash used to identify a position
   */
  function generatePositionHashId(
    address settlementAsset,
    address trader,
    uint16 pairId,
    uint32 index
  ) public pure returns (bytes32 hashId) {
    hashId = keccak256(
      abi.encodePacked(settlementAsset, trader, pairId, index)
    );
  }

  /**
   * Builds the structs with info needed fo 'Triggers' interactions
   */
  function getPositionTriggerInfo(
    bytes32 _positionId
  )
    external
    view
    returns (
      PositionPhase positionPhase,
      uint64 timestamp,
      uint16 pairId,
      bool long,
      uint32 spreadReductionF
    )
  {
    return (
      positionsById[_positionId].phase,
      positionsById[_positionId].inPhaseSince,
      positionIdentifiersById[_positionId].pairId,
      positionsById[_positionId].long,
      positionsById[_positionId].spreadReductionF
    );
  }

  /**
   * Builds the structs with info needed for 'TradersPortal' interactions
   */
  function getPositionPortalInfo(
    bytes32 _positionId
  )
    external
    view
    returns (PositionPhase positionPhase, uint64 timestamp, address trader)
  {
    return (
      positionsById[_positionId].phase,
      positionsById[_positionId].inPhaseSince,
      positionIdentifiersById[_positionId].trader
    );
  }

  /**
   * Builds the structs with info needed for 'PoolAccountnat' interactions
   */
  function getPositionRegistrationParams(
    bytes32 _positionId
  )
    public
    view
    returns (
      PoolAccountantStructs.PositionRegistrationParams memory registrationParams
    )
  {
    Position memory position = positionsById[_positionId];

    registrationParams.collateral = position.collateral;
    registrationParams.leverage = position.leverage;
    registrationParams.openPrice = position.openPrice;
    registrationParams.long = position.long;
    registrationParams.tp = positionLimitsInfoById[_positionId].tp;

    return registrationParams;
  }

  /**
   * @return true if the given asset has a Lex defined for it
   */
  function isSettlementAssetSupported(
    address settlementAsset
  ) public view returns (bool) {
    return lexPoolForAsset[settlementAsset] != address(0);
  }

  /**
   * @return An array of all addresses with an active/pending position in the given SA+pair combination
   */
  function pairTradersArray(
    address _asset,
    uint _pairIndex
  ) external view returns (address[] memory) {
    return pairTraders[_asset][_pairIndex];
  }

  // ***** Initialization functions *****

  /**
   * @notice Part of the Proxy mechanism
   */
  function _become(TradingFloorProxy tradingFloorProxy) public {
    require(msg.sender == tradingFloorProxy.admin(), "!proxy.admin");
    require(tradingFloorProxy._acceptImplementation() == 0, "fail");
  }

  // ***** Registry Functions *****

  /**
   * Adds a new settlement asset to the trading floor with the given LexPool and PoolAccountant
   * @dev This function is only callable by the registry
   */
  function supportNewSettlementAsset(
    address _asset,
    address _lexPool,
    address _poolAccountant
  ) external {
    require(msg.sender == registry, "!Registry");

    require(lexPoolForAsset[_asset] == address(0), "ASSET_ALREADY_SUPPORTED");

    // Store addresses
    lexPoolForAsset[_asset] = _lexPool;
    poolAccountantForAsset[_asset] = _poolAccountant;

    emit SettlementAssetAdded(_asset, _lexPool, _poolAccountant);
  }

  // ***** Admin Functions *****

  /**
   * Setter for numeric parameters
   */
  function setTradeParam(
    AdminNumericParam numericParam,
    uint value
  ) external onlyAdmin {
    require(value > 0, "CANNOT_BE_ZERO");
    string memory name;

    if (numericParam == AdminNumericParam.MAX_TRADES_PER_PAIR) {
      name = "maxTradesPerPair";
      maxTradesPerPair = value;
    } else if (numericParam == AdminNumericParam.MAX_SL_F) {
      // Note: Forcing a value of 50% or above
      require(value >= (FRACTION_SCALE * 50) / 100, "TRADE_PARAM_RESTRICTION");
      name = "maxSlF";
      maxSlF = value;
    } else if (numericParam == AdminNumericParam.MAX_SANITY_PROFIT_F) {
      // Note : Forcing a value of 200% or above
      require(value >= 2 * FRACTION_SCALE, "TRADE_PARAM_RESTRICTION");
      name = "maxSanityProfitF";
      maxSanityProfitF = value;
    } else {
      revert("UNSUPPORTED");
    }

    emit NumberUpdated(name, value);
  }

  /**
   * Allows to pause/unpause the opening/updating of positions in the given pair
   */
  function setPairPaused(uint _pairId, bool _isPaused) external onlyAdmin {
    pausedPairs[_pairId] = _isPaused;
    emit PairPausedChange(_pairId, _isPaused);
  }

  // ***** Traders Portal Interaction *****

  /**
   * This function stores the request for opening a new Position
   * @return positionId The id of the new position (pending to be opened)
   */
  function storePendingPosition(
    OpenOrderType _orderType,
    PositionRequestIdentifiers memory _requestIdentifiers,
    PositionRequestParams memory _requestParams,
    uint32 _spreadReductionF
  ) external override onlyTradersPortal returns (bytes32 positionId) {
    require(
      isSettlementAssetSupported(_requestIdentifiers.settlementAsset),
      "NON_SUPPORTED_SETTLEMENT_ASSET"
    );
    require(!pausedPairs[_requestIdentifiers.pairId], "PAIR_PAUSED");

    require(
      _orderType == OpenOrderType.MARKET || _orderType == OpenOrderType.LIMIT,
      "UNSUPPORTED_ORDER_TYPE"
    );

    requireValidOpenTradeParameters(
      _requestParams.long,
      _requestParams.minPrice,
      _requestParams.maxPrice,
      _requestParams.tp,
      _requestParams.sl
    );

    require(
      pairTradersInfo[_requestIdentifiers.settlementAsset][
        _requestIdentifiers.trader
      ][_requestIdentifiers.pairId].positionsCounter < maxTradesPerPair,
      "MAX_TRADES_PER_PAIR"
    );

    require(
      _requestIdentifiers.positionIndex <= maxTradesPerPair &&
        _requestIdentifiers.positionIndex != 0,
      "INVALID_INDEX"
    );

    positionId = storeIdentifiersIfNeeded(
      _requestIdentifiers.settlementAsset,
      _requestIdentifiers.trader,
      _requestIdentifiers.pairId,
      _requestIdentifiers.positionIndex
    );

    takeSettlement(
      _requestIdentifiers.settlementAsset,
      _requestIdentifiers.trader,
      _requestParams.collateral
    );

    // Add/Increase in pair traders
    increaseOrAddToPairTradersLists(
      _requestIdentifiers.settlementAsset,
      _requestIdentifiers.trader,
      _requestIdentifiers.pairId
    );

    Position storage position = positionsById[positionId];
    require(position.collateral == 0, "ID_USED");

    position.spreadReductionF = _spreadReductionF;

    position.long = _requestParams.long;
    position.leverage = _requestParams.leverage;
    position.collateral = _requestParams.collateral;

    PositionPhase phase = _orderType == OpenOrderType.MARKET
      ? PositionPhase.OPEN_MARKET
      : PositionPhase.OPEN_LIMIT;
    position.phase = phase;
    position.inPhaseSince = uint64(block.timestamp);

    PositionLimitsInfo storage positionLimitInfo = positionLimitsInfoById[
      positionId
    ];

    positionLimitInfo.tp = _requestParams.tp;
    positionLimitInfo.sl = _requestParams.sl;

    PositionTriggerPrices storage triggerPrices = triggerPricesById[positionId];
    triggerPrices.minPrice = _requestParams.minPrice;
    triggerPrices.maxPrice = _requestParams.maxPrice;
    triggerPrices.tpByFraction = _requestParams.tpByFraction;
    triggerPrices.slByFraction = _requestParams.slByFraction;

    emit PendingPositionStored(
      positionId,
      phase,
      _requestIdentifiers,
      _requestParams,
      _spreadReductionF
    );
  }

  /**
   * Sets the given position to be market closed
   */
  function setOpenedPositionToMarketClose(
    bytes32 _positionId,
    uint64 _minPrice,
    uint64 _maxPrice
  ) external override onlyTradersPortal {
    // note : using 'storage' to reduce gas cost and contract size
    Position storage position = positionsById[_positionId];
    require(position.collateral > 0, "NO_SUCH_POSITION");

    require(position.phase == PositionPhase.OPENED, "WRONG_PHASE");
    position.phase = PositionPhase.CLOSE_MARKET;
    position.inPhaseSince = uint64(block.timestamp);

    PositionTriggerPrices storage triggerPrices = triggerPricesById[
      _positionId
    ];
    triggerPrices.minPrice = _minPrice;
    triggerPrices.maxPrice = _maxPrice;

    emit PositionSetForMarketClose(_positionId, _minPrice, _maxPrice);
  }

  /**
   * Updates one of the fields in a pending LIMIT_OPEN position
   */
  function updatePendingPosition_openLimit(
    bytes32 _positionId,
    uint64 _minPrice,
    uint64 _maxPrice,
    uint64 _tp,
    uint64 _sl
  ) external override onlyTradersPortal {
    Position storage _position = positionsById[_positionId];

    require(_position.phase == PositionPhase.OPEN_LIMIT, "WRONG_PHASE");
    require(
      !pausedPairs[positionIdentifiersById[_positionId].pairId],
      "PAIR_PAUSED"
    );

    requireValidOpenTradeParameters(
      _position.long,
      _minPrice,
      _maxPrice,
      _tp,
      _sl
    );

    // Update the pending position timestamp
    _position.inPhaseSince = uint64(block.timestamp);

    // Update the position locally
    PositionLimitsInfo storage _positionLimitInfo = positionLimitsInfoById[
      _positionId
    ];
    _positionLimitInfo.tp = _tp;
    _positionLimitInfo.sl = _sl;
    // Note : Not updating the timestamps as they only need to be updated for Opened positions

    PositionTriggerPrices storage triggerPrices = triggerPricesById[
      _positionId
    ];
    triggerPrices.minPrice = _minPrice;
    triggerPrices.maxPrice = _maxPrice;

    emit PendingPositionUpdated(_positionId, _tp, _sl, _minPrice, _maxPrice);
  }

  // ***** Triggers Interaction *****

  /**
   * Called by the triggers in order to Open a new position by market price
   */
  function openNewPosition_market(
    bytes32 _positionId,
    uint64 assetEffectivePrice,
    uint256 feeForCancellation
  ) external onlyTriggers {
    openNewTradeInternal(
      _positionId,
      PositionPhase.OPEN_MARKET,
      assetEffectivePrice,
      feeForCancellation
    );
  }

  /**
   * Called by the triggers in order to Open a new position by reached limit
   */
  function openNewPosition_limit(
    bytes32 _positionId,
    uint64 assetEffectivePrice,
    uint256 feeForCancellation
  ) external onlyTriggers {
    openNewTradeInternal(
      _positionId,
      PositionPhase.OPEN_LIMIT,
      assetEffectivePrice,
      feeForCancellation
    );
  }

  /**
   * Called by the triggers in order to Close an existing position by market price
   */
  function closeExistingPosition_Market(
    bytes32 _positionId,
    uint64, // assetPrice
    uint64 effectivePrice
  ) external override onlyTriggers {
    PositionTriggerPrices memory triggerPrices = triggerPricesById[_positionId];
    require(triggerPrices.maxPrice > 0, "NO_SUCH_POSITION");

    if (
      triggerPrices.minPrice > effectivePrice ||
      triggerPrices.maxPrice < effectivePrice
    ) {
      cancelMarketCloseForPositionInternal(
        address(this),
        _positionId,
        CloseOrderType.MARKET
      );
    } else {
      (
        uint tradeValue,
        int profitPrecision,
        uint finalClosingPrice
      ) = closeExistingTradeInternal(
          _positionId,
          effectivePrice,
          PositionCloseType.MARKET
        );

      emit PositionClosedMarket(
        _positionId,
        finalClosingPrice,
        tradeValue,
        profitPrecision
      );
    }
  }

  /**
   * Called by the triggers in order to Close an existing position by a reached limit value
   */
  function closeExistingPosition_Limit(
    bytes32 _positionId,
    LimitTrigger limitTrigger,
    uint64, // assetPrice
    uint64 effectivePrice
  ) external override onlyTriggers {
    Position memory position = positionsById[_positionId];
    require(position.collateral > 0, "NO_SUCH_POSITION");

    PositionLimitsInfo memory positionLimitInfo = positionLimitsInfoById[
      _positionId
    ];

    bool triggerValid = false;
    uint effectiveClosingPrice;
    PositionCloseType positionCloseType;

    if (limitTrigger == LimitTrigger.SL) {
      triggerValid = position.long
        ? effectivePrice <= positionLimitInfo.sl
        : effectivePrice >= positionLimitInfo.sl;
      effectiveClosingPrice = positionLimitInfo.sl;
      positionCloseType = PositionCloseType.SL;
    } else if (limitTrigger == LimitTrigger.TP) {
      triggerValid = position.long
        ? effectivePrice >= positionLimitInfo.tp
        : effectivePrice <= positionLimitInfo.tp;
      effectiveClosingPrice = positionLimitInfo.tp;
      positionCloseType = PositionCloseType.TP;
    } else if (limitTrigger == LimitTrigger.LIQ) {
      // Note : The Accountant will be the one to adjust the price for liquidation
      triggerValid = true;
      effectiveClosingPrice = effectivePrice;
      positionCloseType = PositionCloseType.LIQ;
    } else {
      revert("WRONG_LIMIT_TRIGGER");
    }

    // Revert if the conditions are not met for triggering
    require(triggerValid, "FALSE_TRIGGER");

    (
      uint tradeValue,
      int profitPrecision,
      uint finalClosingPrice
    ) = closeExistingTradeInternal(
        _positionId,
        effectiveClosingPrice,
        positionCloseType
      );

    // Checking again for liq to allow quicker failure in TP and SL
    if (positionCloseType == PositionCloseType.LIQ) {
      triggerValid = position.long
        ? effectivePrice <= finalClosingPrice
        : effectivePrice >= finalClosingPrice;
      require(triggerValid, "FALSE_TRIGGER");
    }

    emit PositionClosedLimit(
      _positionId,
      limitTrigger,
      effectivePrice,
      finalClosingPrice,
      tradeValue,
      profitPrecision
    );
  }

  /**
   * Updates a PositionField of an OPEN position
   */
  function updateOpenedPosition(
    bytes32 positionId,
    PositionField updateField,
    uint64 fieldValue,
    uint64 effectivePrice
  ) external onlyTriggers {
    Position storage p = positionsById[positionId];
    PositionLimitsInfo storage limitInfo = positionLimitsInfoById[positionId];
    PositionIdentifiers memory identifiers = positionIdentifiersById[
      positionId
    ];

    require(p.collateral > 0, "NO_SUCH_POSITION");
    require(p.phase == PositionPhase.OPENED, "WRONG_PHASE");

    IPoolAccountantV1 poolAccountant = IPoolAccountantV1(
      poolAccountantForAsset[identifiers.settlementAsset]
    );

    uint64 tpToUse;
    uint64 slToUse;

    if (updateField == PositionField.TP) {
      uint64 correctedTp = correctTp(
        uint64(poolAccountant.maxGainF()),
        p.openPrice,
        p.leverage,
        fieldValue,
        p.long
      );

      // Sanity
      require(correctedTp == fieldValue, "BAD_FIELD_VALUE");
      require(correctedTp != limitInfo.tp, "SAME_TP");
      tpToUse = correctedTp;
      slToUse = limitInfo.sl;
      limitInfo.tpLastUpdated = uint64(block.timestamp);

      // Register the change in the LexPool
      // Might revert if a cap is reached
      poolAccountant.registerUpdateTp(
        positionId,
        identifiers.trader,
        identifiers.pairId,
        p.collateral,
        p.leverage,
        p.long,
        p.openPrice,
        limitInfo.tp,
        tpToUse
      );
    } else if (updateField == PositionField.SL) {
      uint64 correctedSl = correctSl(
        uint64(poolAccountant.maxGainF()),
        p.openPrice,
        p.leverage,
        fieldValue,
        p.long
      );

      // Sanity
      require(correctedSl == fieldValue, "BAD_FIELD_VALUE");
      require(correctedSl != limitInfo.sl, "SAME_SL");

      tpToUse = limitInfo.tp;
      slToUse = correctedSl;
      limitInfo.slLastUpdated = uint64(block.timestamp);
    } else {
      revert("UNSUPPORTED");
    }

    uint effectiveMinPrice = p.openPrice > effectivePrice
      ? effectivePrice
      : p.openPrice;
    uint effectiveMaxPrice = p.openPrice < effectivePrice
      ? effectivePrice
      : p.openPrice;

    // Ensure the new params are valid
    requireValidOpenTradeParameters(
      p.long,
      effectiveMinPrice,
      effectiveMaxPrice,
      tpToUse,
      slToUse
    );

    limitInfo.sl = slToUse;
    limitInfo.tp = tpToUse;

    emit OpenedPositionUpdated(
      positionId,
      identifiers.pairId,
      p.long,
      updateField,
      fieldValue
    );
  }

  // ***** Traders Portal/Triggers Shared Interaction *****

  /**
   * Cancel a pending open position, returning assets to trader.
   */
  function cancelPendingPosition(
    bytes32 _positionId,
    OpenOrderType _orderType,
    uint feeFraction
  ) external override onlyTradersPortalOrTriggers {
    require(feeFraction <= MAX_FEE_FRACTION_FOR_CANCEL, "FEE_FRACTION_TOO_BIG");

    //        PendingOpenTradeOrder memory _order = OrderBookInterfaceV1(orderBook).readAndDeleteOpenOrder(_positionId, _orderType);

    Position memory position = positionsById[_positionId];
    require(positionsById[_positionId].collateral > 0, "NO_SUCH_POSITION");
    PositionIdentifiers memory identifiers = positionIdentifiersById[
      _positionId
    ];

    if (_orderType == OpenOrderType.MARKET) {
      require(position.phase == PositionPhase.OPEN_MARKET, "NOT_MARKET_ORDER");
    } else if (_orderType == OpenOrderType.LIMIT) {
      require(position.phase == PositionPhase.OPEN_LIMIT, "NOT_LIMIT_ORDER");
    } else {
      revert("WRONG_ORDER_TYPE");
    }

    settleCanceledOpenOrderInternal(
      _positionId,
      identifiers.settlementAsset,
      identifiers.trader,
      identifiers.pairId,
      position.collateral,
      position.leverage,
      feeFraction,
      msg.sender
    );
  }

  /**
   * Cancel a CLOSE_MARKET position, returning the position to an OPEN phase.
   * @dev Currently no fee is being taken for this action
   */
  function cancelMarketCloseForPosition(
    bytes32 _positionId,
    CloseOrderType _orderType,
    uint // feeFraction
  ) external override onlyTradersPortalOrTriggers {
    cancelMarketCloseForPositionInternal(msg.sender, _positionId, _orderType);
  }

  /**
   * Handles the cancellation of a "market close" order
   */
  function cancelMarketCloseForPositionInternal(
    address source,
    bytes32 _positionId,
    CloseOrderType _orderType
  ) internal {
    require(positionsById[_positionId].collateral > 0, "NO_SUCH_POSITION");

    delete triggerPricesById[_positionId];

    if (_orderType == CloseOrderType.MARKET) {
      require(
        positionsById[_positionId].phase == PositionPhase.CLOSE_MARKET,
        "WRONG_PHASE"
      );
    } else {
      revert("WRONG_ORDER_TYPE");
    }

    positionsById[_positionId].phase = PositionPhase.OPENED;
    positionsById[_positionId].inPhaseSince = uint64(block.timestamp);

    emit PositionMarketCloseCancelled(_positionId, source, 0);
  }

  // ***** Fees Manager Interaction *****

  /**
   * Sends all fee accrued in the SA+feeType to the '_to' address
   * @dev Allows a dynamic and flexible way to direct fees.
   */
  function collectFee(address _asset, FeeType _feeType, address _to) external {
    require(
      msg.sender == IRegistryV1(registry).feesManagers(_asset),
      "!FeesManager"
    );
    collectFeeInternal(_asset, _feeType, _to);
  }

  // ***** Internal Position Open/Close logic *****

  /**
   * Stores the identifying values of the position.
   * @dev Saves gas after the first time a SA-trader-pair-index position was registered in this contract
   * @return positionId The position id that matched these identifiers
   */
  function storeIdentifiersIfNeeded(
    address settlementAsset,
    address trader,
    uint16 pairId,
    uint32 index
  ) internal returns (bytes32 positionId) {
    positionId = generatePositionHashId(settlementAsset, trader, pairId, index);

    PositionIdentifiers storage identifiers = positionIdentifiersById[
      positionId
    ];

    // Store the identifier once
    if (identifiers.index == 0) {
      identifiers.settlementAsset = settlementAsset;
      identifiers.trader = trader;
      identifiers.pairId = pairId;
      identifiers.index = index;

      emit PositionIdentifiersStored(positionId, identifiers);
    }
  }

  /**
   * Handles verifications and logic for the opening of a new position.
   * @dev This function will "swallow" the "CapError" custom error that can be thrown by the 'PoolAccountant' contract
   *      and in such case will proceed to cancel the open order.
   */
  function openNewTradeInternal(
    bytes32 _positionId,
    PositionPhase _expectedPhase,
    uint64 assetEffectivePrice,
    uint256 feeForCancellation
  ) internal {
    PositionTriggerPrices memory triggerPrices = triggerPricesById[_positionId];

    Position memory _position = positionsById[_positionId];
    require(_position.collateral > 0, "NO_SUCH_POSITION");
    PositionIdentifiers memory identifiers = positionIdentifiersById[
      _positionId
    ];
    require(!pausedPairs[identifiers.pairId], "PAIR_PAUSED");

    require(_position.phase == _expectedPhase, "WRONG_PHASE");

    if (_expectedPhase == PositionPhase.OPEN_MARKET) {
      if (
        triggerPrices.minPrice > assetEffectivePrice ||
        triggerPrices.maxPrice < assetEffectivePrice
      ) {
        emit PositionOpenCancelledByMarketPriceRange(
          _positionId,
          assetEffectivePrice
        );
        settleCanceledOpenOrderInternal(
          _positionId,
          identifiers.settlementAsset,
          identifiers.trader,
          identifiers.pairId,
          _position.collateral,
          _position.leverage,
          feeForCancellation,
          address(this)
        );
        return;
      }
    } else if (_expectedPhase == PositionPhase.OPEN_LIMIT) {
      // Limit Orders cannot be triggered if the price is wrong
      require(
        triggerPrices.minPrice <= assetEffectivePrice &&
          triggerPrices.maxPrice >= assetEffectivePrice,
        "PRICE_RANGE"
      );
    } else {
      revert("UNSUPPORTED");
    }

    address poolAccountant = poolAccountantForAsset[
      identifiers.settlementAsset
    ];
    PositionLimitsInfo
      memory positionLimits = storeCorrectLimitsForOpenedPosition(
        _positionId,
        poolAccountant,
        _position.leverage,
        _position.long,
        assetEffectivePrice,
        triggerPrices
      );

    try
      IPoolAccountantFunctionality(poolAccountant).registerOpenTrade(
        _positionId,
        identifiers.trader,
        identifiers.pairId,
        _position.collateral,
        _position.leverage,
        _position.long,
        positionLimits.tp,
        assetEffectivePrice
      )
    returns (uint openFeePart, uint lexFeePart) {
      Position storage _positionStorage = positionsById[_positionId];
      _positionStorage.openPrice = assetEffectivePrice;

      // Store the position's initial collateral
      initialCollateralByPositionId[_positionId] = _position.collateral;

      // Note : Subtracting the open fee before storing the position
      _positionStorage.collateral = _position.collateral - openFeePart;

      // Mark the position as opened
      _positionStorage.phase = PositionPhase.OPENED;
      _positionStorage.inPhaseSince = uint64(block.timestamp);

      registerFeeInternal(
        _positionId,
        identifiers.settlementAsset,
        FeeType.OPEN_FEE,
        openFeePart - lexFeePart
      );
      sendSettlement(
        identifiers.settlementAsset,
        lexPoolForAsset[identifiers.settlementAsset],
        lexFeePart
      );

      delete triggerPricesById[_positionId];

      emit PositionOpened(
        _positionId,
        assetEffectivePrice,
        positionLimits.tp,
        positionLimits.sl,
        openFeePart,
        lexFeePart
      );
    } catch Error(string memory error) {
      revert(error);
    } catch (bytes memory err) {
      if (bytes4(err) == LexErrors.CapError.selector) {
        LexErrors.CapType capType;
        uint256 value;
        assembly {
          capType := mload(add(err, 0x24))
          value := mload(add(err, 0x44))
        }
        emit PositionOpenCancelledByCap(_positionId, capType, value);
        settleCanceledOpenOrderInternal(
          _positionId,
          identifiers.settlementAsset,
          identifiers.trader,
          identifiers.pairId,
          _position.collateral,
          _position.leverage,
          feeForCancellation,
          address(this)
        );
      } else {
        revert();
      }
    }
  }

  /**
   * Stores the limits values of a newly opened position (after correcting them to the valid ranges if needed)
   */
  function storeCorrectLimitsForOpenedPosition(
    bytes32 positionId,
    address poolAccountant,
    uint32 leverage,
    bool isLong,
    uint64 assetEffectivePrice,
    PositionTriggerPrices memory triggerPrices
  ) internal returns (PositionLimitsInfo memory positionLimits) {
    uint maxGainF = IPoolAccountantV1(poolAccountant).maxGainF();

    positionLimits = positionLimitsInfoById[positionId];

    require(
      positionLimits.tp == 0 || triggerPrices.tpByFraction == 0,
      "MULTIPLE_TP_DEFINITIONS"
    );
    require(
      positionLimits.sl == 0 || triggerPrices.slByFraction == 0,
      "MULTIPLE_SL_DEFINITIONS"
    );

    if (triggerPrices.tpByFraction > 0) {
      uint64 priceDiff = calculatePriceDiffFromFractionAndLeverage(
        assetEffectivePrice,
        triggerPrices.tpByFraction,
        leverage
      );

      positionLimits.tp = isLong
        ? assetEffectivePrice + priceDiff
        : priceDiff < assetEffectivePrice
          ? assetEffectivePrice - priceDiff
          : 0;
    }

    if (triggerPrices.slByFraction > 0) {
      uint64 priceDiff = calculatePriceDiffFromFractionAndLeverage(
        assetEffectivePrice,
        triggerPrices.slByFraction,
        leverage
      );

      positionLimits.sl = isLong
        ? priceDiff < assetEffectivePrice
          ? assetEffectivePrice - priceDiff
          : 0
        : assetEffectivePrice + priceDiff;
    }

    positionLimits.tp = correctTp(
      uint64(maxGainF),
      assetEffectivePrice,
      leverage,
      positionLimits.tp,
      isLong
    );

    positionLimits.tpLastUpdated = uint64(block.timestamp);

    positionLimits.sl = correctSl(
      uint64(maxGainF),
      assetEffectivePrice,
      leverage,
      positionLimits.sl,
      isLong
    );
    positionLimits.slLastUpdated = uint64(block.timestamp);

    // Store
    positionLimitsInfoById[positionId] = positionLimits;
  }

  /**
   * Handles the all closing types of an existing opened position.
   */
  function closeExistingTradeInternal(
    bytes32 _positionId,
    uint effectivePrice,
    PositionCloseType positionCloseType
  )
    internal
    returns (uint tradeValue, int profitPrecision, uint finalClosingPrice)
  {
    PositionIdentifiers memory identifiers = positionIdentifiersById[
      _positionId
    ];

    PoolAccountantStructs.PositionRegistrationParams
      memory positionRegistrationParams = getPositionRegistrationParams(
        _positionId
      );

    require(positionRegistrationParams.collateral > 0, "NO_SUCH_POSITION");

    // Note : 'tradeValue' is the value after subtracting the 'closeFeePart'
    (
      uint closeFeePart,
      uint _tradeValue,
      int _profitPrecision,
      uint _finalClosePrice
    ) = registerTradeCloseInLexInternal(
        _positionId,
        identifiers,
        positionRegistrationParams,
        effectivePrice,
        positionCloseType
      );

    if (_tradeValue > positionRegistrationParams.collateral) {
      uint totalProfitF = ((_tradeValue -
        positionRegistrationParams.collateral) * FRACTION_SCALE) /
        positionRegistrationParams.collateral;
      require(totalProfitF <= maxSanityProfitF, "INVALID_PROFIT");
    }

    profitPrecision = _profitPrecision;
    tradeValue = _tradeValue;
    finalClosingPrice = _finalClosePrice;

    // sanity
    require(
      closeFeePart <= positionRegistrationParams.collateral,
      "CLOSE_FEE_LARGER_THAN_POSITION"
    );
    registerFeeInternal(
      _positionId,
      identifiers.settlementAsset,
      FeeType.CLOSE_FEE,
      closeFeePart
    );

    // Decrease/Remove from the pair traders
    decreaseOrRemoveFromPairTradersLists(
      identifiers.settlementAsset,
      identifiers.trader,
      identifiers.pairId
    );

    // Delete the position
    delete positionsById[_positionId];
    delete positionLimitsInfoById[_positionId];
    delete initialCollateralByPositionId[_positionId];

    // Settle the position
    settleTradeCloseInternal(
      identifiers.trader,
      identifiers.settlementAsset,
      tradeValue,
      positionRegistrationParams.collateral,
      closeFeePart
    );
  }

  // **** LeX-Center Interaction ****

  /**
   * Utility function to inform the "PoolAccountant" that a position is being closed and retrieve it's closing values.
   * @dev Using a separate function to bypass the "stack too depp" issue.
   */
  function registerTradeCloseInLexInternal(
    bytes32 _positionId,
    PositionIdentifiers memory _identifiers,
    PoolAccountantStructs.PositionRegistrationParams
      memory positionRegistrationParams,
    uint closePrice,
    PositionCloseType positionCloseType
  )
    internal
    returns (
      uint closeFeePart,
      uint tradeValue,
      int profitPrecision,
      uint finalClosePrice
    )
  {
    return
      IPoolAccountantFunctionality(
        poolAccountantForAsset[_identifiers.settlementAsset]
      ).registerCloseTrade(
          _positionId,
          _identifiers.trader,
          _identifiers.pairId,
          positionRegistrationParams,
          closePrice,
          positionCloseType
        );
  }

  // **** Trade Closing Settlement ****

  /**
   * Handles the logic for cancellation of a position in any "pending open" for any reason (trader request/timeout/cap error etc...)
   */
  function settleCanceledOpenOrderInternal(
    bytes32 positionId,
    address settlementAsset,
    address trader,
    uint16 pairId,
    uint collateral,
    uint32 leverage,
    uint feeF,
    address canceller
  ) internal {
    delete positionsById[positionId];

    delete triggerPricesById[positionId];

    uint cancellationFee = calculateFractionInternal(
      calculateLeveragedPosition(collateral, leverage),
      feeF
    );

    registerFeeInternal(
      positionId,
      settlementAsset,
      FeeType.TRIGGER_FEE,
      cancellationFee
    );

    // Decrease/Remove from the pair traders
    decreaseOrRemoveFromPairTradersLists(settlementAsset, trader, pairId);

    uint collateralLeft = collateral - cancellationFee;

    sendSettlement(settlementAsset, trader, collateralLeft);

    emit PendingPositionCancelled(positionId, canceller, cancellationFee);
  }

  /**
   * Handles the asset transferring of a closing position.
   */
  function settleTradeCloseInternal(
    address trader,
    address settlementAsset,
    uint tradeValue,
    uint tradeCollateral,
    uint closingFee
  ) internal {
    ILexPoolV1 lexToken = ILexPoolV1(lexPoolForAsset[settlementAsset]);
    uint assetForTraderFromFloor;

    // Trade gain
    if (tradeValue >= tradeCollateral) {
      // Note : The closing fee stays in the TradingFloor
      assetForTraderFromFloor = tradeCollateral - closingFee;

      uint assetForTraderFromPool = tradeValue - assetForTraderFromFloor;

      lexToken.sendAssetToTrader(trader, assetForTraderFromPool);
    }
    // Trade loss
    else {
      assetForTraderFromFloor = tradeValue;

      uint diff = tradeCollateral - tradeValue;

      if (diff > closingFee) {
        // Send to the lex
        sendSettlement(settlementAsset, address(lexToken), diff - closingFee);
      } else {
        // Take the missing amount for the 'closingFee' from the lex
        lexToken.sendAssetToTrader(address(this), closingFee - diff);
      }
    }

    sendSettlement(settlementAsset, trader, assetForTraderFromFloor);
  }

  // ***** Internal Fees *****

  /**
   * Sends all fees collected in for the SA+FeeType and zeros the counter
   */
  function collectFeeInternal(
    address _asset,
    FeeType _feeType,
    address _to
  ) internal {
    uint amount = feesMap[_asset][_feeType];
    feesMap[_asset][_feeType] = 0;
    IERC20(_asset).safeTransfer(_to, amount);

    emit FeeCollected(_asset, _feeType, _to, amount);
  }

  /**
   * Adds '_amount' to the SA+FeeType counter
   */
  function registerFeeInternal(
    bytes32 _positionId,
    address _token,
    FeeType _feeType,
    uint _amount
  ) internal {
    if (_amount > 0) {
      feesMap[_token][_feeType] += _amount;
      emit FeeRegistered(_positionId, _token, _feeType, _amount);
    }
  }

  // ***** Internal Tokens Utils *****

  /**
   * Utility function to safely take an ERC20 settlement asset from a pre-approved account
   */
  function takeSettlement(
    address settlementAsset,
    address from,
    uint amount
  ) internal {
    if (amount > 0) {
      uint balanceBefore = IERC20(settlementAsset).balanceOf(address(this));
      IERC20(settlementAsset).safeTransferFrom(from, address(this), amount);
      uint balanceAfter = IERC20(settlementAsset).balanceOf(address(this));
      require(balanceAfter - balanceBefore == amount, "DID_NOT_RECEIVE_EXACT");
    }
  }

  /**
   * Utility function to safely send an ERC20 settlement asset to an account
   */
  function sendSettlement(
    address settlementAsset,
    address to,
    uint amount
  ) internal {
    if (amount > 0) {
      IERC20(settlementAsset).safeTransfer(to, amount);
    }
  }

  // ***** Internal State Utils *****

  /**
   * Handles counter increment for positionsCounter and addition to the 'pairTraders' list in case this is the
   * trader's first position in this SA+pair
   */
  function increaseOrAddToPairTradersLists(
    address settlementAsset,
    address trader,
    uint16 pairId
  ) internal {
    pairTradersInfo[settlementAsset][trader][pairId].positionsCounter++;

    if (
      pairTradersInfo[settlementAsset][trader][pairId].positionsCounter == 1
    ) {
      pairTraders[settlementAsset][pairId].push(trader);
      pairTradersInfo[settlementAsset][trader][pairId].positionInArray = uint32(
        pairTraders[settlementAsset][pairId].length
      );
    }
  }

  /**
   * Handles counter reduction for positionsCounter and complete removal from the 'pairTraders' list in case the
   * trader has no more positions in this SA+pair
   */
  function decreaseOrRemoveFromPairTradersLists(
    address settlementAsset,
    address trader,
    uint16 pairId
  ) internal {
    if (
      pairTradersInfo[settlementAsset][trader][pairId].positionsCounter == 1
    ) {
      address[] storage p = pairTraders[settlementAsset][pairId];

      if (p.length > 1) {
        uint32 _pairTradersPosition = pairTradersInfo[settlementAsset][trader][
          pairId
        ].positionInArray;

        p[_pairTradersPosition - 1] = p[p.length - 1];
        pairTradersInfo[settlementAsset][p[_pairTradersPosition - 1]][pairId]
          .positionInArray = _pairTradersPosition;
      }

      delete pairTradersInfo[settlementAsset][trader][pairId];
      p.pop();
    } else {
      pairTradersInfo[settlementAsset][trader][pairId].positionsCounter--;
    }
  }

  // ***** Internal Calculation Utils *****

  /**
   * Utility function to calculate a 'FRACTION_SCALE' value of a given amount
   * @return The fraction value out of the given amount
   */
  function calculateFractionInternal(
    uint amount,
    uint feeFraction
  ) internal pure returns (uint) {
    return (amount * feeFraction) / FRACTION_SCALE;
  }

  // Trade validity functions

  /**
   * Runs some sanity requires to ensure the position values are in the right range
   */
  function requireValidOpenTradeParameters(
    bool isLong,
    uint minPrice,
    uint maxPrice,
    uint tp,
    uint sl
  ) internal pure {
    require(minPrice <= maxPrice, "MIN_MAX_REVERSE");
    require(tp == 0 || (isLong ? tp > maxPrice : tp < minPrice), "WRONG_TP");
    require(sl == 0 || (isLong ? sl < minPrice : sl > maxPrice), "WRONG_SL");
  }

  /**
   * Receives the wanted sl for a position (with other relevant params) and makes sure the value is within the expected range
   * @notice In case of TP == 0 or a breach of max value, the value returned will be the max allowed value
   *         (can be capped by 0 in case of a SHORT position)
   */
  function correctTp(
    uint64 maxGainF,
    uint64 openPrice,
    uint64 leverage, // scaled up from 32
    uint64 tp,
    bool buy
  ) internal pure returns (uint64) {
    if (
      tp == 0 ||
      currentProfitFraction(maxGainF, openPrice, tp, buy, leverage) >=
      int64(maxGainF)
    ) {
      uint64 tpDiff = uint64(
        ((uint256(openPrice) * uint256(maxGainF)) * LEVERAGE_SCALE) /
          uint256(leverage) /
          FRACTION_SCALE
      );

      return
        buy
          ? openPrice + tpDiff
          : tpDiff <= openPrice
            ? openPrice - tpDiff
            : 0;
    }
    return tp;
  }

  /**
   * Receives the wanted sl for a position (with other relevant params) and makes sure the value is within the expected range
   * @notice In case of SL == 0 or a breach of max value, the value returned will be the max allowed value
   *         (can be capped by 0 in case of a LONG position)
   */
  function correctSl(
    uint64 maxGainF,
    uint64 openPrice,
    uint64 leverage, // scaled up from 32
    uint64 sl,
    bool buy
  ) internal view returns (uint64) {
    if (
      sl == 0 ||
      currentProfitFraction(maxGainF, openPrice, sl, buy, leverage) <=
      int(maxSlF) * -1
    ) {
      uint64 slDiff = uint64(
        (uint256(openPrice) * maxSlF * LEVERAGE_SCALE) /
          uint256(leverage) /
          FRACTION_SCALE
      );

      return
        buy
          ? slDiff <= openPrice
            ? openPrice - slDiff
            : 0
          : openPrice + slDiff;
    }

    return sl;
  }

  /**
   * Calculates the (positive or negative) profit fraction by given position values
   * @return f The profit fraction, with scale of FRACTION_SCALE
   */
  function currentProfitFraction(
    uint64 maxGainF,
    uint64 openPrice,
    uint64 currentPrice,
    bool buy,
    uint64 leverage // scaled from 32
  ) internal pure returns (int64 f) {
    int64 maxPnlF = int64(maxGainF);

    int64 priceDiff = buy
      ? int64(currentPrice) - int64(openPrice)
      : int64(openPrice) - int64(currentPrice);

    int256 nominator = int256(priceDiff) *
      int256(FRACTION_SCALE) *
      int256(int64(leverage));
    int256 longF = nominator /
      int256(LEVERAGE_SCALE) /
      int256(int64(openPrice));

    f = int64(longF);

    f = f > maxPnlF ? maxPnlF : f;
  }

  function calculatePriceDiffFromFractionAndLeverage(
    uint64 originPrice,
    uint64 fractionDiff,
    uint64 leverage
  ) internal pure returns (uint64) {
    uint64 diffInPrice = uint64(
      (uint256(originPrice) * uint256(fractionDiff) * LEVERAGE_SCALE) /
        FRACTION_SCALE /
        uint256(leverage)
    );
    return diffInPrice;
  }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):