SSSwap

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

Potential Fee Calculation Vulnerability in AMM Swap Operations

Root + Impact

Impact

The fee calculation in the AMM's swap operations does not set a minimum, and the fee is based on the output. This can be bypassed by making swaps that result in output amounts of 333 base units or less. This potentially allows attackers to avoid paying the 0.3% swap fee by splitting large swaps into multiple small swaps.

Vulnerability Details

The vulnerability exists in programs/amm/src/instructions/swap_operations.rsL141-145.

// In swap_exact_in function
let mut amount_out: u64 = numerator.div_floor(&denominator) as u64;
let lp_fees = (amount_out as u128 * 3).div_floor(&1000) as u64; // Fee calculation
amount_out = amount_out - lp_fees;

## Description
The issue is that the fee calculation uses integer division, which rounds down. For any output amount less than 334 base units, the fee calculation `(amount_out * 3) / 1000` will result in 0.
## Proof of Concept
In this exploit scenario, there exists a pool with:
- Token A: USDC (6 decimals, $1 each)
- Token B: EXPENSIVE (6 decimals, $1000 each)
- Pool has 1,000,000 USDC and 1,000 EXPENSIVE (balanced at $1M each side)
An attacker can:
- Make multiple small swaps that result in 333 base units or less of output
- Each swap will have 0 fee because (amount_out * 3) / 1000 rounds down to 0 for any amount ≤ 333 base units
- For example, if swapping USDC for EXPENSIVE:
- A swap resulting in 333 base units of EXPENSIVE (0.000333 EXPENSIVE) would have 0 fee
- A swap resulting in 1 base unit of EXPENSIVE (0.000001 EXPENSIVE) would have 0 fee
- By making multiple swaps in this range, the attacker can avoid paying the 0.3% fee that should be charged
- This works for any token pair in the pool, as long as the output amount is 333 base units or less
## Recommended Fix
1. Calculate fees based on input amount rather than output amount
2. Add a minimum fee amount
3. Use proper decimal arithmetic to avoid rounding issues
## Risk
**Likelihood**: Medium
* Reason 1 // This will occur when an attacker makes many small trades with output resulting less than 333 base units.
* Reason 2 this will be figured out by an attacker very quickly
**Impact**:
* Impact 1
all fees are bypassed
* Impact 2
attacker can make trades without paying any fees
## Proof of Concept
## Recommended Mitigation
the below code is a diff that could mitigate this:
#`rust`#
// In swap_exact_in function
let mut amount_out: u64 = numerator.div_floor(&denominator) as u64;
- let lp_fees = (amount_out as u128 * 3).div_floor(&1000) as u64;
+ // Calculate fee on input amount instead of output amount
+ let lp_fees = (amount_in as u128 * 3).div_floor(&1000) as u64;
+ // Add minimum fee of 1 base unit
+ let lp_fees = std::cmp::max(lp_fees, 1);
amount_out = amount_out - lp_fees;
## Recommended Fix
1. Calculate fees based on input amount rather than output amount
2. Add a minimum fee amount
3. Use proper decimal arithmetic to avoid rounding issues
Updates

Lead Judging Commences

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

div_floor instead div_ceil during lp fee calculations

Appeal created

cheesesteak Submitter
4 days ago
0xtimefliez Lead Judge 3 days ago
Submission Judgement Published
Validated
Assigned finding tags:

div_floor instead div_ceil during lp fee calculations

Support

FAQs

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