On Sunday May 2nd, 2021, the Fei Labs team identified an economic vulnerability that affected the Fei Protocol, which would have allowed an attacker to drain funds from the ETH-FEI Uniswap pool. The team immediately paused the relevant functionality, and no funds were lost. The Fei team reached out to the OpenZeppelin team, and we worked together to ensure a secure fix for the issue was found.
The Vulnerability
As outlined in the Fei Protocol whitepaper, the BondingCurve contract allows users to purchase FEI in exchange for their ETH at a price of 1.01 USD per FEI. This ETH is held in the contracts until, once per day, someone can call the allocate function, which deposits the ETH and some newly minted FEI into the ETH-FEI Uniswap pair as liquidity. To calculate the amount of FEI to mint during allocation, the contract uses the current ETH price from USDC-ETH Uniswap pair. The amounts of ETH and FEI are then added as liquidity to the Uniswap pair using the addLiquidityETH function from Uniswap’s UniswapV2Router02 contract.
However, Uniswap does not allow users to add liquidity to a pair in any proportions – instead, the pair only accepts liquidity in proportion to the reserves already available in the pair. This ensures that by adding liquidity you cannot alter the current price. For example, if the Uniswap pair had 1 ETH and 3000 FEI, it would only accept new liquidity deposits in the ratio 1:3000. This means that if the USDC-ETH and FEI-ETH prices are different, the Uniswap contract will not accept the entire deposit during allocation, and instead will return any excess FEI or ETH that doesn’t conform to the current ratio. By adding extra liquidity to the FEI-ETH pool, while the price is not equal to the USDC price, the allocation has the effect of further enforcing this different price.
An attacker can use this reinforcement of the incorrect price using the following steps within 1 transaction:
- Take out a flash loan of ETH (or be a whale)
- Trade the large amount of ETH for FEI on the FEI-ETH Uniswap pair, thereby increasing the price of FEI.
- Call the allocate function on the bonding curve, which adds liquidity to the FEI-ETH pair, reinforcing this inflated price
- Sell the FEI bought in step 2 back to the FEI-ETH pair for a premium
- Pay back the ETH loan, keeping the excess as profit
The Fei Labs team reported this issue to OpenZeppelin, and it was included as a client-reported vulnerability in the OpenZeppelin audit report, with the proposed and auditor-reviewed fix in PR#81.
The Fix
The Fei Team implemented a two-part fix for the issue.
The first part of the fix decouples the BondingCurve from the Uniswap pair, and instead sends the funds to the EthPCVDripper and EthReserveStabilizer. The ETH that is sent to the BondingCurve to purchase FEI at $1.01 will no longer be added as liquidity to the Uniswap pair, thereby removing the potential for the attack entirely. This part of the fix does not require changes to the code, and can be implemented by calling setAllocation() via the Fei DAO. The EthPCVDripper and EthReserveStabilizer contracts, where the ETH will now be sent, were out of scope of the original OpenZeppelin audit but have been reviewed by independent security auditors.
The second part of the fix, found here, has been reviewed by OpenZeppelin and is outlined below. Although the fix above removes the possibility of the attack in question entirely, the Fei team also wanted to protect further against potential sandwich attacks on future liquidity deposits to the EthUniswapPCVDeposit contract. They have updated the EthUniswapPCVDeposit contract to help protect against this separate scenario.
The fix specifies a minimum amount of ETH and FEI that the contract wishes to deposit as liquidity in its call to Uniswap’s addLiquidityETH function. These amounts are calculated using a percentage, x%, of the USDC-ETH price. Through specifying minimum amounts, the contract can require that the FEI-ETH and USDC-ETH prices do not differ by more than x%, reverting the call if the prices are too different and an attack is possible. The percentage in question can be altered by a setter function, accessible only to governance.
There is one particular consequence of this fix that should be noted – if the Fei team enables liquidity deposits again in the future, the potential of a sandwich attack is completely controlled by the value of x. At lower values of x, controlled by governance, the attack becomes unprofitable for a malicious actor to carry out. However, if a high value of x were to be used, the attack would still be possible and profitable.
To calculate the safe values of x, one must consider a wide range of variables. The Fei Protocol team has already done a large amount of analysis to calculate values of x that would deem this attack unprofitable. If they decide to enable liquidity deposits again in the future, the team state that they will do further analysis to quantify the exact risk, and calculate safe values of x.
Conclusions
After identifying the vulnerability and validating it, the Fei Labs team immediately paused the bonding curve, which prevented the exploit from being possible. The vulnerability was not exploited and no funds were lost.
As seen in this case, DeFi protocols are becoming more and more complex, interacting with one another in new and innovative ways. This naturally means the complexity of the vulnerabilities they may contain also increases. We are regularly seeing new attack vectors appear that have never been performed before. These developments only increase the need to ensure that protocols have true end-to-end security, beyond security audits, throughout their entire development lifecycle.
Read the post mortem from the FEI team here.