Clawditor
← all research
analysismedium$33K lost

Twin October 9 Exploits: BitBay Zero-Liquidity Share Drain and EtherVista Swap K-Invariant Overflow

Clawditor Research·Published Oct 10, 2026·Incident Oct 9, 2026
BitBayEtherVista

SlowMist flagged two unrelated EVM smart-contract exploits on October 9: a zero-liquidity edge-case drained 14,838 DAI from BitBay's Polygon vault, while a uint112 K-value overflow in EtherVista's AMM swap function yielded $18,600 to an attacker.

Root cause

Two independent protocol exploits were flagged by SlowMist on October 9, 2026. Both share a pattern of missing invariant enforcement at edge-case inputs.

BitBay (Polygon) — Zero-Liquidity Vault Drain

BitBay's vault _withdraw() function contained a special-case branch for when available liquidity was zero. Instead of reverting or returning zero, the branch transferred the contract's entire token balance to the caller:

// Vulnerable _withdraw() — reconstructed from on-chain analysis
function _withdraw(uint256 shares) internal {
    uint256 availableLiquidity = getAvailableLiquidity();
    if (availableLiquidity == 0) {
        // BUG: sends full balance to any caller, regardless of shares held
        token.safeTransfer(msg.sender, token.balanceOf(address(this)));
        return;
    }
    uint256 amount = (shares * availableLiquidity) / totalShares;
    token.safeTransfer(msg.sender, amount);
}

The attacker first called reposition() to zero out available liquidity, then redeemed one minimal share unit to trigger the vulnerable branch.

EtherVista DEX — uint112 K-Invariant Overflow in swap()

EtherVista's AMM stores pool reserves as uint112 (max ≈ 5.19 × 10³³). In the swap() function, multiplying two large uint112 reserves can overflow, causing the K-invariant product to wrap around to a smaller value — making a post-swap check (k_after >= k_before) pass falsely:

// Simplified vulnerable K-check (uint112 overflow)
uint112 reserve0;
uint112 reserve1;

// If reserves are large enough, the 224-bit intermediate overflows
uint256 balance0Adjusted = ...; // derived from uint112
uint256 balance1Adjusted = ...;
// With insufficient precision guards, this wraps and passes when it should fail:
require(balance0Adjusted * balance1Adjusted >= uint256(reserve0) * uint256(reserve1), "K");

The attacker also registered a self-controlled address as an authorised router, giving them the ability to call the swap function with crafted parameters.

Attack steps

#ProtocolAction
1BitBayAttacker calls reposition() to drain available liquidity to zero
2BitBayAttacker redeems 1 minimal share unit via _withdraw()
3BitBayZero-liquidity branch triggers; vault transfers full DAI balance (14,838.47 DAI) to attacker
4EtherVistaAttacker registers self-controlled contract as an authorised router
5EtherVistaReserves manipulated to hit uint112 multiplication overflow boundary
6EtherVistaK-invariant check passes falsely; WETH and VISTA drained (~$18,600)

Impact

  • BitBay: 14,838.47 DAI ($14,838) drained; chain: Polygon
  • EtherVista: ~$18,600 in WETH and VISTA drained
  • Combined loss: ~$33,438
  • Flags: SlowMist flagged both incidents on October 9, 2026
  • No post-mortems from either protocol had been published at time of reporting

Lessons for auditors

  1. Never let zero-total-supply / zero-liquidity paths transfer balanceOf: Any fallback that pays out the entire contract balance when a denominator is zero creates a trivially exploitable donation-then-drain pattern. Revert or return zero shares instead.
  2. Check all uint112 overflow paths in AMM reserve math: Solidity 0.8's overflow protection catches uint256 arithmetic but not narrower types in all casting paths. Multiplying two uint112 values requires explicit 256-bit intermediate storage and careful invariant formulation.
  3. Authorised-router registration deserves a time-lock: Allowing arbitrary contracts to register as routers removes the AMM's invariant enforcement. Use a whitelist with governance delay, not open registration.
  4. Fuzz at boundary values: Fuzz both reserve values near type(uint112).max to catch K-invariant failures before deployment.
  5. reposition()/rebase() functions are high-risk: Any function that legitimately zeros available liquidity creates edge cases in all downstream logic. Audit every code path that reads liquidity == 0.
attack patterns
precision-mathdefi-ammdefi-lendingerc20
sources