XYK reflect_curve omits swap fee in order sizing, leaking LP fees
| Title | XYK reflect_curve omits swap fee in order sizing, leaking LP fees |
|---|---|
| Reward | $1493, 2 dups |
| Contest | Dango Dex - 15 Sep 2025 on Sherlock |
| Author | blockace |
| Context | Business Logic |
The reflect_curve function for the XYK pool sizes passive bid and ask orders without accounting for the swap fee, letting takers match them effectively fee-free. Fees that should accrue to liquidity providers are under-collected, a direct loss on top of ordinary impermanent loss.
In dango/dex/src/core/xyk.rs, the total order size at price p is computed from the reserves as if there were no swap fee. For bids, the size uses quote_reserve / p - base_reserve; for asks, it uses base_reserve - quote_reserve / p. The fee factor (1 − f) is omitted, so passive orders are reflected at sizes that do not accrue the fee when taken.
// dango/dex/src/core/xyk.rs (bids)
let quote_reserve_div_price = quote_reserve.checked_div_dec(price).ok()?;
let mut size = quote_reserve_div_price.checked_sub(base_reserve).ok()?;
// dango/dex/src/core/xyk.rs (asks)
let quote_reserve_div_price = quote_reserve.checked_div_dec(price).ok()?;
let size = base_reserve.checked_sub(quote_reserve_div_price).ok()?;
Mathematically, for a bid at price p with reserves Rb and Rq and fee f, the equality
yields
The implementation lacks the (1 − f) term. A symmetric adjustment is required for asks as well.
Detailed derivation
Not needed to understand the issue, but worth going through.
Bids (pool buys base, taker sells base):
Reserves: Rb (base), Rq (quote); price p (quote per base); fee f.
Exact-in (base in) output after fee:
A passive bid at price p promises s · p quote for s base, so set s · p = Qout.
Solving:
Therefore the total size at price p must be Rq · (1 − f)p − Rb.
Asks (pool sells base, taker buys base):
The taker pays quote x = s · p and wants to receive s base.
Exact-in (quote in) output after fee:
Set s = Bout with x = s · p:
Hence:
Therefore the total size at price p must be Rb · (1 − f) − Rqp.
Alpha: always make sure fees are charged in every scenario. If you are not sure, submit anyway.
Conclusion
This finding would earn you $1493, basically from missing functionality, so make sure to always think about what is missing, and not only what is wrong.
Full Report
Codebase