October 20, 2026
|
Exploit Postmortem
Cover image for "BitBay's $14.8K Loss and How Olympix Would Have Prevented It"

BitBay's $14.8K Loss and How Olympix Would Have Prevented It

Date: October 9, 2026

Chain: Polygon

Loss: 14,838.47 DAI, about $14.8K

Class: withdrawal that paid out the whole vault when liquidity hit zero

One wei of DAI bought the attacker a single share of BitBay's DAI/USDC vault on Polygon. On October 9, 2026, they redeemed that one share and received 14,838.47 DAI, every DAI the contract held.

The attack landed at 02:23 UTC, and SlowMist flagged it at 03:28 UTC. The vault's _withdraw() function had a special case for when the vault's liquidity was zero, and in that case it sent the contract's entire token balance to the caller instead of the caller's proportional share. Anyone could push the vault into that state, by moving the market price out of the vault's narrow range and then calling reposition(), and the attacker did exactly that before making the smallest possible withdrawal.

A Stablecoin Vault Built on a Narrow Range

The contract, UsdcDaiV4Vault, held DAI and USDC for depositors and put them to work as a concentrated liquidity position in a Uniswap v4 USDC/DAI pool. According to the DeFiHackLabs reproduction, that position covered a band just three ticks wide, the price range where the vault's liquidity actually earns fees. The vault tracked each depositor's stake as shares, and its liquidity variable recorded how much of the pooled money was sitting in that Uniswap position.

In a share-based vault, holding 1% of the shares should entitle you to 1% of what the vault holds, so a withdrawal pays out the redeemed shares divided by total shares, times the vault's balance. That formula is what keeps one depositor from walking off with another's money.

The Branch That Skipped the Ratio

DeFiHackLabs confirmed the root cause against the vault's verified source. When liquidity was greater than zero, _withdraw() paid out the caller's proportional share. When liquidity equaled zero, it took a different branch: it read the contract's whole DAI balance and whole USDC balance, transferred both to the recipient, emitted an event and returned. The share amount only appeared in the event. SlowMist's alert, as reported by KuCoin and TokenPost, describes the same flaw: a full withdrawal allowed when liquidity equaled zero, with the share ratio ignored.

The second half of the problem was how easy it was to reach that state. reposition() was a plain external function with no owner, keeper or role modifier, and a minter-only guard on it was inactive because the vault's mintThis flag was false. Any caller could use it once the pool's price sat outside the vault's three-tick band. After a reposition from there, the vault's recorded liquidity came out as zero while the former position's value sat in the contract as raw DAI and USDC.

Picture a coat check that normally hands back the coat matching your ticket, but if the computer that tracks tickets goes down, it hands whoever's at the counter every coat in the room, and any customer can unplug the computer. An empty or zero state is still a state the vault can be in, and the payout rule has to hold there too, either by computing the same proportional amount or by reverting.

Four Calls to Empty the Vault

DeFiHackLabs reconstructs the attack, transaction 0x5b79...7ff2 in Polygon block 95,204,942, as a fully permissionless sequence with no owner, admin or signature step. The attacker deposited one wei of DAI and received one share. A swap on the Uniswap v4 pool pushed the price above the top of the vault's range. A call to reposition() then recomputed a new position whose liquidity rounded down to zero, leaving the pooled funds as the vault's raw balance. Finally, withdraw(1, deadline, false) redeemed the single share, hit the liquidity-equals-zero branch, and swept 14,838.466608989135865711 DAI, matching the vault's on-chain Withdrawn event to the wei.

The attacker funded the price-moving swap with nested DAI flash loans, including one from Balancer, and unwound the drained DAI and the borrowed capital to a net profit of about 14,813 USDC. SlowMist's database lists the loss at $14,000 and classifies it as a smart contract vulnerability. The Crypto Times reported that PublicAML described the same attack sequence and loss figure in a later note, and that BitBay hadn't issued a statement on recovery, negotiations with the attacker or compensation at the time of publication.

How Olympix Tests a Withdrawal at the States Nobody Expects

Olympix reads a contract the way an attacker does, as states, transitions and entry points, and it takes in the context around the code, here a share-based vault whose liquidity lives in a narrow Uniswap v4 range, to infer the invariants it relies on but never writes down. For this vault the invariant is one sentence: redeeming shares can never pay out more than those shares' proportional claim on the vault's assets.

The engine maps every path into _withdraw() and every transition that changes the state it reads, and it uses signals to decide which paths to attack. Olympix's static analysis surfaces the signal here: a withdrawal function that branches on liquidity equaling zero, where that branch transfers the full balance with no share ratio applied, alongside an unguarded function, reposition(), that writes the same liquidity value.

BugPoCer starts from the proportional-payout invariant and runs Counterexample-Guided Invariant Refutation across the vault's whole state space, including the corners where liquidity, supply or balance read zero, which a three-tick range puts within easy reach. The sequence it lands on needs only public calls: deposit one wei, move the pool price out of range, call reposition(), then withdraw the single share.

The output is a counterexample: an address holding one share drives liquidity to zero and receives the vault's entire DAI balance. BugPoCer returns that sequence with the minimal conditions that trigger it and a generated Forge test that replays it, which is the October 9 drain produced in development instead of on Polygon. Because Olympix runs automatically in development, at every merge and before release, a new branch in a withdrawal function gets tested against every state the vault can reach the day it's added.

Zero Is Still a Number

BitBay's vault lost every DAI it held to a share bought for one wei, because the one state where its accounting stopped applying was a state any caller could reach with a swap and a public function. Every payout path in a vault needs to honor the share ratio at the edges as well as in normal operation, including when liquidity, supply or balance reads zero. To see how Olympix drives your vault into its edge states and hands back a runnable counterexample for any withdrawal that pays out too much, book a technical walkthrough.

On-Chain References

Protocol: BitBay DAI/USDC vault (UsdcDaiV4Vault)

Chain: Polygon

Date: October 9, 2026, 02:23:42 UTC; SlowMist alert at 03:28 UTC

Loss: 14,838.47 DAI swept from the vault; about 14,813 USDC net to the attacker

Attacker EOA: 0x59Ae...884B

Attack contract: 0xc2C6...23E2

Vulnerable vault (UsdcDaiV4Vault, verified): 0x048E...e5A0

Uniswap v4 PoolManager: 0x6736...5cD6

Exploit tx: 0x5b79...7ff2

Block: 95204942

Sources

SlowMist: BitBay vault exploit alert

DeFiHackLabs: BitBay UsdcDaiV4Vault exploit proof of concept

The Crypto Times: SlowMist Flags BitBay and EtherVista Hacks as Attackers Drain $32,600 From DeFi Vault, Pool

KuCoin: SlowMist Detects Attack on BitBay, Loss of $14,000

TokenPost: BitBay Vault Exploit Results in About 14,838.47 Dai Loss

More from Olympix:

No items found.

Ready to Shift Security Assurance In-House? Talk to Our Security Experts Today.