Balancer DoS Bugfix Review
On May 14th, ChainSecurity employee @k_besic reported a vulnerability classified as “Medium” in Balancer protocol. The vulnerability consisted of a potentially exploitable Denial of Service (DoS) scenario by emptying double entry-point ERC-20 tokens through Balancer’s flash loans. Balancer paid k_besic $50,000 USDC for his excellent find.
While there were no user funds at risk, this submission was a great example of how our whitehat hackers are protecting the DeFi ecosystem, so let’s dive into each piece of this bug.
It’s also a great example of how auditors can contribute to the security of Web3 beyond their normal work of audit reports. Congrats to ChainSecurity and k_besic for being watchful protectors!
Intro to AMMs and Balancer
Automated market makers (AMMs) are smart contracts that enable automated management of crowdsourced liquidity pools (LP), providing tradeable tokens to decentralized exchanges (DEX). AMMs are an important primitive in DeFi, as anyone can deposit their own tokens in an AMM LP, receiving a share of trading fees and the LP tokens in return.
AMMs do not rely on humans to manage trades, which are controlled directly by algorithms. An AMM replaces the buy and sell orders in an order book market with a liquidity pool of two assets, both valued relative to each other. As one asset is traded for the other, the relative prices of the two assets shift, and the new market rate for both is re-calculated.
The majority of AMMs use the constant mean market maker equation (also known as the constant product equation) to define how liquidity pools behave:
Balance of token A * Balance of token B = Constant product
For example, in a pool with 2,000,000 tokens A and 1,000 tokens B, the constant product is 2,000,000,000. This inverse correlation then determines the trading price of the assets at any moment, as the price of one token increases when the price of the second token decreases:
Market price for token A = Balance of token B / Balance of token A
The price curve is often utilized as a way to visualize this relation:
To find the swap price for the assets in a given pool (i.e., the amount of token A received when you exchange a certain quantity of token B), we calculate the balances for each token that are needed in the pool to keep the constant product unchanged after the swap:
The amount of token A received from this swap conveys the “buy price”, and it’s calculated from the relation above:
Launched in 2018, Balancer introduced a protocol for configurable liquidity on the Ethereum blockchain and other EVM-compatible systems. With Balancer, users are able to create liquidity pools with up to eight different ERC-20 tokens, in any ratio. These pools can be thought of as automatically rebalancing portfolios, providing traders with what can be called a decentralized index fund.
Balancer routes several pools together, allowing easy trades from one token in one pool to any other token in another pool. These multi-asset pools work using what is called weighted math, designed to allow swaps between any asset whether or not they have a price correlation. Balancer’s price equation is a generalization of the constant product equation, accounting for multi-tokens and weightings that are not evenly split, so that prices are determined by the pools’ balances and weights.
Balancer’s core smart contract is called “The Vault’’, and it controls and holds all tokens in each of the Balancer pools. The Vault’s architecture separates the token accounting and management from the pool logic, simplifying the pools’ contracts. In this architecture, Balancer’s Protocol Swap Fees are the percentage of swap fees collected by pools defined on a contract named
ProtocolFeesCollector.sol. These fees include flash loan fees, and they are adjusted through governance by the Balancer’s DAO.
In the vulnerability reported in this submission, the whitehacker showed that issuing a flashloan using the Vault contract’s balance could be used to move tokens from the Vault into the
ProtocolFeesCollector (as if they were regular protocol fees), leading to a DoS scenario as the Vault suddenly lacks tokens to transfer during swaps. To understand how, let’s first understand flashloans on Balancer.
Flashloans are special smart contract operations that allow the borrowing of any available amount of assets (up to the total liquidity) without collateral. This is possible as long as the liquidity plus interests are returned within one block transaction. If the borrower is unable to repay the loan, the entire transaction is simply reverted. A common use of flashloans is on arbitrage algorithms, searching for profit by trading an asset on a first DEX, and then getting back the asset plus gains on other DEXs.
In the context of Balancer, a trader could borrow any amount of tokens available on The Vault to use on a flashloan. The logic for these types of transactions is specified in the
FlashLoans.sol contract, more specifically in the
- This function starts with straightforward initializations, ensuring that the length of the token array and the amount array match, and assigning the zero address to the
previousTokenvariable (to ensure the tokens are sorted and unique):
2. The first loop over the tokens’ array is used to record the pre-balance of each token, calculate the loan fees, and transfer each amount of token to the receiver. Note the callback function on receiver,
receiveFlashLoan, right after the loop:
3. The second loop checks whether each token’s balance is equal or larger than their previous balance, then adds any balance surplus as a fee in the
4. This fee is then transferred to
Fees.sol function described below:
Do you see a problem with how the flashloan function is implemented? If not, do not worry; we are very close to understanding this vulnerability! There is just one last piece of information we need: double entry point ERC-20 tokens.
Proxies and Double Entry Point ERC-20 tokens
When ERC-20 tokens operate on a proxy pattern, users interact directly with the proxy contract when performing token transactions. The proxy then connects to a target contract that implements the underlying logic. For a more in-depth review of how proxies work, check out our Wormhole Uninitialized Proxy Bugfix Review.
A double entry point ERC-20 token relies on an architecture on which both users and contracts can interact directly with the target contract. Since the proxy can be bypassed, these tokens have two entry points.
Examples of double entry point tokens that are traded in Balancer pools are the Synthetix tokens, for instance, SNX and sBTC. For context, Synthetix is a derivative liquidity protocol that powers the creation of “synths” (synthetic assets), which can be traded in a decentralized and permissionless manner.
We went over several important concepts, and we are now ready to go in-depth into this vulnerability found in the flash loan method we described above, which is part of Balancer’s Vault contract.
What if an attacker tried to execute a Balancer flashloan for both entry points of the same token? In this case, the flashloan repayment (the second loop) would be interpreted as an excess of token balance at the second entry point and sent as fees to The Vault!
Here is the step-by-step procedure for this exploit:
- In an exploit script, we define the two entry points for a chosen double entry point token, such as sBTC’s, retrieving the target contract and proxy contract:
2. We then craft an Exploit contract that:
a. implements the flash loan function from the
b. Implements the method
receiveFlashLoan, from The Vault’s
3. Back to our script, we deploy this contract, exploiting the flashloan method with the first token as the first entry point and the full balance of the vault, and the second token as the second entry point with zero balance:
4. If we take another look at the flashloan function, in the first loop, we would see that the previous balance for the first entry point (target) is tracked as the full balance (and transferred out). The balance of the second entry point (proxy) is tracked as 0:
5. In the second loop, the check of the previous balance for the first entry point (target) would still be the full balance. The vulnerability lies during the check of the second entry point (proxy). Its previous balance being zero and its post balance being the full balance result in the
receivedFeeAmount receiving a difference equal to the full balance!
6. The Vault’s
_payFeeAmount method would then send the full amount of the sBTC to the
ProtocolFeesCollector contract, draining the Vault entirely! The resulting state of the pool would cause failures for legit transactions such as swaps and pool exits, resulting in a DoS situation!
Since the Balancer governance controls access to the
ProtocolFeeCollector contract, an attacker would not be able to retrieve funds after sending them to this contract. However, as we learned above, exploiting such vulnerability could lead to a DoS to the pool’s resources.
Upon notification, the Balancer Labs team quickly deployed mitigations in the affected contracts, moving the tokens to the ProtocolFeeCollector contract and then back to a restored Vault, resuming normal operations.
We would like to thank whitehat @k_besic for responsibly disclosing this bug. Big props to the Balancer team for the quick response and payout.
This bug was reported via the Immunefi platform — the leading bug bounty platform for Web3. To report additional vulnerabilities, check out Balancer’s bug bounty program with Immunefi.