xanoutas/solidity-security-findings huggingface dataset:
Reward can get stuck in contract
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#L755


# Vulnerability details

## Impact
Portion of reward will remain stuck in contract and can't be distributed to user

## Proof of Concept
1. Lets say `scaledRewardRate` is `1*SCALE_FACTOR per millisecond` and reward duration is 2 days. Total reward to be distributed is `1*SCALE_FACTOR*2 days/SCALE_FACTOR`

2. Lets say there was no staking for 1 day. Lets say timestamp is `T1`

3. User A stakes amount 1 which calls `_checkpointGlobalReward();` function

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

4. Since this is first stake so `rewardPerTokenAccumulatedCheckpoint` will be 0 and `lastCheckpointTime` updates to `T1`

5. Second day completes and Beneficiary claims rewards which again calls `_checkpointGlobalReward` which then calculates `rewardPerTokenAccumulated`

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

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

6. `rewardPerTokenAccumulatedCheckpoint` was 0 and `(lastTimeRewardDistributed() - lastCheckpointTime)` is 1 day so this becomes `1*SCALE_FACTOR*1 day`

7. So Benefiaciary gets `1*SCALE_FACTOR*1 day/SCALE_FACTOR`

8. This means contract has leftover reward amount `1*SCALE_FACTOR*1 day/SCALE_FACTOR` which is now stuck in contract and cannot be used


## Recommended Mitigation Steps
On User staking if `totalStaked == 0` then store the reward accumulated in  `block.timestamp- (rewardEndTime-REWARD_DURATION)` (this will be reward from starting to current time) and distribute it in the next reward cycle


## Assessed type

Other
Delegation should not be allowed to `address(0)`
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If a validator does not get enough funds to run a node (MSR - Minimum staking requirement), all token holders that delegated tokens to the validator cannot switch to a different validator, and might result in funds getting stuck with the nonfunctioning validator for up to 12 months.
 

## Tools Used
manual

## Recommended Mitigation Steps

Allow token holders to withdraw delegation earlier if the validator didn’t get enough funds for running nodes.

 


## Assessed type

call/delegatecall
Delegation should not be allowed to `address(0)`
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/491c7f63e5799d95a181be4a978b2f074dc219a5/src/DelegationSurrogate.sol#L27
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/491c7f63e5799d95a181be4a978b2f074dc219a5/src/DelegationSurrogate.sol#L27


# Vulnerability details

## Impact
"Delegating votes leaves the token owner with votes while giving the delegate additional votes"): if a user delegates to address(0) that vote gets lost.

## Proof of Concept
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/491c7f63e5799d95a181be4a978b2f074dc219a5/src/DelegationSurrogate.sol#L26C6-L26C12

## Tools Used
manual

## Recommended Mitigation Steps

Don't allow delegation to address(0) by adding a check 
 


## Assessed type

call/delegatecall
Expose to Signature re-use
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#L322-L332
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L391-L399
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L432-L442
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L475-L489
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L521-L529
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L545-L551


# Vulnerability details

## Impact
Refer Description

## Description
Following type of attack can possile on code base
1.) Signature reuse across different related projects:
 For instance, `hashTypedDataV4` only contains a _nonce other than function parameters. 
```solidity
_hashTypedDataV4(
        keccak256(
          abi.encode(
            STAKE_TYPEHASH, _amount, _delegatee, _beneficiary, _depositor, _useNonce(_depositor) 
          )
        )
```
Therefore, we can have the following scenario: Bob is the owner of a deposit on project A and signs / submit signature with nonce 0 and some hash. Then, a project B that also has Bob as the owner is created. Attacker Charlie can simply take the _data and _signature that Bob previously submitted to project A and send it to project B. As this project will have a nonce of 0 (fresh created), it will accept it.

2.) Signature reuse across different chains:
Because the chain ID is not included in the data, all signatures are also valid when the project is launched on a chain with another chain ID. For instance, let's say it is also launched on Polygon. An attacker can now use all of the Ethereum signatures there. Because the Polygon addresses of user's (and potentially contracts, when the nonces for creating are the same) are often identical, there can be situations where the payload is meaningful on both chains.

```solidity
_revertIfSignatureIsNotValidNow(
      _depositor,
      _hashTypedDataV4(
        keccak256(
          abi.encode(
            STAKE_TYPEHASH, _amount, _delegatee, _beneficiary, _depositor, _useNonce(_depositor) 
          )
        )
      ),
      _signature
    );
```
```solidity
  function stakeMoreOnBehalf(
    DepositIdentifier _depositId,
    uint256 _amount,
    address _depositor,
    bytes memory _signature
  ) external {
    Deposit storage deposit = deposits[_depositId];
    _revertIfNotDepositOwner(deposit, _depositor);

    _revertIfSignatureIsNotValidNow(
      _depositor,
      _hashTypedDataV4(
        keccak256(
          abi.encode(STAKE_MORE_TYPEHASH, _depositId, _amount, _depositor, _useNonce(_depositor))
        )
      ),
      _signature
    );

    _stakeMore(deposit, _depositId, _amount);
  }
```
```solidity
  function alterDelegateeOnBehalf(
    DepositIdentifier _depositId,
    address _newDelegatee,
    address _depositor,
    bytes memory _signature
  ) external {
    Depos
Potential DOS if PAYOUT token is not equal to REWARD token
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#L536


# Vulnerability details

## Impact
The `claimReward` function will always revert, if PAYOUT token is not equal to REWARD token. This is because of the transfer statement 
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L749

The Unistaker contract might not have enough REWARD token, if the PAYOUT token is different that REWARD token. 

For example if PAYOUT token is WETH and REWARD token is USDC, then when a user calls `claimFees` function, `payoutAmount` would be paid in WETH to the UniStaker contract.
But when a user who have staked his UNI token, wants to claim accrued rewards, he would call `claimReward` function, but that would transfer USDC token, but as the Unistaker contract haven't received any USDC token, so the call will always fail. Thus resulting in DOS.

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

## Tools Used
Manual review

## Recommended Mitigation Steps
Implement a check in constructor, to see that PAYOUT_TOKEN is always equal to REWARD_TOKEN


## Assessed type

DoS
Altering a delegatee or staking more UNI tokens into another surrogate contract will not increase the votes of the delegatee
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/491c7f63e5799d95a181be4a978b2f074dc219a5/src/DelegationSurrogate.sol#L24-L28


# Vulnerability details

## Impact

The delegatee will not get their votes.

## Proof of Concept

In order to use the protocol, the user must stake UNI tokens into the contract. 

UNI tokens can be used as votes through the `delegate()` function. 

```
UNI TOKEN: https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984#code

    /**
     * @notice Delegate votes from `msg.sender` to `delegatee`
     * @param delegatee The address to delegate votes to
     */
    function delegate(address delegatee) public {
        return _delegate(msg.sender, delegatee);
    }
```

The Unistaker protocol allows users to stake UNI tokens into the contract and also delegate their votes to a delegatee.

When a user stakes their tokens into Unistaker.sol via `stake()`, `_fetchOrDeploySurrogate()` will be called. Note that `_delegatee` is a user input when calling `stake()`.

```
> function stake(uint256 _amount, address _delegatee)
    external
    returns (DepositIdentifier _depositId)
  {
    _depositId = _stake(msg.sender, _amount, _delegatee, msg.sender);
  }
```

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


    _checkpointGlobalReward();
    _checkpointReward(_beneficiary);


>   DelegationSurrogate _surrogate = _fetchOrDeploySurrogate(_delegatee);
```

_fetchOrDeploySurrogate either fetches a Surrogate contract or deploys one if it doesn't exist. Note the `new` keyword to create a new contract. If the surrogate contract is created, then the function will not create another one.

```
  function _fetchOrDeploySurrogate(address _delegatee)
    internal
    returns (DelegationSurrogate _surrogate)
  {
    _surrogate = surrogates[_delegatee];


    if (address(_surrogate) == address(0)) {
>     _surrogate = new DelegationSurrogate(STAKE_TOKEN, _delegatee);
      surrogates[_delegatee] = _surrogate;
      emit SurrogateDeployed(_delegatee, address(_surrogate));
    }
```

This is how a Surrogate contract looks like: 

```
contract DelegationSurrogate {
  /// @param _token The governance token that will be held by this surrogate
  /// @param _delegatee The address of the would-be voter to which this surrogate will delegate its
  /// voting weight. 100% of all voting tokens held by this surrogate will be delegated to this
  /// address.
  constructor(IERC20Delegates _token, address _delegatee) {
>   _token.delegate(_delegatee);
>   _token.approve(msg.sender, type(uint256).max);
  }
}
```

Note that delegate is called in the constructor. This means that delegate can only be called once for every surrogate contract.

Now, users has the option to stake more UNI tokens and also change their delegatees. Both of t
The `on behalf` functions currently do not include a deadline parameter
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/491c7f63e5799d95a181be4a978b2f074dc219a5/src/UniStaker.sol#L327


# Vulnerability details

## Bug Description

In the `UniStaker` smart contract, there is a `stakeOnBehalf` function and other functions that can be called on behalf of a user. If a user wants UNI to be staked for them from another address, they sign a message and pass the generated signature, allowing the other address to call the `stakeOnBehalf` function. The issue is that the generated signature is perpetual and can be used at any time.

## Proof-of-Concept

Alice (EOA) trusts Bob (EOA) and provides a signature for Bob to call the `stakeOnBehalf` function and pre-approve the `UniStaker` smart contract. After that, she asks Bob not to send the transaction because she changed her mind.
[UniStaker.sol#L391-L399](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/491c7f63e5799d95a181be4a978b2f074dc219a5/src/UniStaker.sol#L391-L399)
```solidity
function stakeOnBehalf(
    uint256 _amount,
    address _delegatee,
    address _beneficiary,
    address _depositor,
    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);
  }
```

Six months pass, and Bob becomes malicious. Bob can stake on behalf of Alice even after six months because the signature for EOA is not revocable.

## Impact
If a user provides a signature allowing someone else to stake on their behalf using the `UniStaker` smart contract but later decides not to stake, there is a risk that the other party could still use that signature to stake on behalf of the user at any time in the future. 

## Tools Used
Manual

## Recommended Mitigation Steps
Consider adding a deadline value to `STAKE_TYPEHASH` and other type hashes related to `onBehalf` calls:
```diff
bytes32 public constant STAKE_TYPEHASH = keccak256(
-    "Stake(uint256 amount,address delegatee,address beneficiary,address depositor,uint256 nonce)"
+    "Stake(uint256 amount,address delegatee,address beneficiary,address depositor,uint256 nonce,uint256 deadline)"
  );
  
function stakeOnBehalf(
    uint256 _amount,
+   uint256 _deadline,
    address _delegatee,
    address _beneficiary,
    address _depositor,
    bytes memory _signature
  ) external returns (DepositIdentifier _depositId) {
    _revertIfSignatureIsNotValidNow(
      _depositor,
      _hashTypedDataV4(
        keccak256(
          abi.encode(
-            STAKE_TYPEHASH, _amount, _delegatee, _beneficiary, _depositor, _useNonce(_depositor)
+            STAKE_TYPEHASH, _amount, _delegatee, _beneficiary, _depositor, _useNonce(_depositor), _deadline
          )
Economic attack to lock fraction of rewards in Unistaker permanently
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/491c7f63e5799d95a181be4a978b2f074dc219a5/src/UniStaker.sol#L271


# Vulnerability details

## Impact
In Unistaker.sol the stake function allows users to set any beneficiary to receive rewards. If the receiver of rewards is the Unistake Contract then that fraction of rewards will be locked permanently in the contract. The primary impact is this can lock funds permanently in the protocol, cheating users out of significant future earning, dependent on the economic power of the attacker. The secondary impact is this could incentivise degenerate cases(as already known) in false reward distribution as the locked reward token balance will inflate at a rapid rate as more reward are distributed into the protocol. At this point only a single false reward notification is required to leave the protocol insolvent.

## 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");
  address ogStaker = makeAddr("ogStaker");
  address otherStaker = makeAddr("otherStaker");
  address benefit1 = makeAddr("benefit1");
  address benefit2 = makeAddr("benefit2");
  address benefit3 = makeAddr("benefit3");

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

  function test_dos() public {
        
        //Malicious user stakes to the UniStaker Address
        //Alias ogStaker
        //Set up mock staker
        vm.prank(ogStaker);
        newVote.mint(ogStaker,1000000);
        vm.prank(ogStaker);
        newVote.approve(address(newUni),1000000);
        vm.prank(ogStaker);
        newUni.stake(10, fakeDelegate, address(newUni));


        //Set up mock staker
        vm.prank(otherStaker);
        newVote.mint(otherStaker,1000000);
        vm.prank(otherStaker);
        newVote.approve(address(newUni),1000000);


        //Set up notifier
        //Reward distribution
        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);

        //Staker stakes
        //User stakes to protocol
        vm.prank(otherStaker);
        UniStaker.DepositIdentifier depoID = newUni.stake(10, fakeDelegate, benefit1);
        

        //Simulate protocol interaction
        for (uint i = 0; i < 10000; i++) {
                //Set up log values
                uint256
Initial rewards can get locked in UniStaker contract
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#L577-L585
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/main/src/UniStaker.sol#L752-L756


# Vulnerability details

## Impact
The `UniStaker` contract allows users to stake `STAKE_TOKEN`'s to earn rewards in a specified `REWARD_TOKEN`. Rewards in `REWARD_TOKEN` are added to the reward pool through the `UniStaker.notifyRewardAmount()` function which specifies an amount which is added to rewards for the next `REWARD_DURATION` seconds. However, the rewards accrued between the time rewards are added and the time when the first stake happens are not accounted for, leaving these rewards stuck in the contract.

## Proof of Concept
This is the `UniStaker.notifyRewardAmount()` function:
```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;

  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);
}
```
and this is the `UniStaker._checkpointGlobalReward()` function which is called when a user stakes and when a user claims their rewards:
```solidity
function _checkpointGlobalReward() internal {
  rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
  lastCheckpointTime = lastTimeRewardDistributed();
}
```
As can be seen, when rewards are added through the `UniStaker.notifyRewardAmount()` function they are split over the `REWARD_DURATION` and stored in a variable called `scaledRewardRate` which is effectively the amount of rewards that accrue per second (scaled by 1e36). However, because a user can only start earning rewards after `lastCheckpointTime` which is set when a user stakes, the r
`UniStaker::unclaimedReward` Does Not Account For Accumulated Rewards Because of Round Down To Zero 
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
`unclaimedRewards` may return incorrect rewards earned by benificiary because of accumulated rewards being divisible by Large Value of `SCALE_FACTOR = 1e36` which may round down to `Zero`

Also The `_checkpointReward` function then uses this incorrect unclaimedReward value to update the `unclaimedRewardCheckpoint[_beneficiary] = unclaimedReward(_beneficiary);` resulting in an incorrect checkpoint value being stored for the beneficiary, which will affect future reward calculations for this beneficiary.

## Proof of Concept
The unclaimedReward function in the may lead to a rounding down to zero error.

For further elaboration, consider the following mock values 

- `unclaimedRewardCheckpoint[_beneficiary]` is 1000
- `earningPower[_beneficiary]` is 2000
- `rewardPerTokenAccumulated()` returns 5000
- `beneficiaryRewardPerTokenCheckpoint[_beneficiary]` is 3000
- and `SCALE_FACTOR` value is 1e36

The function calculates the unclaimed reward as follows:

`unclaimedReward = 1000 + (2000 * (5000 - 3000)) / 1e36`

This simplifies to 

`unclaimedReward = 1000 + 4000000 / 1e36`

In this case, `4000000 / 1e36` will round down to zero due to large scale factor

And the final unclaimed rewards will be 

`unclaimedReward = 1000 + 0
 unclaimedReward = 1000`

The beneficiary has 1000 unclaimed rewards, which is incorrect as the calculation does not account for the rewards accumulated since the last checkpoint.

## Tools Used
Manual
## Recommended Mitigation Steps
Use some validation checks to ensure that the required calculation doesn't round down to zero because of large-scale factors
A possible workaround for this 
```
uint256 rewardDifference = rewardPerTokenAccumulated() - beneficiaryRewardPerTokenCheckpoint[_beneficiary];
uint256 accumulatedReward = earningPower[_beneficiary] * rewardDifference;

require(accumulatedReward >= SCALE_FACTOR, "Accumulated reward is less than scale factor");

return unclaimedRewardCheckpoint[_beneficiary]
      + (
        accumulatedReward
      ) / SCALE_FACTOR;
```







## Assessed type

Error