xanoutas/solidity-security-findings huggingface dataset:
`claimYieldFeeShares` can lead to loss of `yieldFeeBalance `
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L611


# Vulnerability details

## Summary

`PrizeVault::claimYieldFeeShares` Transfers yield fee shares to the yield fee recipient. instead of decreasing transferred shares it resets the yieldFeeBalance even for partial shares 
## Impact


yieldFeeBalance will lost due to incorrect update

## Proof of Concept
```solidity
    /// @notice Transfers yield fee shares to the yield fee recipient
    /// @param _shares The shares to mint to the yield fee recipient
    /// @dev Emits a `ClaimYieldFeeShares` event
    /// @dev Will revert if the caller is not the yield fee recipient or if zero shares are withdrawn
    function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient {
        if (_shares == 0) revert MintZeroShares();

        uint256 _yieldFeeBalance = yieldFeeBalance;
        if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance);

        yieldFeeBalance -= _yieldFeeBalance;
        //@audit-issue it should be yieldFeeBalance -= shares

        _mint(msg.sender, _shares);

        emit ClaimYieldFeeShares(msg.sender, _shares);
    }
```
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L611

Transferring `_shares` should decrease `yieldFeeBalance` by `_shares`, while it resets the yieldFeeBalance to zero 

## Tools Used
Manual
## Recommended Mitigation Steps

```solidity
-     yieldFeeBalance -= _yieldFeeBalance;
+     yieldFeeBalance -= _shares;
```



## Assessed type

Math
The entire yieldFeeBalance could be permanently lost due to wrong balance update
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L617


# Vulnerability details

## Impact
The entire yieldFeeBalance could be permanently lost due to wrong balance update

## Proof Of Concept
The issue is in the logic of the `claimYieldFeeShares()` function which is intended to allow the yield fee recipient to transfer out yield fee shares. The caller is allowed to specify a non-zero amount of the fee shares to claim and checks that the amount specified is not more than the fee balance else reverts. However, after this check if updates the yield fee balance and then mint the shares for the recipient. The issue here is that the yield fee balance gets updated by the overall balance instead of the amount being claimed:
```solidity
function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient {
        if (_shares == 0) revert MintZeroShares();

        uint256 _yieldFeeBalance = yieldFeeBalance;
        if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance);

@>      yieldFeeBalance -= _yieldFeeBalance;

        _mint(msg.sender, _shares);

        emit ClaimYieldFeeShares(msg.sender, _shares);
    }
```
As you can see, it minuses the entire balance regardless of the amount of shares to be claimed. This means even when a fee recipient attempts to claim any small amount of fee share, the entire yieldFeeBalance will be permanently lost.

## Tools Used
Manual Review

## Recommendation
I recommend adding this fix:
```diff
- yieldFeeBalance -= _yieldFeeBalance;
+ yieldFeeBalance -= _shares;
```


## Assessed type

Error
claimYieldFeeShares implementation is flawed
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L611-L622


# Vulnerability details

## Impact
The `claimYieldFeeShares` function allows the `YieldFeeRecipient` to claim an amount of shares they want to claim from the `yieldFeeBalance` an error happens in the implementation of this function the `YieldFeeRecipient` can specify any amount of shares as long as it's not 0 or larger than the `yieldFeeBalance` when the `YieldFeeRecipient` claims and does not specify the full `yieldFeeBalance` the entire `yieldFeeBalance` is subtracted from the `yieldFeeBalance` leaving it at 0 even if the shares claim was a smaller portion.

## Proof of Concept
Consider the following:
`yieldFeeBalance` = 1e18
The `YieldFeeRecipient` calls `claimYieldFeeShares` with 1 share
`yieldFeeBalance` is now set to 0 and the `YieldFeeRecipient` is minted 1 share 
leaving the rest of the remaining tokens in the contract really to be taken by the next person to call deposit. 

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L614-L617

## Tools Used
Vscode
## Recommended Mitigation Steps
Consider subtracting the shares or only allowing for the full amount to be claimed


## Assessed type

Invalid Validation
Use safeTransfer/safeTransferFrom instead of transfer/transferFrom
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVaultFactory.sol#L118
https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L939


# Vulnerability details

## Impact
The ERC20.transfer() and ERC20.transferFrom() functions return a boolean value indicating success. Some tokens do not revert on failure, but instead return false.(https://github.com/d-xo/weird-erc20/#no-revert-on-failure)
This can lead to serious problems where tokens are not actually transferred as expected, but the code still does not revert.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

safeTransfer was used on https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L854, but transfer was used on https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L939 and https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVaultFactory.sol#L118 this can lead to serious problems where tokens are not actually transferred as expected.

## Tools Used
Manual review

## Recommended Mitigation Steps
Use safeTransfer from SafeERC20 as in https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L854


## Assessed type

ERC20
Prize Vault: A malicious early user/attacker can manipulate the price per share to take an unfair share of future users' deposits
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L475-L479
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L341-L352


# Vulnerability details

## Impact
The attacker can profit from future users' deposits. While the late users will lose part of their funds to the attacker

## Proof of Concept
A malicious early user can call `PrizeVault::deposit()` with `1 wei` of `asset` token as the first depositor, and get `1 wei` of shares

Then the attacker can send `10000e18 - 1` of `asset` tokens and inflate the price per share from 1.0000 to an extreme value of `1.0000e22` (from `(1 + 10000e18 - 1) / 1`) .

As a result, the future user who deposits `19999e18` will only receive `1 wei` (from `19999e18 * 1 / 10000e18`) of shares token

They will immediately lose `9999e18` or half of their deposits if they call `PrizeVault::redeem()` right after `PrizeVault::deposit()`

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L475-L479
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L341-L352

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider requiring a minimal amount of share tokens to be minted for the first minter, and send a portion of the initial mints as a reserve to the vault or burn it so that the price per share can be more resistant to manipulation


## Assessed type

ERC4626
`claimYieldFeeShares` reset the `yieldFeeBalance`
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L617


# Vulnerability details

## Impact
After the `yieldFeeRecipient` invokes part of the yield fee, all yield fee will be reset, and the `yieldFeeRecipient` cannot obtain the remaining fee, resulting in the loss of user funds.

## Proof of Concept

Let's look at this function,`yieldFeeBalance` should subtract `_shares` instead of `_yieldFeeBalance`:

```solidity
    function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient {
        if (_shares == 0) revert MintZeroShares();

        uint256 _yieldFeeBalance = yieldFeeBalance;
        if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance);

@>      yieldFeeBalance -= _yieldFeeBalance;

        _mint(msg.sender, _shares);

        emit ClaimYieldFeeShares(msg.sender, _shares);
    }
```


## Tools Used
vscode, manual

## Recommended Mitigation Steps

```diff
    function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient {
        if (_shares == 0) revert MintZeroShares();

        uint256 _yieldFeeBalance = yieldFeeBalance;
        if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance);

-       yieldFeeBalance -= _yieldFeeBalance;
+       yieldFeeBalance -= _shares;

        _mint(msg.sender, _shares);

        emit ClaimYieldFeeShares(msg.sender, _shares);
    }
```


## Assessed type

Error
`liquidationPair` can add any number of `_yieldFee`
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L928


# Vulnerability details

## Impact
`liquidationPair` adds any amount of yield fees, resulting in the loss of funds.

## Proof of Concept

Let's first look at the problematic function _withdraw,

This function has two if decisions, and if neither of these two conditions is satisfied, then the function does nothing.

```solidity
    function _withdraw(address _receiver, uint256 _assets) internal {
        // The vault accumulates dust from rounding errors over time, so if we can fulfill the withdrawal from the
        // latent balance, we don't need to redeem any yield vault shares.
        uint256 _latentAssets = _asset.balanceOf(address(this));
@>      if (_assets > _latentAssets) {
            // The latent balance is subtracted from the withdrawal so we don't withdraw more than we need.
            uint256 _yieldVaultShares = yieldVault.previewWithdraw(_assets - _latentAssets);
            // Assets are sent to this contract so any leftover dust can be redeposited later.
            yieldVault.redeem(_yieldVaultShares, address(this), address(this));
        }

         //@audit _assets <= _latentAssets && _receiver == address(this) nothing to do 
@>      if (_receiver != address(this)) {
            _asset.transfer(_receiver, _assets);
        }
    }
```

transferTokensOut calls this function:

```solidity
  function transferTokensOut(
        address,
        address _receiver,
        address _tokenOut,
        uint256 _amountOut
    ) public virtual onlyLiquidationPair returns (bytes memory) {
        if (_amountOut == 0) revert LiquidationAmountOutZero();

        uint256 _availableYield = availableYieldBalance();
        uint32 _yieldFeePercentage = yieldFeePercentage;

        // Determine the proportional yield fee based on the amount being liquidated:
        uint256 _yieldFee;
        if (_yieldFeePercentage != 0) {
            // The yield fee is calculated as a portion of the total yield being consumed, such that 
            // `total = amountOut + yieldFee` and `yieldFee / total = yieldFeePercentage`. 
            _yieldFee = (_amountOut * FEE_PRECISION) / (FEE_PRECISION - _yieldFeePercentage) - _amountOut;
        }

        // Ensure total liquidation amount does not exceed the available yield balance:
        if (_amountOut + _yieldFee > _availableYield) {
            revert LiquidationExceedsAvailable(_amountOut + _yieldFee, _availableYield);
        }

        // Increase yield fee balance:
        if (_yieldFee > 0) {
@>          yieldFeeBalance += _yieldFee;
        }

        // Mint or withdraw amountOut to `_receiver`:
        if (_tokenOut == address(_asset)) {
@>          _withdraw(_receiver, _amountOut);            
        } else if (_tokenOut == address(this)) {
            _mint(_receiver, _amountOut);
        } else {
            revert LiquidationTokenOutNotSuppo
Inflation of Minting Ratios in Non-standard PrizeVault NOT prevented via TwabERC20 and TwabController
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L440-L450
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L474-L486
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/TwabERC20.sol#L71-L79


# Vulnerability details

## Impact
The issue revolves around the potential for a customized deployment of the `PrizeVault.sol` contract, wherein the deployer alters the intended 1:1 asset-to-share minting ratio. This could lead to disproportionate allocation of shares relative to the deposited assets, effectively inflating the balance and influence of participants associated with this non-standard vault within the prize pool system.

The impact of such an alteration could be significant, undermining the fairness and integrity of the prize winning distribution mechanism. Participants associated with the standard vault expecting a transparent and equitable system might find their chances of winning prizes unfairly diminished due to inflated balances of select users. This could erode trust in the platform, deter engagement, and potentially lead to a decline in participation.

While non-standard prize vaults are out of scope in this contest, `TwabERC20.sol` (in collaboration with `TwabController.sol`) failing to circumvent this exploit certainly is. 

## Proof of Concept
The `PrizeVault.sol` contract, through inheritance of `TwabERC20`, interfaces with `TwabController` for managing time-weighted average balances. The potential issue arises when the `PrizeVault.sol` contract is customized to alter the minting ratio, such as changing the 1:1 deposit-to-share ratio to something more inflated like 1000:1.

This alteration is not directly prevented by `TwabController.sol`, which focuses on TWAB management and does not enforce strict minting ratios. The critical sections of code allowing for such an exploit would be within the custom `PrizeVault` contract, particularly in the functions handling deposits (deposit and mint) and their interaction with `_mint` function in the TwabERC20 contract.

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/TwabERC20.sol#L76-L79

```solidity
    function _mint(address _receiver, uint256 _amount) internal virtual override {
        twabController.mint(_receiver, SafeCast.toUint96(_amount));
        emit Transfer(address(0), _receiver, _amount);
    }
```
A hypothetical exploit scenario could be constructed as follows:

1. A customized `PrizeVault` contract is deployed with altered minting logic to provide 1000 shares for every 1 unit of deposited asset.
2. All participants' deposits receives inflated share counts that would easily dwarf the [sponsor efforts](https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L548-L565) by other vault owners.
3. These inflated shares enhance the participants' chances of winning within the prize pool due to i
The first deposit on `PrizeVault` can be stolen via inflation attack.
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L500-L508


# Vulnerability details

## Impact
Due to lack of internal accounting of balance which resulted to reliance on balanceOf() ERC20 method, `PrizeVault` is susceptible to first depositor inflation attack.

Here's the exploit scenario:

1. Upon deployment of the contract, it is initialized with `1e5` usdc (0.1 usdc) as a `YIELD_BUFFER`.
2. An honest user deposits 100 USDC via `deposit()` and is expected to receive 100 shares because it's 1:1 as seen in the function `previewDeposit()`.
3. However an attacker sees it in the mempool and front-runs it with the following transactions:
    - Attacker deposits 1 USDC via `deposit()` and results to receiving 1 share. 
    - Attacker donates 100 USDC.
4. Now at this point, the honest user's transaction went through (after the front-running) but based on `previewWithdraw()`, his/her share is equal to zero as shown in the calculation below:
    - shares = assets * totalDebt / totalAssets
    - shares = (100 usdc * 1 share) / (100 usdc + 0.1 usdc + 1usdc)
    - shares = 0
    
    NOTE: The `1e5` usdc "yield buffer" is actually "donated". Hence no shares equivalent and didn't mitigate the attack.
    
5. The attacker withdraws all assets via exact share mechanism `redeem()` which invokes `previewRedeem()`, then invokes `convertToAssets()` and calculates the assets received as follows:
    - assets = shares * totalAssets / totalDebt
    - assets = 1 * (200 usdc + 0.1 usdc + 1 usdc) / 1 share
    - assets = 200 usdc + 0.1 usdc + 1 usdc
    - assets = 201.1 usdc

6. The attacker donated 100 usdc, deposited 1 usdc and ended up having 201.1 usdc.

The impact is HIGH, equivalent to the amount of first depositor's lost funds per multiple contracts per network deployed.

## Proof of Concept

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement an internal accounting of vault balance to avoid reliance on balanceOf() ERC20 method.


## Assessed type

ERC4626
Executing 'PrizeVault::claimYieldFeeShares' could lead to loss of potential yield shares
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L611-L622


# Vulnerability details

## Impact
Potential yield shares could be lost due to applying zero value on yieldFeeBalance, no matter the _shares input value 

## Proof of Concept
1. yieldFeeRecipient executes 'PrizeVault::claimYieldFeeShares' with input value lower than yieldFeeBalance.
2. yieldFeeBalance goes to zero(yieldFeeBalance -= _yieldFeeBalance), no matter the input value
3. Input value shares are minted 
4. yieldFeeBalance - _shares asset value is going to be wasted

## Tools Used

## Recommended Mitigation Steps
There are 2 main ways depending on the protocol logic to solve this issue:
    1. Subtract _shares input value from yieldFeeBalance instead of _yieldFeeBalance as shown below:
    
    function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient {
        if (_shares == 0) revert MintZeroShares();

        uint256 _yieldFeeBalance = yieldFeeBalance;
        if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance);

        --- yieldFeeBalance -= _yieldFeeBalance;
        +++ yieldFeeBalance -= _shares;
        _mint(msg.sender, _shares);

        emit ClaimYieldFeeShares(msg.sender, _shares);
    }
2. Validate _shares input value not to be lower than yieldFeeBalance
    3. Instead using the input value, use _yieldFeeBalance as shown below:
   
    function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient {
        if (_shares == 0) revert MintZeroShares();

        uint256 _yieldFeeBalance = yieldFeeBalance;
        if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance);

        yieldFeeBalance -= _yieldFeeBalance; // @audit if _shares <= _yieldFeeBalance, yieldFeeBalance goes to 0?

        --- _mint(msg.sender, _shares);
        +++ _mint(msg.sender, _yieldFeeBalance);

        emit ClaimYieldFeeShares(msg.sender, _shares);
    }


## Assessed type

Invalid Validation
`convertToAssets()` and `convertToShares()` deviates from the standard ERC-4626 implementation
Audit: 2024-03-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L341-L366


# Vulnerability details

## Impact

Functions `convertToAssets()` and `convertToShares()` deviate from the standard ERC-4626 implementation.
During the code-review process, functions were compared with Open Zeppelin's ERC-4626 implementation.
Some deviations were detected which might lead to incorrect calculations.
Incorrect implementation of `convertToAssets()` and `convertToShares()` may lead to fund loss.


## Proof of Concept


Firstly, let's examine `convertToAssets()` function:

[File: PrizeVault.sol](https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L355-L366)
```
    function convertToAssets(uint256 _shares) public view returns (uint256) {
        uint256 totalDebt_ = totalDebt();
        uint256 _totalAssets = totalAssets();
        if (_totalAssets >= totalDebt_) {
            return _shares;
        } else {
            // If the vault controls less assets than what has been deposited a share will be worth a
            // proportional amount of the total assets. This can happen due to fees, slippage, or loss
            // of funds in the underlying yield vault.
            return _shares.mulDiv(_totalAssets, totalDebt_, Math.Rounding.Down);
        }
    }
```

This function can be rewritten to:

```
   function convertToAssets(uint256 _shares) public view returns (uint256) {
        if (totalAssets() >= totalDebt()) return _shares;
            return _shares.mulDiv(totalAssets(), totalDebt(), Math.Rounding.Down);
    }
```

Let's compare it to Open Zeppelin's implementation:

```
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8cc7f2dcbf368f2a7ea491389dae41f01c16e352/contracts/token/ERC20/extensions/ERC4626.sol#L232

    function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) {
        return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
    }
```

We can spot some deviations from the OZ's ERC-4626 implementation

* In `PrizeVault`, the first `mulDiv()` argument is `totalAssets()`, while in OZ's: `totalAssets() + 1`
* In `PrizeVault,`, the second `mulDiv()` argument is `totalDebt()`, which is defined as below:

[File: PrizeVault.sol](https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L790)
```
    function _totalDebt(uint256 _totalSupply) internal view returns (uint256) {
        return _totalSupply + yieldFeeBalance;
    }
```

This is not the same as `totalSupply()` from OZ's implementation. This implies, that `PrizeVault`'s and OZ's implementations differs.

As we can see, the `PrizeVault` implementation of `_totalDebt()` includes the `yieldFeeBalance`. `convertToAssets()` calls `totalDebt()`. `totalDebt()` calls `_totalDebt