## What happened

On November 3, 2025, Balancer v2's Composable Stable Pools were exploited on Ethereum
and multiple L2 chains (Arbitrum, Polygon, Base, Sonic, Optimism). The attacker
weaponised a rounding inconsistency in the pool's scaling math to systematically
deflate the pool invariant and withdraw undervalued BPT. Forks Beets and Beethoven X
were also targeted by copycat attacks.

The loss was $128 million per rekt.news.
Balancer's own post-mortem gave two inconsistent figures: $94.8 million in its
introduction versus $121.1 million in the Financial Impact section, leaving the
difference unreconciled.
QuillAudits reports approximately $125 million.
[https://rekt.news/out-of-balance/]
[https://quillaudits.com/blog/hack-analysis/the-balancer-hack]

## Root cause

A rounding direction mismatch in the Composable Stable Pool scaling logic allowed
systematic precision loss to be weaponised against the pool invariant D.
Upscaling used `mulDown` while downscaling used `divUp`/`divDown`, so an EXACT_OUT
(`_swapGivenOut`) path could understate the required `amountIn`.
By executing carefully tuned micro-swaps inside a single atomic `batchSwap()`,
the attacker compounded tiny wei-level truncations, quietly reducing D and thus
the BPT price (D / totalSupply) without triggering reverts.
The attacker simulated swaps off-chain using a replica of `StableMath` to place each
swap on a rounding cliff before executing on-chain.
[https://quillaudits.com/blog/hack-analysis/the-balancer-hack]
[https://rekt.news/out-of-balance/]

## Pattern

**Precision / rounding asymmetry** in contract math, amplified by batch atomicity.

The vulnerable shape (pseudocode):

```
function _upscale(amount, scalingFactor):
    return amount.mulDown(scalingFactor)  // rounds toward zero

function _downscale(amount, scalingFactor):
    return amount.divUp(scalingFactor)  // or divDown, rounds inconsistently
```

When `_swapGivenOut` computed the input tokens needed for a desired output, the
asymmetric rounding between upscale and downscale paths produced a smaller computed
`amountIn` than the true cost. Repeating this inside a single `batchSwap` call let
the invariant erode across many iterations atomically, bypassing per-swap checks.
The attacker then redeemed BPT at a deflated virtual price.
[https://quillaudits.com/blog/hack-analysis/the-balancer-hack]
[https://github.com/balancer/balancer-v2-monorepo/pull/322]

## The check that would have caught it

1. **Invariant fuzz test**: Fuzz a sequence of swaps through `batchSwap` and assert
   that the pool invariant D never decreases below its pre-swap value after fees
   (i.e. invariant `D_post >= D_pre - fees`). Run this across extreme balance
   ratios, small token amounts (single wei), large amplification parameters, and
   different token decimal combinations.

2. **Round-trip property test**: For every `_upscale(x, s)` and `_downscale(y, s)`,
   assert that compose-and-round-trip never produces net invariant loss. Specifically,
   for random inputs, verify that scaled-up amounts survive the round-trip through
   computation without systematic bias. The audit rule: every scaling path must use
   rounding that is conservative for the pool (never under-reports `amountIn`).

[https://quillaudits.com/blog/hack-analysis/the-balancer-hack]

## Sources

- https://rekt.news/out-of-balance/ — rekt.news post-mortem (October 14, 2026)
- https://quillaudits.com/blog/hack-analysis/the-balancer-hack — QuillAudits
  technical analysis (November 7, 2025)
- https://github.com/balancer/balancer-v2-monorepo/pull/322 — Balancer PR #322
  "Stable pool rounding" (February 2021), showing the relevant rounding function

The official Balancer protocol post-mortem on Medium
(medium.com/balancer-protocol/balancer-v2-pools-exploit-post-mortem-f44a74e9ac21)
could not be accessed (blocked by Cloudflare). Searches for analyses from BlockSec,
SlowMist, CertiK, Halborn, and Verichains specific to this incident did not return
accessible results. The rekt.news article cites the post-mortem's figures and is
treated as a proxy for its contents where noted.
