xanoutas/solidity-security-findings huggingface dataset:
Rewards for the initial period may be forfeited
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#L570-L599


# Vulnerability details

## Description
Rewards in the UniStaker contract are initiated when the reward notifier calls the `notifyRewardAmount` function. This function calculates the reward rate per second and records the start of the reward period.
``` solidity
function notifyRewardAmount(uint256 _amount) external {
    if (!isRewardNotifier[msg.sender]) revert UniStaker__Unauthorized("not notifier", msg.sender);

    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();

    if (
      (scaledRewardRate * REWARD_DURATION) > (REWARD_TOKEN.balanceOf(address(this)) * SCALE_FACTOR)
    ) revert UniStaker__InsufficientRewardBalance();

    emit RewardNotified(_amount, msg.sender);
}
```
The aim here is to determine the number of tokens to be rewarded per unit of time (second) and to keep track of the duration of the reward cycle.
However, there exists an edge case where rewards are not accounted for during the initial period of time until at least one staker has staked their tokens.

##### Example
When the `notifyRewardAmount` function is called with a reward duration of 30 days, the intention is to distribute one reward per second to stakers throughout the 30-day period.

Timestamp = `x`
scaledRewardRate = `1e36`
rewardEndTime = `x + 2592000`

After a period referred to as `y` has elapsed, the first staker begins staking a certain amount of tokens:

Timestamp = `x + y`
rewardPerTokenAccumulatedCheckpoint = `0`;
lastCheckpointTime = `x + y`

Therefore, for this staker, the clock starts ticking from `x + y`, and they will start accumulating rewards from this moment onward.
If we consider the delay `y` to be 30 minutes, then only 2,590,200 tokens will be distributed, and the remaining 1,800 tokens will remain unused in the contract.

Inspired by: [0xmacro blog](https://0xmacro.com/blog/synthetix-staking-rewards-issue-inefficient-reward-distribution/)

## Impact
* Rewards for the initial period can be lost and may remain in the contract indefinitely.

## Proof of Concept

``` solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.23;

import {Vm, Test, console} from "forge-std/Test.sol";
import {UniStaker} from "src/UniStaker.sol";
import {IERC20Delegates} from "src/interfaces/IERC20Delegates.sol";
import {ERC20, IERC20} from "openzeppelin/token/ERC20/ERC20.sol";
import {ERC20VotesMock} from "te
Stuck protocol fees in pool
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#L181-L198


# Vulnerability details

## Description
Claiming fees is a crucial aspect of providing staking rewards in UniStaker. However, this method of claiming fees in exchange for `PAYOUT_TOKEN` is more suitable for active and high-volume pools. The payout amount cannot be too low for high-volume pools, as it can extend the rewards distribution time.
Conversely, it cannot be too high because lower trading volume pools, as protocol fees may not become profitable enough to exchange with `PAYOUT_TOKEN`.

There are several situations where protocol fees may not be profitable to exchange with `PAYOUT_TOKEN`.

##### Low trading volume pools
In Uniswap, there are many pools with very low trading volumes, which may never be profitable for anyone to attempt to claim.

##### Pools lacking liquidity
If liquidity providers in a specific pool decide to withdraw liquidity, trading will cease, and protocol fees will remain in the contract, making it unprofitable to exchange them with `PAYOUT_TOKEN`.

Only the `V3FactoryOwner` contract, which serves as the owner of the `UniswapV3Factory` contract, possesses the ability to collect protocol fees through the `collectProtocol` function in the `UniswapV3Pool` contract.
``` solidity
function collectProtocol(
  address recipient,
  uint128 amount0Requested,
  uint128 amount1Requested
) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1){
  // ...
}
```
In the current state, without a function in the `V3FactoryOwner` contract enabling the admin (Uniswap Governance) to collect protocol fees, these fees will indeed remain stuck in the pool indefinitely.

## Impact
* Protocol fees will remain stuck in the pool forever.

## Tools Used
Manual Review


## Assessed type

Other
Malicious actor can DoS `UniStaker#permitAndStake()`
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#L292


# Vulnerability details

## Impact

A vulnerability in the `UniStaker#permitAndStake()` function allows a malicious actor to cause a Denial of Service (DoS) by frontrunning the function call with a `STAKE_TOKEN.permit()` call. This can lead to the `UniStaker#permitAndStake()` execution reverting, potentially resulting in a permanent DoS under certain conditions, especially when third-party services or protocols are involved.

This vulnerability poses a risk in scenarios where a third-party service or protocol, operating on behalf of its users through signed messages, integrates with UniStaker. A malicious actor exploiting this issue can cause a DoS for all or any users of the service. The potential for a permanent DoS arises particularly because the `permitAndStake()` function relies on a nonce for replay protection as specified in the [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612#specification), which, if used in a malicious manner, can prevent legitimate function execution.

**NOTE:** this issue may appear to be an OOS as per:

> 5. Permit based convenience methods front running griefing attack: someone could front-run the call to permit which would cause these functions to revert.

However, as demonstrated by the proof of concept (PoC) below, in certain situations, the `permitAndXXX()` functions represent the exclusive means for certain users to engage with UniStaker, not merely ***convenience*** methods.

Therefore, this issue should not be classified as OOS under the previously mentioned category.

## Proof of Concept

The `permitAndStake()` function calls `STAKE_TOKEN.permit()`. However, as per the [EIP-2612 specification](https://eips.ethereum.org/EIPS/eip-2612#specification) the permit object includes a nonce for replay protection. This means that any subsequent calls to `permit()` with the same signatures will fail.

Let's take the following example:

1. Alice opts to stake 10 WETH using a third-party dApp, CoolStaker, that integrates UniStaker for staking operations.
2. Alice signs a message off-chain for staking and sends this signature to CoolStaker to perform the staking operation on UniStaker on her behalf.
3. When CoolStaker attempts to execute `UniStaker#permitAndStake()` using Alice's signature, Bob, a malicious actor, sees the transaction in the mempool and frontruns it by calling `STAKE_TOKEN.permit()` with Alice's signature.
4. The CoolStaker's transaction to `UniStaker#permitAndStake()` reverts because the nonce associated with Alice's permit has already been used, resulting in a failure and DoS for Alice.

## Tools Used

Manual review

## Recommended Mitigation Steps

Use a `try / catch` block as per the following pattern proposed by Trust to make the implementation resilient to frontrunning DoS:

https://github.com/trust1995/trustlessPermit/blob/main/TrustlessPermit.sol


## Assessed type

DoS
Token owners can unfairly claim protocol fees through DOS attack
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#L189-L190
https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L856-L865


# Vulnerability details

## Description
In UniStaker, protocol fees are collected via the `claimFees` function, enabling MEV bots or anyone to pay in `WETH` and receive pool protocol fees for themselves.
``` 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);
}
```
`V3FactoryOwner` with access to retrieve protocol fees from pools will call the `collectProtocol` function within the pool contract to transfer the fees to the recipient provided by the caller.
``` solidity
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);
}
```
In decentralized exchanges like Uniswap, tokens within pools may not adhere to the same standards. Some of them incorporate functionalities such as blacklisting and pausing, which token owners can utilize, potentially leading to denial-of-service (DOS) attacks.

##### Examples
* In the `USDC/ETH` pool, the [USDC](https://etherscan.io/address/0x43506849d7c04f9138d1a2050bbf3a0c054402dd#code) token includes a blacklist functionality that can prevent the `claimFees` caller by adding the fees recipient to the blacklist and reverting the transacti
Attacker can make signature invalid by increasing `nonce` of depositor
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#L315-#L334


# Vulnerability details

## Vulnerability details
In all functions that use signature, _useNonce(_depositor) is a part of the hash. For example in `stakeOnBehalf()` function:

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

`_useNonce()` function in [OpenZeppelin's library](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Nonces.sol):

    function _useNonce(address owner) internal virtual returns (uint256) {
        // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
        // decremented or reset. This guarantees that the nonce never overflows.
        unchecked {
            // It is important to do x++ and not ++x here.
            return _nonces[owner]++;
        }
    }

It can be seen that whenever this function is called, _nonces[owner] will be increased, and it will make hash is not valid anymore.

Along with UNI token does not revert on transfer 0 amount [link](https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984#code):

    function transferFrom(address src, address dst, uint rawAmount) external returns (bool) {
        address spender = msg.sender;
        uint96 spenderAllowance = allowances[src][spender];
        uint96 amount = safe96(rawAmount, "Uni::approve: amount exceeds 96 bits");

        if (spender != src && spenderAllowance != uint96(-1)) {
            uint96 newAllowance = sub96(spenderAllowance, amount, "Uni::transferFrom: transfer amount exceeds spender allowance");
            allowances[src][spender] = newAllowance;

            emit Approval(src, spender, newAllowance);
        }

        _transferTokens(src, dst, amount);  //<---
        return true;
    }

    function _transferTokens(address src, address dst, uint96 amount) internal {
        require(src != address(0), "Uni::_transferTokens: cannot transfer from the zero address");
        require(dst != address(0), "Uni::_transferTokens: cannot transfer to the zero address");

        balances[src] = sub96(balances[src], amount, "Uni::_transferTokens: transfer amount exceeds balance");
        balances[dst] = add96(balances[dst], amount, "Uni::_transferTokens: transfer amount overflows");
        emit Transfer(src, dst, amount);

        _moveDelegates(delegates[src], delegates[dst], amount);  //<----
    }

    function _m
Staker with a new delegate address can steal funds from surrogate contract and brick withdrawal
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#L638-L664


# Vulnerability details

## Impact
During deployment of the `delegationsurrogate` contract the msg.sender is approved for unlimited transfers but this msg.sender is the address that initiated the stake transaction not `Unistaker.sol`

## Proof of Concept
users use any of the external stake functions which calls the internal `_stake` and this function calls `_fetchOrDeploySurrogate()` which if the delegate address is new deploys a new one using `new`.
```solidity
  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);
    _depositId = _useDepositId();


    totalStaked += _amount;
    depositorTotalStaked[_depositor] += _amount;
    earningPower[_beneficiary] += _amount;
    deposits[_depositId] = Deposit({
      balance: _amount,
      owner: _depositor,
      delegatee: _delegatee,
      beneficiary: _beneficiary
    });
    _stakeTokenSafeTransferFrom(_depositor, address(_surrogate), _amount);
    emit StakeDeposited(_depositor, _depositId, _amount, _amount);
    emit BeneficiaryAltered(_depositId, address(0), _beneficiary);
    emit DelegateeAltered(_depositId, address(0), _delegatee);
  }
```
This issue here is that `_stake` and `_fetchOrDeploySurrogate()` are all internal functions so the msg.sender does not change as the `new` opcode is used so the msg.sender being approved is the caller of the stake function.
```solidity
  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));
    }
  }
```
The staker is approved here and use transferfrom to steal back the UNI tokens and still continue earning rewards from the protocol

## Tools Used
manual review

## Recommended Mitigation Steps
The surrogate contracts should be made to approve`unistaker.sol` rather than msg.sender


## Assessed type

Access Control
Front-Running Potential in permitAndStake Function
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#L292


# Vulnerability details

## Impact
The permitAndStake function exposes a potential front-running vulnerability, where an attacker could intercept and manipulate transactions by submitting their own transaction with a higher gas price after observing a valid permit signature on the blockchain. If an attacker observes a valid permit signature for a specific amount and deadline, they can use that signature to execute their own stake transaction with modified parameters, potentially redirecting funds or changing the beneficiary address.

## Proof of Concept
```javascript
  function permitAndStake(
    uint256 _amount,
    address _delegatee,
    address _beneficiary,
    uint256 _deadline,
    uint8 _v,
    bytes32 _r,
    bytes32 _s
  ) external returns (DepositIdentifier _depositId) {
    STAKE_TOKEN.permit(msg.sender, address(this), _amount, _deadline, _v, _r, _s);
    _depositId = _stake(msg.sender, _amount, _delegatee, _beneficiary);
  }

```

## Tools Used
manual analysis

## Recommended Mitigation Steps
mitigate front-running attacks, such as using a commit-reveal scheme


## Assessed type

Other
Permit-related functions can be easily griefed by anyone
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#L292
https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5a2761c8277541a24bc551fbd624413b384bea94/src/UniStaker.sol#L360


# Vulnerability details

## Description

The UniStaker contract exposes two utility functions for staking with the `permit()` function, to save user the hassle of approving the UniStaker.
```
  function permitAndStake(
    uint256 _amount,
    address _delegatee,
    address _beneficiary,
    uint256 _deadline,
    uint8 _v,
    bytes32 _r,
    bytes32 _s
  ) external returns (DepositIdentifier _depositId) {
    STAKE_TOKEN.permit(msg.sender, address(this), _amount, _deadline, _v, _r, _s);
    _depositId = _stake(msg.sender, _amount, _delegatee, _beneficiary);
  }
```

The issue is that the (v,r,s) `permit()` signature exists in the mempool and can be easily executed directly on the token contract while frontrunning the `permitAndStake()` transaction. When that is done, the user's TX will revert because the nonce has already been spent and they will lose the gas fees spent for the TX without the staking functionality.

For more information refer to the [blog post](https://www.trust-security.xyz/post/permission-denied) on Trust Security's website.

## Impact

Griefing and loss of gas for anyone using the `permitAndStake()` and `permitAndStakeMore()` functions

## Tools Used

Manual audit

## Recommended Mitigation Steps

Use the `trustlessPermit()` variant [here](https://github.com/trust1995/trustlessPermit/tree/main).



## Assessed type

ERC20
The Factory incentive structure is deeply flawed due to sharing of payoutAmount across all pools
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#L72


# Vulnerability details

## Description

The new fee collection operates by any Bot / searcher calling `claimFees()` on the V3FactoryOwner. They pay the global `payoutAmount` defined in the contract (in WETH) in exchange for receiving the protocol fees from the pool. Note that for each `claimFees()` call, the fee recipients collectively and implicitly give up the cost of delivering the TX by the searcher. That cost is comprised of the gas expenses, bribing fees for frontrunning and any infrastructure overhead. To give an example, if the bot costs are $20 and `payoutAmount` is $50, the bot would start being incentivized to call `claimFees()` when the fee value is $70+. In that scenario, the racer overhead is $20/$70 = ~28% before the racer is even profitable.

It is hopefully clear that the `payoutAmount` needs to be very big in relation to the racer costs so that a massive chunk of the fees won't go to the race overhead. However, note that `payoutAmount` is the same for collection of all pools, while the rate of accumulation varies by several orders of magnitude between the pools. 
For example, USDC/USDT could make $5,000 per day in fees, while MEME/ETH would make $15 per day. 
The existence of wide fee ranges makes choosing a reasonable `payoutAmount` impossible. The higher it is, the lower the yield paid off for the racer, but it would make it take too long (if ever) for the low-capacity pairs to generate enough fees to make a racer pay for it.
Therefore it's important to be able to set `payoutAmount` individually for each pool, or have it calculated automatically based on pool trading volume in some way. This way the large pools could be set with a high amount to minimize leak of rewards, while it would be set low for small pools to not block off their fee stream. 

Note that the origin fee collection function didn't leak any value out of the protocol fees, the issue is introduced by the upgrade.

## Impact

The fee claiming design significantly reduces the yield paid to UNI stakers.

## Tools Used

Manual audit

## Recommended Mitigation Steps

`payoutAmount` should be a mapping item per pool which the `admin` can set.


## Assessed type

Other