Dutch Auctions
Generalized Order Book
Common implements a fully on-chain order book, where each order is, in a sense, a separate dutch auction (or even more general, see the details below).
High Level Overview
Traditional Limit Orders
In a traditional order book, the user typically submits a LimitOrder
having the following parameters:
token_in
- token the user wants to selltoken_out
- token the user wants to buyamount_in
- how many tokens user wants to sellprice
- determines how many tokens user wants to buy, specifically, the want at leastprice*amount_in
tokens of typetoken_out
.
Such an order sits in the order book until matched by a matching engine. Matching happens against orders in the opposite direction. Orders are matched if they are "crossing", i.e., when both price constraints can be satisfied at the same time. The price at which the trade is executed depends on the matching engine. Orders can be matched partially. Typically some kind of fee is deducted after a trade happens.
On-chain "Dutch" Orders
A Dutch auction is an auction mechanism for selling an asset, in which an initial price p_start
is selected for the auctioned item, and then, as the auction progresses, the price gradually decreases, little by little. At the moment when a buyer is satisfied by the current price, they bid, and win the auction right away, paying the current price for the item. Assuming that the initial price p_start
was set correctly (higher than what's the item worth) then the auction finishes with the item being sold at an optimal price.
Common uses the Dutch auction mechanism to implement generalized limit orders. Roughly speaking, if the user has some token_in
asset and they want to sell amount_in
of them for token_out
asset, then they can set some initial price p_start
and limit price p_stop
and let the tokens be traded over some time, at the price varying linearly between p_start
and p_stop
over a selected time segment. Of course, it is natural to set p_start
to be a price that is higher than the current stop market price, so that the auction achieves its goal of trading the assets at a price favorable to the user.
The order book consists then of a set of "living" orders – orders whose prices change in time.
Orders are matched not via a centralized matching engine but via a permissionless group of "fillers" who are actors who can fill one or more orders at the same time. Fillers compete with each other to make profit from arbitrage opportunities.
Details of Dutch Orders
Creating Orders
To create an order user sends the following transaction
Where token_in
, token_out
, amount_in
are self-explanatory, and:
price_curve
is a function that takes timestamp as an argument and outputs a price that is acceptable for this order at this time. For instance:For limit orders we have
price_curve(t) = const
For dutch auctions we have
price_curve(t) = ((t1 - t)p1 + (t-t2)p2)/(t1-t2)
i.e., a linear function with a specific timestampt1, t2
and pricep1, p2
parametersWe could have more general price curves too, for instance exponential, which would be useful for price discovery of very exotic tokens.
expiration_time
is a timestamp when the order is automatically cancelled. It can be set toinfty
in order to make the order live till being filled, or manually canceled.
As a result, an order, with a unique order_id
is created and stored in the contract.
Note that as part of the create_order
transaction, the user locks amount_in
tokens token_in
in the order book contract.
Filling Orders
To fill an order or multiple orders one uses the following contract call:
Note that the above requires developing an executor contract, so it is expected to be used by a professional filler only. A simplified call for casual traders will be also provided to make it simpler.
The fill
method can be logically split into 3 steps:
Send input funds to the
executor_contract
Execute a callback by the
executor_contract
Receive the swapped funds from the
executor_contract
There are a few reasons why the proposed implementation of fill
(based on steps 1. 2. 3. as above) is convenient for the fillers:
When creating a transaction, the filler cannot predict: 1) if some of the orders they try to fill will not get filled (partially) before the transaction lands on chain, 2) what will be the timestamp of the block their transaction is included. This implies that the filler doesn't know the final amounts of the order and the price at which it will be executed. That's why the
execute
call includes bothorders
andprices
and is expected to make the final decision on what to trade and how.In case when an order is filled, or the price is not the expected one, the
execute
method can simply abort, in order to rollback the transaction.Thanks to the fact that the step
1.
happens before3.
it is possible for the filler to fill orders without any liquidity held by the filler. Specifically,fill
is a variant of a flash-loan. What the filler can do for instance is:Fill one order by trading it against an AMM
Fill two orders by trading them against each other.
Any combination of these, as long as it's atomic.
Cancelling Orders
Orders can be cancelled at any time by the user who submitted them, in which case they get a full refund on what remains untraded (token_in
) + the amount in token_out
that was traded.
Fees
Incentives for Fillers
Last updated