SSSwap

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

Issue: Incorrect Fee Calculation

Incorrect Fee Calculation leads to loss of funds

Description

  • Normal behavior: In a Uniswap V2-style AMM, a 0.30 % fee is taken on the input amount.

  • In the audited code, both swap_exact_in and swap_exact_out compute fees incorrectly by taking the fee in terms of the output amount.

// Root cause in the codebase with @> marks to highlight the relevant section
pub fn swap_exact_in(… zero_for_one: bool ) -> Result<()> {
if zero_for_one {
// 1) Compute gross output ignoring fees:
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;
// @> Wrong: lp_fees is 0.3% of this output
let lp_fees = (amount_out as u128 * 3).div_floor(&1000) as u64;
amount_out = amount_out - lp_fees;
} else {
// symmetric for token B→A
}
}
pub fn swap_exact_out(… zero_for_one: bool ) -> Result<()> {
if zero_for_one {
// 1) Compute “no‐fee” input needed for exact output:
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)?;
require!(denominator > 0, AmmError::DivisionByZero);
let amount_in_no_fee: u64 = numerator.div_floor(&denominator) as u64;
// @> Wrong: lp_fees is 0.3% added on top of no‐fee input
let fee_numerator_u128 = (amount_in_no_fee as u128)
.checked_mul(3)
.ok_or(AmmError::Overflow)?;
let lp_fees: u64 = fee_numerator_u128.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;
} else {
// symmetric for token B→A
}
}

Risk

Likelihood:

  • Occurs every time a user calls swap_exact_in or swap_exact_out

Impact:

  • Incorrect price: By deducting 0.3 % on the output rather than the input, the user receives slightly more or less of the output token than intended under the constant product formula.

Proof of Concept

// Compare “correct Uniswap V2” vs. “current implementation” for simple reserves:
fn simulate_swap_exact_in() {
// Let reserve_in = 10_000, reserve_out = 20_000, amount_in = 1_000
let reserve_a: u128 = 10_000;
let reserve_b: u128 = 20_000;
let amount_in: u128 = 1_000;
// Uniswap V2 correct:
// amount_in_with_fee = 1_000 * 997 = 997_000
// numerator = 997_000 * 20_000 = 19_940_000_000
// denominator = (10_000 * 1_000) + 997_000 = 10_000_000 + 997_000 = 10_997_000
// amount_out_correct = floor(19_940_000_000 / 10_997_000) = floor(1_812.897…) = 1_812
let amount_in_with_fee = amount_in * 997;
let numerator_correct = amount_in_with_fee * reserve_b;
let denominator_correct = (reserve_a * 1_000) + amount_in_with_fee;
let amount_out_correct = (numerator_correct / denominator_correct) as u64;
assert_eq!(amount_out_correct, 1_812);
// Current implementation:
// gross_out = floor( reserve_b * amount_in / (reserve_a + amount_in) )
// = floor( 20_000 * 1_000 / (10_000 + 1_000) )
// = floor(20_000_000 / 11_000) = 1_818
// lp_fees = floor(1_818 * 3 / 1000) = floor(5.454) = 5
// amount_out_impl = 1_818 - 5 = 1_813
let numerator_impl = reserve_b * amount_in;
let denominator_impl = reserve_a + amount_in;
let gross_out_impl = (numerator_impl / denominator_impl) as u64; // 1_818
let lp_fees_impl = ((gross_out_impl as u128 * 3) / 1000) as u64; // 5
let amount_out_impl = gross_out_impl - lp_fees_impl; // 1_813
// Differences:
// Uniswap V2 would give 1_812 tokens out;
// current code gives 1_813 tokens out and charges a 5‐token fee on output.
//
// Because of rounding / ordering, the user actually ends up 1 token richer,
// and the pool loses 1 additional token. Over many swaps, this drains reserves.
println!("Uniswap V2 gives {} vs impl gives {}", amount_out_correct, amount_out_impl);
// Output: Uniswap V2 gives 1812 vs impl gives 1813
}

Recommended Mitigation

Compute the fee as part of the input amount rather than the output amount

- remove this code
+ add this code
Updates

Lead Judging Commences

0xtimefliez Lead Judge 5 days ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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