Part 1: DeFi Madness ft Curve, iEarn & Stable-Coin Whales
A special edition of DeFi Weekly covering a highly sophisticated DeFi exploit attempt.
This is a special edition of DeFi Weekly and going to be relatively lengthy as well. It's an important read that I think everyone should understand as once again it has larger implications for how we should think about deployed DeFi protocols and how little we truly understand about this beast we've created.
I've had to split this up into two parts since the entire post is too long for Substack!
Strange On-Chain Activity
Feb 29, I'm out diving, enjoying the Australian summer, when I get tagged in this tweet by Brendan:
After checking out the initial transaction, it did appear on the surface that someone turned $89,752 USDC into $465,242 BUSD. Word got out on Twitter that someone drained the iEarn/Curve finance contract for a few millions. I was ready to settle at that explanation until I decided to dig a bit more and realise that there's more than what appears on the surface with many interaction transactions in a short period of time from multiple parties.
Andre (founder of IEarn), was accused of losing user funds and Crypto Twitter proceeds to go full blast on ensuring that he's held accountable for losing people's money. In order to provide more clarity, Andre releases a short post mortem outlining what happened in more detail - https://gist.github.com/andrecronje/decacace45d41dd0c6de11ea1d6d6b57.
Key details from the report are summarised below:
Someone turns $450k+ USD into $25k USD via slippage on Curve Finance (user #1)
He gets a whale friend to help recoup the losses by executing two large trades to reverse the slippage and help make the lost user whole (user #3)
Someone made a small profit between both of these events (user #2)
As a result, user #1 only lost $146.00 and no further funds were claimed to be lost.
However, given the events that unfolded with bZx claiming funds "were SAFU" and the flurry of transactions made, many people still didn't understand the technicalities of the transactions and were skeptical that funds weren't lost.
A few hours later Andre released a spreadsheet with about 40 rows of data including transaction hashes (https://docs.google.com/spreadsheets/d/1CQYJI0qZfESinv2xGdhd-LWJto5fMdgy7S7DoBPewz0/edit#gid=0). I spent close to 1 hour trying to understand the spreadsheet but reached a dead end. Hoping to deepen my own understanding, I shot Andre a message and was on a call with him in less than a hour.
Before I delve into the next part it's important that we're all aware of the following protocols and how they work at a high level:
iEarn is a yield aggregator that determines the best place to put your stable coins to work to earn the most interest. It creates a new "y-token" out of your position eg. yUSDC, yDAI, yUSDT etc. This new token is priced higher than $1 as it's actually accrued interest.
Curve Finance is an automated market maker protocol (an exchange that doesn't have an order-book and uses math to automatically determine the price) for just stable coins. People who provide stable coins to Curve earn trading fees and interest on the liquidity they've provided.
Zaps are a way to automatically to multiple actions in one transaction. DeFiZap is the main one by Nodar, it's important to note that these Zaps are not made by DeFiZap/Nodar and instead a fork created by Andre.
Upgrade Time
Initially, iEarn and Curve supported the following currencies: yUSDC, yDAI, yUSDT, yTUSD. After a few weeks of being live in prod, TUSD made up over 25% of the lending pool in Curve and was dragging down LP rewards as it only gave a yield of 1-2% and very little trading fees activity.
The obvious thing to do here is eliminate TUSD and replace it with something more high yield, BUSD (Binance USD) was the appropriate choice as Binance aims to offer 15% on their CEX platform so rates on-chain should theoretically match that. Hence Curve updated their protocol to support BUSD. iEarn deployed the yBUSD code and a "zap" that would swap liquidity from Curve v3 to v4. To ensure the Curve pool had enough liquidity to begin with it was seeded with the following currencies and quantities:
56,526 yDAI
136,583 yUSDC
214,618 yUSDT
95,961 yBUSD
Now this is where the story gets interesting, when Andre and Michael deployed the new contracts, they didn't notify anyone that they had deployed new contracts or the new UI’s url. What follows is the activity of certain users who were interacting with these new, unreleased contracts on-chain at their own discretion.
For transparency's sake I'll be attaching all the contract addresses, account addresses and transaction hashes as I go along:
CurveV3: https://etherscan.io/address/0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51
CurveV4: https://etherscan.io/address/0x79a8c46dea5ada233abaffd40f3a0a2b1e5a4f27
ZapSwap: https://etherscan.io/address/0x101dfbd91120aae48fc6e8d007cfec5ac4953dbd
Charlie: https://etherscan.io/address/0x44e59f7c598644a68975ef41fed052362c4c8ed3
Bob: https://etherscan.io/address/0x8183866223445441b6fb9206b9f0b583410977e6
Friendly Whale: https://etherscan.io/address/0x431e81e5dfb5a24541b5ff8762bdef3f32f96354
Let The Madness Begin
Now that we've got the necessary context on what's happened, it's time to start digging into the numbers and Etherscan transactions. You'll want to pay attention. All my numbers can be viewed here directly:
https://docs.google.com/spreadsheets/d/1DW-JF4EtF5iLKYogo_be4kKmcc66aa864lHvEEXCxZY/edit?usp=sharing
Tx 1:
0x14923a9f5688c79f9cd5ff93ee51168961617563cc07a26ba15f2ce44f338fd0 is from Curve and added $503k worth of stable coins into the pool. Curve shares = the total number of LP shares this address has for depositing liquidity. Shares can be redeemed for the underlying collateral held. Nothing too crazy going on here. The actual amounts here are slightly different since 0x44e added some more liquidity but for simplicity’s sake I’ve just stated the starting liquidity in total is
New total:
Tx 2:
Introducing our whale Charlie (0x44e), who zap swaps 146,000 shares by moving liquidity from Curve v3 to Curve 4 at: 0xdf292a8fcee39f897a525cf473187ce2f45f859f6ebe0a85fd84084fe38232e4.
This whale is the kind of person who has millions to play around with DeFi protocols. He's also interacting with contracts which aren't announced. We'll see why this becomes suspicious as we move on. Anyways, we've now got Charlie who has 146,165 curve shares.
New total:
Tx 3:
This is where things start getting a bit weird. Charlie's just deposited $146k, but then decides he wants to remove all of it straight away (https://etherscan.io/tx/0x67721c4c6dff94e41f4cb4bbf24ce384cc464bf7c61d7eacb5e2ceacfaf3cb96).
He's effectively redeemed all of his curve shares for stable coins.
New total:
Note: he's already lost a small bit of money as the pool has more money compared to before he left.
Tx 4:
If you're thinking "where is this going?" you'll be even more surprised. Charlie's next move is to re-add all the liquidity he just removed! Why would someone do that? We don't know but it's pretty clear that a succession of odd transactions seem to be forming.
https://etherscan.io/tx/0x1b1b1043e37a21604acc519452bc00d8a23b295647f475c18299340ff9fb3b6
New total:
Tx 5:
Charlie's next move is to withdraw 16,104 USDC from the pool. Notice the similarity between his number of shares (146104) and the amount — he's missing a digit. Fat finger fail rofl. https://etherscan.io/tx/0x2dbd7bee05fe901b51b9f09446e78558abdb1f084ad9be60b66809c4e4f69778
New total:
Tx 6:
To finish the task he set out to do, he withdraws the remaining amount of USDC from the pool (131,494).
New total:
Tx 7-12:
He basically does similar transactions as above, deposit large sums of BUSD and then drain the USDC pool multiple times. Why? Well he's probably realised that if he can drain the USDC then the exchange rate between USDC to BUSD will be highly favourable allowing him to profit $246,747. Similarly, he could drain the DAI pool and exchange it for USDT and profit another $44,692. This behaviour is clearly very deliberate as his deposits double in size every 2 transactions (143k, 258k, 445k deposited). Only thing is he loses over 75% of each deposit due to slippage but thinks it's a good idea to continue anyways in hope of profits.
The main question here is how do you lose all of your Curve shares even though you only received less than 1/3 of the underlying collateral? It's with the way the Zap was coded and Curve's redemption mechanism. Let's look at the code.
For those of you that don't code, I've got a picture that shows that this piece of code is doing:
So essentially it does a recursive call to keep swapping everything else into yUSDC and then redeem it for USDC. However the key here is how curve does it's calculations. I won't use exact numbers as they cause confusion but instead give a conceptual overview.
Since the total amount of USDC left is very low, each sub swap from BUSD, USDT, DAI results in majority of the USDC pool being eaten up which Curve in turn gives a very low rate (they're trying to get $150k+ of USDC with only $10k USDC being available). This was most likely deliberate.
New total:
That’s it for part 1. Part 2 should be in your inbox or directly available at defiweekly.substack.com