diff --git a/src/SMv2SessionValidationModule.sol b/src/SMv2SessionValidationModule.sol index 371becd..294472f 100644 --- a/src/SMv2SessionValidationModule.sol +++ b/src/SMv2SessionValidationModule.sol @@ -1,7 +1,7 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.18; -import {IAccount} from "lib/smart-margin/src/interfaces/IAccount.sol"; +import {IAccount} from "src/kwenta/smv2/IAccount.sol"; import { ISessionValidationModule, UserOperation @@ -14,7 +14,6 @@ import {ECDSA} from "src/openzeppelin/ECDSA.sol"; * @author Fil Makarov - * @author JaredBorders (jaredborders@pm.me) */ - contract SMv2SessionValidationModule is ISessionValidationModule { error InvalidSelector(bytes4 selector); error InvalidSMv2ExecuteSelector(bytes4 selector); diff --git a/src/kwenta/smv2/IAccount.sol b/src/kwenta/smv2/IAccount.sol new file mode 100644 index 0000000..95da875 --- /dev/null +++ b/src/kwenta/smv2/IAccount.sol @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.18; + +/// @title Kwenta Smart Margin Account v2.1.0 Implementation Interface +/// @author JaredBorders (jaredborders@pm.me), JChiaramonte7 (jeremy@bytecode.llc) +interface IAccount { + /*/////////////////////////////////////////////////////////////// + Types + ///////////////////////////////////////////////////////////////*/ + + /// @notice Command Flags used to decode commands to execute + /// @dev under the hood ACCOUNT_MODIFY_MARGIN = 0, ACCOUNT_WITHDRAW_ETH = 1 + enum Command { + ACCOUNT_MODIFY_MARGIN, // 0 + ACCOUNT_WITHDRAW_ETH, + PERPS_V2_MODIFY_MARGIN, + PERPS_V2_WITHDRAW_ALL_MARGIN, + PERPS_V2_SUBMIT_ATOMIC_ORDER, + PERPS_V2_SUBMIT_DELAYED_ORDER, // 5 + PERPS_V2_SUBMIT_OFFCHAIN_DELAYED_ORDER, + PERPS_V2_CLOSE_POSITION, + PERPS_V2_SUBMIT_CLOSE_DELAYED_ORDER, + PERPS_V2_SUBMIT_CLOSE_OFFCHAIN_DELAYED_ORDER, + PERPS_V2_CANCEL_DELAYED_ORDER, // 10 + PERPS_V2_CANCEL_OFFCHAIN_DELAYED_ORDER, + GELATO_PLACE_CONDITIONAL_ORDER, + GELATO_CANCEL_CONDITIONAL_ORDER, + UNISWAP_V3_SWAP, + PERMIT2_PERMIT // 15 + } + + /// @notice denotes conditional order types for code clarity + /// @dev under the hood LIMIT = 0, STOP = 1 + enum ConditionalOrderTypes { + LIMIT, + STOP + } + + /// @notice denotes conditional order cancelled reasons for code clarity + /// @dev under the hood CONDITIONAL_ORDER_CANCELLED_BY_USER = 0, CONDITIONAL_ORDER_CANCELLED_NOT_REDUCE_ONLY = 1 + enum ConditionalOrderCancelledReason { + CONDITIONAL_ORDER_CANCELLED_BY_USER, + CONDITIONAL_ORDER_CANCELLED_NOT_REDUCE_ONLY + } + + /// @notice denotes what oracle is used for price when executing conditional orders + /// @dev under the hood PYTH = 0, CHAINLINK = 1 + enum PriceOracleUsed { + PYTH, + CHAINLINK + } + + /// @param factory: address of the Smart Margin Account Factory + /// @param events: address of the contract used by all accounts for emitting events + /// @param marginAsset: address of the Synthetix ProxyERC20sUSD contract used as the margin asset + /// @param perpsV2ExchangeRate: address of the Synthetix PerpsV2ExchangeRate + /// @param futuresMarketManager: address of the Synthetix FuturesMarketManager + /// @param systemStatus: address of the Synthetix SystemStatus + /// @param gelato: address of Gelato + /// @param ops: address of Ops + /// @param settings: address of contract used to store global settings + /// @param universalRouter: address of Uniswap's Universal Router + /// @param permit2: address of Uniswap's Permit2 + struct AccountConstructorParams { + address factory; + address events; + address marginAsset; + address perpsV2ExchangeRate; + address futuresMarketManager; + address systemStatus; + address gelato; + address ops; + address settings; + address universalRouter; + address permit2; + } + + /// marketKey: Synthetix PerpsV2 Market id/key + /// marginDelta: amount of margin to deposit or withdraw; positive indicates deposit, negative withdraw + /// sizeDelta: denoted in market currency (i.e. ETH, BTC, etc), size of Synthetix PerpsV2 position + /// targetPrice: limit or stop price target needing to be met to submit Synthetix PerpsV2 order + /// gelatoTaskId: unqiue taskId from gelato necessary for cancelling conditional orders + /// conditionalOrderType: conditional order type to determine conditional order fill logic + /// desiredFillPrice: desired price to fill Synthetix PerpsV2 order at execution time + /// reduceOnly: if true, only allows position's absolute size to decrease + struct ConditionalOrder { + bytes32 marketKey; + int256 marginDelta; + int256 sizeDelta; + uint256 targetPrice; + bytes32 gelatoTaskId; + ConditionalOrderTypes conditionalOrderType; + uint256 desiredFillPrice; + bool reduceOnly; + } + /// @dev see example below elucidating targtPrice vs desiredFillPrice: + /// 1. targetPrice met (ex: targetPrice = X) + /// 2. account submits delayed order to Synthetix PerpsV2 with desiredFillPrice = Y + /// 3. keeper executes Synthetix PerpsV2 order after delay period + /// 4. if current market price defined by Synthetix PerpsV2 + /// after delay period satisfies desiredFillPrice order is filled + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /// @notice thrown when commands length does not equal inputs length + error LengthMismatch(); + + /// @notice thrown when Command given is not valid + error InvalidCommandType(uint256 commandType); + + /// @notice thrown when conditional order type given is not valid due to zero sizeDelta + error ZeroSizeDelta(); + + /// @notice exceeds useable margin + /// @param available: amount of useable margin asset + /// @param required: amount of margin asset required + error InsufficientFreeMargin(uint256 available, uint256 required); + + /// @notice call to transfer ETH on withdrawal fails + error EthWithdrawalFailed(); + + /// @notice base price from the oracle was invalid + /// @dev Rate can be invalid either due to: + /// 1. Returned as invalid from ExchangeRates - due to being stale or flagged by oracle + /// 2. Out of deviation bounds w.r.t. to previously stored rate + /// 3. if there is no valid stored rate, w.r.t. to previous 3 oracle rates + /// 4. Price is zero + error InvalidPrice(); + + /// @notice thrown when account execution has been disabled in the settings contract + error AccountExecutionDisabled(); + + /// @notice thrown when a call attempts to reenter the protected function + error Reentrancy(); + + /// @notice thrown when token swap attempted with invalid token (i.e. token that is not whitelisted) + /// @param tokenIn: token attempting to swap from + /// @param tokenOut: token attempting to swap to + error TokenSwapNotAllowed(address tokenIn, address tokenOut); + + /// @notice thrown when a conditional order is attempted to be executed during invalid market conditions + /// @param conditionalOrderId: conditional order id + /// @param executor: address of executor + error CannotExecuteConditionalOrder( + uint256 conditionalOrderId, address executor + ); + + /// @notice thrown when a conditional order is attempted to be executed but SM account cannot pay fee + /// @param executorFee: fee required to execute conditional order + error CannotPayExecutorFee(uint256 executorFee, address executor); + + /*////////////////////////////////////////////////////////////// + VIEWS + //////////////////////////////////////////////////////////////*/ + + /// @notice returns the version of the Account + function VERSION() external view returns (bytes32); + + /// @return returns the amount of margin locked for future events (i.e. conditional orders) + function committedMargin() external view returns (uint256); + + /// @return returns current conditional order id + function conditionalOrderId() external view returns (uint256); + + /// @notice get delayed order data from Synthetix PerpsV2 + /// @dev call reverts if _marketKey is invalid + /// @param _marketKey: key for Synthetix PerpsV2 Market + /// @return delayed order struct defining delayed order (will return empty struct if no delayed order exists) + function getDelayedOrder(bytes32 _marketKey) + external + returns (IPerpsV2MarketConsolidated.DelayedOrder memory); + + /// @notice checker() is the Resolver for Gelato + /// (see https://docs.gelato.network/developer-services/automate/guides/custom-logic-triggers/smart-contract-resolvers) + /// @notice signal to a keeper that a conditional order is valid/invalid for execution + /// @dev call reverts if conditional order Id does not map to a valid conditional order; + /// ConditionalOrder.marketKey would be invalid + /// @param _conditionalOrderId: key for an active conditional order + /// @return canExec boolean that signals to keeper a conditional order can be executed by Gelato + /// @return execPayload calldata for executing a conditional order + function checker(uint256 _conditionalOrderId) + external + view + returns (bool canExec, bytes memory execPayload); + + /// @notice the current withdrawable or usable balance + /// @return free margin amount + function freeMargin() external view returns (uint256); + + /// @notice get up-to-date position data from Synthetix PerpsV2 + /// @param _marketKey: key for Synthetix PerpsV2 Market + /// @return position struct defining current position + function getPosition(bytes32 _marketKey) + external + returns (IPerpsV2MarketConsolidated.Position memory); + + /// @notice conditional order id mapped to conditional order + /// @param _conditionalOrderId: id of conditional order + /// @return conditional order + function getConditionalOrder(uint256 _conditionalOrderId) + external + view + returns (ConditionalOrder memory); + + /*////////////////////////////////////////////////////////////// + MUTATIVE + //////////////////////////////////////////////////////////////*/ + + /// @notice sets the initial owner of the account + /// @dev only called once by the factory on account creation + /// @param _owner: address of the owner + function setInitialOwnership(address _owner) external; + + /// @notice executes commands along with provided inputs + /// @param _commands: array of commands, each represented as an enum + /// @param _inputs: array of byte strings containing abi encoded inputs for each command + function execute(Command[] calldata _commands, bytes[] calldata _inputs) + external + payable; + + /// @notice execute queued conditional order + /// @dev currently only supports conditional order submission via PERPS_V2_SUBMIT_OFFCHAIN_DELAYED_ORDER COMMAND + /// @param _conditionalOrderId: key for an active conditional order + function executeConditionalOrder(uint256 _conditionalOrderId) external; +} + +interface IPerpsV2MarketConsolidated { + struct Position { + uint64 id; + uint64 lastFundingIndex; + uint128 margin; + uint128 lastPrice; + int128 size; + } + + struct DelayedOrder { + bool isOffchain; + int128 sizeDelta; + uint128 desiredFillPrice; + uint128 targetRoundId; + uint128 commitDeposit; + uint128 keeperDeposit; + uint256 executableAtTime; + uint256 intentionTime; + bytes32 trackingCode; + } + + function marketKey() external view returns (bytes32 key); + + function positions(address account) + external + view + returns (Position memory); + + function delayedOrders(address account) + external + view + returns (DelayedOrder memory); + + function baseAsset() external view returns (bytes32 key); + + function assetPrice() external view returns (uint256 price, bool invalid); + + function transferMargin(int256 marginDelta) external; + + function withdrawAllMargin() external; + + function modifyPositionWithTracking( + int256 sizeDelta, + uint256 desiredFillPrice, + bytes32 trackingCode + ) external; + + function closePositionWithTracking( + uint256 desiredFillPrice, + bytes32 trackingCode + ) external; + + function submitCloseOffchainDelayedOrderWithTracking( + uint256 desiredFillPrice, + bytes32 trackingCode + ) external; + + function submitCloseDelayedOrderWithTracking( + uint256 desiredTimeDelta, + uint256 desiredFillPrice, + bytes32 trackingCode + ) external; + + function submitDelayedOrderWithTracking( + int256 sizeDelta, + uint256 desiredTimeDelta, + uint256 desiredFillPrice, + bytes32 trackingCode + ) external; + + function submitOffchainDelayedOrderWithTracking( + int256 sizeDelta, + uint256 desiredFillPrice, + bytes32 trackingCode + ) external; + + function cancelDelayedOrder(address account) external; + + function cancelOffchainDelayedOrder(address account) external; +} diff --git a/src/kwenta/smv3/IEngine.sol b/src/kwenta/smv3/IEngine.sol new file mode 100644 index 0000000..1589a05 --- /dev/null +++ b/src/kwenta/smv3/IEngine.sol @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.18; + +/// @title Kwenta Smart Margin v3: Engine Interface +/// @author JaredBorders (jaredborders@pm.me) +interface IEngine { + /*////////////////////////////////////////////////////////////// + TYPES + //////////////////////////////////////////////////////////////*/ + + /// @notice order details used to create an order on a perps market within a conditional order + struct OrderDetails { + // order market id + uint128 marketId; + // order account id + uint128 accountId; + // order size delta (of asset units expressed in decimal 18 digits). It can be positive or negative + int128 sizeDelta; + // settlement strategy used for the order + uint128 settlementStrategyId; + // acceptable price set at submission + uint256 acceptablePrice; + // bool to indicate if the order is reduce only; i.e. it can only reduce the position size + bool isReduceOnly; + // tracking code to identify the integrator + bytes32 trackingCode; + // address of the referrer + address referrer; + } + + /// @notice conditional order + struct ConditionalOrder { + // order details + OrderDetails orderDetails; + // address of the signer of the order + address signer; + // an incrementing value indexed per order + uint256 nonce; + // option to require all extra conditions to be verified on-chain + bool requireVerified; + // address that can execute the order if requireVerified is false + address trustedExecutor; + // array of extra conditions to be met + bytes[] conditions; + } + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /// @notice thrown when msg.sender is not authorized to interact with an account + error Unauthorized(); + + /// @notice thrown when an order cannot be executed + error CannotExecuteOrder(); + + /// @notice thrown when number of conditions exceeds max allowed + /// @dev used to prevent griefing attacks + error MaxConditionSizeExceeded(); + + /// @notice thrown when address is zero + error ZeroAddress(); + + /// @notice thrown when attempting to re-use a nonce + error InvalidNonce(); + + /// @notice thrown when attempting to verify a condition identified by an invalid selector + error InvalidConditionSelector(bytes4 selector); + + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /// @notice emitted when the account owner or delegate successfully invalidates an unordered nonce + event UnorderedNonceInvalidation( + uint128 indexed accountId, uint256 word, uint256 mask + ); + + /*////////////////////////////////////////////////////////////// + AUTHENTICATION + //////////////////////////////////////////////////////////////*/ + + /// @notice check if the msg.sender is the owner of the account + /// identified by the accountId + /// @param _accountId the id of the account to check + /// @param _caller the address to check + /// @return true if the msg.sender is the owner of the account + function isAccountOwner(uint128 _accountId, address _caller) + external + view + returns (bool); + + /// @notice check if the msg.sender is a delegate of the account + /// identified by the accountId + /// @dev a delegate is an address that has been given + /// PERPS_COMMIT_ASYNC_ORDER_PERMISSION permission + /// @param _accountId the id of the account to check + /// @param _caller the address to check + /// @return true if the msg.sender is a delegate of the account + function isAccountDelegate(uint128 _accountId, address _caller) + external + view + returns (bool); + + /*////////////////////////////////////////////////////////////// + NONCE MANAGEMENT + //////////////////////////////////////////////////////////////*/ + + /// @notice invalidates the bits specified in mask for the bitmap at the word position + /// @dev the wordPos is maxed at type(uint248).max + /// @param _accountId the id of the account to invalidate the nonces for + /// @param _wordPos a number to index the nonceBitmap at + /// @param _mask a bitmap masked against msg.sender's current bitmap at the word position + function invalidateUnorderedNonces( + uint128 _accountId, + uint256 _wordPos, + uint256 _mask + ) external; + + /// @notice check if the given nonce has been used + /// @param _accountId the id of the account to check + /// @param _nonce the nonce to check + /// @return true if the nonce has been used, false otherwise + function hasUnorderedNonceBeenUsed(uint128 _accountId, uint256 _nonce) + external + view + returns (bool); + + /*////////////////////////////////////////////////////////////// + COLLATERAL MANAGEMENT + //////////////////////////////////////////////////////////////*/ + + /// @notice modify the collateral of an account identified by the accountId + /// @param _accountId the account to modify + /// @param _synthMarketId the id of the synth being used as collateral + /// @param _amount the amount of collateral to add or remove (negative to remove) + function modifyCollateral( + uint128 _accountId, + uint128 _synthMarketId, + int256 _amount + ) external; + + /*////////////////////////////////////////////////////////////// + ASYNC ORDER MANAGEMENT + //////////////////////////////////////////////////////////////*/ + + /// @notice commit an order for an account identified by the + /// accountId to be executed asynchronously + /// @param _perpsMarketId the id of the perps market to trade + /// @param _accountId the id of the account to trade with + /// @param _sizeDelta the amount of the order to trade (short if negative, long if positive) + /// @param _settlementStrategyId the id of the settlement strategy to use + /// @param _acceptablePrice acceptable price set at submission. Compared against the fill price + /// @param _trackingCode tracking code to identify the integrator + /// @param _referrer the address of the referrer + /// @return retOrder the order committed + /// @return fees the fees paid for the order + function commitOrder( + uint128 _perpsMarketId, + uint128 _accountId, + int128 _sizeDelta, + uint128 _settlementStrategyId, + uint256 _acceptablePrice, + bytes32 _trackingCode, + address _referrer + ) external returns (IPerpsMarketProxy.Data memory retOrder, uint256 fees); + + /*////////////////////////////////////////////////////////////// + CONDITIONAL ORDER MANAGEMENT + //////////////////////////////////////////////////////////////*/ + + /// In order for a conditional order to be committed and then executed there are a number of requirements that need to be met: + /// + /// (1) The account must have sufficient snxUSD collateral to handle the order + /// (2) The account must not have another order committed + /// (3) The order’s set `acceptablePrice` needs to be met both on committing the order and when it gets executed + /// (users should choose a value for this that is likely to execute based on the conditions set) + /// (4) The order can only be executed within Synthetix’s set settlement window + /// (5) There must be a keeper that executes a conditional order + /// + /// @notice There is no guarantee a conditional order will be executed + + /// @notice execute a conditional order + /// @param _co the conditional order + /// @param _signature the signature of the conditional order + /// @return retOrder the order committed + /// @return fees the fees paid for the order to Synthetix + /// @return conditionalOrderFee the fee paid to executor for the conditional order + function execute(ConditionalOrder calldata _co, bytes calldata _signature) + external + returns ( + IPerpsMarketProxy.Data memory retOrder, + uint256 fees, + uint256 conditionalOrderFee + ); + + /// @notice checks if the order can be executed based on defined conditions + /// @dev this function does NOT check if the order can be executed based on the account's balance + /// (i.e. does not check if enough USD is available to pay for the order fee nor does it check + /// if enough collateral is available to cover the order) + /// @param _co the conditional order + /// @param _signature the signature of the conditional order + /// @return true if the order can be executed based on defined conditions, false otherwise + function canExecute( + ConditionalOrder calldata _co, + bytes calldata _signature + ) external view returns (bool); + + /// @notice verify the conditional order signer is the owner or delegate of the account + /// @param _co the conditional order + /// @return true if the signer is the owner or delegate of the account + function verifySigner(ConditionalOrder calldata _co) + external + view + returns (bool); + + /// @notice verify the signature of the conditional order + /// @param _co the conditional order + /// @param _signature the signature of the conditional order + /// @return true if the signature is valid + function verifySignature( + ConditionalOrder calldata _co, + bytes calldata _signature + ) external view returns (bool); + + /// @notice verify array of conditions defined in the conditional order + /// @dev + /// 1. all conditions are defined by the conditional order creator + /// 2. conditions are encoded function selectors and parameters + /// 3. each function defined in the condition contract must return a truthy value + /// 4. internally, staticcall is used to protect against malicious conditions + /// @param _co the conditional order + /// @return true if all conditions are met + function verifyConditions(ConditionalOrder calldata _co) + external + view + returns (bool); + + /*////////////////////////////////////////////////////////////// + CONDITIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice determine if current timestamp is after the given timestamp + /// @param _timestamp the timestamp to compare against + /// @return true if current timestamp is after the given `_timestamp`, false otherwise + function isTimestampAfter(uint256 _timestamp) + external + view + returns (bool); + + /// @notice determine if current timestamp is before the given timestamp + /// @param _timestamp the timestamp to compare against + /// @return true if current timestamp is before the given `_timestamp`, false otherwise + function isTimestampBefore(uint256 _timestamp) + external + view + returns (bool); + + /// @notice determine if the current price of an asset is above a given price + /// @dev assets price is determined by the pyth oracle + /// @param _assetId id of an asset to check the price of + /// @param _price the price to compare against + /// @param _confidenceInterval roughly corresponds to the standard error of a normal distribution + /// + /// example: + /// given: + /// PythStructs.Price.expo (expo) = -5 + /// confidenceInterval (conf) = 1500 + /// PythStructs.Price.price (price) = 12276250 + /// + /// conf * 10^(expo) = 1500 * 10^(-5) = $0.015 + /// price * 10^(-5) = 12276250 * 10^(-5) = $122.7625 + /// + /// thus, the price of the asset is $122.7625 +/- $0.015 + /// + /// @return true if the current price of the asset is above the given `_price`, false otherwise + function isPriceAbove( + bytes32 _assetId, + int64 _price, + uint64 _confidenceInterval + ) external view returns (bool); + + /// @notice determine if the current price of an asset is below a given price + /// @dev assets price is determined by the pyth oracle + /// @param _assetId id of an asset to check the price of + /// @param _price the price to compare against + /// @param _confidenceInterval roughly corresponds to the standard error of a normal distribution + /// + /// example: + /// given: + /// PythStructs.Price.expo (expo) = -5 + /// confidenceInterval (conf) = 1500 + /// PythStructs.Price.price (price) = 12276250 + /// + /// conf * 10^(expo) = 1500 * 10^(-5) = $0.015 + /// price * 10^(-5) = 12276250 * 10^(-5) = $122.7625 + /// + /// thus, the price of the asset is $122.7625 +/- $0.015 + /// + /// @return true if the current price of the asset is below the given `_price`, false otherwise + function isPriceBelow( + bytes32 _assetId, + int64 _price, + uint64 _confidenceInterval + ) external view returns (bool); + + /// @notice can market accept non close-only orders (i.e. is the market open) + /// @dev if maxMarketSize to 0, the market will be in a close-only state + /// @param _marketId the id of the market to check + /// @return true if the market is open, false otherwise + function isMarketOpen(uint128 _marketId) external view returns (bool); + + /// @notice determine if the account's (identified by the given accountId) + /// position size in the given market is above a given size + /// @param _accountId the id of the account to check + /// @param _marketId the id of the market to check + /// @param _size the size to compare against + /// @return true if the account's position size in the given market is above the given '_size`, false otherwise + function isPositionSizeAbove( + uint128 _accountId, + uint128 _marketId, + int128 _size + ) external view returns (bool); + + /// @notice determine if the account's (identified by the given accountId) + /// position size in the given market is below a given size + /// @param _accountId the id of the account to check + /// @param _marketId the id of the market to check + /// @param _size the size to compare against + /// @return true if the account's position size in the given market is below the given '_size`, false otherwise + function isPositionSizeBelow( + uint128 _accountId, + uint128 _marketId, + int128 _size + ) external view returns (bool); + + /// @notice determine if the order fee for the given market and size delta is above a given fee + /// @param _marketId the id of the market to check + /// @param _sizeDelta the size delta to check + /// @param _fee the fee to compare against + /// @return true if the order fee for the given market and size delta is below the given `_fee`, false otherwise + function isOrderFeeBelow(uint128 _marketId, int128 _sizeDelta, uint256 _fee) + external + view + returns (bool); +} + +/// @title Consolidated Perpetuals Market Proxy Interface +/// @notice Responsible for interacting with Synthetix v3 perps markets +/// @author Synthetix +interface IPerpsMarketProxy { + /*////////////////////////////////////////////////////////////// + ACCOUNT MODULE + //////////////////////////////////////////////////////////////*/ + + /// @notice Mints an account token with an available id to `msg.sender`. + /// Emits a {AccountCreated} event. + function createAccount() external returns (uint128 accountId); + + /// @notice Returns the address that owns a given account, as recorded by the system. + /// @param accountId The account id whose owner is being retrieved. + /// @return owner The owner of the given account id. + function getAccountOwner(uint128 accountId) + external + view + returns (address owner); + + /// @notice Returns the address for the account token used by the module. + /// @return accountNftToken The address of the account token. + function getAccountTokenAddress() + external + view + returns (address accountNftToken); + + /// @notice Grants `permission` to `user` for account `accountId`. + /// @param accountId The id of the account that granted the permission. + /// @param permission The bytes32 identifier of the permission. + /// @param user The target address that received the permission. + /// @dev `msg.sender` must own the account token with ID `accountId` or have the "admin" permission. + /// @dev Emits a {PermissionGranted} event. + function grantPermission( + uint128 accountId, + bytes32 permission, + address user + ) external; + + /// @notice Revokes `permission` from `user` for account `accountId`. + /// @param accountId The id of the account that revoked the permission. + /// @param permission The bytes32 identifier of the permission. + /// @param user The target address that no longer has the permission. + /// @dev `msg.sender` must own the account token with ID `accountId` or have the "admin" permission. + /// @dev Emits a {PermissionRevoked} event. + function revokePermission( + uint128 accountId, + bytes32 permission, + address user + ) external; + + /// @notice Returns `true` if `user` has been granted `permission` for account `accountId`. + /// @param accountId The id of the account whose permission is being queried. + /// @param permission The bytes32 identifier of the permission. + /// @param user The target address whose permission is being queried. + /// @return hasPermission A boolean with the response of the query. + function hasPermission(uint128 accountId, bytes32 permission, address user) + external + view + returns (bool hasPermission); + + /// @notice Returns `true` if `target` is authorized to `permission` for account `accountId`. + /// @param accountId The id of the account whose permission is being queried. + /// @param permission The bytes32 identifier of the permission. + /// @param target The target address whose permission is being queried. + /// @return isAuthorized A boolean with the response of the query. + function isAuthorized(uint128 accountId, bytes32 permission, address target) + external + view + returns (bool isAuthorized); + + /*////////////////////////////////////////////////////////////// + ASYNC ORDER MODULE + //////////////////////////////////////////////////////////////*/ + + struct Data { + /// @dev Time at which the Settlement time is open. + uint256 settlementTime; + /// @dev Order request details. + OrderCommitmentRequest request; + } + + struct OrderCommitmentRequest { + /// @dev Order market id. + uint128 marketId; + /// @dev Order account id. + uint128 accountId; + /// @dev Order size delta (of asset units expressed in decimal 18 digits). It can be positive or negative. + int128 sizeDelta; + /// @dev Settlement strategy used for the order. + uint128 settlementStrategyId; + /// @dev Acceptable price set at submission. + uint256 acceptablePrice; + /// @dev An optional code provided by frontends to assist with tracking the source of volume and fees. + bytes32 trackingCode; + /// @dev Referrer address to send the referrer fees to. + address referrer; + } + + /// @notice Commit an async order via this function + /// @param commitment Order commitment data (see OrderCommitmentRequest struct). + /// @return retOrder order details (see AsyncOrder.Data struct). + /// @return fees order fees (protocol + settler) + function commitOrder(OrderCommitmentRequest memory commitment) + external + returns (Data memory retOrder, uint256 fees); + + /// @notice For a given market, account id, and a position size, returns the required total account margin for this order to succeed + /// @dev Useful for integrators to determine if an order will succeed or fail + /// @param accountId id of the trader account. + /// @param marketId id of the market. + /// @param sizeDelta size of position. + /// @return requiredMargin margin required for the order to succeed. + function requiredMarginForOrder( + uint128 accountId, + uint128 marketId, + int128 sizeDelta + ) external view returns (uint256 requiredMargin); + + /// @notice Simulates what the order fee would be for the given market with the specified size. + /// @dev Note that this does not include the settlement reward fee, which is based on the strategy type used + /// @param marketId id of the market. + /// @param sizeDelta size of position. + /// @return orderFees incurred fees. + /// @return fillPrice price at which the order would be filled. + function computeOrderFees(uint128 marketId, int128 sizeDelta) + external + view + returns (uint256 orderFees, uint256 fillPrice); + + /*////////////////////////////////////////////////////////////// + PERPS ACCOUNT MODULE + //////////////////////////////////////////////////////////////*/ + + /// @notice Modify the collateral delegated to the account. + /// @param accountId Id of the account. + /// @param synthMarketId Id of the synth market used as collateral. Synth market id, 0 for snxUSD. + /// @param amountDelta requested change in amount of collateral delegated to the account. + function modifyCollateral( + uint128 accountId, + uint128 synthMarketId, + int256 amountDelta + ) external; + + /// @notice Gets the account's collateral value for a specific collateral. + /// @param accountId Id of the account. + /// @param synthMarketId Id of the synth market used as collateral. Synth market id, 0 for snxUSD. + /// @return collateralValue collateral value of the account. + function getCollateralAmount(uint128 accountId, uint128 synthMarketId) + external + view + returns (uint256); + + /// @notice Gets the account's total collateral value. + /// @param accountId Id of the account. + /// @return collateralValue total collateral value of the account. USD denominated. + function totalCollateralValue(uint128 accountId) + external + view + returns (uint256); + + /// @notice Gets the details of an open position. + /// @param accountId Id of the account. + /// @param marketId Id of the position market. + /// @return totalPnl pnl of the entire position including funding. + /// @return accruedFunding accrued funding of the position. + /// @return positionSize size of the position. + function getOpenPosition(uint128 accountId, uint128 marketId) + external + view + returns (int256 totalPnl, int256 accruedFunding, int128 positionSize); + + /// @notice Gets the available margin of an account. It can be negative due to pnl. + /// @param accountId Id of the account. + /// @return availableMargin available margin of the position. + function getAvailableMargin(uint128 accountId) + external + view + returns (int256 availableMargin); + + /// @notice Gets the exact withdrawable amount a trader has available from this account while holding the account's current positions. + /// @param accountId Id of the account. + /// @return withdrawableMargin available margin to withdraw. + function getWithdrawableMargin(uint128 accountId) + external + view + returns (int256 withdrawableMargin); + + /// @notice Gets the initial/maintenance margins across all positions that an account has open. + /// @param accountId Id of the account. + /// @return requiredInitialMargin initial margin req (used when withdrawing collateral). + /// @return requiredMaintenanceMargin maintenance margin req (used to determine liquidation threshold). + function getRequiredMargins(uint128 accountId) + external + view + returns ( + uint256 requiredInitialMargin, + uint256 requiredMaintenanceMargin + ); + + /*////////////////////////////////////////////////////////////// + PERPS MARKET MODULE + //////////////////////////////////////////////////////////////*/ + + /// @notice Gets the max size of an specific market. + /// @param marketId id of the market. + /// @return maxMarketSize the max market size in market asset units. + function getMaxMarketSize(uint128 marketId) + external + view + returns (uint256 maxMarketSize); +}