
AMMHacks
Balancer V2 Exploit Drains $128M — An attack we warned about in October
November 3, 2025•
M3D
8 min read
•900 views
•
TL;DR
- What happened: An authorization or state-integrity flaw inside Balancer V2's Vault operations corrupted the internal ledger.
 - Impact: ~$128M drained across mainnet and forked deployments.
 - Root cause: Inadequate state control during multi-call Vault operations exploiting the Vault-Pool trust boundary.
 - Affected: Balancer V2 and forks (Beets, Berachain).
 - Unaffected: Balancer V3 — its atomic accounting model prevents this class of exploit.
 - Key lesson: Audits must test cross-contract trust boundaries and implicit state transitions.
 
Updated Nov 3, 2025: Analysis reflects ongoing investigation into the exact exploit mechanism.
On November 3, 2025, Balancer V2, a protocol many considered "battle-tested," was drained of over $128 million. For many, it was a shock. For readers of my October 16 analysis of Balancer’s AMM architecture, it was a terrifying confirmation.
The attacker didn't find a new, impossibly complex bug. They weaponized an architectural weakness we had already identified.
This wasn't a flaw in the math. This was a far more insidious and elegant bug: a protocol-level logic flaw that exploited a flawed state machine. The attacker tricked the protocol into misunderstanding its own state.
Let's break down the attack vector, how it worked, and why anyone who understood the core risk of V2's architecture could have seen this coming.
The architectural setup: The singleton vault and the "trust boundary"

To understand the exploit, you must first understand Balancer V2's architecture. It’s built on two core concepts:
- The Singleton Vault (Vault.sol): A single, massive contract that holds all assets for all Balancer V2 pools. This is the "bank."
 - External Pool Contracts (LinearPool.sol, etc.): Separate, external contracts that only contain logic (the AMM math). They are asset-less.
 
When you perform a swap, the Vault takes your tokens and then makes an external callback to the relevant pool contract (e.g., 
onSwap). It essentially asks, "I have 100 WETH, how much osETH do I give back?" The pool does the math and returns the amount.This creates a critical trust boundary. The Vault (which holds the assets) must trust the pool (which holds the logic). The entire security of the $128M+ in assets relies on this callback mechanism being secure.
The other key piece is the "internal user balance" system. To save gas, the Vault doesn't do an ERC-20 transfer for every step. It just updates an internal 
mapping(address => uint256) ledger. The attacker's ultimate goal was not to steal tokens directly, but to get a fraudulent entry written into this ledger.The root cause: A state machine vulnerability
Update (Nov 3, 2025): While early reports speculated a reentrancy path via the Pool's 
initialize() during onSwap(), on-chain evidence now suggests the exploit may have abused an authorization or internal balance inconsistency within the Vault's user-balance flow. The architectural issue — a fragile trust boundary between Vault and Pool logic — remains unchanged.The core vulnerability was likely an authorization or state-integrity flaw during multi-call Vault operations.
The exploit appears to have targeted a probable authorization weakness within the Vault's internal balance management system, potentially exploiting cross-contract re-entry or privilege escalation during a Vault ↔ Pool callback sequence.
The architectural flaw: The developers likely implemented proper access controls for normal operation flows but failed to anticipate complex multi-call scenarios where privileged operations could be invoked mid-execution.
The vulnerability suggests the system had implicit states that weren't properly secured:
- State A: idle (waiting for a call)
 - State B: in-operation (in the middle of executing multi-call operations)
 
Critical operations were protected in State A, but potentially vulnerable during the complex state transitions of State B.
The exploit chain: How to lie to the Vault
The attacker's execution was flawless. Here is the probable step-by-step logical chain:
1. The Setup
The attacker executed a sequence of multi-call operations through the Vault, likely leveraging complex interaction patterns between the Vault and Pool contracts.
2. The Vulnerability Trigger
During the multi-call sequence, the attacker likely triggered a state inconsistency where the Vault's internal balance management system could be manipulated through improper authorization checks.
3. The Privilege Escalation
A privileged logic path was invoked mid-operation due to missing state validation. This suggests the exploit abused the trust boundary between Vault and Pool contracts during a complex callback sequence.
4. The State Corruption
This is the kill shot. The authorization flaw allowed the attacker to manipulate the Vault's internal balance tracking system.
The vulnerability likely enabled a privileged operation to instruct the Vault to set the attacker's internal user balance to a massive, fraudulent amount (for example, 6,500 WETH).
The Vault, due to the authorization bypass, obediently updated its internal ledger: 
internalBalances[attacker] = 6500e18.5. The Cleanup
The malicious operation sequence completed successfully, leaving the Vault's internal state permanently corrupted while appearing to execute normally.
6. The Normal Withdrawal
In a separate, non-suspicious transaction, the attacker called the standard 
exitPool or withdraw function.7. The Drain
The Vault checked its (now-corrupted) ledger, saw the attacker had a balance of 6,500 WETH, and dutifully transferred the real WETH from its contract to the attacker's wallet.
The exploit wasn't a theft. It was a fraud. The attacker tricked the Vault into giving them a fake deposit slip, which the Vault then honored with real cash.
The "fork-bomb": Why Berachain halted its L1
As researchers, the blast radius is often more terrifying than the initial bug. This vulnerability was in the core Balancer V2 codebase, which means every protocol that forked V2 was also vulnerable.
- Beets Finance: Drained for millions.
 - Berachain: This was the unprecedented part. Berachain's native DEX (BEX) is a Balancer V2 fork. The vulnerability wasn't just in an app on their chain; it was in a foundational primitive of their L1's DeFi ecosystem.
 
They were forced to take the most drastic step possible: halt the entire Layer 1 blockchain. This was the only way to prevent the attacker from draining the BEX and to give validators time to deploy an emergency hard fork to patch the application-layer bug. This is a terrifying example of application-layer risk escalating to a consensus-layer failure.
Why Balancer V3 was safe
V3’s atomic BPT and token accounting model closes this exact gap.
Because all internal state changes settle atomically inside the Vault (thanks to transient accounting and unified BPT management), it’s impossible for a pool to re-enter and mutate Vault balances mid-operation.
This design evolution validates the architectural improvements Balancer made — and highlights why audits must examine system-level state integrity, not just function-level logic.
The "we warned about this": This exact risk was flagged weeks ago
This exploit, while novel in its exact mechanism, should not have been a surprise. The architectural weakness it exploited was the exact risk I highlighted in my October 16 analysis of Balancer's AMM architecture.
An auditor or developer who read that analysis would have been on high alert for this precise class of vulnerability. Here is what was correctly identified weeks before the attack:
- The exact risk zone: V2 callbacks. The previous analysis explicitly called out vulnerabilities in the V2 architecture's callback system. While the focus was on known reentrancy vectors, the fundamental weakness was identical: the fragile trust boundary between Vault and Pool contracts during callback operations.
 - The flawed cross-contract trust assumption. The core of the old analysis was that the V2 design creates dangerous cross-contract trust assumptions. This exploit is the poster child for that thesis. The Vault (asset management) trusted the Pool logic during complex operations, and the attacker exploited this exact trust assumption to corrupt the Vault's internal state.
 - The V2 vs. V3 security distinction. The V3 architecture was correctly identified as the solution to this problem, as its atomic management of tokens and BPTs mitigates this specific class of state-corruption risk. The post-mortem confirmed this, noting V3 pools were completely unaffected.
 
This is a sobering lesson: the goal of security analysis is not just to find known bugs but to identify architectural weaknesses. The V2 callback model was the known weak point. On November 3, an attacker simply walked through the unlocked door we had already pointed to.
Our key takeaways as auditors
This exploit provides three critical lessons for every Solidity developer and auditor.
- Authorization must be context-aware. This exploit likely demonstrates that access controls designed for normal operation flows can fail during complex multi-call scenarios. Critical functions must validate not just who is calling them, but when and under what conditions.
 - Explicit state machines beat implicit assumptions. The vulnerability suggests the system had implicit states that weren't properly secured. This class of bug could be prevented by using explicit state management and ensuring all privileged operations validate the current system state.
 - Forking audited code is inheriting risk. The Beets and Berachain teams forked "battle-tested" code. This is a reminder that forking is not a security shortcut. You inherit 100% of the original protocol's latent vulnerabilities. An independent audit of forked code is non-negotiable.
 
How to prevent this
At Zealynx, we specialize in uncovering architectural vulnerabilities before they become nine-figure headlines.
If your protocol integrates a Vault-style or modular pool architecture, this exploit is your wake-up call.

