Back to Blog
CASE STUDY
November 20, 2025
8 min read

The $120M Gap: Why Smart Contract Security Needs to Evolve Beyond Periodic Audits

On November 3, 2025, the Balancer V2 exploit resulted in over $120 million in losses. This incident exposes systemic gaps in how smart contract security is approached.

On November 3, 2025, the Balancer V2 exploit resulted in over $120 million in losses. OpenZeppelin has since published a detailed post-mortem analyzing the vulnerability and attack vector.

This incident, like other major exploits, exposes systemic gaps in how smart contract security is approached: the limitations of point-in-time audits, the challenges of detecting complex vulnerability patterns, and the difficulty of maintaining security as protocols evolve.

With the initial response phase complete and technical analysis available, examining this exploit offers insights into how security tooling and practices need to evolve to address these recurring failure modes.

Since we're developing AI-based detection tools for these types of vulnerabilities, this incident provides a concrete test case to validate whether our approach can identify the patterns that led to this exploit.

The Timeline That Changed Everything

OpenZeppelin's analysis revealed a detail that should concern every protocol team:

  • First audit completed: March 15, 2021
  • Second audit completed: September 10, 2021
  • Vulnerability introduced: September 20, 2021

Ten days between the final security audit and the introduction of a $120 million vulnerability. The code that enabled the exploit (ComposableStablePool with its overridden _scalingFactors function) was never reviewed by OpenZeppelin or any other auditor. It was added after the audits concluded, deployed to production, and sat dormant for years until it was catastrophically exploited.

The Vulnerability in Detail

The original Balancer StablePool implementation contained a prescient warning in the _upscale function:

"Upscale rounding wouldn't necessarily always go in the same direction... as the impact of this rounding is expected to be minimal (and there's no rounding error unless _scalingFactor() is overridden)."

The developers understood the risk. They documented it clearly. They built the system assuming _scalingFactors would return unitary values for decimal normalization (like 1e12), not variable exchange rates.

When ComposableStablePool was introduced on September 20, 2021, it overrode _scalingFactors to include exchange rates. This critical change meant the scaling factors now included the exchange rate, not just decimal scaling, violating the documented assumption.

The Mathematics of Disaster

When an attacker manipulated pool liquidity to extremely low levels and used carefully chosen amounts, the rounding behaviour became catastrophic:

amount: 17
scalingFactor: 1058132408689971699 (representing a ~5.8% exchange rate)
Expected result: 17.98
Actual result: 17 (truncated by Solidity's integer arithmetic)

The entire 5.8% exchange rate adjustment vanished due to truncation, essentially allowing free swaps. By repeating this pattern hundreds of times through Balancer's batchSwap mechanism (which allows transient state changes before final settlement), the attacker drained the pools.

  1. Prime: Position the pool for maximum truncation
  2. Exploit: Execute the swap with rounding loss
  3. Reset: Restore balances and repeat

After hundreds of iterations, $120M was gone.

Why Traditional Security Failed

This vulnerability reveals four critical weaknesses in the traditional audit model:

1. Temporal Blindness

Audits are snapshots frozen in time. Code changes after an audit receive no scrutiny until the next scheduled audit, whether those changes happen days, weeks, or months later. Balancer's ten-day gap was enough for a critical vulnerability to slip through.

2. Scope Fragmentation

Balancer engaged four different audit firms, each reviewing different scopes. The interaction between audited code (_upscale) and new code (_scalingFactors override) created a vulnerability at the boundaries. No single auditor saw the complete picture.

3. Documentation Isn't Enforcement

The original code comment explicitly warned against overriding _scalingFactors. This crucial institutional knowledge lived in documentation and audit reports, not in enforceable systems. When a new developer added ComposableStablePool months later, there was no mechanism to surface this warning or block the dangerous override.

4. Evolution Outpaces Review

Modern DeFi protocols iterate rapidly. Between July and September 2021, Balancer introduced MetaStablePool, LinearPool, and StablePhantomPool (later renamed ComposableStablePool) — three major pool types in three months. All introduced the vulnerable pattern. None were in audit scope.

The Real Cost of Security Gaps

OpenZeppelin has identified over 700 critical and high-severity vulnerabilities across their audit engagements, representing billions in protected value and countless users saved from losses. This is the seen benefit of security audits, and it's substantial.

But what about the unseen risks?

  • Vulnerabilities introduced in the days after an audit concludes
  • Code changes made between scheduled audits
  • Interactions between new features and existing, audited code
  • Refactors that subtly violate documented security assumptions

The Value Proposition: Continuous Security for Continuous Development

This is why SafeStackAI exists. Not to replace traditional security audits (they remain essential) but to close the gaps that periodic reviews inherently cannot address.

What Our System Found

We ran SafeStackAI's multi-agent vulnerability detection system against the ComposableStablePool code. Here's what it identified:

SafeStackAI Critical Finding - AMM Invariant & Rounding Manipulation
SafeStackAI's detection of the critical AMM invariant & rounding manipulation vulnerability

Finding: Critical Invariant & Rounding Manipulation Vulnerability

Our system correctly identified all five critical components:

1. Non-Unitary Scaling Factors ✓

Our analysis detected:

"The final scaling factor is: scalingFactor[i] = decimalScaling[i] * tokenRate[i] / 1e18 (via mulDown), where tokenRate[i] is an arbitrary 18-decimal exchange rate delivered by an external IRateProvider. This is exactly the 'non-unitary scaling factor' condition."

OpenZeppelin Confirms:

"the code went from unitary scaling factors (ie 1e12) to non-unitary exchange rates. This shift is what the _upscale warns against, opening the door for rounding errors"

2. Unidirectional Rounding ✓

"_scalingFactors() multiplies decimal scaling by getTokenRate() using mulDown... the rate component has already been rounded down... This yields systematic truncation to the pool's advantage whenever the operation uses the rate-augmented scaling factor."

OpenZeppelin Confirms:

"These functions always round-down (mulDown) independently from the direction of the swap."

3. Small Amount Exploitation ✓

"For a token with a large rate and low liquidity... use Vault batchSwap to perform many hops of carefully chosen small swap sizes... each time accumulating rounding error due to floor operations"

We even identified the mathematical pattern:

"e.g. 17 units where scalingFactor*rate truncation gives >5% loss per hop"

OpenZeppelin Confirms: The actual attack used amounts as small as 17 units.

4. Batch Swap Chain ✓

"ComposableStablePool inherits from BaseGeneralPool... this pool is fully compatible with batchSwap. An attacker can chain many small swaps in a single transaction, repeatedly triggering the same rounding behavior."

OpenZeppelin Confirms:

"The batchSwap functionality of the Balancer Vault contract, which allows for transient swaps to happen before the need to settle the outstanding deltas."

5. No Minimum Swap Size ✓

"Scanning the ComposableStablePool code there is no require(amount >= MINIMUM_SWAP_AMOUNT) or similar... Thus, arbitrarily tiny swap amounts are allowed, which is exactly what is needed to maximize the percentage effect of truncation on each iteration."

OpenZeppelin Confirms: The attack exploited this exact gap with amounts as small as 17 units.

Technical Accuracy

Our LLM-based framework didn't just identify "a vulnerability." It identified:

  • ✅ The exact root cause (non-unitary scaling with mulDown)
  • ✅ The precise vulnerable code paths (_scalingFactors, _upscale)
  • ✅ The attack vector (batch swap chains)
  • ✅ The exploitation requirements (low liquidity, small amounts)
  • ✅ The specific parameter values that would be weaponized (17-unit swaps against ~1.058 rate)

This wasn't a lucky guess. This was systematic analysis of:

  • Scaling factor composition
  • Rounding behaviour across call chains
  • State manipulation opportunities
  • Batch execution capabilities
  • Edge cases in amount validation

What This Demonstrates

This finding validates three critical aspects of SafeStackAI's approach:

SafeStackAI Function Call Tracing
SafeStackAI traces function calls and analyzes execution flow for vulnerabilities and gas optimizations

1. We Catch Interaction Vulnerabilities

The Balancer exploit wasn't a single bug. It was an interaction between:

  • _scalingFactors override (introduced September 20, 2021)
  • _upscale rounding behaviour (existed since 2021)
  • batchSwap mechanism (Vault functionality)
  • Low liquidity states (achievable through preparation)

Traditional pattern-matching tools look for known vulnerability patterns in isolation. SafeStackAI analyzes how functions interact across contracts and under what conditions those interactions become exploitable.

2. We Understand Subtle Mathematical Invariants

Catching this vulnerability required understanding:

  • Fixed-point arithmetic truncation behaviour
  • How exchange rates amplify rounding errors
  • Why small amounts maximize percentage loss
  • How iteration compounds small errors into large profits

This isn't pattern matching. This is semantic understanding of economic invariants and where they break.

The Path Forward

The blockchain industry has made tremendous progress on security. The existence of specialized audit firms, bug bounty programs, formal verification, and security-focused development practices has prevented countless exploits.

But we can do better.

  • We can move from periodic security validation to continuous security assurance.
  • We can make security findings immediately actionable instead of delivered weeks after development.
  • We can make institutional security knowledge enforceable instead of just documented.
  • We can make expert human auditors more effective by having them focus on complex problems requiring judgment.

SafeStackAI is built on a simple premise: security should evolve with your code, not lag behind it.

SafeStackAI Dashboard Overview
SafeStackAI dashboard providing continuous security monitoring and actionable insights

Ready to Close the Gap?

SafeStackAI is currently in closed beta. If you're interested in bringing continuous security to your protocol, reach out to contact@safestackai.com to request access and join our waitlist.

Protect Your Smart Contracts

Get continuous security analysis with AI-powered vulnerability detection that evolves with your code

Start Free Trial