TIP-1005: Fix Ask Swap Rounding Loss
Status: Draft
Authors: Dan Robinson
Authors: Dan Robinson
Abstract
This TIP fixes a rounding bug in theswapExactAmountIn function when filling ask orders. Due to double-rounding, the maker can receive slightly less quote tokens than the taker paid, causing tokens to be lost.
Motivation
When a taker swaps quote tokens for base tokens against an ask order, the following calculation occurs:- Convert taker’s
amountIn(quote) to base:base_out = floor(amountIn / price) - Credit maker with quote:
makerReceives = ceil(base_out * price)
makerReceives can be less than amountIn. For example:
- Taker pays
amountIn = 102001quote at price 1.02 (tick 2000) base_out = floor(102001 / 1.02) = 100000makerReceives = ceil(100000 * 1.02) = 102000- 1 token is lost
Specification
Bug Location
The bug is in_fillOrdersExactIn when processing ask orders (the baseForQuote = false path). Specifically, when a partial fill occurs:
fillAmount(base) is calculated by rounding down:baseOut = (remainingIn * PRICE_SCALE) / price_fillOrderis called withfillAmount- Inside
_fillOrder, the maker’s quote credit is re-derived:quoteAmount = ceil(fillAmount * price)
remainingIn information.
Fix
For partial fills in the ask path, pass the actualremainingIn (quote) to _fillOrder and use it directly for the maker’s credit, rather than re-deriving it from fillAmount.
The fix requires:
- Modify
_fillOrderto accept an optionalquoteOverrideparameter for ask orders - In
_fillOrdersExactIn, when partially filling an ask, passremainingInas the quote override - When
quoteOverrideis provided, use it directly for the maker’s balance increment instead of computingceil(fillAmount * price)
Reference Implementation Changes
The fix requires changes to two functions indocs/specs/src/StablecoinDEX.sol:
1. _fillOrder
Add an optional quoteOverride parameter. When non-zero and the order is an ask, use quoteOverride directly for the maker’s balance increment:
2. _fillOrdersExactIn
In the partial fill branch for asks, pass remainingIn as the quote override:
Affected Code Paths
_fillOrdersExactInwithbaseForQuote = false(ask path), partial fill case only- Full fills are not affected because the quote amount is derived from
order.remaining, notremainingIn - Bid swaps are not affected because the taker pays base tokens directly
Example: Before and After
Before (buggy):Invariants
- Zero-sum: for any swap,
takerPaid == makerReceived(within the same token) - Taker receives
floor(amountIn / price)base tokens (rounds in favor of protocol) - Maker receives exactly what taker paid in quote tokens