Exotic culinary: Hypernative systems caught a unique Sandwich Attack against Curve Finance
By Vazi
At Hypernative our mission is to distinguish and classify in a highly accurate manner, attacks and in most cases alert before they are happening.
As part of our ongoing on-chain security monitoring, our systems classified one very interesting exploit transaction which later upon further research we discovered as a Sandwich Attack on Curve Finance.
This technical blog post will revisit the discovery and will explain the fundamentals that lead to it.
Fundamentals
Before we dive into the technicals of the attack, it’s important to understand the mechanism and the environment conditions which allowed it to happen, so let’s begin by covering the Curve protocol fees.
Curve Fees
In the Curve protocol, trading fees are distributed 50/50 between LP providers and CRV stakers (aka veCRV holders). The CRV stakers rewards fees are also called “Admin Fees”, These fees are collected by a ‘Burner Contract,’ which we’ll discuss in more detail shortly. The collected fees are then utilized to purchase 3CRV tokens, which are subsequently distributed to veCRV holders.
For those who are not familiar, 3CRV is the LP token of the Curve TriPool (Curve DAI/USDC/USDT pool) — the pool that was harmed in the incident we are about to analyze.
Burner Contract
One question stands from the previous paragraph, what’s that “Burner Contract”, while sounds pretty scary the main role of this contract is to collect trading fees and distribute them to CRV stakers,
That process includes 3 separate transactions, let’s explain each one of them.
Step 1:
As previously mentioned, Curve Exchange contracts do introduce a fee on almost every action involving the Exchange Contract (an example of such a contract is the TriPool) these fees are typically 0.04% on Swaps and between 0% and 0.02% on liquidity providing/removing liquidity.
As the official documentation suggests:
The Curve PoolOwner contract which is a Curve DAO controller contract is the owner of the TriPool (and many others) which periodically calls a function that triggers fees withdrawal from the pools.
At that point, withdrawn funds are sitting at the PoolOwner
contract.
Step 2:
The next station of those withdrawn funds is the Burner contract, in Curve, every asset has a different Burner contract and that data is stored at a Hashmap on the PoolOwner
contract (the Vyper equivalent to solidity mapping)
Let’s check the burner address of DAI for example:
Which means that every time the burn()
function of the PoolOwner
contract will be called with DAI as a parameter, the configured Burner contract of DAI will be called with the function burn()
And here’s the PoolOwner
contract burn()
function:
As the code hints, the burn()
function is implementation dependent, but for 3Pool assets, it simply transfers to the corresponding Burner contract (using transferFrom)
The burn() function as implemented by the UnderlyingBurner
:
In the following TX you can see a simple burn transaction of DAI:
Step 3:
Now that the funds are at the Burner contract the only thing left is to distribute them to veCRV holders. This is done by calling the execute()
function of the UnderlyingBurner
contract, which will move all the deposited tokens in it (stables) to provide liquidity to the 3Pool and then move the 3CRV (LP token of the TriPool) to the fee distributor contract for the veCRV holders to collect.
Attack Flow
What can go wrong? Those of you with sharp eyes may have noticed two things that together can be an issue:
- anyone can call the
execute()
function - The infamous
UnderlyingBurner
is willing to provide liquidity AT ALL COSTS! By supplying a minimum amount of 0 toadd_liquidity()
function which basically means that this function is slippage tolerant
A very common form of Sandwich attacks is by wrapping a victim transaction with two other transactions (buy low — sell high)
We will show how a sophisticated attacker managed to Sandwich Curve within the setup we just covered, and introduced the Curved Sandwich.
Step by step:
That’s the exploiting transaction, which we’ll analyze step by step
- The attacker gets an enormous flash loan from several funding sources
2. Attacker providers $155,000,000 USDT liquidity to 3pool and gets 3CRV LP tokens:
3. Attacker removes (almost all!) DAI and USDC Liquidity from the pool and burns the 3CRV LP tokens:
Essentially makes the pool almost entirely USDT, which temporarily turns it way cheaper than the DAI and USDC.
4. Calls the UnderlyingBurner
contract execute()
function:
Interestingly, notice the UnderlyingBurner
held mainly USDT, thus by calling it the pool becomes even more imbalanced and increases the relative value of DAI and USDC value that the Attacker gained In step 3
5. The attacker then proceeds to add the DAI and USDC that he still holds to the liquidity and enjoys the premium, which is translated to getting a higher amount of 3CRV LP tokens:
6. Attacker closes the loop with a USDT profit, by burning his 3CRV LP tokens and withdrawing USDT liquidity:
7. Attacker pays the Flashloan back and retains a profit of $36.8K USDT:
And here’s how the attack was captured by our systems:
We can see the attacker making several attempts — deploying contracts, failing on execution, redeploying, and then succeeding.
Disclosure & Mitigation
On 02/08/23 we analyzed the root cause and reached the Curve team to disclose the finding, which said they are working on a fix, a recent Curve DAO proposal suggests that the UnderlyingBurner
will be switched to a new Burner, which will enforce a maximum slippage of 0.5%
And here is the burn()
function of the new burner contract — notice the min_amount
parameter:
Now when querying the burners
mapping of the PoolOwner
contract with DAI token address as input, we’ll get the new Burner address, notice the different result we get when specifying the attack block (17823543)
and here’s the new Stables Burner contract which was deployed 12 days ago as a result of the DAO proposal.
Insights and recommendations
- This case shows that on-chain monitoring is a crucial component of blockchain protocols and contracts security. While an audit is important to detect potential issues prior to deployment it does not provide full coverage against every attack vector. With Defi protocols growing in their complexity, it becomes difficult to predict potential attack vectors, making continuous monitoring a must.
- On-chain monitoring allows the detection of recurring attack patterns, so even if an attack steals a small portion of the protocol funds, on-chain monitoring can detect it in real time, and help the protocol devs fix it before it happens again.
If you’re interested in additional details about this case or to learn more about the Hypernative platform, please contact us at contact@hypernative.io
Originally published at https://medium.com on August 14, 2023.