SSSwap

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

No Minimum Liquidity Lock in the `liquidity_operations::remove_liquidity` function

Description: The AMM doesn't permanently lock a minimum amount of liquidity, allowing complete drainage of pools and potential numerical precision issues. The AMM allows liquidity providers to remove 100% of their liquidity from the pool. This creates scenarios where pools can be completely drained, leading to division by zero errors or extremely small reserves that cause precision issues. Most AMM protocols (like Uniswap) permanently lock a small amount of liquidity to prevent these edge cases.

Impact:

  1. Pools can be completely drained, causing subsequent operations to fail

  2. Extremely small reserves can lead to precision issues and unfair pricing

  3. Potential for temporary DoS of specific pools

Proof of Concept: A user who owns 100% of the LP tokens can remove all liquidity:

// User has all LP tokens (total_supply)
// In remove_liquidity function:
let amount_a_to_return_u128 = (total_supply as u128)
.checked_mul(reserve_a as u128)
.ok_or(AmmError::Overflow)?
.checked_div(total_supply as u128)
.ok_or(AmmError::DivisionByZero)?;
// This returns all of reserve_a
// Same for reserve_b
// Result: Pool is completely drained

Recommended Mitigation: Implement a minimum liquidity lock mechanism in the initialize_pool function:

pub fn initialize_pool(context: Context<InitializePool>, amount_token_a: u64, amount_token_b: u64) -> Result<()> {
// Existing code...
// Calculate LP tokens to mint
let lp_amount_to_mint: u64 = liquidity_calculation(amount_token_a, amount_token_b)?;
// Define minimum liquidity to lock (e.g., 1000)
const MINIMUM_LIQUIDITY: u64 = 1000;
// Ensure we're minting enough to lock some
require!(lp_amount_to_mint > MINIMUM_LIQUIDITY, AmmError::InsufficientInitialLiquidity);
// Mint LP tokens to creator
let creator_lp_amount = lp_amount_to_mint - MINIMUM_LIQUIDITY;
// Mint to creator
mint_to(cpi_context, creator_lp_amount)?;
// Mint minimum liquidity to a dead address or the protocol itself
let cpi_accounts_min_liquidity = MintTo {
mint: context.accounts.lp_mint.to_account_info(),
to: dead_address_account.to_account_info(), // An account that can never spend
authority: context.accounts.liquidity_pool.to_account_info(),
};
let cpi_context_min_liquidity = CpiContext::new_with_signer(
context.accounts.token_program.to_account_info(),
cpi_accounts_min_liquidity,
signer_seeds,
);
mint_to(cpi_context_min_liquidity, MINIMUM_LIQUIDITY)?;
Ok(())
}
Updates

Lead Judging Commences

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

Permanent DoS of Pools if initial liquidity gets removed

Support

FAQs

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