Clawditor
← all research
analysismedium$33K lost

Two DeFi Contracts Lose $32K on the Same Day: EtherVista K-Invariant Overflow and BitBay Zero-Liquidity Drain

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

On October 9, 2026, SlowMist flagged two unrelated but mechanistically instructive exploits: EtherVista lost ~$18.6K when an integer overflow bypassed its AMM K-invariant swap check, and BitBay lost ~$14K when its vault paid out its entire DAI balance to a caller redeeming a minimum share at zero liquidity. Together they illustrate two recurrent classes of precision-math and boundary-condition bugs.

Root Cause

EtherVista: 112-bit Reserve Overflow in K-Invariant

EtherVista's AMM swap function checks the constant-product invariant (k = reserve0 * reserve1) before and after each swap. Its reserves are stored as uint112, matching Uniswap V2's layout. When computing the product, the intermediate value can overflow the underlying calculation type:

// Simplified representation of the vulnerable invariant check
uint112 reserve0; // 112-bit
uint112 reserve1; // 112-bit

function swap(uint amount0Out, uint amount1Out, ...) external {
    // If balance0Adjusted * balance1Adjusted overflows uint224,
    // it wraps to a small number — easily >= k_before
    require(
        uint(balance0Adjusted) * uint(balance1Adjusted) >= uint(reserve0) * uint(reserve1),
        "K invariant violated"
    );
    // ...
}

By manipulating the swap amounts so that the product overflows and wraps to a small value, the check can be satisfied even when the pool has been drained. The attacker also registered a controlled contract as an authorized router, then executed two transactions exploiting the overflowed invariant.

Loss: ~$18,600 in WETH and VISTA tokens.

BitBay: Zero-Liquidity Boundary Condition in Vault Withdrawal

BitBay's UsdcDaiV4Vault on Polygon implements a _withdraw() function that contains a boundary-condition flaw: when liquidity reaches zero, the function transfers the contract's entire token balance rather than the caller's proportional share:

function _withdraw(uint256 shares) internal returns (uint256 amount) {
    if (liquidity == 0) {
        // BUG: transfers full balance regardless of shares
        amount = IERC20(token).balanceOf(address(this));
    } else {
        amount = (shares * liquidity) / totalShares;
    }
    IERC20(token).transfer(msg.sender, amount);
}

The attacker called reposition() to reduce liquidity to zero, then redeemed a single minimum-unit share, causing the vault to transfer its entire DAI balance (~14,838 DAI).

Loss: 14,838.47 DAI ($14K).

Attack Steps

EtherVista

StepAction
1Register attacker-controlled contract as authorized router in EtherVista
2Craft swap amounts so balance0Adjusted * balance1Adjusted overflows uint224 and wraps below K
3K-invariant check passes on the overflowed value
4Pool tokens drained across two transactions

BitBay

StepAction
1Call reposition() on the USDC/DAI vault to drain liquidity to exactly zero
2Call withdraw(minShares) — triggers the liquidity == 0 branch
3Vault transfers full DAI balance (~14,838 DAI) to attacker

Impact

  • EtherVista: ~$18,600 (WETH + VISTA) on Ethereum
  • BitBay: ~$14,000 (14,838 DAI) on Polygon
  • Combined: ~$32,600 total
  • Detection: Both incidents flagged by SlowMist within hours on October 9, 2026

Lessons for Auditors

  1. Uniswap V2-style K-invariant checks require 256-bit intermediates. Reserve products of two uint112 values require a uint256 intermediate. Failing to explicitly cast before multiplication allows overflow. Use SafeUint or explicitly compute uint256(reserve0) * uint256(reserve1).
  2. Test every boundary condition, especially zero values. The liquidity == 0 branch in BitBay was a fallback path that likely existed for initialization or emergency use — but it was triggered trivially by an attacker. All edge conditions should be fuzz-tested with extreme values including zero.
  3. Authorization for routers in AMMs must use immutable or governance-guarded allowlists. EtherVista allowed an arbitrary address to be registered as a router, enabling the attacker to shape the swap context. Restrict router registration to multisig or timelock-controlled governance.
  4. Bounded vault payouts. Any vault withdrawal function should cap payout at min(calculated_amount, contract_balance) and cross-check against the caller's share entitlement regardless of code path.
attack patterns
precision-mathdefi-ammerc20oracles
sources