SSSwap

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

Critical Missing Bounds Allow High Volatility and Exploitable Swaps

Summary

The swap_exact_in and swap_exact_out functions in swap_operations.rs allow token swaps without enforcing a minimum liquidity or restricting large input amounts relative to pool reserves. In scenarios where reserves are low, even moderate swaps cause significant price impact, enabling MEV bots to execute arbitrage and sandwich attacks that extract value from other users. This design flaw leads to extreme volatility and loss of user funds.

Vulnerability Details

The core swap logic relies on the constant product formula:

let reserve_a: u64 = context.accounts.token_vault_a.amount;
let reserve_b: u64 = context.accounts.token_vault_b.amount;
let numerator = (reserve_out as u128).checked_mul(amount_in as u128)?;
let denominator = (reserve_in as u128).checked_add(amount_in as u128)?;
let amount_out = (numerator.checked_div(denominator)?).try_into()?;

There are no safeguards preventing amount_in from being a large fraction of reserve_in. For instance, if amount_in equals 50% of reserve_in, the formula results in a disproportionately low amount_out, drastically shifting the internal price and allowing attackers to exploit this slippage.

Example

If a pool has reserve_A = 100, reserve_B = 100, and a user swaps amount_in = 50, then:

amount_out = floor(100 × 50 / (100 + 50)) = floor(5000 / 150) = 33;

The internal price shifts from 1:1 to approximately 1 A = 0.66 B (−33%). This exposes the user to significant slippage and allows bots to:

  • Front-run with small trades that adjust the price,

  • Let the victim's large swap execute at the new unfavorable price,

  • Back-run to restore balance and pocket the difference.

With small pools, a single transaction can cause price deviations exceeding 20–50%, draining most of the liquidity.

Impact

This issue leads to:

  • High slippage and volatile pricing for regular users,

  • MEV exploitation via sandwich and arbitrage attacks,

  • Drained user value and degraded trust in the protocol's swap functionality.

The severity is High, especially in pools with limited liquidity.

Proof of Concept

Simulate on-chain or unit test using:

  • A small pool: 100 A ↔ 100 B.

  • Call swap_exact_in(50) and verify amount_out = 33.

  • Deploy a bot that executes a front-run (10 A), victim swap (50 A), and back-run (adjustment to extract arbitrage profit).

Tools Used

This issue was identified through manual analysis of the swap_operations.rs logic and standard AMM formula behavior.

Recommendations

Limit input size relative to reserves:

let max_pct_bp: u128 = 500; // 5%
let max_amount_in = (reserve_in as u128)
.checked_mul(max_pct_bp).ok_or(AmmError::TooLargeAmount)?
.checked_div(10_000).ok_or(AmmError::TooLargeAmount)?;
require!(
(amount_in as u128) <= max_amount_in,
AmmError::AmountExceedsMaxPct
);

Enforce user-defined slippage limits:

let price_before = reserve_in as u128 * 10_000 / reserve_out as u128;
let price_after = (reserve_in + amount_in) as u128 * 10_000 / (reserve_out - amount_out) as u128;
let impact_bp = if price_after > price_before {
price_after.checked_sub(price_before).ok_or(AmmError::Overflow)?
} else {
price_before.checked_sub(price_after).ok_or(AmmError::Overflow)?
};
require!(
impact_bp <= max_price_impact_bp as u128,
AmmError::PriceImpactTooHigh
);

Set minimum liquidity threshold for pool creation and deposits:

if reserve_a < MIN_LIQUIDITY || reserve_b < MIN_LIQUIDITY {
return Err(AmmError::InsufficientLiquidity.into());
}

Note: Add the following custom errors to the errors.rs file:

  • TooLargeAmount

  • AmountExceedsMaxPct

  • PriceImpactTooHigh

  • Overflow

  • InsufficientLiquidity


Off-chain monitoring of reserve ratio and slippage dynamics:

Implement real-time alerts to detect abrupt reserve ratio changes (>5%) within a few blocks. Upon detecting volatility spikes, take automated or manual action (e.g., pausing the pool, raising an alert).

Updates

Lead Judging Commences

0xtimefliez Lead Judge 11 days ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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