Once an order book grows close to it's maximum capacity (65000+ order ids minted, or asset.orderId >= 65000
), it enables the arbitrary cancelling of new orders that have just been added to the order book.
Let's take a look at the mechanism the protocol provides for deterring attackers, ignoring the functionality that only the DAO has access to and focusing on the functionality that any one can call:
First, s.asset[asset].orderId
functions as a counter for the next new order's id. When a new order is added to an order book, the LibOrders#addOrder()
function is called:
If there are no cancelled orders whose order id can be reused, a new order id is issued. Given enough time and protocol popularity, this order id can surely grow up to the 65000 mark. Also the fact that both bids, shorts and asks rely on the very same counter makes this easier.
Back to the cancelOrderFarFromOracle()
method:
Had that asset.orderId
counter reached the value of 65000 once, it becomes a child's play to cancel legitimate trader's orders right after they are placed (they need to be the last one in the linked list).
Because of how cancel order works, it simply links the ids of adjacent orders of the one being cancelled:
And moves it left of HEAD in the orders linked list, and changes it's status to O.Cancelled
.
This becomes an issue when legitimate traders want to add a LIMIT order (be it a bid, an ask or god forbid a short order).
In the case of a limit bid order, the ETH needed for executing the exchange is taken from the trader:
In the case of a limit ask order, the ERC being sold is taken from the trader in the same fashion:
And in the case of a limit short order, the collateral for the position is deducted from the trader's ETH escrow balance:
Once an order is added to the order book, an attacker can cancel it and thus trap any amount of asset or ETH that were posted by a trader, rendering them unrecoverable by the trader.
Normally, when an order is cancelled using the functions that the OrdersFacet
provides, the posted asset or ETH is credited back to the trader who owns the order before marking their order as closed. In cancelOrderFarFromOracle()
however, as a form of punishment (I assume), the protocol does not refund the posted funds back to the order owner.
The trader can no longer use the order cancel functionality that the OrdersFacet
provides because any of the three functions (cancelBid
, cancelAsk
, cancelShort
) require the order that's being cancelled to not be Cancelled
.
Manual review
Allow only DAO to cancel orders or refactor the part of the functionality that allows arbitrary users to cancel the last orders so that posted funds are returned to the order owner but the caller receives a fee that covers the transaction cost so they have an incentive to do that part of the housekeeping for the orderbook.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.