UniStaker Infrastructure - osmanozdemir1's results

Staking infrastructure to empower Uniswap Governance.

General Information

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

Uniswap Foundation

Findings Distribution

Researcher Performance

Rank: 9/47

Findings: 1

Award: $5,987.35

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

5987.3522 USDC - $5,987.35

Labels

bug
grade-a
primary issue
QA (Quality Assurance)
satisfactory
Q-30

External Links

Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/V3FactoryOwner.sol#L193

Vulnerability details

This protocol is built to reward UNI token stakers by allowing them to claim some portion of the protocol fees in UNI pools. But there are so many pools and so many different fee tokens and distributing them separately would be a pain. That's why this protocol creates a mechanism and brings MEV searchers to the game.

Basically, MEV searchers race each other, buy the accumulated protocol fees from UNI pools by paying fixed amount of WETH, and get profit if the value of accumulated fees are greater than the paid WETH. This way MEV searcher gain profit, and Uniswap distributes rewards in WETH only.

Logically,

  • We can expect these MEV searchers to buy accumulated fees as soon as they are profitable.

  • We can also expect these bots to collect all fees in a pool to gain maximum value.

However, collecting all fees in a pool will always revert because of an integration issue between V3FactoryOwner.sol and Uniswap pools.

First, let's check the V3FactoryOwner::claimFees

  function claimFees(
    IUniswapV3PoolOwnerActions _pool,
    address _recipient,
    uint128 _amount0Requested,
    uint128 _amount1Requested
  ) external returns (uint128, uint128) {
    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) { //@audit it expects amounts to be equal at least
      revert V3FactoryOwner__InsufficientFeesCollected();
    }
    emit FeesClaimed(address(_pool), msg.sender, _recipient, _amount0, _amount1);
    return (_amount0, _amount1);
  }

This function collects protocol fees from the Uniswap pool and checks the collected amounts. It reverts if the collected amount is less than requested.

Now let's check the Uniswap pool.
https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L848C1-L868C6

    function collectProtocol(
        address recipient,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) {
        amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested;
        amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested;

        if (amount0 > 0) {
-->         if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings
            protocolFees.token0 -= amount0;
            TransferHelper.safeTransfer(token0, recipient, amount0);
        }
        if (amount1 > 0) {
-->         if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings
            protocolFees.token1 -= amount1;
            TransferHelper.safeTransfer(token1, recipient, amount1);
        }

        emit CollectProtocol(msg.sender, recipient, amount0, amount1);
    } 

As we can see here, the Uniswap pool decrements 1 wei from the requested amount to save gas if user requests all fees. However, as I mentioned above, the V3FactoryOwner does not allow even 1 wei difference. claimFees function will revert in that case.

The fastest MEV bot will try to collect all fees to gain profit, it will revert. In the meantime, another bot will collect fees before the first bot tries again.

I acknowledge that MEV searchers can try again with different values but the race will be lost already while trying again. Or one can argue that the bots should have requested all fees - 1 in the first place. However, the whole logic behind this reward mechanism is creating a race between MEV searchers, and I think this discrepancy between two parts of the Uniswap creates an unfair race.

Impact

MEV searchers that try to collect all fees in an Uniswap pool will lose their race.

Proof of Concept

Detailed explanation is provided above.

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/V3FactoryOwner.sol#L193

https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L857

Tools Used

Manual review

Since the Uniswap itself subtracts 1 wei from maximum claimable amount during fee collection, V3FactoryOwner should allow 1 wei difference and not revert for a fair race and for these two parts of the Uniswap to be perfectly matched.

-    if (_amount0 < _amount0Requested || _amount1 < _amount1Requested) {
+    if (_amount0 < _amount0Requested - 1 || _amount1 < _amount1Requested - 1) {

Assessed type

Invalid Validation

#0 - c4-judge

2024-03-07T13:40:02Z

MarioPoneder marked the issue as duplicate of #34

#1 - c4-judge

2024-03-14T01:39:08Z

MarioPoneder marked the issue as satisfactory

#2 - c4-judge

2024-03-17T14:34:28Z

MarioPoneder marked the issue as selected for report

#3 - MarioPoneder

2024-03-17T14:35:46Z

Selected for report due to discussion of competitive impact.
See #34 for preceding discussion.

#4 - dmvt

2024-03-26T19:14:21Z

Summary of the issue

When calling claimFees, using exactly the claimable amount leads to a revert because of a 1 wei difference.

Picodes view

My interpretation of C4 rules is that the question here is to know whether this is closer to Medium severity under "function of the protocol or its availability could be impacted" or of Low severity under "function incorrect as to spec, issues with comments". I see some comments about the likelihood of the issue but it seems irrelevant under C4 rules.

  • The fact that in its test base, the sponsor is correctly subtracting 1 wei shows that they were aware of this and at most failed to document it properly and points toward "issues with comments"
  • Some wardens and the judge are arguing that the default way of using this function is to pass the maximum claimable amount in _amount0Requested . I don't think that's at all how MEV works and to me searchers should use this argument to pass the minimum amount under which they break even. Unless there is no competition between searchers I see no real reason to use the exact claimable amount.
  • I don't think the DoS argument is convincing as the protocol is not affected by this, and as the maximal impact is that MEV searchers which are all advanced players will send invalid transactions. They should simulate and figure this out before sending or use private RPCs.

So for all these reasons to me this should be Low under "function incorrect as to spec, issues with comments"

gzeon's view

I agree with Picodes and believe issue 45 should be QA. Claimable amount is always 1 wei less, this is simply a documentation error and can be trivially detected by a RPC simulation, in which all MEV bot should do locally and even conditional their tx with a bundle. I don't see any DOS risk here but only user (MEV bot) error.

LSDan's view

I agree that this should be QA for all of the reasons mentioned above. At core is a lack of documentation. Even if we assume that the documentation is never added this would fall under QA inside of the SC ruling here: https://docs.code4rena.com/awarding/judging-criteria/supreme-court-decisions-fall-2023#verdict-severity-standardization-conditional-on-user-mistake

Result Issue #45 will be marked as valid, but low risk / QA.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter