xanoutas/solidity-security-findings huggingface dataset:
Attacker can force users to delegate to `SPONSORSHIP_ADDRESS`
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L480-L482
https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L494-L504
https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L982-L994


# Vulnerability details

## Impact

An attacker can change the delegatee of a user who deposited into the vault to the `SPONSORSHIP_ADDRESS` address by calling one of the functions `sponsor` or `sponsorWithPermit` and giving the address of the user as `_receiver`.

The impact of this issue is that the attacker can stop users from winning prizes because the users who have delegated to the `SPONSORSHIP_ADDRESS` address get their chances of winning prizes revoked.

## Proof of Concept

As the functions `sponsor` and `sponsorWithPermit` do the same thing except the later include a permit functionality which doesn't have an impact on this issue, i will explain how this issue can be exploited with the `sponsor` function and the same apply to the `sponsorWithPermit` function.

We start with the `sponsor` function :

```solidity
function sponsor(uint256 _assets, address _receiver) external returns (uint256) {
    return _sponsor(_assets, _receiver);
}
```

As you can see the function can be called by anyone, and it takes the `_assets` amount of asset to deposit into the vault and a `_receiver` address that the user wants to deposit to, the function calls the internal function `_sponsor` below :

```solidity
function _sponsor(uint256 _assets, address _receiver) internal returns (uint256) {
    uint256 _shares = deposit(_assets, _receiver);

    if (
      _twabController.delegateOf(address(this), _receiver) != _twabController.SPONSORSHIP_ADDRESS()
    ) {
      _twabController.sponsor(_receiver);
    }
    
    emit Sponsor(msg.sender, _receiver, _assets, _shares);
    
    return _shares;
}
```

Inside this function we can see that it call the `deposit` function for performing the deposit of the asset amount, and then the function checks the current delegatee of the `_receiver` address, if this one is different from the `SPONSORSHIP_ADDRESS` address the function will call the `_twabController.sponsor` function to changes the `_receiver` delegatee address to the `SPONSORSHIP_ADDRESS` address : 

```solidity
// TwabController.sponsor
function sponsor(address _from) external {
    _delegate(msg.sender, _from, SPONSORSHIP_ADDRESS);
}
```

In the comments of the `TwabController` contract we can see the following : 

```solidity
/// @notice Allows users to revoke their chances to win by delegating to the sponsorship address.
address public constant SPONSORSHIP_ADDRESS = address(1);
```

Basically any users who is delegating to the `SPONSORSHIP_ADDRESS` address is not included in the prize distribution and has no chances to win.

So to resume any attacker can change all other users delegat
Deposit transaction is prone to being front-run by bad actors.
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L407-L415


# Vulnerability details

## Impact
It is possible for an attacker to front-run a user's deposit transaction while transferring fewer amount of assets than the user and minting an equivalent amount of shares as the user could have.

## Proof of Concept
The scenario described below is for the `deposit` function in the `Vault` contract.

Consider this scenario with Party A and Party B when `_vaultAssets` are not 0. Party B calls the deposit function such that the asset amount satisfies the condition: 

`_assets` > `_vaultAssets`, where `_vaultAssets` is the balance of the vault for `_asset` token. 

This means that when the transaction gets executed the asset amount that is transferred from party B is:

` _assetsDeposit = _assets - _vaultAssets;`.

Then the shares are minted based on the value of `_assets` to party B.

But, this design is prone to front-running. If party A is a malicious actor, they would see party B's transaction and send a similar transaction with similar inputs with higher gas fees to get their transaction executed first. 

This would mean that party A would only be transferring ` _assetsDeposit = _assets - _vaultAssets;` from their address. And after this transaction, `_vaultAssets` would be 0 as they get transferred to the yield vault. Then party B's transaction executes after party A's. Since `_vaultAssets` are 0, the amount that will be transferred from party B will be equal to the `_assets` in the input which is more than the amount that was transferred from party A (` _assetsDeposit = _assets - _vaultAssets;`).

What is important to note is that party B could be paying a higher amount of assets (for almost an equivalent amount of shares) than party A, which is unfair to party B. This scenario is significant when the exchange rate for party B does not deviate much after party A's transaction.   

## Tools Used
Manual Review

## Recommended Mitigation Steps
It is recommended that the protocol does not implement the deposit function in this manner. Assets supplied in the input should always be transferred from the user and not the `_assetsDeposit` amount.

Or if the protocol wants to continue using `_assetsDeposit`, then the protocol should mint shares which are calculated from `_assetsDeposit` rather than `_assets`. The convertToShares function should take `_assetsDeposit` as the input, not `_assets`.





## Assessed type

MEV
Claim failure can result to loss of funds
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L407-L456


# Vulnerability details

## Impact
Caller will lose funds through gas fees when claim fails

## Proof of Concept
The claimPrize function allows any caller to claim a prize by providing the necessary parameters: _winner (address of the winner), _tier (prize tier), _prizeIndex (prize index), _prizeRecipient (address to receive the prize), _fee (claim fee), and _feeRecipient (address to receive the fee).
However, the function does not verify that the caller (msg.sender) is actually the winner of the specified prize. It relies on the _isWinner internal function to check whether the provided _winner, _tier, and _prizeIndex match the winning criteria. If _isWinner returns true, the prize is claimed.
The vulnerability arises from the fact that _isWinner relies solely on the provided parameters without validating the caller's identity. This means that anyone can call the claimPrize function and provide arbitrary _winner, _tier, and _prizeIndex values. However the contract will revert with a DidNotWin error leading to loss of funds through gas fees


## Tools Used
Manual
## Recommended Mitigation Steps
Add an additional check at the beginning of theclaimPrize  function to ensure that the caller is the actual winner of the prize they are claiming. This can be done by comparing msg.sender with the _winner parameter and reverting if they don't match.


## Assessed type

Token-Transfer
No access control for `mintYieldFee()`
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L394


# Vulnerability details

## Impact
Anyone can mint the yield fee using `mintYieldFee()`.

## Proof of Concept
`mintYieldFee()` mints the shares for yield fee.

```solidity
  function mintYieldFee(uint256 _shares, address _recipient) external {
    _requireVaultCollateralized();
    if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply);

    _yieldFeeTotalSupply -= _shares;
    _mint(_recipient, _shares);

    emit MintYieldFee(msg.sender, _recipient, _shares);
  }
```

This function mints the shares to the `_recipient` and reduces `_yieldFeeTotalSupply` accordingly.

But there is no access control here and anyone can steal the yield fee using this function.

## Tools Used
Manual Review

## Recommended Mitigation Steps
`mintYieldFee()` should have a whitelist for callers.


## Assessed type

Access Control
The output amount validation in `Vault.liquidate()` is not correct.
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/tree/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L566-L568


# Vulnerability details

## Impact
The output amount validation is not correct in `Vault.liquidate()`, so the method might accept invalid output amount and refuse valid output amount.


## Proof of Concept
In `Vault.liquidate()`, there is a validation about the output share amount should be less than or equal to the liquidatable yield.

```solidity
    uint256 _liquidableYield = _liquidatableBalanceOf(_tokenOut);
    if (_amountOut > _liquidableYield) 
      revert LiquidationAmountOutGTYield(_amountOut, _liquidableYield); 
```

The liquidatable yield amount is in underlying asset token. So the comparison between the share amount and the underlying asset amount is not appropriate. 

We could get share tokens from asset tokens via exchange rate. The vault gets `_liquidableYield` and mints `_amountOut`, so the correct asset amount equivalent to `_amountOut` of the share token will be `_amountOut` * exchange rate. The correct validation should use the asset amount and the current implementation is not correct when the exchange rate is not 1.


## Tools Used
Manual Review

## Recommended Mitigation Steps
We should use the underlying equivalent with the exchange rate for the validation.


## Assessed type

Error
Users might lose their balances when they set delegates
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/TwabController.sol#L648-L664


# Vulnerability details

## Impact
Undelegated users will lose their balances if they set delegate to themselves.

## Proof of Concept
A User can set a delegate and the delegated balance of the user will be accounted on the delegate's delegated balance in the TWAB controller.
The internal method `_delegate` will handle this functionality as follows:

```solidity
  function _delegate(address _vault, address _from, address _to) internal {
    address _currentDelegate = _delegateOf(_vault, _from);
    if (_to == _currentDelegate) {
      revert SameDelegateAlreadySet(_to);
    }

    delegates[_vault][_from] = _to;

    _transferDelegateBalance(
      _vault,
      _currentDelegate,
      _to,
      uint96(userObservations[_vault][_from].details.balance)
    );

    emit Delegated(_vault, _from, _to);
  }
```

If a user sets his delegate to himself when he is not delegated through the external `delegate` method, `_to` is address(0), and `_currentDelegate` will be same as `_from` and this passes the if statement in the next line. But this is the case when the delegates are the same, so we need to revert here.

After that, `delegates` storage variable is not changed. In this situation, `_transferDelegateBalance` will be called and this will cause the user loses his balance.


## Tools Used
Manual Review

## Recommended Mitigation Steps
We should revert when `_currentDelegate` is same as `_from` and `_to` is address(0).


## Assessed type

Error
The exchange rate is decreasing in Vault
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/tree/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L1168-L1187


# Vulnerability details

## Impact
The exchange rate of the vaults will be decreasing and this will block core functionalities.


## Proof of Concept
The exchange rate of the Vault is calculated as follows:

```
min(_withdrawableAssets, _totalSupplyToAssets) * _assetUnit / _totalSupplyAmount
= min(_withdrawableAssets * _assetUnit / _totalSupplyAmount, _lastRecordedExchangeRate)
```

So the resulting exchange rate from `_currentExchangeRate` method is less than or equal to `_lastRecordedExchangeRate` in normal situation when the total supply of current vault and the withdrawable assets from the yield vault is not zero.


`_lastRecordedExchangeRate` is only updated by the result of `_currentExchangeRate` method, so the exchange rate of the vault will be decreasing. It starts with 1, so the exchange rate will be less than 1. In this situation, `maxDeposit` and `maxMint` will return 0, so depositing to the vault will not work. The liquidation pair also can't call `liquidate`. Since these functionalities take important roles in the protocol, so the impact will be huge.


## Tools Used
Manual Review

## Recommended Mitigation Steps
I think the implementation of `_currentExchangeRate` is not correct at the moment, but I am not sure how to mitigate this method.


## Assessed type

Error
Malicious claimer could arbitrage the prize-claiming functionality
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L611


# Vulnerability details

## Impact
The  `_feePerClaim` is a user controlled parameter which tops at `tierLiquidity.prizeSize` for a given `_tier` (see [here](https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L417) for that). That means the `CLAIMER` can set arbitrary fees for a given call to `claimPrize` to increase maliciously the collected fees with

```
// PrizePool.sol#L455
claimerRewards[_feeRecipient] += _fee;
``` 

(see [here](https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L455))

and collect them with `withdrawClaimRewards`. The severity here is because winners will be losing part of their prizes (or everything if there is an issue with the `_getTier` function in `TieredLiquidityDistributor.sol` file that computes the wrong `tierLiquidity.prizeSize`) because of this "arbitrage" from the `CLAIMER`

(for the judges' confort, so they do not go from one file to another, here is the affected code)
```
// Vault.sol#L609
function claimPrizes(
    uint8 _tier,
    address[] calldata _winners,
    uint32[][] calldata _prizeIndices,
    uint96 _feePerClaim, <--------------------------------------------------
    address _feeRecipient                                                  |
  ) external returns (uint256) {                                           |
    if (msg.sender != _claimer) revert CallerNotClaimer(msg.sender, _claimer);
                                                                           |
    uint totalPrizes;                                                      |
                                                                           |
    for (uint w = 0; w < _winners.length; w++) {                           |
      uint prizeIndicesLength = _prizeIndices[w].length;                   |
      for (uint p = 0; p < prizeIndicesLength; p++) {                      |
        totalPrizes += _claimPrize(                                        |
          _winners[w],                                                     |
          _tier,                                                           |
          _prizeIndices[w][p],                                             |
          _feePerClaim, // <--------------------------------------------- HERE
          _feeRecipient
        );
      }
    }

    return totalPrizes;
  }


// Vault.sol#L1046
function _claimPrize(
    address _winner,
    uint8 _tier,
    uint32 _prizeIndex,
    uint96 _fee,
    address _feeRecipient
  ) internal returns (uint256) {
    VaultHooks memory hooks = _hooks[_winner];
    address recipient;
    if (hooks.useBeforeClaimPrize) {
      recipient = hooks.implementation.beforeClaimPrize(_winner, _tier, _prizeIndex);
    } else {
      recipient = _winner;
    }

    uint prizeTotal
Incorrect amount of tokens is transferred before updating the accumulator and emitting the ContributePrizeTokens event
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L311-L330


# Vulnerability details

## Impact
An attacker can call the contributePrizeTokens function with a lower amount of tokens than claimed, misleading the contract to update the accumulator and emit the event based on the incorrect amount. As a result, the prize pool's internal state will be inconsistent, and participants may not receive the correct rewards.

## Proof of Concept
In the contributePrizeTokens function, the amount of tokens being contributed is not verified against the actual token transfer made by the caller. The contract assumes that the caller has already transferred the correct amount of tokens to the contract before calling the function.
This vulnerability can be exploited by an attacker to manipulate the prize pool's state and potentially drain the tokens from the contract. 

## Tools Used
Manual
## Recommended Mitigation Steps
Add require(msg.value == _amount, "Tokens not transferred") to the contributePrizeTokens function


## Assessed type

Token-Transfer
Incorrect distribution of shares and liquidity as a result of total number of shares not equaling 100
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L235


# Vulnerability details

## Impact
If the shares are not properly validated and do not add up to 100, there will be an imbalance in the distribution of funds resulting in loss of funds or locked funds that cannot be accessed or distributed correctly.

## Proof of Concept
The constructor of the TieredLiquidityDistributor contract sets the values of _tierShares, _canaryShares, and _reserveShares based on the arguments passed to it. However, it does not include any validation to ensure that the sum of these values equals 100, which is a common requirement for distributing shares.
## Tools Used
Manual
## Recommended Mitigation Steps 
Modify  the constructor to ensure that _tierShares + _canaryShares + _reserveShares == 100. This check will ensure the correct distribution of shares and liquidity among tiers and prevent any unintended consequences due to incorrect share allocations.


## Assessed type

Invalid Validation
The liquidate function lets the caller mint amountOut tokens without providing any 
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L565


# Vulnerability details

## Impact
Ther are a lot of check to ensure the parameters of the `liquidate` function are correct. However, it does not checki for `_amountIn` to NOT be 0, thus it lets the caller proceed and mint `_amountOut` tokens to `_account` without providing any

## Proof of Concept
```
function liquidate(
    address _account,
    address _tokenIn,
    uint256 _amountIn,
    address _tokenOut,
    uint256 _amountOut
  ) public virtual override returns (bool) {
    _requireVaultCollateralized();
    if (msg.sender != address(_liquidationPair)) //  modify en storage y blolquear
      revert LiquidationCallerNotLP(msg.sender, address(_liquidationPair));
    if (_tokenIn != address(_prizePool.prizeToken()))
      revert LiquidationTokenInNotPrizeToken(_tokenIn, address(_prizePool.prizeToken()));
    if (_tokenOut != address(this)) //  no sería $POOL
      revert LiquidationTokenOutNotVaultShare(_tokenOut, address(this));
    if (_amountOut == 0) revert LiquidationAmountOutZero();

    // what about _amountIn ??
    ...
```

The only place it is used is in 

```
_prizePool.contributePrizeTokens(address(this), _amountIn); // #L570 in PrizePool.sol
```

which calls

```
function contributePrizeTokens(address _prizeVault, uint256 _amount) external returns (uint256) {
    uint256 _deltaBalance = prizeToken.balanceOf(address(this)) - _accountedBalance();
    if (_deltaBalance < _amount) {
      revert ContributionGTDeltaBalance(_amount, _deltaBalance);
    }
    DrawAccumulatorLib.add(
      vaultAccumulator[_prizeVault],
      _amount,
      lastClosedDrawId + 1,
      smoothing.intoSD59x18()
    );
    DrawAccumulatorLib.add(
      totalAccumulator,
      _amount,
      lastClosedDrawId + 1,
      smoothing.intoSD59x18()
    );
    emit ContributePrizeTokens(_prizeVault, lastClosedDrawId + 1, _amount);
    return _deltaBalance;
  }
```

and neither validate the `_amount` parameter, thus it proceeds to the [mint](https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L584) call in line 584 of Vault.sol without reverting

## Tools Used
Manual analysis

## Recommended Mitigation Steps
Just check `_amountIn` to not be 0


## Assessed type

Invalid Validation
Anyone can mint to themselves type(uint96).max if _isVaultCollateralized() returns true
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L440


# Vulnerability details

## Impact
There is no check that ensures the caller to `mint` is a trusted one. Moreover, there is a flaw which lets anyone to mint `type(uint96).max` number of shares

## Proof of Concept
First, the `mint` function does not implement any check for the caller to be someone with a role `MINTER`, `OWNER` or whatever (like in many other projects), thus it lets anyone to call it. 

``` 
function mint(uint256 _shares, address _receiver) public virtual override returns (uint256) {
    uint256 _assets = _beforeMint(_shares, _receiver);

    _deposit(msg.sender, _receiver, _assets, _shares);

    return _assets;
  }
``` 

Second, the number of `_assets` is given by `_beforeMint`, which checks that `_shares > maxMint(_receiver)` for the `_receiver`

```
function _beforeMint(uint256 _shares, address _receiver) internal view returns (uint256) {
    if (_shares > maxMint(_receiver)) revert MintMoreThanMax(_receiver, _shares, maxMint(_receiver));
    return _convertToAssets(_shares, Math.Rounding.Up);
  }

function maxMint(address) public view virtual override returns (uint256) {
    return _isVaultCollateralized() ? type(uint96).max : 0;
  }
```

However, it does NOT take into account the `_receiver` and will return `type(uint96).max` if the vault is collaterized (ALWAYS, even when `_currentExchangeRate() == _assetUnit`, making the exchange rate drop after the transaction due to the huge increment in one of the sides). Thus a malicious transaction could call the `mint` and give the caller "unlimited" tokens

## Tools Used
Manual analysis

## Recommended Mitigation Steps
First add the propper checks to the `mint` function to ensure the caller is a trusted one (`require`s and all of that). Second, do NOT let the `maxMint` function to proceed returning `type(uint96).max` without validating the `_receiver` parameter (either giving him an amount proportional to something, like its deposit, IDK, that's up to the devs)


## Assessed type

Access Control