The Cream Hacks

Composability within DeFi Lending (part I)

DefiDuck
6 min readNov 1, 2021

Introduction

Ola.finance is a platform for creating and managing isolated lending networks. Compound and Cream are examples of isolated lending networks, each one with its own configuration and composition of tokens. Isolation means that when Cream gets hacked Compound is not affected and vice versa. However, in a given lending network, it is enough to have a single problematic token listed (eg. yUSD or AMP) to cause all users, even those who had no direct interaction with the problematic token (ie. users who did not deposit or borrow it) to lose funds. Thus we say that a lending network is as strong as its weakest link.

While lending network creators under the Ola platform are free to list whichever tokens they’d like, Ola is responsible for implementing price oracles for these tokens. In order to make sure that we provide reliable price feeds, and list only tokens that are compatible with the design of our lending networks, we study the last two hacks in Cream and make our best effort to draw the necessary conclusions.

In these hacks, the attackers exploited specific mechanisms in AMP and yUSD in order to trick the system into letting them borrow higher value relative to what they had deposited. These hacks were a result of composability issues — these tokens shouldn’t have been vanilla-listed in Cream (or in any Compound-like lending network).

In this post we’ll analyze the yUSD hack and in the next post we’ll discuss the AMP hack.

yUSD hack

Main heist transaction

https://etherscan.io/tx/0x0fe2542079644e107cbf13690eb9c2c65963ccb79089ff96bfaf8dced2331c92

For a chronological rundown of the transaction’s actions and events see this great post.

Vaults

Before diving in, it is important to give some background on Yearn’s vaults. A vault is a smart contract that accepts deposits in a given underlying token (eg. TKN) and issues vault shares (eg. yTKN) against deposits. The vault invests its TKN in different strategies to generate more TKN (yield). To keep track of how much of the yield each depositor is owed, vaults implement a simple accounting mechanism, tracking the value of one share in terms of the underlying token. When a user deposits TKN, this value is used in order to calculate how many shares they should be credited for. Similarly, when a user redeems its shares, this value is used to calculate how many underlying tokens the vault should send him. Therefore, depositing TKN or redeeming yTKN doesn’t affect share price. However, when the vault generates yield it receives TKN without issuing more shares and so the share price increases. The share-to-underlying exchange rate is straightforward — it is the ratio between the total amount of shares in circulation and the total amount of TKN in possession of the vault and its strategies.

Curve Y Pool yVault is a typical vault

Before the attack

  • yUSD was listed in Cream with a collateral factor of 75%.
  • The total value of yCrv in the yUSD vault was around $11M.
  • The total amount of assets available for borrowing on Cream was over $100M.

Attack steps

Get yUSD. The attacker (0x96) flash-loans 500M DAI, turns them into yDAI, then to yCrv and finally to ~447,202,022 yUSD shares (at ~1.118 yCrv per share).

Get crYUSD. The attacker deposits ~446,756,774 yUSD into the Cream yUSD market and gets $500M worth of crYUSD in return. This lets the attacker borrow from other Cream markets. But not yet…

Get more crYUSD without getting more yUSD. The attacker now cleverly inflates his crYUSD holdings without actually depositing “new” yUSD tokens. He deposits (from another account, 0xf7) ~$2B ETH into the Cream ETH market (after flash-loaning ~524,102 WETH and transferring 6,000 ETH to 0x96 for other purposes), borrows the yUSD he had just deposited and re-deposits it. He then transfers the crYUSD to 0x96, doubling his holdings. He does this loop 1 more time. Current state of affairs:

  • 0x96 holds ~$1.5B in crYUSD and has a 500M DAI open flash-loan.
  • 0xf7 borrowed ~$1B in yUSD and has a ~$2B ETH open flash-loan which is used as collateral in the form of crETH.
  • It’s interesting to note that while there is only around $500M worth of yUSD in circulation, there is ~$1.5B worth of crYUSD. Soon there will be only ~$8M worth of yUSD in existence and ~$1.5B worth of crYUSD.

Redeem yUSD. The attacker now redeems as many yUSD shares as possible. First, he borrows, one more time, the $500M yUSD from Cream and redeems these shares for yCrv. To make his next move a bit more efficient he gets hold of another ~3M yUSD tokens by redeeming DefiDollar. (To do this, he swaps ~1,874 ETH to ~7.5M USDC, turns about half of it to 3Crv and swaps it to DUSD here). After redeeming all this yUSD, there’s about $8M worth of yCrv in the yUSD vault.

Transfer yCrv to the yUSD vault (event log 91). The attacker transfers just over 8.4M yCrv to the yUSD vault. Note that he funds these ~$8M from the flash-loan that he originally took and he’s not going to get these $8M back. By doing this the attacker doubles the value of each yUSD share (since the value of yCrv in the vault is worth around $8M as was established earlier). Indeed anyone that was in the vault doubled their investment. But the attacker doesn’t care about losing these $8M as he has $1.5B worth of crYUSD which are now going to be worth $3B. Let’s look at the attacker’s accounts again:

  • 0x96 holds the same crYUSD, but they are now worth ~$3B (at least in the eyes of the Cream’s Comptroller) and he still has a 500M DAI open flash-loan.
  • 0xf7 borrowed ~1.5B yUSD (which is now worth $3B) and has a ~$2B ETH open flash-loan which is used as collateral in the form of crETH. Note that under the current conditions, this account can be liquidated, but this will be irrelevant by the end of the transaction.

Drain Cream by borrowing everything (with 0x96):

  • ~523,208 ETH (~5,100 ETH more than he had just deposited). This is enough to repay his ETH flash-loan from Aave and leave about $10M profit in ETH.
  • ~12,266 Cream ETH 2. About $40M.
  • ~623,760 xSushi. About $8M.
  • ~135,402 WNXM. About $7M.
  • ~447,222 PERP. About $7M.
  • ~418,917 RUNE. About $6M.
  • ~15,567 DPI. About $5M.
  • ~​​156,629 UNI. About $4.5M.
  • ~4,324,457 USDC.
  • ~3,817,374 FEI.
  • ~3,780,808 USDT.
  • ~747 Curve stETH Pool yVault.
  • ~6,937 GNO.
  • ~38,922 FTT.
  • ~341,681 YGG.

To finish up, the attacker redeems the yCrv he got for the yUSD and redeems the yCrv back to DAI. He uses previously bought USDC to buy more DAI that he needs in order to repay his DAI flash-loan.

The attack cost ~$8M for the yUSD price distortion and another ~471 ETH for the ETH flash-loan. The DAI flash-loan is free from MakerDAO. The attacker’s net profit is over $100M.

Conclusion

This attack was quite sophisticated. The attacker first inflated their own crYUSD holdings by borrowing and re-depositing yUSD at a fair price. Then, they paid a relatively small amount in order to double yUSD’s price per share. The share, whose price actually doubled, to the benefit of all of its holders, doubled also in the eyes of Cream’s price Oracle. Now, the attacker who had a tremendous amount of yUSD deposited in the market could borrow much more than he had invested.

An easy mitigation could have been to use a price Oracle that did not take into account the exchange rate between yUSD shares and their underlying asset yCrv, eg. using a flat 1:1 rate. This would lower yUSD borrowing power, but would reduce any motivation to distort the yUSD price per share. As we can see, the price to distort such shares is relative to the amount of underlying assets that the share issuer (in this case the Yearn vault) holds.

Another mitigation and probably a good best practice in designing oracles for lending protocols is to make sure that per transaction, the price of a given asset is constant and cannot change. This would at least force the attacker to use its own capital and take some risk in conducting such an attack.

--

--