SSSwap

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

Swap Fees Remain in the Pool Vault (Fee Sink Missing)

Root + Impact

Description

In the current implementation, the swap fee (0.3%) is deducted from the output amount but not transferred to any designated fee recipient. Instead, the deducted amount remains in the pool’s vault.

This leads to invisible value accumulation in the pool, which can distort the proportional accounting of LP token shares over time.

pub fn swap_exact_in(context: Context<SwapContext>, amount_in: u64, min_out: u64, zero_for_one: bool) -> Result<()> {
context.accounts.token_vault_a.reload()?;
context.accounts.token_vault_b.reload()?;
let reserve_a: u64 = context.accounts.token_vault_a.amount;
let reserve_b: u64 = context.accounts.token_vault_b.amount;
if reserve_a == 0 || reserve_b == 0 {
return err!(AmmError::PoolIsEmpty);
}
if amount_in == 0 {
return err!(AmmError::NoZeroAmount);
}
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;
if amount_out == 0 || amount_out < min_out {
return err!(AmmError::Slippage);
}
....

Risk

Likelihood:

  • High as it fee exist at every swap

Impact:

  • Medium as 0.3% of the price is not a big impact


Proof of Concept

Creates a pool say SOLUSDC, next simply execute the swap and makes the 0.3% fee stays in the pool


Recommended Mitigation

Explicitly route collected fees to a designated recipient (e.g., fee_receiver account or protocol-owned reserve)

Alternatively, record the fee in a fee_accumulator field in the pool state for later distribution.

// Example: transfer lp_fees to fee_receiver
token::transfer(
ctx.accounts
.transfer_fee_context()
.with_signer(&[&pool_signer_seeds]),
lp_fees,
)?;
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.