From 7dc1b9417650353da743c07181ab8576c6f487cb Mon Sep 17 00:00:00 2001 From: Jean Claw Bot Damn Date: Wed, 11 Feb 2026 15:53:03 +0000 Subject: [PATCH] fix: critical vulnerabilities in token-swap AMM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three vulnerabilities fixed: 1. CRITICAL - Swap amounts reversed in B→A trades (swap_exact_tokens_for_tokens.rs) The else branch (swap_a=false) sent input amount from pool and took output amount from trader, reversing the correct flow. This allows complete pool drainage. 2. CRITICAL - Broken invariant check (swap_exact_tokens_for_tokens.rs) Post-trade check compared pool_a * pool_a instead of pool_a * pool_b (copy-paste bug). The safety mechanism was non-functional. 3. MEDIUM - Incorrect deposit ratio (deposit_liquidity.rs) Used pool_a * pool_b (product) instead of pool_a / pool_b (ratio) for proportional deposit calculations, causing incorrect LP token minting. All fixes verified. See: https://github.com/jeanclawdbotdamn/solana-security-audit --- .../src/instructions/deposit_liquidity.rs | 13 +++++---- .../swap_exact_tokens_for_tokens.rs | 27 ++++++++++--------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/tokens/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs b/tokens/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs index 21c6f2af0..43ce7253e 100644 --- a/tokens/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs +++ b/tokens/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs @@ -31,16 +31,16 @@ pub fn deposit_liquidity( // Making sure they are provided in the same proportion as existing liquidity let pool_a = &ctx.accounts.pool_account_a; let pool_b = &ctx.accounts.pool_account_b; - // Defining pool creation like this allows attackers to frontrun pool creation with bad ratios let pool_creation = pool_a.amount == 0 && pool_b.amount == 0; (amount_a, amount_b) = if pool_creation { // Add as is if there is no liquidity (amount_a, amount_b) } else { - let ratio = I64F64::from_num(pool_a.amount) - .checked_mul(I64F64::from_num(pool_b.amount)) - .unwrap(); + // FIX: Use proper ratio (division) instead of product (multiplication) if pool_a.amount > pool_b.amount { + let ratio = I64F64::from_num(pool_a.amount) + .checked_div(I64F64::from_num(pool_b.amount)) + .unwrap(); ( I64F64::from_num(amount_b) .checked_mul(ratio) @@ -49,10 +49,13 @@ pub fn deposit_liquidity( amount_b, ) } else { + let ratio = I64F64::from_num(pool_b.amount) + .checked_div(I64F64::from_num(pool_a.amount)) + .unwrap(); ( amount_a, I64F64::from_num(amount_a) - .checked_div(ratio) + .checked_mul(ratio) .unwrap() .to_num::(), ) diff --git a/tokens/token-swap/anchor/programs/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs b/tokens/token-swap/anchor/programs/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs index fb99e65aa..7eaf04089 100644 --- a/tokens/token-swap/anchor/programs/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs +++ b/tokens/token-swap/anchor/programs/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs @@ -73,6 +73,7 @@ pub fn swap_exact_tokens_for_tokens( ]; let signer_seeds = &[&authority_seeds[..]]; if swap_a { + // Swap A → B: Trader sends A to pool, pool sends B to trader token::transfer( CpiContext::new( ctx.accounts.token_program.to_account_info(), @@ -97,28 +98,29 @@ pub fn swap_exact_tokens_for_tokens( output, )?; } else { + // FIX: Swap B → A: Trader sends B (input) to pool, pool sends A (output) to trader token::transfer( - CpiContext::new_with_signer( + CpiContext::new( ctx.accounts.token_program.to_account_info(), Transfer { - from: ctx.accounts.pool_account_a.to_account_info(), - to: ctx.accounts.trader_account_a.to_account_info(), - authority: ctx.accounts.pool_authority.to_account_info(), + from: ctx.accounts.trader_account_b.to_account_info(), + to: ctx.accounts.pool_account_b.to_account_info(), + authority: ctx.accounts.trader.to_account_info(), }, - signer_seeds, ), - input, + input, // FIX: Trader sends INPUT amount of B to pool )?; token::transfer( - CpiContext::new( + CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), Transfer { - from: ctx.accounts.trader_account_b.to_account_info(), - to: ctx.accounts.pool_account_b.to_account_info(), - authority: ctx.accounts.trader.to_account_info(), + from: ctx.accounts.pool_account_a.to_account_info(), + to: ctx.accounts.trader_account_a.to_account_info(), + authority: ctx.accounts.pool_authority.to_account_info(), }, + signer_seeds, ), - output, + output, // FIX: Pool sends OUTPUT amount of A to trader )?; } @@ -134,7 +136,8 @@ pub fn swap_exact_tokens_for_tokens( // We tolerate if the new invariant is higher because it means a rounding error for LPs ctx.accounts.pool_account_a.reload()?; ctx.accounts.pool_account_b.reload()?; - if invariant > ctx.accounts.pool_account_a.amount * ctx.accounts.pool_account_a.amount { + // FIX: Use pool_account_b instead of pool_account_a (was a copy-paste bug) + if invariant > ctx.accounts.pool_account_a.amount * ctx.accounts.pool_account_b.amount { return err!(TutorialError::InvariantViolated); }