SSSwap

First Flight #41
Beginner FriendlyRust
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Incorrect Formula Used in `swap_exact_out` Calculation

Description

The swap_exact_out function uses an incorrect formula to calculate the required amount_in to receive a specified amount_out during a token swap. The current formula:

let numerator: u128 = (reserve_a as u128)
.checked_mul(amount_out as u128)
.ok_or(AmmError::Overflow)?;
let denominator: u128 = (reserve_b as u128)
.checked_sub(amount_out as u128)
.ok_or(AmmError::Underflow)?;
let amount_in_no_fee: u64 = numerator.div_floor(&denominator) as u64;
let lp_fees: u64 = ((amount_in_no_fee as u128)
.checked_mul(3)
.ok_or(AmmError::Overflow)?)
.div_floor(&1000) as u64;
let amount_in_final = (amount_in_no_fee as u128)
.checked_add(lp_fees as u128)
.ok_or(AmmError::Overflow)? as u64;

applies the fee incorrectly after computing the base input amount. In constant product AMMs like Uniswap, fees must be factored into the input amount before applying the invariant formula.


Impact

  • Incorrect pricing for swap inputs may result in users overpaying or underpaying for swaps.

  • Misleading slippage protection, allowing outcomes outside user expectations.

  • Inconsistent reserves, leading to pool imbalance and potential losses for liquidity providers.


Recommendation

Use the correct formula for constant product AMMs with fees applied upfront. For a 0.3% fee, the effective rate is 0.997 (i.e., 997 / 1000). The corrected calculation should look like this:

Fix:

let amount_out_u128 = amount_out as u128;
let reserve_in_u128 = reserve_a as u128;
let reserve_out_u128 = reserve_b as u128;
let numerator = reserve_in_u128
.checked_mul(amount_out_u128)
.and_then(|v| v.checked_mul(1000))
.ok_or(AmmError::Overflow)?;
let denominator = reserve_out_u128
.checked_sub(amount_out_u128)
.and_then(|v| v.checked_mul(997))
.ok_or(AmmError::Underflow)?;
require!(denominator > 0, AmmError::DivisionByZero);
let amount_in = numerator
.checked_div(denominator)
.and_then(|v| v.checked_add(1)) // round up
.ok_or(AmmError::Overflow)? as u64;

This version preserves the invariant and includes the fee during the calculation, not after.

Updates

Lead Judging Commences

0xtimefliez Lead Judge 5 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

abduo_ov Submitter
5 days ago
0xtimefliez Lead Judge
4 days ago
0xtimefliez Lead Judge 3 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.