Skip to content
Request an audit

XYK reflect_curve omits swap fee in order sizing, leaking LP fees

TitleXYK reflect_curve omits swap fee in order sizing, leaking LP fees
Reward$1493, 2 dups
ContestDango Dex - 15 Sep 2025 on Sherlock
Authorblockace
ContextBusiness 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.

rust
// 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

s · p = Rq · sRb + s · (1 − f)

yields

s = Rq · (1 − f)pRb

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:

Qout = Rq · sRb + s · (1 − f)

A passive bid at price p promises s · p quote for s base, so set s · p = Qout.

Solving:

p = RqRb + s · (1 − f) ⇒ Rb + s = Rq · (1 − f)ps = Rq · (1 − f)pRb

Therefore the total size at price p must be Rq · (1 − f)pRb.

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:

Bout = Rb · xRq + x · (1 − f)

Set s = Bout with x = s · p:

s = Rb · s · pRq + s · p · (1 − f) ⇒ 1 = Rb · pRq + s · p · (1 − f)

Hence:

Rq + s · p = Rb · p · (1 − f) ⇒ s · p = Rb · p · (1 − f) − Rqs = Rb · (1 − f) − Rqp

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

Message on Telegram All 62 posts