xanoutas/solidity-security-findings huggingface dataset:
An attacker could exploit the `V3FactoryOwner::claimFees` due to flaw in order of `claimFees` 
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#L181


# Vulnerability details

An attacker could exploit the `claimFees` function to drain funds from the `V3FactoryOwner` contract or the `REWARD_RECEIVER` contract. 
Let's see how it could impact the functionality:

1. Order of Operations: If the `REWARD_RECIEVER` expects to be notified after the fees are collected, it could lead to incorrect state updates.

2. External Calls: If any of the external calls fail, the entire transaction will revert, which could be undesirable and could potentially lock up funds if the caller has not approved the transfer of the payout amount.

3. Gas Costs: The function performs multiple external calls, which can be expensive in terms of gas costs. This could lead to high gas costs for users and potentially make the function unusable if the gas costs exceed the user's balance or the block gas limit.

4. If the `_recipient` address is a contract that does not handle the received tokens correctly, it could lead to a loss of funds. 

5. Reentrancy: Without the `nonReentrant` modifier, the function is vulnerable to reentrancy attacks, which could allow an attacker to drain funds or manipulate the state of the contract.


## Proof of Concept
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/V3FactoryOwner.sol#L181
```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);
  }
```

Here's attack scenario:

1. The attacker calls `claimFees` with a malicious `_recipient` contract that has a fallback function that calls back into `claimFees` before the `notifyRewardAmount` call.

2. The `collectProtocol` call transfers fees to the `_recipient`, which triggers the fallback function.

3. The fallback function calls `claimFees` again, which transfers more fees to the `_recipient`.

4. This recursive call continues until the `REWARD_RECEIVER` contract's balance is drained, or the gas limit is reached, potentially leading to a loss of funds.

Here's a simplified example: To execute this attack, the attacker would need to deploy a malicious contract that interacts with the V3FactoryOwner contract.

```javascript
contract MaliciousRecipient {
    V3FactoryOwner public
V3FactoryOwner.sol : absence of cool down period or pause between `claimFees` and `setPayoutAmount` lead to front running and benefiting more
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#L119-L124
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/V3FactoryOwner.sol#L181-L199


# Vulnerability details

## Impact
Attacker can front run and claim more fees.

## Proof of Concept

In order to claim the fee, user has to deposit the `PAYOUT_TOKEN` to the `REWARD_RECEIVER`. this deposit amount value is decided by the owner with `payoutAmount`

The payoutAmount is fixed by the admin based on certain conditions. so that when more payoutAmount is needed, this can be increased or in other way.

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

```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);
  }
```

Some situations, when the fee token value are higher than the payout token amount value, admin can increase the payoutAmount value.

This payoutAmount can be increased by calling the below function.
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/V3FactoryOwner.sol#L119-L124

```solidity
  function setPayoutAmount(uint256 _newPayoutAmount) external {
    _revertIfNotAdmin();
    if (_newPayoutAmount == 0) revert V3FactoryOwner__InvalidPayoutAmount();
    emit PayoutAmountSet(payoutAmount, _newPayoutAmount);
    payoutAmount = _newPayoutAmount;
  }
```
lets consider the following case.

current payoutAmount value is 100.

if the payoutAmount is going to be increased to 1000. Bob who want to use the 100 amount value can front run the setPayoutAmount call and use 100 amount value to claim the fees.

## Tools Used

Manual review.

## Recommended Mitigation Steps

We would suggest to implement a pause or cool down period either for `setPayoutAmount` or for `claimFees` function.

pause the `claimFees` for 10 mins and set the payoutAmount. after 10 more min (total 20 mins) allow claim fee.

So that, there could be no front running possible between setting these two values.


## Assessed type

Timing
Griefing attack: attacker can prevent other users from claiming reward by continously update `rewardPerTokenAccumulatedCheckpoint`
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

## Vulnerability details
Function `rewardPerTokenAccumulatedCheckpoint()` is called in most of the function that user can interact in the `UniStaker` contract:

  function _checkpointGlobalReward() internal {
    rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
    lastCheckpointTime = lastTimeRewardDistributed();
  }

It update `rewardPerTokenAccumulatedCheckpoint` and `lastCheckpointTime` variable by calling `rewardPerTokenAccumulated()` and `lastTimeRewardDistributed()` function.

Function `rewardPerTokenAccumulated()`:

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

    return rewardPerTokenAccumulatedCheckpoint
      + (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalStaked;
  }
It can be seen that it will return old `rewardPerTokenAccumulatedCheckpoint` plus  `(scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalStaked`.

In the formula, `totalStaked` is total UNI token staked, and `scaledRewardRate` is updated in the `notifyRewardAmount()` function as below:

    if (block.timestamp >= rewardEndTime) {
      scaledRewardRate = (_amount * SCALE_FACTOR) / REWARD_DURATION;
    } else {
      uint256 _remainingReward = scaledRewardRate * (rewardEndTime - block.timestamp);
      scaledRewardRate = (_remainingReward + _amount * SCALE_FACTOR) / REWARD_DURATION;
    }
And `REWARD_DURATION` = 30 days, `SCALE_FACTOR` = 1e36.


So, since the division can equal to 0 when there are more and more stakers, attacker can abusing this to make a griefing attack by continously call this function every block, to make `lastTimeRewardDistributed() - lastCheckpointTime` as smaller as possible (1 block ~ 12 seconds in the mainnet). Which lead to `rewardPerTokenAccumulatedCheckpoint` is not increase, and other users do not have reward to claim

## Impact
Other users loss reward to claim

## Tools Used
Manual review

## Recommended Mitigation Steps
Only updating `lastCheckpointTime` when `rewardPerTokenAccumulatedCheckpoint` is increase


## Assessed type

Other
Some tokens may be locked and not be distributed due to a rounding error for `scaledRewardRate` that occurs in `notifyRewardAmount()`
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#L570


# Vulnerability details

## Impact
Function `notifyRewardAmount()` calculates `scaledRewardRate`. To calculate the mentioned reward rate it divides the reward amount by duration but because of the rounding error in the division, some of the reward amounts wouldn't get distributed and are stuck in the contract. Also, the contract doesn't redistribute them or doesn't have any mechanism to recover them.


## Proof of Concept
The function `rewardPerTokenAccumulated()` is responsible for fetching the value of the global reward per token variable. It uses the `scaledRewardRate` inside the calculation:

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

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

This parameter is set inside the `notifyRewardAmount()`:

```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 problem lies in the fact that the `scaledRewardRate` is calculated using the multiplication of `_amount` and `SCALE_FACTOR` and then divided to `REWARD_DURATION`. Furthermore, this variable is multiplied with `(lastTimeRewardDistributed() - lastCheckpointTime)` inside the `rewardPerTokenAccumulated()` function.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider adding an extra amount so it would be distributed on the next `notifyRewardAmount()` or add another mechanism to recover it.


## Assessed type

DoS
Users will forever lose their accrued rewards if they call withdraw before calling claimReward first
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#L638
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L723


# Vulnerability details

## Impact
Users will forever lose their accrued rewards.


## Proof of Concept
The contract is implemented so that if a user calls `withdraw()`/`withdrawOnBehalf()` without first calling `claimReward()`/`claimRewardOnBehalf()` for each reward pool then the staker will lose all of his unclaimed rewards forever, they will be locked into the contract.

```Solidity
function _withdraw(Deposit storage deposit, DepositIdentifier _depositId, uint256 _amount)
    internal
  {
    _checkpointGlobalReward();
    _checkpointReward(deposit.beneficiary);

    deposit.balance -= _amount; // overflow prevents withdrawing more than balance
    totalStaked -= _amount;
    depositorTotalStaked[deposit.owner] -= _amount;
    earningPower[deposit.beneficiary] -= _amount;
    _stakeTokenSafeTransferFrom(address(surrogates[deposit.delegatee]), deposit.owner, _amount);
    emit StakeWithdrawn(_depositId, _amount, deposit.balance);
  }
```
a user will lose access to all their pending rewards and lose access to future rewards which gives the impression that you can re-stake and then you will re-gain access to your unclaimed rewards.

## Tools Used
Manual Review

## Recommended Mitigation Steps
One possible solution is to enforce zero unclaimed rewards when a call to withdraw/withdrawOnBehalf is made by reverting if there are any such unclaimed rewards.
Another one is to just call claimReward in withdraw/withdrawOnBehalf.


## Assessed type

DoS
Some of the Reward tokens can be frozen.
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#L570-L599
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L229-L234


# Vulnerability details

## Impact
Some reward funds can stay locked in the `Unistaker` Contract forever.

## Proof of Concept
`Unistaker` is designed to distribute rewards to stakers based on their staked token amount and staking time. The reward distribution begins with the `notifyRewardAmount` function of Unistaker.sol, facilitated by the reward notifier.
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L570-L599

```
  function notifyRewardAmount(uint256 _amount) external {
    if (!isRewardNotifier[msg.sender]) revert UniStaker__Unauthorized("not notifier", msg.sender);

    // We checkpoint the accumulator without updating the timestamp at which it was updated, because
    // that second operation will be done after updating the reward rate.
    rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();

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

    rewardEndTime = block.timestamp + REWARD_DURATION;
    lastCheckpointTime = block.timestamp;

    if ((scaledRewardRate / SCALE_FACTOR) == 0) revert UniStaker__InvalidRewardRate();

    // This check cannot _guarantee_ sufficient rewards have been transferred to the contract,
    // because it cannot isolate the unclaimed rewards owed to stakers left in the balance. While
    // this check is useful for preventing degenerate cases, it is not sufficient. Therefore, it is
    // critical that only safe reward notifier contracts are approved to call this method by the
    // admin.
    if (
      (scaledRewardRate * REWARD_DURATION) > (REWARD_TOKEN.balanceOf(address(this)) * SCALE_FACTOR)
    ) revert UniStaker__InsufficientRewardBalance();

    emit RewardNotified(_amount, msg.sender);
  }
```
The notifier calls this function with `_amount` as a parameter, representing the quantity of reward tokens the staking contract is being notified of. 
This indicates that all reward tokens are intended to be distributed to the `UNI` stakers. However, in certain scenarios, some reward tokens may be locked within the `UniStaker` contract.

-The Notifier calls this function to initialize the `UniStaker` contract with the `_amount`. At this point, `rewardPerTokenAccumulatedCheckpoint` is zero, and `scaledRewardRate` is configured to distribute the `_amount` (reward token amount) to the stakers over a period of 30 days. Additionally, `_remainingReward` is set to zero.
```
    if (
      (scaledRewardRate * REWARD_DURATION) > (REWARD_TOKEN.balanceOf(address(this)) * SCALE_FACTOR)
    ) revert UniStaker__I
Wrong duration in scaledRewardRate definition inside the `notifyRewardAmount()` can cause DOS for some users
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#L570-L599


# Vulnerability details

## Impact
Wrong duration set inside the `notifyRewardAmount()` may lead to a DOS situation.


## Proof of Concept
```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 updated scaledRewardRate is calculated based on the newly added amount of tokens, and the `REWARD_DURATION`.
While the `REWARD_DURATION` is set to be 30 days, the period from the current time to `rewardEndTime` can be much longer.

```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;
  }
```

As a result, `rewardPerTokenAccumulated()` can be much higher than expected, which makes the total amount of reward tokens less than the total amount of rewards accumulated by all the users.

This makes the users who claim the rewards later unable to retrieve their rewards as the balance can be insufficient.


## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider calculating `scaledRewardRate` base on `timeUntilNextPeriodFinish` to next period finish:

```Solidity
    uint nextPeriodFinish = lastTimeRewardDistributed() + REWARD_DURATION;
    uint timeUntilNextPeriodFinish = nextPeriodFinish - block.timestamp;

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


## Assessed type

DoS
Missing Deadline in `UniStaker.sol` Stake Delegation Leads to Potential Misuse
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#L315
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L382
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L512
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L544


# Vulnerability details

## Impact
The absence of a `deadline` check in the `stakeMoreOnBehalf`, `stakeOnBehalf`, `withdrawOnBehalf`, `claimRewardOnBehalf` functions could lead to unauthorized staking actions if signatures are reused or intentions change over time, potentially compromising the integrity of the staking process and user funds.

## Proof of Concept
let's take a example of `stakeMoreOnBehalf`.
Generate a signature for `stakeMoreOnBehalf` without a deadline.
```javascript
function stakeOnBehalf(
    uint256 _amount,
    address _delegatee,
    address _beneficiary,
    address _depositor,
    // @audit missing deadline
    bytes memory _signature
  ) external returns (DepositIdentifier _depositId) {
    _revertIfSignatureIsNotValidNow(
      _depositor,
      _hashTypedDataV4(
        keccak256(
          abi.encode(
            STAKE_TYPEHASH, _amount, _delegatee, _beneficiary, _depositor, _useNonce(_depositor)
          )
        )
      ),
      _signature
    );
    _depositId = _stake(_depositor, _amount, _delegatee, _beneficiary);
  }
```
Simulate a delay before using the signature to stake more on behalf of the user.

Observe that the signature remains valid indefinitely, allowing for potential misuse.

For Example:

Alice initially decides to increase her stake and provides Bob with a signature to do so.

Over the next 30 days, the market conditions change, and Alice decides not to add more to her stake.

Alice assumes the signature she gave Bob is no longer valid because too much time has passed.

However, Bob or a malicious actor who obtained the signature proceeds to call `stakeMoreOnBehalf` after the 30 days.

The UniStaker contract accepts the signature since there's no deadline mechanism in place.

The additional tokens are staked under Alice's deposit ID, contrary to her current investment strategy.

The lack of a deadline allows for the execution of an action that no longer aligns with the user's intent, demonstrating the vulnerability.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Add expiration param to the following functions: `stakeMoreOnBehalf`, `stakeOnBehalf`, `withdrawOnBehalf`, `claimRewardOnBehalf`.


## Assessed type

Error
Block Gas Limit DoS in `_claimReward` Due to Non-Standard ERC20 Token `transfer` Implementations
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#L740


# Vulnerability details

## Vulnerability Details
The `_claimReward` function in the UniStaker contract facilitates the claiming of accrued rewards by beneficiaries. This function relies on the ERC20 `transfer` operation to move rewards from the contract to the beneficiary's address. However, when interacting with ERC20 tokens that incorporate custom logic within their `transfer` functions, the operation can incur unpredictably high gas costs. This variability arises from additional checks, state updates, or external contract interactions included in these non-standard `transfer` implementations.

## Proof of Concept
Non-standard implementations of the ERC20 `transfer` function can significantly impact the gas efficiency of the UniStaker contract's `_claimReward` method. This method is designed with the expectation that token transfer gas costs are predictable and manageable. However, additional logic in `transfer` can lead to increased gas consumption, affecting the overall gas cost of the `_claimReward` call.

```solidity
function _claimReward(address beneficiary) internal {
    _checkpointGlobalReward();
    _checkpointReward(beneficiary);

    uint256 reward = unclaimedRewardCheckpoint[beneficiary];
    if (reward > 0) {
        unclaimedRewardCheckpoint[beneficiary] = 0;
        emit RewardClaimed(beneficiary, reward);
        SafeERC20.safeTransfer(REWARD_TOKEN, beneficiary, reward); // Potential for increased gas costs
    }
}
```

### Impact 
- If the gas cost approaches or exceeds the block gas limit, it could be exploited as a vector for DoS attacks, preventing legitimate transactions from succeeding.
- Beneficiaries claiming rewards may face higher-than-expected transaction fees
- The unpredictability of transaction costs can lead to failed transactions if the provided gas limit is too low.


## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider implementing mechanisms within the UniStaker contract to handle or mitigate the impact of high gas costs when interacting with non-standard ERC20 tokens, such as gas cost limits or alternative reward distribution mechanisms.


## Assessed type

DoS