xanoutas/solidity-security-findings huggingface dataset:
`rewardPerTokenAccumulated` can be inflated drastically when total staked decreases, making users not getting their rewards
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L229-L234


# Vulnerability details

## Impact
When most of the users decide to remove their staked assets from the UniswapStake contract, this will inflate the `rewardPerTokenAccumulated` and make the remaining users couldn't claim their rewards when the balance is not sufficient.

## Proof of Concept
In the UniStaker contract, the function `notifyRewardAmount()` updates the rewardRate for the token.
In other words, the scaledRewardRate is calculated based on the newly added amount of tokens (amount), the remaining amount of existing rewards, and the `REWARD_DURATION`.

```Solidity
    if (block.timestamp >= rewardEndTime) {
      scaledRewardRate = (_amount * SCALE_FACTOR) / REWARD_DURATION;
    } else {
      uint256 _remainingReward = scaledRewardRate * (rewardEndTime - block.timestamp);
      scaledRewardRate = (_remainingReward + _amount * SCALE_FACTOR) / REWARD_DURATION;
    }
```

The `REWARD_DURATION` is a fixed period i.e. 30 days. Now let's look at the reward per token calculations.
`rewardPerTokenAccumulated()` is calculated based on the current time, lastCheckpointTime, scaledRewardRate, and totalStaked.

```Solidity
  /// @notice Live value of the global reward per token accumulator. It is the sum of the last
  /// checkpoint value with the live calculation of the value that has accumulated in the interim.
  /// This number should monotonically increase over time as more rewards are distributed.
  /// @return Live value of the global reward per token accumulator.
  function rewardPerTokenAccumulated() public view returns (uint256) {
    if (totalStaked == 0) return rewardPerTokenAccumulatedCheckpoint;

    return rewardPerTokenAccumulatedCheckpoint
      + (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalStaked;
  }
```

The `totalStaked` parameter shows the currently staked amount across all deposits. 

Consider a situation where most of the users have withdrawn their assets and `totalStaked` has decreased significantly.
This makes the `rewardPerTokenAccumulated()` mistakenly show a huge number. At this point, users call the `claimReward()` function. If the balance of the UniStake contract becomes insufficient, then this call gets reverted.

Thus, this makes the users who claim the rewards later unable to retrieve their rewards as the balance of the contract can be insufficient.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider calculating the `rewardPerTokenAccumulated()` with the help of the current balance or put a cap based on the current balance


## Assessed type

DoS
The Uniswap V3 Factory Owner Contract lacks control over the global protocol fee.
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The lack of an effective mechanism to regulate global protocol fees in Uniswap V3 could lead to inefficiencies in fee management across multiple pools. This might hinder the community's ability to promptly and collectively adjust protocol fees, potentially affecting the overall efficiency and competitiveness of the Uniswap ecosystem.

## Proof of Concept

The `V3FactoryOwner` contract in Uniswap V3 oversees protocol fee management and administers functions for the Uniswap V3 factory and its pools. While the setFeeProtocol function allows the admin to adjust protocol fees for individual pools, this approach becomes cumbersome when aiming for a global change across all pools. This inefficiency poses a challenge when attempting to swiftly modify fees for the entire Uniswap V3 ecosystem.

```solidity
function setFeeProtocol(
IUniswapV3PoolOwnerActions _pool,
uint8 _feeProtocol0,
uint8 _feeProtocol1
) external {
_revertIfNotAdmin();
_pool.setFeeProtocol(_feeProtocol0, _feeProtocol1);
}
```



## Tools Used
Manual review 

## Recommended Mitigation Steps
To streamline protocol fee management across Uniswap V3 pools, we propose the following changes:

Introduce a Global Fee Control Mechanism: The V3FactoryOwner contract will be modified to include a global fee control mechanism. This allows the admin to directly set or adjust the protocol fee within the factory contract, eliminating the need for individual adjustments in each pool.

Maintain Individual Pool Fee Control (Optional): While the global fee provides a centralized approach, we can also retain the setFeeProtocol function with modifications. This allows individual pools to opt-in to the global fee or define their own fee relative to the global setting, offering flexibility at the pool level.

In essence, this approach combines the efficiency of a global fee with the option for individual pool customization.



## Assessed type

Context
DOS possible in Unistaker rewardPerTokenAccumulated due to overflow from minimal totalstaked
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L229


# Vulnerability details

## Impact
The main impact is that all function that rely on rewardPerTokenAccumulated() could be permanently out of service. The attack is highly costly, and possibly not economically feasible. However the design of the protocol could allow such an attack to occur. Leaving the Unistaker contract in a state of denied service.

## Proof of Concept

```
pragma solidity ^0.8.17;

import "forge-std/Test.sol";

import {UniStaker} from "src/UniStaker.sol";
import {ERC20VotesMock} from "test/mocks/MockERC20Votes.sol";
import {ERC20Fake} from "test/fakes/ERC20Fake.sol";


contract DosTest is Test {

  UniStaker newUni;
  ERC20VotesMock newVote;
  ERC20Fake newToken;
  address adminDao = makeAddr("adminDao");
  address fakeDelegate = makeAddr("fakeDelegate");
  address notifierMock = makeAddr("NotifierMock");

  function setUp() public {
        newToken = new ERC20Fake();
        newVote = new ERC20VotesMock();
        newUni = new UniStaker(newToken,newVote,adminDao);
  }

  function test_dos() public {
        //Set up mock staker
        vm.prank(adminDao);
        newVote.mint(adminDao,1000000);
        vm.prank(adminDao);
        newVote.approve(address(newUni),1000000);

        //Set up notifier
        vm.prank(adminDao);
        newUni.setRewardNotifier(notifierMock,true);
        newToken.mint(notifierMock, 1e18);
        vm.prank(notifierMock);
        newToken.transfer(address(newUni),1e18);
        vm.prank(notifierMock);
        newUni.notifyRewardAmount(1e18);

       //skip(100000);

        //Staker stakes in intervals
        //As can be seen in console logs
        //Reward Per Token continues to increase linearly
        //Until the reward duration has passed
        for (uint i = 0; i < 2; i++) {
                vm.prank(adminDao);
                //minimize totalstaked in order to maximize dos possibility
                newUni.stake(1,fakeDelegate);
                skip(1000);
                uint256 rewardPerToken = newUni.rewardPerTokenAccumulated();
                console.log(rewardPerToken);
        }

        
        //the notifier restakes
        newToken.mint(notifierMock, 1e18);
        vm.prank(notifierMock);
        newToken.transfer(address(newUni),1e18);
        vm.prank(notifierMock);
        newUni.notifyRewardAmount(1e18);
        skip(100000);

        //the user calls stake with 0 amount
        uint256 count = 0;
        for (uint i = 0; i < 10000; i++) {
                vm.prank(adminDao);
                newUni.stake(0,fakeDelegate);
                skip(100);
                uint256 rewardPerToken = newUni.rewardPerTokenAccumulated();
                console.log(rewardPerToken);
                ++count;
                if (count == 1000) {
                        newToken.mint(notifierMock, 1e18);
                        vm.prank(notifierMock
V3FactoryOwner.claimFees() doesn't handle fee-on-transfer tokens
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/V3FactoryOwner.sol#L189-L195


# Vulnerability details

## Impact
The `V3FactoryOwner.claimFees()` function allows MEV searchers to claim Uniswap V3 pool fees in exchange for a specified amount of `PAYOUT_TOKEN`. However, the `V3FactoryOwner.claimFees()` function does not account for tokens with a fee-on-transfer, meaning the check in `V3FactoryOwner.claimFees()` which protects the caller from receiving less than they requested is faulty and users can still get less than they requested.

## Proof of Concept
This is the `V3FactoryOwner.claimFees()` function:
```solidity
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) {
    revert V3FactoryOwner__InsufficientFeesCollected();
  }
  emit FeesClaimed(address(_pool), msg.sender, _recipient, _amount0, _amount1);
  return (_amount0, _amount1);
}
```
and this is the `UniswapV3PoolFactory.collectProtocol()` function:
```solidity
/// @inheritdoc IUniswapV3PoolOwnerActions
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);
}
```

Let's say there is a pool with ETH and a token with a fee on transfer of 2% (an example implementation of a fee-on-transfer token is here: https://gist.github.com/MiloTruck/6fe0a13c4d08689b8be8a55b9b14e7e1).

Let's say the `payoutAmount` is \$2000 of the `PAYOUT_TOKEN` and fees have accrued that are worth more than the `payoutAmount`: \$1001 in each token of the pool (so \$2002 in total).

A searcher comes along and decides to claim fees by paying the `payoutAmount` in the `PAYOUT_TOKE
Rounding not favoring the protocol
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L241-L247


# Vulnerability details

## Impact
There is a minor rounding error that does not favor the protocol, potentially leading to a loss of up to $10^{-10}$ per block. If the payout amount is substantial, it may incentivize attackers.

This issue can also be accounted as a Low one, but per the audit scope, rounding not favoring the protocol is considered as Medium.

## Proof of Concept
Specifically, the reward per user is calculated as the difference between two `rewardPerTokenAccumulated` values at different checkpoints, namely `rewardPerTokenAccumulatedCheckpoint_A - rewardPerTokenAccumulatedCheckpoint_B`.

This means that any precision loss in `rewardPerTokenAccumulatedCheckpoint_B` (i.e., the `rewardPerTokenAccumulated` at an earlier block) allows users to extract more rewards than expected.

Given the current precision of $10^{36}$ and assuming $10^8$ Uni tokens are staked (with 18 decimals) in the contract, the rate of loss could be $10^{-10}$.

This loss can be triggered per block (by `withdraw` zero token), and accumulated as a high number. If the payout is significantly large, attackers may be incentivized to exploit it, especially considering the low gas costs on layer-2 networks.

## Recommended Mitigation Steps

Increase the `SCALE_FACTOR` for scenarios involving low gas fees.


## Assessed type

Other
Validators with access to two consecutive blocks can consistently extract RewardToken from the staking contract.
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L220


# Vulnerability details

## Summary
This report identifies a potential vulnerability in the ```UniStaker.sol``` smart contract, stemming from its reliance on ```block.timestamp``` for time tracking instead of ```block.number```. This design choice opens up a theoretical attack vector for validators with significant stakes, allowing them to consistently extract reward tokens by coordinating deposits & withdrawals in conjunction with an AMM pool's Uni reserve.

## Vulnerability description:
The ```UniStaker.sol``` contract utilizes ```block.timestamp``` for calculating staking durations and reward entitlements. This method introduces a risk where validators overviewing two consecutive blocks can adjust the timestamp within the permissible 11-13 second range to artificially extend staking periods. Specifically, an attacker could:

* Swap substantial reserves from an AMM pool(akin to a flash loan) and stake them in the ```UniStaker.sol``` contract towards the end of one block.

* Increase the block.timestamp value in the following block by 13 seconds.

* Quickly claim/withdraw these stakes and restore the pools reserves at the beginning of the next block, effectively minimizing exposure to arbitrage bots and market corrections.

This maneuver would enable the attacker to accrue rewards from the ```unclaimedReward``` function, having staked a large part of the pool's reserve, exploiting the brief but significant staking period manipulated through ```block.timestamp``` adjustments from the past block.header in the following block.

Presently, the feasibility of this attack is constrained by economic factors, primarily the trading fees associated with acquiring the necessary staking tokens outweighing the potential rewards from the contract. This imbalance renders the attack unprofitable under current conditions or at least under the scope of my research.

However, this vulnerability's potential impact should not be underestimated. Changes in fee structures, especially in AMMs offering zero-fee swaps like ```Salty.IO```, recently audited at Code4rena, could tip the scales, making the attack vector viable, allowing for consistent reward extraction whenever any validator knows ahead of time, he will be validating 2 consecutive blocks.
Please note that constant product pools offering 0 fees on swaps, basically act as loans in this scenario, since returning the ratio of one token, will consistently return the previous one when swapped in correct ratios. 

## POC
Any regular contract interaction can be considered POC's since the contract has no granular check on minimum time staked, furthermore the design choice of using block.timestamp over block.number, gives the contract no way of knowing a validator increased this value from the previous block.header.
```
function testDobbleBlockMev() public {
        usersStaki
User can use flashloans to claimFees in V3FactoryOwner at low risk
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/V3FactoryOwner.sol#L181


# Vulnerability details

## Impact
A user can use flashloans to claim the fees in the protocol through `V3FactoryOwner.claimFees` at low risk

## Proof of Concept
When claimFees is called, the user is required to have pre approved the reward token(weth) which is then transferred to the reward_notifier contract before the Protocol Fees is then transferred to the user. The user may collect a flashloan for payout amount when collected fees are higher than the payout amount, call claimFees during which the loaned amount is transferred to reward contract, collect the fees, convert these fees to the flashloaned tokens and then pay back the flashloan, keeping any profit they may have achieved during this process.

## Tools Used
Manual review



## Assessed type

Token-Transfer
malicious user can create surrogate and impersonat as real and will take token out
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/DelegationSurrogate.sol#L20-L29


# Vulnerability details

## Impact
malicious user will create surrogate themselves , and when user delegate to that contract , will transfer token out .

## Proof of Concept
      contract DelegationSurrogate {
    
         constructor(IERC20Delegates _token, address _delegatee) {
            _token.delegate(_delegatee);
           _token.approve(msg.sender, type(uint256).max);
        }
        }

token approve to msg.sender , so creator can easily transfer out the token .

## Tools Used
manual view
## Recommended Mitigation Steps



## Assessed type

Context
UniStaker.sol : `rewardPerTokenAccumulatedCheckpoint` is not reset to zero when all the deposits are withdrawn.
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Old `rewardPerTokenAccumulatedCheckpoint` will be used in a case where the all the stake amount is withdrawn and deposit are started again.

## Proof of Concept

UniStaker has the tracking member `rewardPerTokenAccumulatedCheckpoint` which will track the Checkpoint value of the global reward per token accumulator.

Whenever the deposit or withdraw is made, this will be updated.

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

```solidity
  /// @notice Checkpoints the global reward per token accumulator.
  function _checkpointGlobalReward() internal {
    rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
    lastCheckpointTime = lastTimeRewardDistributed();
  }
```

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

```solidity
  function rewardPerTokenAccumulated() public view returns (uint256) {
    if (totalStaked == 0) return rewardPerTokenAccumulatedCheckpoint;

    return rewardPerTokenAccumulatedCheckpoint
      + (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalStaked;
  }
```

later this will be used to update the beneficiary rewards.

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

```solidity
  function _checkpointReward(address _beneficiary) internal {
    unclaimedRewardCheckpoint[_beneficiary] = unclaimedReward(_beneficiary);
    beneficiaryRewardPerTokenCheckpoint[_beneficiary] = rewardPerTokenAccumulatedCheckpoint;
  }
```

The issue here is, once all the deposit were withdrawn, this `rewardPerTokenAccumulatedCheckpoint` is not reset to zero.

if deposit again started, contract will use the old `rewardPerTokenAccumulatedCheckpoint` to update the beneficiary rewards.

## Tools Used

Manual review.

## Recommended Mitigation Steps

inside the function [_withdraw](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/UniStaker.sol#L723-L735), when all deposits were withdrawn, reset the `rewardPerTokenAccumulatedCheckpoint` to zero.


## Assessed type

Error
Part of the reward tokens can be permanently frozen in extreme cases.
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L230


# Vulnerability details

## Impact

In extreme cases, a portion of the reward tokens may be permanently frozen, particularly when there are no Uni tokens staked.

Assessing the vulnerability as Medium or Low (info) is challenging due to the lack of explicit discussion about scenarios where `TotalStaked == 0`. The reality is that when `TotalStaked == 0`, reward tokens are effectively "burned" (i.e., distributed to no one) and thus become frozen. 

The question of whether this issue should be considered a vulnerability is subject to debate. I classify it as `Medium` because I believe a straightforward/simple solution exists to prevent such permanent freezing.

## Proof of Concept

It's important to note that the absence of staked Uni tokens (`TotalStaked == 0`) prevents any increase in `rewardPerTokenAccumulatedCheckpoint`.

Two scenarios could lead to this issue:

First, if there are no staked tokens and a notifier calls `notifyRewardAmount`, the reward tokens will be distributed to no recipient.

Second, if the last stakeholder withdraws their entire deposit, `rewardPerTokenAccumulatedCheckpoint` ceases to increase, despite increases in `block.timestamp` and `lastCheckpointTime`. This situation results in the reward tokens being frozen.

## Recommended Mitigation Steps

The straightforward and easiest strategy is to ensure that 1) `notifyRewardAmount` cannot be invoked when `TotalStaked == 0`, and 2) withdrawals cannot deplete all staked tokens. 

Note that, these measures (two simple checks) incentivize MEV bots to deposit at least 1 Wei of Uni Token.


## Assessed type

Other