Note: “Placed” in this document refers to an order which has gone through taker order matching via memclob.PlaceOrder. An order can exist in state without having been placed yet.
For more details on definitions, see the v4-chain repo here (opens in a new tab).
|purpose||Short-lived orders which are meant to placed immediately (in the same block the order was received). These orders stay in-memory up to 20 blocks, with only their fill amount and expiry block height being committed to state. Intended for use by market makers with high throughput, or for market orders. IoC and FoK orders are also considered short-term orders. Short-term orders do not survive a network restart.||Long-lived orders which may execute far in the future. These orders should not be lost during a validator restart (placed in the block after the order was received). In the event a validator restarts, all stateful orders are placed back onto the in-memory orderbook (opens in a new tab). Likely to be used primarily by retail traders. The front end would be sending stateful orders for all order types other than market orders.|
Two types of stateful orders:
1. Long-Term Orders
• meant to be added to the orderbook as soon as possible. Due to certain technical limitations, long-term orders are placed in the block after they are written to state. E.g. if
• Order types requiring immediate execution such as fill-or-kill / immediate-or-cancel are disallowed as these should be placed as short term orders; Long-term FoK/IoC orders would never be maker orders, so there is no benefit to writing them to state.
2. Conditional Orders
• execute when the oracle price becomes either LTE or GTE to specified trigger price, depending on the type of conditional order (e.g. stop loss sell = LTE, take profit buy = GTE)
• orders are placed in the block after their condition is met and they become triggered
• it is possible for a conditional order to become triggered in the same block they are initially written to state in. Conditional orders are placed in block ≥ N+1.
• valid OrderFlags values are 32 (conditional) and 64 (long-term) for stateful orders
Short term cancellations are handled best-effort, meaning they are only gossiped and not included in MsgProposedOperations
Short term orders have a maximum GTB of current block height + ShortBlockWindow (opens in a new tab). Currently this value is 20 blocks, or about 30 seconds. Short term orders can only be GTB because in the interest of being resilient to chain halts or slowdowns.
Stateful orders have a maximum GTBT of current block time + StatefulOrderTimeWindow (opens in a new tab). Currently this value is 95 days.
GTBT is used instead of GTB to give a more meaningful expiration time for stateful orders.
|inclusion in block||Normal cosmos transaction. The original Tx which included the |
|signature verification||Short-term orders must undergo custom signature verification because they are included in an app-injected transaction.|
The memclob stores each short term order placement’s raw transaction bytes in the memclob. When the order is included in a match, an
|Normal cosmos transaction signature verification, executed by the app’s antehandler.|
|replay prevention||Keep orders in state until after Good-Till-Block aka expiry (even if fully-filled or cancelled)||Cosmos SDK sequence numbers, verified to be strictly increasing in the app’s antehandler.|
Note that their use of sequence numbers requires stateful orders to be received in order otherwise they would fail. If placing multiple stateful orders they should be sent to the same validator to prevent issues.
|time placed (matching logic)|
Short term orders are only included in a block when matched. See “time added to state” below.
|long-term: Block N+1 in PrepareCheckState (opens in a new tab) where |
conditional: Block N+1
|what is stored in state|
• key = OrderId
• value = OrderFillAmount & PrunableBlockHeight
• key = block height
• value = list of potentially prunable OrderIds
PrunableBlockHeight holds the block height at which we can safely remove this order from state. BlockHeightToPotentiallyPrunableOrders stores a list of order ids which we can prune for a certain block height. These are used in conjunction for replay prevention of short term orders
• key = OrderId
• value = Order
• key = time
• value = list of OrderIds expiring at this GTBT
• key = OrderId
• value = OrderFillAmount & PrunableBlockHeight (prunable block height unused for stateful orders)
|time added to state|
|time removed from state||Always in EndBlocker (opens in a new tab) based off of prunable block height||• cancelled by user: removed from state in |
• forcefully-cancelled by protocol: removed from state in
• fully-filled: removed from state in
• expired: pruned during EndBlocker based off of GTBT
also removed from state in CheckTx for cancellations. This is for spam mitigation purposes.
|time added to in-memory orderbook||When placed in ||When placed in |
|time removed from in-memory orderbook||• when fully-filled: removed in |
• when cancelled: (CheckTx)
• when expired: PrepareCheckState, removed using memclob.openOrders.blockExpirationsForOrders data structure which stores expiration times for short term orders based off of GTB
|• when fully-filled: removed in |
• when cancelled: removed in
• when expired: removed in