Understanding Liquidity Pools, AMMs, Vaults, and the yDAI Exploit
In the first part of this article series, we looked at flash loans and how they work. In this part, we will be exploring the yDAI exploit. However, in order to understand the exploit itself, we need to understand what liquidity pools are and how they work.
This is a 3-part article series - read the first part on flash loans.
Liquidity pools and Automated Market Makers
Liquidity pools and Automated Market Makers (AMMs) are both crucial in understanding the exploit we will be covering. At a high level, the exploiter makes use of certain characteristics of a liquidity pool to force a DeFi platform to take bad trades, with the exploiter positioning themselves on the opposite side of the bad trades. This allows them to profit from the DeFi platform’s loss.
A liquidity pool on a blockchain is simply a basket which traders interact with to trade between the assets. Typically a pool only consists of two assets, and these can be anything. If traders want to trade between ETH and USDC, they will interact with an ETH-USDC liquidity pool. If a trader wants to buy ETH, they will deposit USDC and remove ETH from the pool (and the reverse operation if they want to sell ETH).
However, for any trade to be possible, there needs to be some ETH and USDC in the pool. This is taken care of by liquidity providers. In this example, liquidity providers will supply both ETH and USDC in specific amounts. In return, they passively earn trading fees from traders.
Trading in a liquidity pool changes the supply of its two assets. Intuitively, we can already see that to reflect the new amounts of the two assets, prices must change. If a trader deposits USDC and removes ETH (so trader buys ETH), the latter is now more scarce and so commands a higher price. The pricing is determined by the type of AMM model used.
Constant Product AMM
One type of AMM model determines the price from the ratio of the two assets: if the pool contains 3 ETH and 6,000 USDC, the mid-market price will be 6,000/3 = 2,000 USDC per ETH. However, trades will never occur at this exact price, as this does not account for the trade size.
Instead, a trader will incur slippage. If they want to buy 1 ETH, they will pay 3,000 instead of 2,000 USDC. This is a slippage of 50%, as they are paying 50% more than what the quoted mid-market price is. This happens because the product of the two token quantities must remain constant:
- 6,000 * 3 = 18,000, making 18,000 the constant product.
- Buying 1 ETH means removing 1 ETH from the pool, which would leave 2 ETH in the pool.
- If the constant product is to remain the same (18,000), and there are 2 ETH in the pool, then there needs to be 9,000 USDC in the pool to maintain the product (18,000/2 = 9000 USDC).
- We started with 6,000 USDC in the pool, so the buyer needs to provide 3,000 USDC to get the total to 9,000 and keep the constant product unchanged.
We just described a constant product AMM, and see why all trades on DEXs using this model will incur slippage.
Constant Sum AMM
Suppose now that the pool consisted of two stablecoins, DAI and USDC. We know that the constant product model always incurs slippage, so it wouldn’t make sense to always be penalised with slippage for assets that are both supposed to represent $1. A better model would be a constant sum AMM, where instead of multiplying the two token quantities, we simply add:
- A pool contains 4,000 USDC and 3,000 DAI.
- The constant sum is 4,000 USDC + 3,000 DAI = 7,000.
- ‘Buying’ 2000 DAI means removing this amount from the pool, leaving only 1,000 DAI in the pool.
- If the constant sum is to remain the same, and there will be 1,000 DAI in the pool, then there needs to be 7,000 - 1,000 = 6,000 USDC in the pool.
- We started with 4000 USDC, so the buyer needs to provide 2,000 USDC to get the total to 6,000 USDC and maintain the constant sum.
The net result is a simple swap of 2,000 USDC to 2,000 DAI. This makes sense as both are stablecoins with equal value. However, there is a problem with this approach. Suppose another trader wants to do the same, and takes the remaining 1,000 DAI. The pool now only has 7,000 USDC with zero DAI, rendering it only half as useful as now only DAI to USDC trades are possible. This model is never used in practice, but helps us see how the StableSwap AMM functions.
Without going into the math, curve.fi (the largest stablecoin liquidity pool platform on the Ethereum network) uses the StableSwap model, which is essentially a combination of the two. For instances where there are enough tokens for both of the assets in the pool, it follows a constant sum model. When the quantities for each stablecoin have significantly deviated with respect to each other, it follows a constant product model. The StableSwap AMM penalises the trader who further depletes the stablecoin that’s low in supply with large slippage, or rewards the trader who provides it.
We can also apply the bolded statement to liquidity providers. With the StableSwap AMM, it is possible to provide single assets into the liquidity pool. Providing assets that are already in high supply will result in a penalty, whereas providing assets that are in short supply will result in a reward.
One final point to note is that this AMM model can be extended to any number of assets, not just two. In Curve’s case, the largest liquidity pool is the ‘3pool’, which consists of three assets: USDC, USDT, and DAI. This is also the pool involved in the exploit.
Initial Attack Plans
From the above bolded statements, we can already start to think of possible attack patterns. Suppose we add a large amount of liquidity to a StableSwap-based liquidity pool. The volume is large enough to push the AMM to start functioning as a constant product AMM, where any additional liquidity provided for the same asset will be heavily penalised. If we can somehow force someone else to do this (provide liquidity after us for the same asset), then we can take advantage of the second part of the bolded statement: we remove the excess and are rewarded for doing so. A high-level overview of an attack will look something like this:
- We provide a large amount of USDC to a StableSwap liquidity pool containing USDC, USDT, and DAI. In return we get liquidity pool (LP) tokens back. These enable us to retrieve our deposit in any of the three stablecoins.
- As the pool is now heavy in USDC, anyone depositing USDC will be penalised with slippage. This is realised by the depositor receiving fewer LP tokens than normal.
- We force a platform to deposit USDC to the liquidity pool.
- We redeem our LP tokens from Step 1 and get back more USDC than we deposited, as we are also being rewarded for removing excess liquidity.
Note that in the first step, we require a large deposit. This is made possible with a flash loan and provided we repay the loan by the end of the transaction, the exploit will be validated. The liquidity pool used in the example is Curve.fi’s 3pool, and the platform being manipulated is typically a vault. The following section describes the vault which was exploited.
Vaults in DeFi are automated strategies to maximise the yield of the deposited assets. Some lending-focused vaults will actively switch between lending stablecoins on Aave and Compound, depending on whichever is providing a higher interest rate. Other vaults will provide liquidity to several decentralised exchanges to earn trading fees, switching to whichever provides higher fees. There are other types of strategies, and many vaults will combine these to achieve higher returns. By depositing to these vaults, users benefit from active management of their funds in an automated fashion.
In the yDAI vault’s case, the strategy is as follows:
- Deposit DAI into Curve’s 3pool liquidity pool to earn trading fees. This will return 3crv tokens (LP tokens) to the depositor, representing their fair share of the pool which they would use to redeem their deposit. These are tradeable tokens, and sending them to someone else effectively means they’ve sent their deposit.
- The 3crv tokens can then be deposited into the 3pool Liquidity Gauge. This is a feature on Curve.fi which lets users deposit 3crv tokens to receive free CURVE tokens (the governance token for the Curve.fi platform). The depositor is free to sell the CURVE tokens on the open market (Uniswap or Binance). This can be seen as an incentive mechanism to attract liquidity providers, as this is an additional source of income for the depositor.
- The depositor then immediately sells all generated income for DAI, and repeats steps 1 and 2 to compound their yield.
These three steps are what the exploited vault would automate. It allowed depositors to follow a compounding strategy without having to do the manual work of regularly carrying out the several transactions and paying the associated gas fees.
Now that we have an understanding of liquidity pools, AMMs and the yDAI vault, we now have all the necessary pieces to understand this exploit. The sequence of steps are as follows:
- Take out flash loans totalling 200,000+ ETH from Aave and dYdX.
- Use the ETH as collateral to borrow DAI and USDC from Compound.
- Add all of the borrowed USDC and some of the DAI to the Curve 3pool liquidity pool, receiving 3crv tokens. Relative to USDT, DAI and USDC now have extra supply. Anyone supplying DAI or USDC (or removing USDT) will be penalised and receive fewer 3crv tokens (they will experience slippage).
- To magnify the penalty, the exploiter redeems the 3crv tokens and withdraws USDT. Note that they experience slippage themselves when withdrawing USDT, though this is intentionally done to push the liquidity pool into a sweet spot (see the next step). The pool is now very heavy in USDC and DAI, and very short on USDT.
- When the exploiter deposits the saved DAI from Step 3 to the yDAI vault, the vault is forced to forward this to the 3pool to receive some 3crv tokens. The amount received is roughly 1% less than what would normally be received, giving 1% less ownership of the pool to the yDAI vault. This is the sweet spot referred to earlier, as the yDAI vault had a check in place to deny deposits that would result in slippage greater than 1%. What is important to appreciate here is that the vault is blind to this fact, as slippage is not taken into account when issuing yDAI-LP tokens to the exploiter. These are the LP token equivalent for the vault, allowing depositors to retrieve their funds. As the vault is blind to slippage when issuing yDAI-LP tokens, the entire vault suffers the cost of slippage, not just the depositor causing it.
- The exploiter now reverses some of the imbalance by returning the USDT from Step 4. Because the yDAI vault was forced to push the imbalance further in Step 5, more 3crv tokens are issued to the exploiter in this step (for returning USDT) than were redeemed in Step 4; remember that the greater the imbalance, the greater the reward for restoring it. This is the avenue through which the exploiter collects profits.
- The exploiter now withdraws from the vault using their redeemable yDAI shares. Since some of the imbalance was restored in Step 6, more 3crv tokens are burned from the vault than it received in Step 5, which leads to its drainage.
- The above steps are repeated for many cycles to slowly drain funds from the vault.
This summary has simplified some elements and omits token amounts for simplicity, but the idea remains. As reported in the disclosure by Yearn Finance, part of the reason why this exploit was profitable was due to temporarily disabling withdrawal fees from the vault to encourage migration to v2 vaults.
The exploit boils down to identifying a flaw in how deposits to the vault are accounted for, and how this can be manipulated for profit. We can see that identifying the exploit requires someone with substantial knowledge of smart contracts. However, with the availability of flash loans, executing the exploit does not require large capital (200,000+ ETH in this case). As we saw in the first part of the flash loans article, flash loans are simply a tool for enabling an efficient market in DeFi. In this case, flash loans also enable a larger group of people to carry out exploits. Whether the attacker returns the funds and acts altruistically (whitehats) or keeps the money (blackhats) is a separate issue, though both push DeFi through its teething period.
The final part of this series will propose an initial framework to quantify a platform's susceptibility to flash loan attacks.