xanoutas/solidity-security-findings huggingface dataset:
UniStaker.sol : `lastCheckpointTime` update is done at incorrect place. this will avert the reward accumulation to users.
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The reward calculation will not accumulate any reward.

## Proof of Concept
Whenever user stakes or withdraw or claim, following updates is done prior to proceeding further in those functions.

For example,

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

```solidity
    _checkpointGlobalReward();
    _checkpointReward(_beneficiary);
```

Current reward calculation depend on `lastCheckpointTime`. This is updated inside the function `_checkpointGlobalReward()`.

Lets see how this would impact the reward accumulation.

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

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

`rewardPerTokenAccumulatedCheckpoint ` is computed using the function `rewardPerTokenAccumulated()`. 

Further the updated `lastCheckpointTime ` is the current_time. --->>> @@audit

`lastTimeRewardDistributed()` - returns the last updated reward time.

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

```solidity
  function lastTimeRewardDistributed() public view returns (uint256) {
    if (rewardEndTime <= block.timestamp) return rewardEndTime;
    else return block.timestamp;
  }
``` 

when this function is called before the rewardEndTime, it would return the current time. ------>>> @@ audit

Now, lets see the function `_checkpointReward(_beneficiary)`.

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

`unclaimedRewardCheckpoint[_beneficiary]` is updated by calling the `unclaimedReward(_beneficiary)`.

Lets look at the `unclaimedReward(_beneficiary)`.

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

```solidity
  function unclaimedReward(address _beneficiary) public view returns (uint256) {
    return unclaimedRewardCheckpoint[_beneficiary]
      + (
        earningPower[_beneficiary]
          * (rewardPerTokenAccumulated() - beneficiaryRewardPerTokenCheckpoint[_beneficiary])
      ) / SCALE_FACTOR;
  }
```

Here, `rewardPerTo
Rewards accrued will be permanently locked when there are no stakers
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When an MEV searcher claims the fees and there are no stakers for the entire `REWARD_PERIOD`, rewards will be locked forever.

Stakers only have 30 days to stake their tokens before all rewards are permanently locked.

A partial amount of these rewards will be lost anyway if someone stakes before the full 30 days (e.g. first stake after 3 days will result in a 10% total loss).

## Proof of Concept
Suppose the following: no one has staked their token in UniStaker yet, so `totalStaked = 0`.

An MEV searcher calls `V3FactoryOwner.claimFees` immediately after deployment with a valid pool. Then `UniStaker.notifyRewardAmount` is also [called](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L570):

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

    ...
  }
```

Due to `totalStaked = 0`, then `rewardPerTokenAccumulatedCheckpoint` [is also zero](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L230):

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

Let's analyze the previous values step by step, let's say we are at `block.timestamp = 1_000_000` for simplicity, `REWARD_DURATION = 30 days = 2_592_000`, and `rewardAmount = 10e18`

```solidity
   rewardPerTokenAccumulatedCheckpoint = 0
   scaledRewardRate = (10e18 * 1e36) / 30 ~= 1e55
   rewardEndTime = 1_000_000 + REWARD_DURATION = 3_592_000
   lastCheckpointTime = 1_000_000
```

Worst-case scenario: suppose that in these 30 days, no one calls `stake`. We are now at `timestamp = 3_592_001`, and someone finally calls [`stake`](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L638-L664):

```solidity
  function _stake(address _depositor, uint256 _amount, address _delegatee, address _beneficiary)
    internal
    returns (DepositIdentifier _depositId)
  {
    _revertIfAddressZero(_delegatee);
    _revertIfAddressZero(_beneficiary);

    _checkpointGlobalReward();
    _checkpointRewa
unsafe delegate
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
an attacker can gain control via the ` _token.delegate(_delegatee)` in order to `selfdestruct()` each of the factories' implementations, preventing each factory from functioning further, including the withdrawal of tokens by any party,bricked by `selfdestruct()` implementations 

 
## Tools Used
manual 

## Recommended Mitigation Steps

Use a state/contract variable for anything requiring being delegated to.




## Assessed type

call/delegatecall
`_delegatee` risk
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
delegateBySig allows the user to vote to address 0, which causes the user to permanently lose his vote and cannot transfer his tokens.
when the user votes with the delegate function, the delegatee will not be address 0


## Proof of Concept
https://github.com/code-423n4/2022-08-nounsdao/blob/45411325ec14c6d747b999a40367d3c5109b5a89/contracts/base/ERC721Checkpointable.sol#L197-L208


## Tools Used
manual

## Recommended Mitigation Steps
```diff
    constructor(IERC20Delegates _token, address _delegatee) {
+   require(_delegatee != address(0));

    _token.delegate(_delegatee);
    _token.approve(msg.sender, type(uint256).max);
  }
```



## Assessed type

call/delegatecall
Attacker can claim greater chunk of the reward pool by depositing large amount of stake tokens 
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
An attacker can claim a greater share of the reward pool, by staking and withdrawing a large amount of stake tokens (UNI). The attack involves staking a huge amount of stake tokens by the attacker to increase his `earningPower` which will increase his reward share. This also becomes a griefing attack for other users, as their share will get decreased, and they will receive a less share of the reward pool, then originally intended.

So as soon as, a notifier notifies about a reward, attacker will wait for few blocks to pass. Then he will call `stake` and deposit a large amount of Stake tokens. This will increase his `earningPower` by a significant amount.
 
Finally when the reward duration is finished, attacker will claim the generated reward by calling `claimReward()`and withdraw his staked tokens. The claimed reward would be way higher than other staked users. Also the reward of the other users would be significantly decreased.

One thing to note is that attacker must be a whale. As he needs to have a large amount of stake tokens (UNI).  

Due to this, 
## Proof of Concept
```
function testbugdeposit() public {
    uint amount = 10000000; 
    address depositor = vm.addr(444);
    address depositor2 = vm.addr(555);
    address attacker = vm.addr(666);
    govToken.mint(depositor, amount); // 10 UNI token
    govToken.mint(depositor2, amount); // 10 UNI token
    govToken.mint(attacker, 100000 * 1000000); // 100000 UNI token

    vm.startPrank(depositor);
    govToken.approve(address(uniStaker), amount);
    address delegatee = vm.addr(123);
    uniStaker.stake(amount, delegatee);
    vm.stopPrank();

    vm.startPrank(depositor2);
    govToken.approve(address(uniStaker), amount);
    address delegatee2 = vm.addr(124);
    uniStaker.stake(amount, delegatee2);
    vm.stopPrank();

    _mintTransferAndNotifyReward(100 ether);
    // 2% of the reward duration passes
    _jumpAheadByPercentOfRewardDuration(2);

    vm.startPrank(attacker);
    govToken.approve(address(uniStaker), 100000 * 1000000);
    address delegatee3 = vm.addr(125);
    UniStaker.DepositIdentifier depositId = uniStaker.stake(100000 * 1000000, delegatee3);
    vm.stopPrank();

    // wait for few blocks to pass
    _jumpAheadByPercentOfRewardDuration(100);

    vm.startPrank(attacker);
    uniStaker.claimReward();
    uniStaker.withdraw(depositId, 100000 * 1000000);
    uint attacker_reward = uniStaker.REWARD_TOKEN().balanceOf(address(attacker));
    console.log("Reward A", attacker_reward); // Attacker reward
    vm.stopPrank();

    vm.startPrank(depositor);
    uniStaker.claimReward();

    uint256 balance1 = uniStaker.REWARD_TOKEN().balanceOf(address(depositor));
    console.log("Reward 1", balance1); // Depositor 1 reward
    vm.stopPrank();

    vm.startPrank(depositor2);
    uniStaker.claimReward();
    uint256 balance2 = uniStaker.
`_delegatee` to incorrect address
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`_delegatee` to incorrect address can make contract to be lost and lost of funds.
`_delegatee` can have a self destruct and  distroy the contract and send all the eth to the attacker. funds can be locked in the contract if is an wrong address

 
## Tools Used
manual

## Recommended Mitigation Steps
must add a function that can update the `_delegatee` address


## Assessed type

call/delegatecall
Admin can set ` setFeeProtocol ` to zero by front-running ` claimFees `
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
withdrawals can fail because some tokens don't do zero transfers. if admin updates pool fees to zero users will get front-runned and their transaction will not go through.
 
## Tools Used
manual
## Recommended Mitigation Steps
`setFeeProtocol` must never be set to zero and if is zero function must revert.


## Assessed type

Governance
Potential Discrepancy in Fee Claiming Due to Reward Notifier Approval
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The claimFees function allows miners to claim fees from the pool by paying a fixed payoutAmount. However, if the REWARD_RECEIVER.notifyRewardAmount function, responsible for notifying that rewards have been received, has a modifier checking if the msg.sender is an allowed notifier address, and if this address is not initially set, miners will be unable to claim fees until the address is approved. This delay in approving the reward notifier address could result in a substantial accumulation of fees within the protocol. When the address is eventually approved, miners would still only need to pay the initially set fixed payoutAmount to claim fees, resulting in a potential discrepancy where miners receive a much higher amount from the pool than what they paid in fees.

## Proof of Concept
REWARD_RECEIVER.notifyRewardAmount function has a modifier checking if msg.sender is an allowed notifier address.

Initially, the reward notifier address is not set or approved in the contract.
Fees continue to accumulate within the protocol over time.

Miners attempt to claim fees but are unable to do so due to the unapproved reward notifier address.

After some time, the reward notifier address is approved by the admin.

Miners can now claim fees by paying the fixed payoutAmount, which might be significantly lower than the accumulated fees within the protocol.

Miners effectively receive a much higher amount from the pool than what they paid in fees, resulting in a discrepancy.

## Tools Used
manual analysis

## Recommended Mitigation Steps
Ensure that the reward notifier address is set or approved in the contract promptly after deployment to avoid delays in fee claiming.


## Assessed type

Other
UniStaker.sol : alterBeneficiary ->_alterBeneficiary : rewards can be accumulated for same staked supply by swapping the Beneficiary between the same accounts 
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/UniStaker.sol#L453-L457
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/UniStaker.sol#L704-L718


# Vulnerability details

## Impact
More rewards can be accumulated by simply swapping the beneficiary account between the same address.

## Proof of Concept

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

```solidity
  function _alterBeneficiary(
    Deposit storage deposit,
    DepositIdentifier _depositId,
    address _newBeneficiary
  ) internal {
    _revertIfAddressZero(_newBeneficiary);
    _checkpointGlobalReward();
    _checkpointReward(deposit.beneficiary);
    earningPower[deposit.beneficiary] -= deposit.balance;

    _checkpointReward(_newBeneficiary); --------------->> rewards would be accumulated here.
    emit BeneficiaryAltered(_depositId, deposit.beneficiary, _newBeneficiary);
    deposit.beneficiary = _newBeneficiary;
    earningPower[_newBeneficiary] += deposit.balance;
  }
```

As shown in above code snippet, the old and new beneficiary could be the same account.
By swapping like this between the same account, rewards could be accumulated more.

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;
  }
``` 
`_checkpointReward` updates rewards based on last global updated check point value.

Since the old and new beneficiary address are same, the total staked amount is same. only the rewards are accumulated to the beneficiary.

## Tools Used

Manual review.

## Recommended Mitigation Steps

Implement check to ensure that the beneficiary swap should not be done between same addresses.


## Assessed type

Error
Inconsistent Hash Generation in stakeOnBehalf and stakeMoreOnBehalf Functions
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The stakeOnBehalf and stakeMoreOnBehalf functions utilize different methods for generating the hashTypedDataV4 bytes data, resulting in inconsistent hash generation. This inconsistency can lead to signature mismatches and cause transactions to revert erroneously.

## Proof of Concept
```javascript
//SPDX-License-Identifier:MIT
pragma solidity 0.8.24;
contract unistaker{
 
 bytes32 public hash1;
  bytes32 public hash2;
     bytes32 public constant STAKE_TYPEHASH =
        keccak256(
            "Stake(uint256 amount,address delegatee,address beneficiary,address depositor,uint256 nonce)"
        );

 function hashed(uint _amount, address _delegatee, address _beneficiary,address _depositor) public returns( bytes32){
hash1 =  ( keccak256(abi.encode(
                        STAKE_TYPEHASH,
                        _amount,
                        _delegatee,
                        _beneficiary,
                        _depositor,
                       23
                    )));
                     }
                     
             function hashed2(uint _amount, address _delegatee, address _beneficiary,address _depositor) public returns( bytes32){
hash2 =  ( keccak256(abi.encode(
                        STAKE_TYPEHASH,
                        _beneficiary,
                        _depositor,
                        _amount,
                        _delegatee,
                        
                        23
                    )));
                     }
}

output -> hashed =0x0f652c68c70b6c2eeda49787a36f7b824fb7d4b271439e8b0b24304136173ace

hashed2=  0xf5699303c13e97375f2b34c4526b31f4dd8118eb9b1cba0b5d18070d68ec40d6
```

## Tools Used
remix ide

## Recommended Mitigation Steps
Standardize Hash Generation:

Ensure consistent hash generation across functions by standardizing the order and encoding of data in abi.encode.


## Assessed type

Other
Users backrunning notifyRewardAmount() with stake() receive same rewards as users staking since rewardEndTime
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When rewards are fully streamed i.e. reward duration has ended, the [lastTimeRewardDistributed()](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L220C12-L220C37) function starts returning `rewardEndTime`, which is 30 days + timestamp when [notifyRewardAmount()](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L570) was last called. 

This stops reward accumulation per token since `lastTimeRewardDistributed() - lastCheckpointTime` would evaluate to 0 [here](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L233), causing only the `rewardPerTokenAccumulatedCheckpoint` last recorded [here](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L232C12-L232C47) to be returned.

The issue is that once block.timestamp has passed the `rewardEndTime` but before [notifyRewardAmount()](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L570) is called to start the new period, the users staking during this period all are checkpointed to the last recorded checkpoint. This is unfair to users who staked beforehand while other users could just backrun when `notifyRewardAmount()` is next called since they receive the same initial checkpoint to start earning rewards.

For example:
 - User A stakes at t = 100
 - Reward end time (duration of a period i.e. reward period ends) at t = 100
 - User B stakes (backruns notifyRewardAmount) at t = 200
 - notifyRewardAmount is called through V3FactoryOwner at t = 200  

So basically user B stakes with time difference = 99 (compared to user A) and still receives same rewards as A when the next distribution period starts even though A staked for longer. **Note: We assume both A and B stake same amount of tokens for simplicity.**

### Why I believe this is an issue

This issue is not dependent on the design spec since if the design spec is correct currently, there is an issue and if it is wrong, then the issue identifies the incorrect design spec. Let's take a look at what I mean by this:

If the project intends to allow users to accumulate rewards even when rewardEndTime is met, then user A and B are not checkpointed correctly.

If the project intends to not allow users to accumulate rewards when rewardEndTime is met, then it is a problem since both user A and B receive rewards from rewardEndTime onwards i.e. the last user specific checkpoint `beneficiaryRewardPerTokenCheckpoint`. Note that when [notifyRewardAmount()](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd
Potential Delay in Fee Claiming Due to Missing Reward Notifier
Audit: 2024-02-uniswap-foundation-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The absence of a configured reward notifier address may lead to a delay in fee claiming within the protocol. If the reward notifier address is not set, users would be unable to claim fees until the notifier address is properly configured by the admin. This delay could impact user experience and potentially lead to frustration or dissatisfaction among Miners who expect immediate fee claiming functionality. Moreover, if the reward notifier address is accidentally omitted or forgotten during deployment, it could result in a denial-of-service (DoS) scenario where users are unable to claim fees until the issue is resolved.

## Proof of Concept
```javascript
 function setRewardNotifier(address _rewardNotifier, bool _isEnabled) external {
    _revertIfNotAdmin();
    isRewardNotifier[_rewardNotifier] = _isEnabled;
    emit RewardNotifierSet(_rewardNotifier, _isEnabled);
  }

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

```

## Tools Used
Manual analysis 

## Recommended Mitigation Steps
t the reward notifier address is properly configured during contract deployment or shortly thereafter by the admin.


## Assessed type

DoS