Platform: Code4rena
Start Date: 23/02/2024
Pot Size: $92,000 USDC
Total HM: 0
Participants: 47
Period: 10 days
Judge: 0xTheC0der
Id: 336
League: ETH
Rank: 4/47
Findings: 1
Award: $5,987.35
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: CodeWasp
Also found by: 0xdice91, 0xlemon, Aamir, Al-Qa-qa, AlexCzm, BAHOZ, Bauchibred, Breeje, DadeKuma, Fassi_Security, PetarTolev, Shield, SpicyMeatball, Trust, ZanyBonzy, cheatc0d3, gesha17, haxatron, imare, jesjupyter, kutugu, lsaudit, marchev, merlinboii, nnez, osmanozdemir1, peanuts, radev_sw, twicek, visualbits
5987.3522 USDC - $5,987.35
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/V3FactoryOwner.sol#L190
There will be two types of MEV users that will call the claimFees
function:
claimFees
on any of those pools when it is profitable. These users are not the target of this issue.claimFees
calls from other MEV searchers or bots. These are the main targets.Anyone can trick MEV bots into calling the claimFees
function with a fake pool, as the function doesn't check if it is a legit pool.
This can result in:
MEV bots can be tricked with the following steps:
MaliciousPool
, a IUniswapV3PoolOwnerActions
pool that he controls. It is a legit USDC/USDT
pool (it can be any credible token), but Bob controls this pool and he can deactivate it at will.claimFees
with MaliciousPool
as a target and provides a small amount of gas to this TX.Now, two scenarios may happen.
1st scenario: No one replicates his TX to get the MaliciousPool
fees. In this case:
3a. Bob frontruns his own TX, and he disables MaliciousPool
(e.g. he calls a MaliciousPool
function so that any subsequent calls to MaliciousPool.collectProtocol
fail) so that the TX on point 2 will revert. This is to avoid wasting the payoutAmount
that would be sent in case it succeeds. Bob will try again with a different token combination and/or a higher amount of fees.
or, 2nd scenario:
3b. Another MEV user, Alice, replicates Bob's profitable transaction (which is simulated locally, and Alice can see that fees are transferred to the _recipient
), and she frontruns the TX on point 2, changing the _recipient
to her address.
collectProtocol
is called. _amount0
and _amount1
will be correct to avoid reverting during the slippage protection check, but the pool won't transfer any fees.Supposing the MEV user doesn't have a slippage check themselves they will lose money, as only the return value of _pool.collectProtocol
is validated inside V3FactoryOwner
.
This slippage protection check is misleading as the asset balance is not checked afterward (i.e. _pool.collectProtocol
return value can differ from the actual amount that was trasferred):
(uint128 _amount0, uint128 _amount1) = _pool.collectProtocol(_recipient, _amount0Requested, _amount1Requested); // Protect the caller from receiving less than requested. See `collectProtocol` for context. if (_amount0 < _amount0Requested || _amount1 < _amount1Requested) { revert V3FactoryOwner__InsufficientFeesCollected(); }
payoutAmount
to the protocol.Manual review
Consider keeping track of authorized pools, and let bots choose to execute the claim function only when a pool is authorized, for example:
function claimFees( IUniswapV3PoolOwnerActions _pool, address _recipient, uint128 _amount0Requested, uint128 _amount1Requested, + bool onlyAuthorizedPool ) external returns (uint128, uint128) { + if (onlyAuthorizedPool && !authorizedPools[address(_pool)]) { + revert V3FactoryOwner__PoolNotAuthorized(); + } PAYOUT_TOKEN.safeTransferFrom(msg.sender, address(REWARD_RECEIVER), payoutAmount); REWARD_RECEIVER.notifyRewardAmount(payoutAmount); (uint128 _amount0, uint128 _amount1) = _pool.collectProtocol(_recipient, _amount0Requested, _amount1Requested); // Protect the caller from receiving less than requested. See `collectProtocol` for context. if (_amount0 < _amount0Requested || _amount1 < _amount1Requested) { revert V3FactoryOwner__InsufficientFeesCollected(); } emit FeesClaimed(address(_pool), msg.sender, _recipient, _amount0, _amount1); return (_amount0, _amount1); }
As alternative, consider checking the delta balance of each pool token before and after _pool.collectProtocol
is called, instead of checking its return value.
MEV
#0 - c4-judge
2024-03-07T18:29:07Z
MarioPoneder marked the issue as primary issue
#1 - MarioPoneder
2024-03-07T18:34:02Z
On the one hand this is user error when MEV searchers fall for this. But on the other hand the protocol has a responsibility protect its users (including good faith MEV searchers), especially when it can be mitigated so easily by the protocol.
Leaning more towards QA, but leaving as it is for now.
#2 - c4-sponsor
2024-03-12T15:41:36Z
apbendi (sponsor) disputed
#3 - apbendi
2024-03-12T15:46:17Z
The scenario described here is both unrealistic and outside the scope of our contracts to mitigate. To briefly summarize only a few of the reasons:
There are many more reasons why we find scenario to be unlikely, and why we believe mitigating it is outside of the scope of our contracts anyway, but this briefly summarizes the biggest reasons.
#4 - MarioPoneder
2024-03-13T21:37:43Z
The sponsor's points are solid.
Nevertheless, I'll refrain from invalidation and downgrade to QA due the proposed mitigation measures.
#5 - c4-judge
2024-03-13T21:37:51Z
MarioPoneder changed the severity to QA (Quality Assurance)
#6 - c4-judge
2024-03-14T13:26:51Z
MarioPoneder marked the issue as grade-a
#7 - DadeKuma
2024-03-14T20:39:13Z
First of all, thank you, everyone, for the in-depth replies. I still believe that this is a serious issue that should be mitigated by the sponsor, as it can seriously harm users. I will explain why this is the case.
The answer is simple: stakers have a high financial incentive to do so. That's because all the funds stolen from the MEV users will be redistributed as a reward to the stakers.
The higher their stake, the more likely a user would want to perform this attack, as they would receive a higher reward.
I will explain by citing the sponsor's reasons, one by one:
- It's highly unlikely a valid searcher call to claim fees would ever land in the mempool, because such a transaction would be immediately frontrun, and searchers know this. All valid claims are likely to go directly to block builders via MEV providers like Flashbots.
I will describe three possible scenarios:
So, in the meanwhile, it will be called by any user, as it's profitable to do so. 85% of all transactions use the public mempool, so it makes sense for already existing MEV bots to copy these transactions to snipe those profits.
All valid claims are likely to go directly to block builders via MEV providers like Flashbots.
This is an interesting game theory problem. Private transactions are slower than public ones. A MEV bot could use the previous statement to get an advantage in terms of speed if everyone uses private transactions. If they submit their TX into the public mempool, no one will frontrun them, as they would assume that it won't be a valid claim, and the bot will easily win the reward.
- Any MEV searcher can see what pools have been activate or not by observing onchain data.
Like I said and described in the Impact section, this issue targets a specific subset of MEV users: MEV bots that snipe profitable transactions. These users simply replicate and frontrun transactions; they are not aware of the context.
- Any MEV bot that tries to snipe profitable transactions out of the mempool is at risk from carefully crafted attacks like the one described—no amount of protections can be put in place to prevent all of these from happening.
MEV bots are indeed vulnerable to this attack. But I disagree with the latter part; there is a very simple mitigation that would ensure that this attack is impossible to carry out.
A duplicate of this issue suggests using IUniswapV3PoolOwnerActions.getPool
as a form of validation.
If we use a wrong combination of input, the function would return the zero address, as no valid pool exists. The mitigation would look like this:
function claimFees( - IUniswapV3PoolOwnerActions _pool, + address tokenA, + address tokenB, + uint24 fee, address _recipient, uint128 _amount0Requested, uint128 _amount1Requested ) external returns (uint128, uint128) { + IUniswapV3PoolOwnerActions _pool = FACTORY.getPool(tokenA, tokenB, fee); + if(_pool == address(0)) + revert V3FactoryOwner__InvalidPool(); ... }
- The attacker would have to set up the full Uniswap V3 infrastructure (a factory, a pool with liquidity, etc...) to have a chance to carry out this attack, and configure that infrastructure to point toward our contracts as the factory owner. All of this would require a large amount of capital at risk for a very low probability of a very small reward.
I disagree with this statement. The only requirement for the attacker is to have a contract that implements the collectProtocol
function; it's not necessary to deploy the entire Uniswap V3 infrastructure to trick MEV bots.
There are many more reasons why we find scenario to be unlikely, and why we believe mitigating it is outside of the scope of our contracts anyway, but this briefly summarizes the biggest reasons.
For these reasons, I believe this issue is in-scope for this contest.
Citing the Supreme Court verdict for the definition of user mistake:
Findings that require the user to be careless or enter the wrong information into a contract call are QA or Invalid. Non-privileged users are expected to preview their transactions to protect against input mistakes. Anyone using modern tooling will have previews built into their toolset.
The only way to protect against this behavior for the user is to deploy a smart contract and bundle this transaction with a slippage check executed on the balance itself. I don't think this is reasonable, and I will offer this counterexample to prove it:
Imagine a scenario where a project offers a swap without any slippage protection; users provide the correct data, but they can be frontrun nevertheless, which will result in a loss of funds.
Surely, they could deploy a smart contract and interact with the project's contract by adding their own slippage protection on top of the swap call, if the project does not provide it. But is it considered reasonable from a security standpoint? Personally, I don't think so. From a competitive contest perspective, these types of issues are usually considered valid, and not a user error.
For these reasons, given the high impact (total loss of funds for MEV bots) and low likelihood (the attack is difficult to craft and perform), I think Medium severity is the most appropriate for this issue.
#8 - Haxatron
2024-03-14T21:23:54Z
There are 2 types of MEV: i) Honest MEV that discovers pool that have high enough fees worth claiming ii) Dishonest MEV that copies the tx.
Why should we care about dishonest MEV? And its common for dishonest MEV to get tricked in other protocols anyway.
#9 - quentin-abei
2024-03-14T21:29:28Z
That's because all the funds stolen from the MEV users will be redistributed as a reward to the stakers.
By doing so attacker is also rewarding other stakers because rewarda will evenly get sistributed to all stakers. Nobody will do this unless he is "robin des bois".
#10 - DadeKuma
2024-03-15T02:29:39Z
Why should we care about dishonest MEV? And its common for dishonest MEV to get tricked in other protocols anyway.
What you are probably referring to is sandwich MEVs that extract value from users due to slippage, which is not the case here.
This is an auction between MEVs: the faster one wins the fees accrued; that's how MEV works.
A faster liquidation of rewards is actually highly beneficial for users. I honestly don't see how they would be considered malicious/dishonest.
By doing so attacker is also rewarding other stakers because rewarda will evenly get sistributed to all stakers. Nobody will do this unless he is "robin des bois".
Respectfully, but the protocol doesn't work in this way.
The reward potential for a staker depends on how much they stake. Rewards are not distributed uniformly among all users.
They would not care about other stakers, as long as it is financially profitable for themselves.
#11 - quentin-abei
2024-03-15T06:47:10Z
Like I said and described in the Impact section, this issue targets a specific subset of MEV users: MEV bots that snipe profitable transactions. These users simply replicate and frontrun transactions; they are not aware of the context.
Warden is suggesting to protect MEV frontrunners. Why would the protocol protect frontrunners? It's frontrunners responsibility to check the tx outcome themselves. And as such this issue is user own error
#12 - MarioPoneder
2024-03-17T12:36:55Z
Thank you for all the comments!
First of all, kudos to the Wardens, this an amazing finding regardless of how it is to be judged in the context of the current contest.
We can expect 2 types of MEV bots:
are not aware of the context
, didn't emend their calls into a contract which performs reasonable slippage/profit checks. In the present highly competitive MEV environment, they'd constantly become a victim to such tricks/attacks. Furthermore, I also consider this an external risk / user error and cannot reasonably defend Medium severity from a judging perspective although I highly appreciate this finding from a personal perspective.Thank you for your understanding!
#13 - DadeKuma
2024-03-18T01:17:35Z
@MarioPoneder Lack of context applies for generic MEVs; these wouldn't be users but external actors, so I see your point here.
But I think some Uniswap users may decide to monitor this specific contract, as it seems to be a viable strategy to become the race winner by frontrunning, rather than polling the price of every single pool to ensure a profit, which is much more complex.
I don't believe these users are being dishonest, and they can fall victim to what I've described here.
So hopefully, the sponsor decides to fix this issue regardless of the judging outcome.