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: 19/47
Findings: 1
Award: $694.30
🌟 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
694.2987 USDC - $694.30
Honest MEV searchers can be tricked and may lose their money. This can lead to them going insolvent and not being able to claimFees again.
It is expected that V3FactoryOwner.claimFees() will be used by MEV searchers to use their funds to get more WETH. MEV bots often try to offer more money for transaction fees to get their transactions included in the next block before others. This situation creates a chance for dishonest MEV bots to make fake Uniswap pools, misleading honest MEV searchers into executing claimFees on those pools, only to find out they receive no rewards.
Here's how it happens:
Imagine two MEV searchers competing: A
, who is malicious, and B
.
A
sets up a simple fake Uniswap pool with just a collectProtocol
function, pretending to have the return values needed to pass the if (_amount0 < _amount0Requested || _amount1 < _amount1Requested) check.
Example implementation:contract FakePool is IUniswapPool { function collectProtocol( address recipient, uint128 amount0Requested, uint128 amount1Requested ) external returns (uint128 amount0, uint128 amount1) { amount0 = amount0Requested; amount1 = amount1Requested; } }
A
uses V3FactoryOwner.claimFees(), passing the fake pool, hoping to entice others to attempt to frontrun this transaction.B
sees A
's tempting transaction and tries to get ahead by submitting a similar transaction with a higher transaction fee.B
ends up without any reward.Manual Review
It is suggested to change the claimFees
function to accept tokenA
, tokenB
, and fee
as inputs, and to internally derive the pool address from the UniswapV3Factory.
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); PAYOUT_TOKEN.safeTransferFrom(msg.sender, address(REWARD_RECEIVER), payoutAmount); REWARD_RECEIVER.notifyRewardAmount(payoutAmount); (uint128 _amount0, uint128 _amount1) = _pool.collectProtocol(_recipient, _amount0Requested, _amount1Requested); // Ensure the caller gets no less than they asked for. See `collectProtocol` for context. if (_amount0 < _amount0Requested || _amount1 < _amount1Requested) { revert V3FactoryOwner__InsufficientFeesCollected(); } emit FeesClaimed(address(_pool), msg.sender, _recipient, _amount0, _amount1); return (_amount0, _amount1); }
MEV
#0 - c4-judge
2024-03-07T18:29:23Z
MarioPoneder marked the issue as duplicate of #380
#1 - c4-judge
2024-03-13T21:37:49Z
MarioPoneder changed the severity to QA (Quality Assurance)
#2 - c4-judge
2024-03-14T13:27:55Z
MarioPoneder marked the issue as grade-b