Vulnerability disclosure 2021-02-04
- An exploit against Yearn's v1 yDAI vault has led to 11m DAI of vault deposits being lost.
- Acting in roughly 11 minutes, Yearn's security team and multi-sig wallet signers were able to stop the exploit while it was underway, saving 24m DAI out of the vault's total 35m DAI deposits.
- By creating exchange rate imbalances in Curve's 3pool, an exploiter was able to cause Yearn's yDAI vault to deposit and withdraw funds from 3pool at unfavorable rates across a series of transactions.
- The exploiter profited from the loss by holding a good portion of the Curve 3pool during the attack, and withdrawing to a combination of USDT, DAI, and ETH. It is estimated to have resulted in a 2.7m DAI profit.
At 21:45 (UTC), Andre Cronje notices the complex transaction pattern of a contract that is interacting with Yearn vaults. Yearn's security team is called into action, and what eventually is determined to be an active exploit on Yearn's v1 yDAI vault, is mitigated 11 minutes later.
The fast response leads to 24m DAI out of the vault's total 35m DAI AUM being saved, with 11m DAI being lost as part of the exploit. The profit of the exploiter is estimated to be 2.7m DAI. 
Details of exploit
At a high level, the exploiter was able to profit through the following steps:
- Debalance the exchange rate between stablecoins in Curve's 3CRV pool.
- Make the yDAI vault deposit into the pool at an unfavorable exchange rate.
- Reverse the imbalance caused in step 1.
This pattern was repeated in a series of 11 transactions executed over 38 minutes before being mitigated.
Step by Step
Using one of the transactions as an example.
- Mint 3crv shares by depositing 134m USDC and 36m DAI to Curve 3pool.
- Withdraw 165m USDT from Curve 3pool. The pool is now at an imbalance, having significantly less USDT in proportion to USDC and DAI.
- Repeat the following step several times, for increasingly smaller amounts:
- Deposit DAI into yDAI vault. This causes the vault to deposit DAI into the imbalanced 3pool, at an unfavorable exchange rate.
- Deposit 165m USDT into the Curve 3pool again, partially restoring the imbalance in the pool.
- Withdraw DAI from yDAI vault. The 3pool returns only 92.3m DAI, 0.7m DAI of the yVault's funds remain in the 3pool.
- Withdraw 165m USDT from the 3pool to cause the imbalance again.
- In the final repetition, instead of withdrawing USDT, redeem the initial 3crv shares and withdraw 134m USDC and 39.4m DAI, i.e. 2.9m DAI more than what was deposited originally.
- The vault's slippage protection was set too loose at 1%.
- The normal 0.5% withdrawal fee was set to 0%, to encourage migrations to v2 vaults without incurring costs.
- This being a v1 vault, the exploiter was able to call
earn()and push deposits into the vault's strategy at will.
The combination of these factors led to the attack being profitable enough to perform.
- Simulation of attack, by Wen-Ding Li
- Step by step by Igor Igamberdiev
- Step by step by Bartek Kiepuszewski
- tx2uml output by Nick Addison
Details of mitigation
setMin(0) to the yDAI vault, deposits into the strategy were effectively disabled, preventing further exploits from taking place.
Timeline of events
Feb 04, 2021, 21:45 (UTC): Security team becomes aware of an odd transaction mined roughly 30 minutes earlier.
21:48: Initial review suggests an issue with the DAI v1 vault. Additional transactions are detected, the exploit is underway. Decision to
setMin(0) on DAI vault, and to do the same on USDC, USDT, and TUSD as a precautionary measure.
setMin(0) successfully set on DAI vault.
setMin(0) successfully set on USDC, USDT, and TUSD vaults.
22:09: An initial public announcement of the issue is made via Twitter.
Feb 05, 2021: Disclosure
Third party disclosure
As per Yearn's security process document, the project does not currently have any established bilateral disclosure agreements. In line with our security policy, no disclosure has therefore been made to third parties prior to this publication.
- https://gist.github.com/xu3kev/cb1992269c429647d24b6759aff6261c (run with ganache forked at block 11792183)