CATEGORIES

How an Attacker Drained $128M from Balancer Through Rounding Error Exploitation

November 5, 2025

By: Dikla Barda, Roaman Zaikin & Oded VanunuĀ 

On November 3, 2025, Check Point Research’s blockchain monitoring systems detected a sophisticated exploit targeting Balancer V2’s ComposableStablePool contracts. The attacker exploited arithmetic precision loss in pool invariant calculations to drain $128.64 million across six blockchain networks in under 30 minutes.

The attack leveraged a rounding error vulnerability in the _upscaleArray function that, when combined with carefully crafted batchSwap operations, allowed the attacker to artificially suppress BPT (Balancer Pool Token) prices and extract value through repeated arbitrage cycles. The exploitation occurred primarily during attacker smart contract deployment, with the constructor executing 65+ micro-swaps that compounded precision loss to devastating effect.


Introduction

In the early morning hours of November 3, 2025, Check Point’s Blockchain Threat Analysis system flagged unusual activity on the Ethereum mainnet involving Balancer’s V2 Vault contract. Within minutes, our automated detection identified a critical exploit in progress, with massive fund outflows from multiple liquidity pools.

Balancer V2

The attack exploited a mathematical vulnerability in how Balancer’s ComposableStablePools handle small-value swaps. When token balances are pushed to specific rounding boundaries (8-9 wei range), Solidity’s integer division causes significant precision loss. The attacker weaponized this by executing batched swap sequences that accumulated these tiny errors into catastrophic invariant manipulation.


Background: Balancer V2 Architecture

The Vault System

Balancer V2 uses a centralized “Vault” contract (0xBA12222222228d8Ba445958a75a0704d566BF2C8) that holds all tokens across all pools, separating token storage from pool logic to reduce gas costs and enable capital efficiency. This shared liquidity design meant a single vulnerability in pool math could affect all ComposableStablePools simultaneously—exactly what happened in this attack.

Internal Balance Mechanism

Balancer V2’s Internal Balance system allows users to deposit tokens once and use them across multiple operations without repeated ERC20 transfers:

mapping(address => mapping(IERC20 => uint256)) private _internalTokenBalance;

This system became critical to the attack. The exploit contract accumulated stolen funds in its internal balance during deployment, then withdrew them to the final recipient address in subsequent transactions.


The Vulnerability: Arithmetic Precision Loss in Stable Pool Math

The Root Cause

ComposableStablePools use Curve’s StableSwap invariant formula to maintain price stability between similar assets. The invariant D represents total pool value, and BPT price is calculated as D divided by totalSupply. However, the scaling operations that prepare balances for invariant calculations introduce rounding errors.

Vulnerable Code Path:

function _upscaleArray(uint256[] memory amounts, uint256[] memory scalingFactors) 
    private pure returns (uint256[] memory) {
    
    for (uint256 i = 0; i < amounts.length; i++) {
        amounts[i] = FixedPoint.mulDown(amounts[i], scalingFactors[i]);
    }
    return amounts;
}
// Simplified representation - actual implementation is more complex
function _calculateInvariant(uint256[] memory balances) private pure returns (uint256) {
    uint256[] memory scaledBalances = _upscaleArray(balances, scalingFactors);
    uint256 invariant = computeStableInvariant(scaledBalances, amplificationParameter);
    return invariant;
}

The mulDown function performs integer division that rounds down. When balances are small (8-9 wei range), this rounding creates significant relative errors—up to 10% precision loss per operation.

This precision error propagates to the invariant D calculation, causing abnormal reduction in the calculated value. Since BPT price equals D divided by total supply, the reduced D directly lowers BPT price, creating arbitrage opportunities for the attacker.

Individual swaps produce negligible precision loss, but within a single batchSwap transaction containing 65 operations, these losses compound dramatically. The lack of invariant change validation allowed the attacker to systematically suppress BPT price through accumulated precision errors, extracting millions in value per pool

Attack Analysis

The Three Phase Pattern

The attacker executed a sophisticated three-stage swap sequence within single `batchSwap` transactions:

Stage 1: Adjustment to Rounding Boundary

Swap large amounts of BPT for underlying tokens to push one token’s balance to the critical 8-9 wei threshold where rounding errors are maximized.

Stage 2: Trigger Precision Loss

Execute small swaps involving the boundary-positioned token. The _upscaleArray function rounds down during scaling, causing the invariant D to be underestimated and BPT price to drop artificially.

Stage 3: Extract Value

Mint or purchase BPT at the suppressed price, then immediately redeem for underlying assets at full value. The price discrepancy represents pure profit.

This three-phase cycle repeated 65 times within the same batchSwap transaction. All stages occur atomically, preventing intervention and ensuring precision losses accumulate across the shared balance state, ultimately extracting millions from each targeted pool.

Having understood the vulnerability mechanism, let’s examine how the attacker automated this exploitation.

Exploit Contract Architecture

The attacker deployed contract `0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d` with a three-address operational structure:

– Exploiter 1: 0x506D1f9EFe24f0d47853aDca907EB8d89AE03207 (deployer)

– Exploit Contract: 0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d

– Exploiter 2: 0xAa760D53541d8390074c61DEFeaba314675b8e3f (recipient)

Constructor-Based Attack

Analysis of transaction 0x6ed07db… revealed the theft occurred during contract deployment. The constructor automatically executed the rounding error exploitation, targeting two Balancer pools simultaneously.

 The constructor generated 65 token transfers to Balancer’s Protocol Fees Collector—these are swap fees collected during the manipulation, not the stolen funds themselves. The transfer amounts display the characteristic pattern of iterative precision exploitation, decreasing from 0.414 osETH down to 0.000000000000000003 osETH as the rounding errors compound to negligible values.

The stolen value appears in InternalBalanceChanged events, which record balance updates within the Vault’s internal accounting system. The exploit contract’s internal balance increased by:

Pool 1 (osETH/wETH-BPT): +4,623 WETH, +6,851 osETH
Pool 2 (wstETH-WETH-BPT): +1,963 WETH, +4,259 wstETH
Combined total: 6,586 WETH (4,623 + 1,963) + 6,851 osETH + 4,259 wstETH

These internal balance increases represent the actual stolen funds. The InternalBalanceChanged events show that the exploit contract’s Vault-internal account was credited with the drained assets. While the underlying tokens physically remained in the Vault contract, the Vault’s accounting system now recognized the exploit contract as the owner of these balances, enabling later withdrawal.

Withdrawal Function

After the constructor accumulated stolen funds, function 0x8a4f75d6 transferred them to Exploiter 2:

function 0x8a4f75d6(address[] calldata targetPools) public {
    require(msg.sender == _callTx);
    
    poolIndex = 0;
    while (poolIndex < targetPools.length) {
        poolId = targetPools[poolIndex].getPoolId();
        (tokens[],) = vault.getPoolTokens(poolId);
        internalBals[] = vault.getInternalBalance(address(this), tokens);
        
        tokenIndex = 0;
        while (tokenIndex < tokens.length) {
            operations[tokenIndex] = UserBalanceOp({
                kind: 1,
                asset: tokens[tokenIndex],
                amount: internalBals[tokenIndex],
                sender: address(this),
                recipient: 0xAa760D53541d8390074c61DEFeaba314675b8e3f
            });
            tokenIndex++;
        }
        
        vault.manageUserBalance(operations);
        poolIndex++;
    }
}

This function withdraws the contract’s own internal balance. The UserBalanceOp has sender equal to the exploit contract address because the contract legitimately owns the funds accumulated during constructor execution.

Transaction `0xd155207…` confirms this withdrawal transferred 6,586 WETH from the exploit contract’s internal balance to Exploiter 2 address.

The TwoStage Attack

Stage 1 – Theft (Constructor Execution):

TX: 0x6ed07db1a9fe5c0794d44cd36081d6a6df103fab868cdd75d581e3bd23bc9742

Action: Deploy exploit contract

Method: Constructor executes batchSwap operations against two pools

Result: $63M drained via rounding error, stored in contract’s internal balance

Evidence: 65 fee transfers + InternalBalanceChanged events showing +6,586 WETH, +6,851 osETH, +4,259 wstETH

Stage 2 – Extraction (Function Call):

TX: 0xd155207261712c35fa3d472ed1e51bfcd816e616dd4f517fa5959836f5b48569

Action: Call function 0x8a4f75d6

Method: Withdraw internal balance to Exploiter 2

Result: Funds transferred to final recipient

Evidence: manageUserBalance with sender = exploit contract

Conclusion

The Balancer exploit demonstrates how mathematical vulnerabilities in DeFi protocols can be weaponized through automation and careful parameter tuning. The attacker’s success stemmed from recognizing that negligible rounding errors become exploitable when amplified through dozens of operations in atomic transactions.

Despite extensive audits, the vulnerability persisted because traditional testing focuses on individual operation correctness, not cumulative effects of adversarial batch operations. The industry must evolve toward continuous security validation, economic attack modeling, and adversarial testing that considers how tiny flaws compound into catastrophic exploits.

POPULAR POSTS

BLOGS AND PUBLICATIONS

  • Check Point Research Publications
  • Global Cyber Attack Reports
  • Threat Research
February 17, 2020

ā€œThe Turkish Ratā€ Evolved Adwind in a Massive Ongoing Phishing Campaign

  • Check Point Research Publications
August 11, 2017

ā€œThe Next WannaCryā€ Vulnerability is Here

  • Check Point Research Publications
January 11, 2018

ā€˜RubyMiner’ Cryptominer Affects 30% of WW Networks