The One-Directional Rounding Bug That Cost Balancer $128 Million Across Nine Chains
In just over an hour on November 3rd, an attacker drained more than $128 million from Balancer's Composable Stable Pools spread across multiple blockchains, in what PeckShield's live tracker flagged as one of the largest DeFi exploits of 2025. The root cause traced back to a single design choice: a scaling function that always rounded in one direction, regardless of context — a decision Balancer's own developers had described in a code comment as carrying "minimal" impact.
Balancer had operated since 2020 and processed billions of dollars in volume without an incident of this scale. In 2022, Certora had formally verified core solvency properties of Balancer V2, confirming that BPT (Balancer Pool Token) supply could never exceed total underlying assets and that new BPT could not be minted without corresponding deposits. Those proofs, however, never examined rounding-direction risk. Separately, Trail of Bits had raised similar rounding concerns in Balancer's Linear Pools roughly four years before the attack. The gap between provable "aggregate conservation of balances" at a high level and the low-level arithmetic reality of the code turned out to be exactly where the attacker operated.

01How the discovery and response unfolded
The timeline, reconstructed from on-chain data and public statements, began at 07:46 UTC, when Hypernative's monitoring flagged unusual swap activity moving through Balancer's Composable Stable Pools. About thirty minutes later, PeckShield posted a public warning: "Hi Balancer, you may want to take a look." Five minutes after that, PeckShield followed up with a dollar figure: $70.8 million already drained. Lookonchain's own tracking climbed in near-real time, from $98 million to $116.6 million within minutes. Certora's engineers were reportedly already examining the code, trying to determine whether this was an access-control failure, a reentrancy bug, or something novel.
Within twenty minutes of the initial alert, Hypernative's emergency controls had triggered across every affected network, and a minute after that every Composable Stable Pool v6 (CSPv6) that Balancer could pause had been frozen. Recovery Mode was activated and the factory contract responsible for deploying vulnerable pools was disabled. But the response hit a structural limit: Composable Stable v5 pools had pause windows that had already expired, leaving years-old contracts — still carrying millions in liquidity — with no way to be halted. Balancer's team coordinated a cross-chain war room for containment and communications while the attacker continued moving through pool after pool on Ethereum, Base, Arbitrum, Polygon, Avalanche, Gnosis, Berachain, Sonic, and Optimism, replicating the same exploit pattern on each chain.
More than eight hours after the first alert, Balancer's official account publicly confirmed the incident: "Today, around 7:48 AM UTC, an exploit affected Balancer V2 Composable Stable Pools." By then PeckShield's running total had reached $128.64 million.
02Individual chains improvised their own defenses
Roughly two hours into the attack, Berachain's validators took the unusual step of halting the entire chain rather than pausing a single protocol. Berachain's BEX exchange had been built on Balancer v2 code; $12.86 million was rescued by a white hat during the exploit, and the network executed an emergency hard fork to reverse the remaining damage, with the affected funds recovered afterward.
Sonic Labs rolled out a new security mechanism on its Beets Protocol mid-incident, freezing the attacker's addresses and zeroing their balances. Gnosis restricted outbound bridge transfers to stop funds from leaving the chain, and Polygon's validators began outright censoring the attacker's transactions, effectively freezing the stolen assets through social coordination rather than any code-level mechanism.
Some funds were also clawed back through automated and manual intervention: StakeWise ran emergency multisig transactions to recover 5,041 osETH (about $19 million) and 13,495 osGNO (about $1.7 million) — roughly 75% of what had been taken from its pools. Separately, BitFinding's bots intercepted an additional $600,000, and MEV bots on Base recovered $150,000 more.
03Copycats arrived within the hour
Because the attacker's auxiliary math-helper contract was deployed on-chain and publicly readable, it effectively doubled as a tutorial. Within about an hour of the original exploit, imitators began launching their own attempts by swapping in a different pool address and sender address and redeploying the same logic. At least 27 forks of Balancer V2 carried the identical flaw, and while many held live liquidity, not every one of them was hit. Tikkala Security later reported that copycat attacks were still occurring a full week after the original incident.
04The technical flaw: a scaling function that only rounded one way
The vulnerability lived in a routine called _upscale(), defined in BaseGeneralPool.sol and executed ahead of every swap, after access checks, flash-loan guards, and reentrancy protections had already passed. Its job was mundane: normalize tokens with different decimal precision — USDC's 6, DAI's 18, WBTC's 8 — up to a common 18-decimal scale so Balancer's invariant math could operate consistently.
Balancer's engineers clearly understood rounding mattered, since they had built a dedicated library, FixedPoint, with explicit mulUp (round up) and mulDown (round down) functions. The intended pattern favored the protocol: round down when the pool received tokens, round up when it sent them out. But _upscale() broke that pattern — it called mulDown unconditionally, in every context, rounding the same direction whether that favored the protocol or not.
The reasoning was spelled out directly in the code, in ScalingHelpers.sol: "Upscale rounding wouldn't necessarily always go in the same direction... This is the only place where we round in the same direction for all amounts, as the impact of this rounding is expected to be minimal." The developers chose mulDown and documented why. For the overwhelming majority of swaps, that assumption held.
05From rounding quirk to extraction mechanism
When a user swaps Token A for Token B, the pool scales the input up to 18 decimals, runs the invariant math, and scales the result back down to Token B's native decimal count. If Token B uses 6 decimals and the calculation yields 1,234,567 scaled units, converting back means dividing by 10^12 — an operation that produces a remainder, and a remainder means a rounding decision has to be made.
Conventional AMM design rounds against the user and in favor of the pool whenever precision is lost, as Certora's writeup notes. Balancer's _upscale(), however, rounded down before the invariant was even calculated, meaning the pool's internal math started from numbers that were already slightly understated. In an ordinary trade, that discrepancy is trivial — smaller than typical slippage or gas costs. But Composable Stable Pools are not ordinary pools, and the attacker specifically targeted their unusual features.
The pools' signature design let LP tokens (BPT) trade as assets within the pool itself — a user could swap DAI directly for BPT in one transaction, enabling recursive liquidity movement without an explicit withdrawal step. Combined with Balancer's deferred-settlement architecture, where token transfers don't finalize until a batch transaction completes, this created the conditions for abuse. The attacker used Balancer's internal-balance accounting to briefly hold BPT inside a single batchSwap call — effectively a deficit or "internal credit" position — then used those temporary BPT claims to swap for underlying tokens, pushing pool balances toward low, edge-case levels before the transaction settled. Analysts have described this as functionally equivalent to a transaction-local "flash mint" of BPT made possible by the Vault's internal accounting.
The attack sequence, step by step
- Deploy a helper contract. A separate contract handled the complex precision math, sidestepping Solidity's stack-depth limits.
- Mint BPT without deposits. Using Balancer's composability, the attacker generated LP tokens without actually supplying assets.
- Push the pool toward minimum liquidity. Swapping BPT for underlying tokens drove pool balances to the edge cases where rounding errors become significant.
- Target the worst-case rounding combinations. Because
_upscale()always rounds down viamulDown, each individual swap slightly understated the invariant — an effect amplified by choosing token and decimal-scale pairs that maximized the remainder. - Repeat at scale. The attacker looped this micro-swap pattern dozens of times inside one transaction — on-chain traces show more than 65 micro-swaps — letting small per-iteration losses compound into a materially deflated invariant.
- Exploit the resulting mismatch. With the invariant desynchronized from reality, the BPT the attacker held was now worth more than the pool's actual assets could support.
- Redeem for profit. Burning the inflated BPT for real underlying tokens left the pool insolvent.
Every individual step was a technically valid contract call — no reentrancy, no leverage tricks, just arithmetic pushed to its edges by someone who understood the math better than the people who wrote it.
06Two transactions, two roles
The attack was typically split into two on-chain transactions. The first executed the rounding-manipulation loop and drained value into Balancer's internal balance ledger, where it wasn't visible to external observers — no profit appeared to move, and nothing looked obviously wrong to automated monitors. The second transaction converted that internal position into real tokens by calling manageUserBalance(WITHDRAW_INTERNAL), at which point the funds left the protocol entirely.
07Attacker addresses and contracts
Ethereum
- Primary: 0x506D1f9EFe24f0d47853aDca907EB8d89AE03207
- Secondary: 0xAa760D53541d8390074c61DEFeaba314675b8e3f
- Intermediary: 0x766a892f8ba102556c8537d02fca0ff4cacfc492
Arbitrum
- 0x506D1f9EFe24f0d47853aDca907EB8d89AE03207
- 0x310ebc4ffe858ab40b95343de0c2431b95892962
- 0x0000000000004f3d8aaf9175fd824cb00ad4bf80
Base
Optimism (Beethoven X)
Polygon
Sonic (Beets)
Consolidation address: 0x872757006b6f2fd65244c0a2a5fdd1f70a7780f4 (Arbitrum profits)
Attack contracts:
- Math helper contract (published on-chain with source, effectively a blueprint for copycats): 0x679B362B9f38BE63FbD4A499413141A997eb381e
- Ethereum coordinator contract: 0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d
08Damage by chain
Ethereum — the largest single loss
Nearly 25,000 ETH was drained across several pools.
- Primary attack transaction: 0x6ed07db1a9fe5c0794d44cd36081d6a6df103fab868cdd75d581e3bd23bc9742
- Withdrawal transaction: 0xd155207261712c35fa3d472ed1e51bfcd816e616dd4f517fa5959836f5b48569
Pools affected:
- osETH/WETH — Pool ID
0xdacf5fa19b1f720111609043ac67a9818262850c000000000000000000000635, contract 0xDACf5Fa19b1f720111609043ac67A9818262850c. Drained: 4,623 WETH + 6,851 osETH ≈ 11,474 ETH. - wstETH/WETH — Pool ID
0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd0000000000000000000005c2, contract 0x93d199263632a4EF4Bb438F1feB99e57b4b5f0BD. Drained: 4,259 wstETH + 1,963 WETH ≈ 6,222 ETH. - rsETH/WETH — Pool ID
0x58aadfb1afac0ad7fca1148f3cde6aedf5236b6d00000000000000000000067f, contract 0x58AAdFB1Afac0ad7fca1148f3cdE6aEDF5236B6D. Drained: 1,192 rsETH + 891 WETH ≈ 2,083 ETH. - weETH/rETH — Pool ID
0x05ff47afada98a98982113758878f9a8b9fdda0a000000000000000000000645, contract 0x05ff47AFADa98a98982113758878F9A8B9FddA0a. Drained: 703 rETH + 495 weETH = 1,198 ETH. - wstETH/rETH/sfrxETH Pool #1 — Pool ID
0x5aee1e99fe86960377de9f88689616916d5dcabe000000000000000000000467, contract 0x5aEe1e99fE86960377DE9f88689616916D5DcaBe. Drained: 4 ETH. - wstETH/rETH/sfrxETH Pool #2 — Pool ID
0x42ed016f826165c2e5976fe5bc3df540c5ad0af700000000000000000000058b, contract 0x42ED016F826165C2e5976fe5bC3df540C5aD0Af7. Drained: 191 wstETH + 2,148 sfrxETH + 220 rETH = 2,559 ETH. - ezETH/WETH — Pool ID
0x596192bb6e41802428ac943d2f1476c1af25cc0e000000000000000000000659, contract 0x596192bB6e41802428Ac943D2f1476C1Af25CC0E. Drained: 751 ezETH + 442 WETH = 1,193 ETH.
Ethereum total: 24,733 ETH, worth roughly $91.5 million at the time.
Arbitrum
- Primary attack transaction: 0xe4dfc8b8b54eb7e101d59cd9f87f389186b2e8f6e188557ae9dfdbea2b12e703
- Withdrawal transaction: 0x4e5be713d986bcf4afb2ba7362525622acf9c95310bd77cd5911e7ef12d871a9
- Additional attack transactions: 0x5258dcfdd5fa04a81648e1e6d8caffd7438cf27d6bcfc8d1cb0e8c005307eee1, 0x962e95dfa66936d96c25e75cf7aba023e0486b3d63f477664899dbbf54d7aa86, 0xfe3d66d50b4f837994d0a06220e3b14f7652e79c12bc92362f6a760b630c6999
wstETH/rETH/cbETH — Pool ID 0x4a2f6ae7f3e5d715689530873ec35593dc28951b000000000000000000000481, contract 0x4a2F6Ae7F3e5D715689530873ec35593Dc28951B. Drained: 462 ETH.
Proceeds were consolidated to 0x872757006b6f2fd65244c0a2a5fdd1f70a7780f4 and bridged back to Ethereum via Stargate. Arbitrum total: 462 ETH, worth $1.7 million at the time.
Base
Attack contract: 0x56e5Adab68b594B0c2aD6C112D94AE5aCA98A001. Primary attack transaction: 0x29135f912d67db38478d0be70b9f2a1fab3b121b74d776f835ac66d6df134ec5
- rETH/WETH — Pool ID
0xc771c1a5905420daec317b154eb13e4198ba97d0000000000000000000000023, contract 0xC771c1a5905420DAEc317b154EB13e4198BA97D0, attack tx 0xe9245fb124c3a6ff6a0e39c6d0db02b74b3a3d805f6bf016f4b9ac56cbfb73ae. Drained: 17 WETH + 24 rETH = 41 ETH. - weETH/wETH — Pool ID
0xab99a3e856deb448ed99713dfce62f937e2d4d74000000000000000000000118, contract 0xaB99a3e856dEb448eD99713dfce62F937E2d4D74, attack tx 0x927c9e6d9fc26b2ee13b88f553701a4e7514f8220d34e6517c634ddd135cd874. Drained: less than 1 ETH. - cbETH/WETH — Pool ID
0xfb4c2e6e6e27b5b4a07a36360c89ede29bb3c9b6000000000000000000000026, contract 0xFb4C2E6E6e27B5b4a07a36360C89EDE29bB3c9B6, attack tx 0xd61f26bd435b31f781165a522fc78a040f864eafc74e07f86314ca265d96287d. Drained: less than 1 ETH.
Base total: 42 ETH (roughly $155,000 at $3,700/ETH).
Optimism (Beethoven X)
Attack transaction: 0x3c9d2d16404a79feed9876a79f168af334726ad3ee1371f581d50ebebfe6b8c6. Withdrawal transaction: 0xbd417633433e45c1dddf9fac7680f86dfde832c07b93f4de5ce69c6312d19381. Total: roughly $1.3 million.
Polygon
Attack transaction: 0x167993d4cc39771923a6cd11d2d6e73a1b68c7464ea3c76ba41fbd32df7a96da. Withdrawal transaction: 0x9630b26a49c451365989cbd2d9696ea3bdf02505bcb297b6239f330f114c9673. Total: roughly $390,000.
Sonic (Beets)
Attack transaction: 0xd7996c8e187b9bd539a04a4f39de4d8c7c1670c601134329937738b4dfa6f8ad. Withdrawal transaction: 0xc0cc599fa5c1ec2a43a96b018fd653783cf8dd3e6f670f94961c89b61ce8c0f9. The attacker moved 19.5 million stS (about $3 million) to 0x0e9c9473D0c504Da72763426719F6f03A15544D5 using permit() and transferFrom(), then swapped into WBTC and bridged to Ethereum via LayerZero. Sonic total: $3.44 million.
Berachain (BEX)
$12.86 million was actively rescued by white hats after validators halted the chain and pushed through an emergency hard fork to reverse the damage. Berachain total: $12.86 million, fully recovered.
09Reconciling the totals

Primary exploiter haul: Ethereum 24,733 ETH ($91.5M), Arbitrum 462 ETH ($1.7M), Base 42 ETH ($155K) — subtotal 25,237 ETH (~$93.4M).
Fork-related activity: Sonic/Beets $3.44M, Berachain/BEX $12.86M (recovered), Optimism/Beethoven X ~$1.3M, Polygon ~$390K, plus unconfirmed smaller amounts reportedly on Gnosis and Avalanche that have not been publicly detailed — fork subtotal roughly $18 million.
Total stolen across the incident is put at approximately $128 million, the figure cited by PeckShield and treated as industry consensus. Independent forensic tracing accounts for about $111 million in confirmed primary-attacker withdrawals, leaving a gap of roughly $17 million attributed to copycat attacks and the undisclosed Gnosis/Avalanche losses. PeckShield's $128 million figure likely reflects the fuller ecosystem-wide picture, including copycats and minor chains, rather than just the original attacker's take.
10Where the stolen funds sit now
As of reporting, roughly $37 million remained visible in known attacker-controlled wallets:
- 0x0e9c9473d0c504da72763426719f6f03a15544d5 — $2.6M
- 0x506d1f9efe24f0d47853adca907eb8d89ae03207 — $11M
- 0xf19fd5c683a958ce9210948858b80d433f6bfae2 — $574K
- 0xaa760d53541d8390074c61defeaba314675b8e3f — $20M
- 0x0e9c9473D0c504Da72763426719F6f03A15544D5 — $2.6M
- 0x1c7da4e9740f99279c193540328314c04e2edc00 — $22K
- 0x045371528A01071D6E5C934d42D641FD3cBE941c — $520K
11The copycat wave, in detail
The first copycat transaction landed just 53 minutes after the original attack, at 08:39 UTC: 0x14fb45dd869208edffcb221add152a20292283be172ddb6ccfd2d73e3710b6f4. This was possible because the attacker's math-helper contract had been deployed on-chain with full, readable source code, including all calculation logic and even Balancer's custom error types — effectively a ready-made exploitation kit for anyone able to read Solidity. All that remained was to swap in a target pool address and a new sender address. At least 27 Balancer v2 forks carried the identical vulnerability, and some were hit in the hours that followed.
12Whitehat recoveries
- StakeWise's emergency multisig action recovered 5,041 osETH (
$19M) and 13,495 osGNO ($1.7M), around 73.5% of what had been taken from its pools. - BitFinding's whitehat bots intercepted $600,000.
- Base MEV bots recovered $150,000.
- A separate white-hat effort on V2 Meta-Stable Pools secured $4.1 million into controlled custody.
- Berachain's hard fork recovered $12.86 million.
Combined, roughly $38.4 million was recovered or frozen across these efforts.
13Audit history: what was proven, and what wasn't
Balancer's contracts had been reviewed repeatedly since 2020 by OpenZeppelin, Trail of Bits, Certora, and ABDK Consulting — more than a dozen audit reports in total, plus formal verification work and a long-running Immunefi bug bounty program.
Certora's 2022 review of Balancer V2 Stable Pools proved high-level solvency: BPT supply could never exceed total assets, minting BPT always required a matching asset increase, and aggregate balances were conserved. Trail of Bits, in its 2021 audit, had flagged a related rounding concern in Linear Pools under finding TOB-BALANCER-004, but could not determine at the time whether it was actually exploitable given how those pools were configured; the firm flagged it anyway, based on similar findings elsewhere, and recommended fuzz testing to confirm rounding directions matched expectations across all arithmetic operations.
According to OpenZeppelin's post-incident analysis, the vulnerable pattern was first introduced on July 16, 2021, when MetaStablePool overrode the _scalingFactors function (commit 059284e). The same pattern was carried into LinearPool on September 1, 2021 (commit 4e9e70a), and into StablePhantomPool — later renamed ComposableStablePool — on September 20, 2021 (commit f450760). OpenZeppelin notes that none of the prior audits had covered these specific pools. Composable Stable Pool relied on the same shared scaling utility as the earlier pool types — the _upscale() helper calling FixedPoint.mulDown — carrying the "minimal impact" comment along with it.
Certora's own analysis later identified the properties that would have caught the bug: a roundtrip swap invariance check (swapping Token A to Token B and back should never return more than the original amount — a violation reveals compounding rounding asymmetries, exactly the kind that becomes catastrophic across 65-plus iterations in one transaction), and a BPT share-value invariant ("for any user operation, the share value of a single BPT must not decrease"), which would catch exactly this kind of desynchronization between total assets and total supply. Certora's 2022 audit had abstracted away the underlying StableMath functions to simplify its proofs, and consequently never tested numeric precision under extreme low-liquidity or edge-case conditions. As the firm put it afterward: "Solvency proofs are not sufficient. Aggregate conservation of balances does not imply rounding safety."
Trail of Bits' 2021 review reflected the threat model of its time — access control, reentrancy, phishing — with rounding treated as a moderate, somewhat underemphasized risk rather than a top-tier concern, since precision-based exploits were not yet widespread. That changed by 2023–2024, when precision-loss exploits became a recognizable pattern, claiming both Hundred Finance and Onyx Protocol. By 2025, Abracadabra and Bunni had also been hit by comparable rounding-based attacks earlier in the year — establishing rounding-edge exploitation as an established playbook well before Balancer was hit.
14Market and governance fallout
Balancer's BAL token fell 5% in the immediate aftermath, and protocol TVL dropped 46% within 24 hours, from $626 million to $338 million, as users withdrew from pools that resembled the exploited ones even where they weren't directly affected. Venus Protocol paused BAL borrowing on Ethereum and set loan-to-value ratios to zero as a precaution.
Chain-level responses diverged sharply along philosophical lines. Berachain halted its network entirely and hard-forked to undo the theft. Polygon relied on validator-level transaction censorship. Sonic Labs shipped a freeze mechanism mid-attack. Gnosis cut off outbound bridging. Haseeb Qureshi of Dragonfly commented that smaller ecosystems should prioritize user safety over strict adherence to "code is law."
15Assessment
The exploit required no reentrancy, no flash-loan trickery in the traditional sense, and no external leverage — only valid, approved contract calls executed by someone who understood Balancer's math more precisely than its own engineering and audit history had. Certora's 2022 work proved solvency at a high level; it did not test roundtrip invariants at the boundaries. Trail of Bits flagged the same rounding pattern in 2021 but could not prove it exploitable at the time and rated it accordingly. Four years later, an attacker proved it decisively, at a cost of $128 million spread across nine networks, dozens of pools, and 27 vulnerable forks — several of which required their host chains to intervene at the validator level, hard-fork, or freeze balances outside of any code path, in order to limit the damage.
Get new scam files the moment we publish them — usually 2–3 emails a week.