Balancer Hacks: Root Cause and Loss Analysis

Started at 06:03:11 PM +UTC, Jun-28–2020, the DeFi platform, Balancer, was attacked by exploiting its flawed handling of ERC20 deflationary tokens. Technically, the main logic behind the incident is the incompatibility between Balancer and deflationary tokens, which is then misused by the attacker to create skewed STA/STONK pools states and make profits from that.

This hack consists of four different steps:

  • Flashloan Borrow: The bad actor borrowed a flash loan (104,331 WETH) from dYdX.
  • STA Depletion: With the borrowed WETH, the bad actor performed a flurry of swaps to deplete almost all STA tokens owned by a Balancer pool. Note that STA is a deflationary token that will charge 1% on every token transfer. The result of STA depletion is that there is only 1e-18 STA left in the pool.
  • Exploitation for Profit The bad actor exploited the flawed handling of STA in Balancer and stoled the pool assets approximately valued $523,616.52.
  • Flashloan Repay Finally, the bad actor repaid the dYdX flash loan and walked away with the stolen assets.

In the following, we analyze this particular hack as demonstrated in the transaction. http://oko.palkeo.com/0x013be97768b702fe8eccef1a40544d5ecb3c1961ad5f87fee4d16fdc08c78106/).

Image for post
Image for post
Figure 1: Balancer Hack Breakdown

Step 1: Flashloan Borrow

This step basically takes advantage of the dYdX flashloan feature to borrow 104,331 ETH. This part is already known and we will not go into the details.

Image for post
Image for post
Step 1: The Flash Loan Borrowing WETH From dYdX

Step 2: STA Depletion

In this step, the bad actor performed multiple swapExactAmountIn() calls within the same transaction to drain the STA balance in the attacked Balancer pool. We notice that swapExactAmountIn() sets the limit on the swap amount, i.e., inRecord.balance * MAX_IN_RATIO. The hacker calculated the limit and swapped the maximum allowed amount of WETH for STA via a flurry of operations as follows:

Image for post
Image for post
Step 2: Instant STA Depletion (Part I)

The result of performing the above swaps is to intentinally left 1e-18 in the Balancer pool:

Image for post
Image for post
Step 2: Instant STA Depletion (Part II)

Consequently, we now have _records[STA] and STA._balance[BPool] as follows:

Image for post
Image for post

Due to the fact that the amount of STA in BPool is close to zero, its price relative to other assets is extremely high. Anyone can swap 1 STA for a huge amount of other assets at this moment.

Step 3: Exploitation for Profit

After the previous two steps, this step essentially exploitsed the vulnerability to steal the pool assets.

Image for post
Image for post
Step 3: Exploitation for Profit (Part I)

Specifically, by sending in 1e-18 STA into BPool via swapExactAmountIn(), the bad actor swapped out 30,347 WETH in the first run. In internal records for book-keeping, _records[STA] is increased by tokenAmountIn (i.e., 1) before the BPool contract actually collects the corresponding STA tokens form the msg.sender.

Image for post
Image for post
Image for post
Image for post
Step 3: Exploitation for Profit (Part I — continued)

At the bottom of swapExactAmountIn(), the _pullUnderlying() function collected the STA tokens. However, as mentioned earlier, STA is a deflationary token that charges shown 1% on every token transfer. Because of the transfer fee cut, the Balancer pool (or Pool) actually got zero STA tokens. Therefore, there’s a mismatch between the actual STA balance of BPool and its internal records (i.e., _records[STA]).

Image for post
Image for post
Image for post
Image for post
Step 3: Exploitation for Profit (Part II: gulp resets internal records of STA balance)

To remedy the mismatch, here comes the interesting part. The gulp() is exploited to reset the _records[STA], which helps the bad actor to maintain the state that BPool has only 1e-18 STA. Therefore, the bad actor can continue to swap all pool assets out with the extremely valuable 1e-18 STA.

Image for post
Image for post

Step 4: Flashloan Repay

The final step repaid the flashloan back to dYdX.

Image for post
Image for post
Step4: Repay dYdX Loan

Mitigation

This incident emphasizes the challenges posed by DeFi composability that may create less obvious incompatibility from deflationary tokens. It also reminds earlier incidents that show the incompatibility from ERC777 tokens. We expect the incompatibility will likely continue to exist and there is no easy solutions.

For this particular incident, on one hand, deflationary tokens (such as STA/STONK) should revert or return false when the _value for transfer() or transferFrom() is less than the charged fee. On the other hand, Balancer may need to always check the balance of BPool after each transferFrom() call. Nevertheless, there is always a need to find out all corner cases with better coding practices and improved test coverage without making any unnecessary assumption on the expected behaviors of ERC20s, ERC777s, and other DeFi components.

Aftermath

The Balancer hack will likely not be the last incident. In the following, we put together the amount loss of various assets in this incident:

Image for post
Image for post

About us

PeckShield Inc. is an industry leading blockchain security company with the goal of elevating the security, privacy, and usability of current blockchain ecosystem. For any business or media inquiries (including the need for smart contract auditing), please contact us at telegram, twitter, or email.

Written by

A Blockchain Security Company (https://peckshield.com)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store