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