Skip to content

Stuck Swap Pools

id: LS44M
title: Stuck Swap Pools
baseSeverity: M
category: liquidity
language: solidity
blockchain: [ethereum]
impact: Permanent or prolonged trading disablement
status: draft
complexity: low
attack_vector: external
mitigation_difficulty: medium
versions: [">0.6.0", "<0.8.25"]
cwe: CWE-664
swc: SWC-131

๐Ÿ“ Description

  • Stuck swap pools occur when Automated Market Makers (AMMs) or liquidity contracts enter a state where further swaps, deposits, or withdrawals are impossible, typically due to:
  • Imbalanced reserves, Precision loss or math overflows in the pricing formula,Token transfer failures ,Paused or non-upgradable pool logic preventing recovery.
  • This leads to a frozen liquidity pool, user frustration, and potential protocol revenue loss.

๐Ÿšจ Vulnerable Code

function swap(uint256 amountIn, address tokenIn, address tokenOut) external {
    uint256 amountOut = getAmountOut(amountIn, tokenIn, tokenOut);
    require(amountOut > 0, "Insufficient output");

    IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
    IERC20(tokenOut).transfer(msg.sender, amountOut); // โŒ Can fail silently (non-returning)
}

๐Ÿงช Exploit Scenario

Step-by-step:

  1. A user swaps large amounts of a stablecoin for a volatile token.
  2. The volatile token fails to transfer (e.g., due to a deflationary fee or ERC20 non-compliance).
  3. The pool records the swap, reducing internal reserves, but no tokens are delivered.
  4. The pool has invalid reserves,Future swaps are mispriced or fail,liquidity providers cannot remove their tokens safely.

Assumptions:

  • The swap pool smart contract does not include fallback mechanisms for failed withdrawals.
  • If the smart contract is vulnerable to reentrancy attacks, it may lock tokens during simultaneous interactions.

โœ… Fixed Code

function swap(uint256 amountIn, address tokenIn, address tokenOut) external {
    require(amountIn > 0, "Zero input");

    uint256 balanceBefore = IERC20(tokenOut).balanceOf(address(this));

    IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
    uint256 amountOut = getAmountOut(amountIn, tokenIn, tokenOut);
    require(amountOut > 0, "Insufficient output");

    bool success = IERC20(tokenOut).transfer(msg.sender, amountOut);
    require(success, "Token transfer failed");

    // โœ… Optional: Check final balance
    uint256 balanceAfter = IERC20(tokenOut).balanceOf(address(this));
    require(balanceBefore - balanceAfter == amountOut, "Token accounting mismatch");
}

๐Ÿงญ Contextual Severity

- context: "Default"
  severity: M
  reasoning: "Common risk in AMMs but often recoverable with admin/manual rebalance."
- context: "High-volume public DEX"
  severity: H
  reasoning: "Traders may get stuck, harming UX and possibly enabling manipulation."
- context: "Private swap pool with few users"
  severity: L
  reasoning: "Limited harm, but could still disrupt liquidity provision."

๐Ÿ›ก๏ธ Prevention

Primary Defenses

  • Always check return values from token transfer() and transferFrom().
  • Use SafeERC20 from OpenZeppelin for compatibility with non-standard ERC20s.
  • Add reserve sanity checks before and after swaps.
  • Validate against fee-on-transfer, rebase, and ERC777 hooks where applicable.

Additional Safeguards

  • Implement emergency withdrawal mechanisms for LPs.
  • Use a circuit breaker if reserve imbalance crosses a safe threshold.
  • Add monitoring for stuck swaps: repeated reverts or output = 0.

Detection Methods

  • Slither: unchecked-transfer, reserve-mismatch, swap-deadlock detectors.
  • Manual simulation of edge cases with:
  • Tokens returning false,Fee-on-transfer tokens,Token with internal hooks (ERC777, rebase).
  • Fuzz testing with various token behaviors.

๐Ÿ•ฐ๏ธ Historical Exploits

  • Name: SushiSwap Liquidity Pool Bug
  • Date: April 9, 2023
  • Loss: $3.3 million
  • Post-mortem: Link to post-mortem

๐Ÿ“š Further Reading


โœ… Vulnerability Report

id: LS44M
title: Stuck Swap Pools 
severity: M
score:
impact: 5         
exploitability: 3 
reachability: 4   
complexity: 2     
detectability: 5  
finalScore: 4.1

๐Ÿ“„ Justifications & Analysis

  • Impact: Critical โ€” frozen liquidity breaks economic guarantees.
  • Exploitability: Medium โ€” not always malicious, but common via broken tokens or math.
  • Reachability: Common in forks or AMM clones with minor swaps.
  • Complexity: Simple to fix, devastating if ignored.
  • Detectability: High โ€” Slither and test coverage highlight this quickly.