The DeFi ecosystem, rich in innovation, has birthed diverse trading mechanisms, each with its unique set of advantages and challenges. Central to these mechanisms are the Central Limit Order Books (CLOBs), known for their capacity to enable precise price discovery and offer traders unparalleled control. Historically, the transition of CLOBs to on-chain platforms was riddled with challenges. The inherent throughput limitations of traditional L1 and L2 solutions curbed the swift operations CLOBs demanded, often rendering them unprofitable for market makers. Additionally, the transparent nature of on-chain operations opened the door for validators to engage in front-running, exploiting the spread by manipulating transaction sequences. Furthermore, the intricate operations intrinsic to CLOBs could lead to skyrocketing gas costs, especially during network congestion periods. The emergence of advanced scaling solutions and faster EVMs is reshaping the landscape, presenting fresh opportunities and challenges for on-chain CLOB implementations. This article introduces an optimised on-chain CLOB tackling all these challenges.
Orders are stored in a hash map and price ordering is not done on-chain. Sorted list price points/order IDs are sent as calldata while placing market orders which removes the scope for MEVs by front-running transactions. We rely on the fact that market orders want the best price and hence will send the sorted list of price points/order IDs. Only time priority is verified by the smart contract on-chain as there is no incentive for market orders to sort order IDs in this order. We use a sliding window mechanism to enforce time priority.
Before we dive into the algorithm, let us understand some basic structures used for storing limit orders.
orderId
and stores the following data:ownerAddress
: Address of the makerprice
: Price point to place the limit ordersize
: Size of the orderacceptableRange
(this is used for Time priority validation, more about it later)price
and have the following data:totalCompletedOrCanceledOrders
: Sum of all the order sizes claimed or cancelled orders at this price point.totalOrdersAtPrice
: Sum of all the order sizes placed at this price point.executableSize
: Total volume of market orders that that have been placed but not claimed by the maker order.In the cranked order book, matching and settlement are two different steps. That is, when a market order is placed, the volume filled by this market order is marked as claimable on-chain and maker orders have to be claimed separately either individually or in a batched transaction.
The cranked orderbook maintains Price Time Priority.
orderIds
.orderId
, the acceptableRange
for this order is equal to the totalOrdersAtPrice
for the given price point.totalOrdersAtPrice
of the price point of the order is incremented with the order size.orderId
.totalCompletedOrCanceledOrders
for the price point corresponding to this order is increased by the orderSize
.pricePoints
: a sorted list of price points you want to execute against.size
: order sizeexecutableSize
of the price point such that it stops iterating when either the size requirement is met at that price point, there are no more price points in the call data.orderId
.claimedSize
of the order is defined as:acceptableRange > executableSize + totalCompletedOrCanceledOrders
executableSize + totalCompletedOrCanceledOrders - acceptableRange
otherwisesize - claimedSize
.In the crankless order book, market orders directly fill limit orders. The crankless orderbook maintains Price Time Priority.
Placing and cancelling limit orders are the same as cranked order book. The crankless order book does not involve claiming limit orders. For market orders, the crankless order book utilises orderIds
instead of price points. Validation remains the same as claiming.
Efficiently placing and cancelling limit orders has been given the highest priority as deep order books with large liquidity are extremely important for price discovery. If it is expensive to place/cancel limit orders market makers will be disincentivised to provide liquidity. High gas fees on limit orders make it almost impossible for HFT firms to take part in providing liquidity as they can’t make profits on the spread.
Keeping this in mind, our architecture allows the placing and cancelling of limit orders in O(1) at any price point on the order book.
Market orders need to be matched on-chain with the limit orders with price-time priority. While deciding on how market orders should be matched, we explored two options, that is, a cranked order book where market orders fill price points and limit orders can be settled at a later time and a crankless order book where market orders execute limit orders and are settled in a single transaction.
We benchmarked both the order books with a spot market. The gas utilisation data includes gas utilised for transferring an ERC20 token.
In the cranked order book, market orders fill price points and limit orders can be settled at a later point. The smart contract enforces that the orders are settled with price-time priority.
We implemented this and benchmarked the gas utilisation over a hardhat fork and found the following results.
The above graphs show the gas utilisation for different types of transactions:
In the crankless order book, market orders directly fill limit orders. The smart contract enforces that the orders are settled with price-time priority.
We implemented this and benchmarked the gas utilisation over a hardhat fork and found the following results.
Placing limit orders at new price points and existing price points and cancelling limit orders behave exactly the same way as in a cranked order book.
Using a cranked order book instead of a crankless one increases the gas utilisation of limit orders by almost 60% which is a big jump disincentivizing market makers and HFTs.
The additional gas paid by takers for settling every limit order is 17,074 in stark contrast to 77,611 for claiming a maker order. However, batching claim transactions will result in significant gas optimisations for claiming limit orders.