Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

In-Depth Analysis: The Balancer V2 Exploit- 1782

BlockSecPosted 4 Months Ago
  • $125M was stolen from Balancer's V2 Composable Stable Pools, alongside several forked projects of it. This article is a breakdown of the incident. Composable Stable Pools are assets that are expected to have a nearly 1 to 1 parity, allowing large swaps with minimal price impact. For instance, USDC to USDT. The pool uses stable math with Price(BPT) = (D / total supply) where D represents the pool's virtual value and BPT is Balancer Pool Token.
  • If D becomes smaller, then the BPT price will appear cheaper. In Balancer, batchSwap() is used to perform multi-hop swaps. The user can specify the exact amount in or the actual amount out to receive.
  • To normalize calculations across different token balances, Balancer has to perform scaling. Sometimes, this involves upscaling, while other times it is downscaling.
  • When performing a batch swap and specifying the amount out, the function _swapGivenOut() must calculate the input amount that is required for the transfer to succeed. Upon doing this, the function upscale() rounds down to benefit the user. It is standard practice always to have rounding benefit the protocol; otherwise, incidents like this can occur.
  • For example, let's say the pool uses 89 to calculate a fair amount in for a trade going from wstETH to cbETH. To perform a trade to get 8 cbETH, the math is (amount * 100) / 9 = ?. This means (8 * 100) / 9 = 800 / 9 = 88.888 in practice. Now, the upscale function will round to 88 instead of 89. In practice, this is catastrophic. It allows a user to exchange a smaller amount of one underlying asset (wstETH) for another (cbETH)! Over time, this decreases the invariant D, since there is now less liquidity. As a result, the corresponding BPT becomes deflated.
  • The process for manipulating the pool works as follows:
    1. Swap BPT for the underlying asset to make one of the assets be on the edge of a rounding boundary, such as being at 9. This sets up the precision loss.
    2. Perform a swap using the crafted amount to trigger the rounding error. The delta can be set up as 8.918 to 8. Hence, this underestimates the number of tokens required for the trade from the user's perspective. This leads to the BPT price becoming deflated.
    3. Reverse swap the assets back into BPT. By restoring the assets at the deflated BPT price, the attacker gains a profit.
    4. Repeat this over and over again to get large amounts of BPT at a discounted rate.
  • Rounding errors that have massive impacts have always been weird to me. How can a token with nine decimals that loses a single point of precision lead to catastrophic losses? In this case, it's because of the D value. The price manipulation of D is how the attackers profit. In a separate transaction, they purchase the BPT at a significant discount and sell it for a substantial profit. This could have been done in the same transaction, but they likely did it in a separate one to prevent frontrunning.
  • The most challenging aspect of the attack was determining the optimal parameters to maximize the effect of the precision loss. The attacker performed off-chain calculations and then on-chain simulations for the hop parameters to manipulate the pool precisely for the exploit to succeed.
  • The code received several audits by Certora and Trail of Bits. The game is hard and we can't blame anyone, though: find all bugs or else it's a challenging game to play. The vulnerability was particularly complex, as indicated by much of Twitter, which initially thought this was an access control issue. Great write-up on the nuances of this issue!
  • Another article, written by one of the auditors Certora, that did formal verification. The primary purpose was to ensure the solvency of BPT supply, parity of assets, and minting of BPT. The properties they tested ensured that no token could be created from nothing and that user balances always reflected the actual underlying value. So, what happened?
  • Solvency was verified at a high level, but not strong enough to detect the rounding errors. The verified properties did not constrain the relationship between individual swaps or rounding behaviour. Iterative operations, such as token round-trip swaps, could then increase in value due to the rounding bias. Two additional properties for formal verification would have caught this: round-trip swap invariant and BPT share value invariant. Security is hard! I'm not the biggest fan in formal verification - I kind of wish it had a different name.
  • Another post provided additional insights into the aftermath of the exploit. The author claims that the attacker wasn't very experienced. First, they didn't use flashbots, which led to some frontrunners. The hacker took 20 minutes to finish the attack on the various chains as well. They even left forks on the table to be exploited. The first copy-cat took the contract code of the attacker, replaced pool information and profited heavily without my effort.
  • They also pointed out that Balancer didn't pause any of their pools. Since they aim to be decentralized, pausing is prevented after a specific deployment window. Decentralization and speed are often enemies. Another interesting aspect was that Polymarket had a bet on "Crypto Hack Over $100M in 2025" that lagged by about 10 minutes after the attack. What a crazy world we live in.