SSSwap

First Flight #41
Beginner FriendlyRust
100 EXP
View results
Submission Details
Severity: high
Valid

Incorrect formula and fee calculation in `swap_exact_in` instruction

Description

The swap_exact_in instruction calculates the output token amount and fees incorrectly. Specifically, it does not properly account for token decimals, which can lead to inaccurate swap rates when tokens have different decimal places. Additionally, the fee calculation is applied to the output amount (amount_out) rather than the input amount (amount_in), which is inconsistent with common AMM designs where fees are deducted from the input before calculating the output. These issues can cause incorrect token amounts to be swapped, resulting in unfair trades and potentially unexpected losses for users.

Infected Code Snippet

if zero_for_one {
let numerator: u128 = (reserve_b as u128)
.checked_mul(amount_in as u128)
.ok_or(AmmError::Overflow)?;
let denominator: u128 = (reserve_a as u128)
.checked_add(amount_in as u128)
.ok_or(AmmError::Overflow)?;
if denominator == 0 {
return err!(AmmError::DivisionByZero);
}
let mut amount_out: u64 = numerator.div_floor(&denominator) as u64;
let lp_fees = (amount_out as u128 * 3).div_floor(&1000) as u64;
amount_out = amount_out - lp_fees;
}

Impact

  • Incorrect handling of decimals may cause the swap output to be skewed, especially for tokens with differing decimals.

  • Calculating fees on the output rather than input can distort the actual value exchanged, potentially disadvantaging liquidity providers or traders.

  • The combination of these issues can result in loss of funds or unfair exchange rates, degrading user trust and platform reliability.

Recommendation / Fix

  1. Handle token decimals properly: Normalize token amounts to a common base (e.g., convert to u128 with decimals factored in) before calculations.

  2. Calculate fees on the input amount: Deduct the fee from amount_in before computing the output, as is standard in AMM designs.

  3. Use the correct constant product formula with fees:

let fee_numerator = 997u128; // 0.3% fee
let fee_denominator = 1000u128;
if zero_for_one {
let amount_in_with_fee = (amount_in as u128)
.checked_mul(fee_numerator)
.ok_or(AmmError::Overflow)?;
let numerator = amount_in_with_fee
.checked_mul(reserve_b as u128)
.ok_or(AmmError::Overflow)?;
let denominator = (reserve_a as u128)
.checked_mul(fee_denominator)
.ok_or(AmmError::Overflow)?
.checked_add(amount_in_with_fee)
.ok_or(AmmError::Overflow)?;
let amount_out = numerator.checked_div(denominator).ok_or(AmmError::DivisionByZero)?;
// amount_out is the final amount after fees
}

This approach correctly applies the 0.3% fee on the input amount and then calculates the output amount using the constant product formula. Token decimals should be normalized before these calculations if tokens have different decimal places.

Updates

Lead Judging Commences

0xtimefliez Lead Judge 5 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Protocol is incompatible with differenct decimals

Support

FAQs

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