xanoutas/solidity-security-findings huggingface dataset:
Incorrect implementation of `previewMint`
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#L447-L450


# Vulnerability details

## Impact

Function `previewMint()` is incorrectly implemented. It should return the number of assets we can get from the provided shares. However, it directly returns the number of shares instead. Returning incorrect result may lead to fund loss.

## Proof of Concept

[File: PrizeVault.sol](https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L447-L450
)
```
    function previewMint(uint256 _shares) public pure returns (uint256) {
        // shares represent how many assets an account has deposited, so they are 1:1 on mint
        return _shares;
    }
```

As demonstrated above, function `previewMint()` takes as a parameter number of shares (`_shares`), and directly returns it. It's an incorrect implementation of this function, since it should call `convertToAssets(_shares)`, instead of `_shares`.

To confirm that this implementation is indeed incorrect, we can examine how `previewMint()` implementation looks like in Open Zeppelins ERC-4626:

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

    function previewMint(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Ceil);
    }
``` 

As demonstrated above, OZ's implementation calls `_convertToAssets()` on `shares`.

## Tools Used

Manual code review


## Recommended Mitigation Steps

The correct implementation of `previewMint()` should look like below:

```
     function previewMint(uint256 _shares) public pure returns (uint256) {
        // shares represent how many assets an account has deposited, so they are 1:1 on mint
        return convertToAssets(_shares);
    }
```


## Assessed type

ERC4626
Incorrect implementation of `previewDeposit`
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#L441-L444


# Vulnerability details

## Impact

Function `previewDeposit()` is incorrectly implemented. It should return the number of shares we can get from the provided assets. However, it directly returns the number of assets instead. Returning incorrect result may lead to fund loss.

## Proof of Concept

[File: PrizeVault.sol](https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L441-L444
)
```
    function previewDeposit(uint256 _assets) public pure returns (uint256) {
        // shares represent how many assets an account has deposited, so they are 1:1 on deposit
        return _assets;
    }
```

As demonstrated above, function `previewDeposit()` takes as a parameter number of assets (`_assets`), and directly returns it. It's an incorrect implementation of this function, since it should call `convertToShares(_assets)`, instead of `_assets`.

To confirm that this implementation is indeed incorrect, we can examine how `previewDeposit()` implementation looks like in Open Zeppelins ERC-4626:

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

    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Floor);
    }
``` 

As demonstrated above, OZ's implementation calls `_convertToShares()` on `assets`.

## Tools Used

Manual code review


## Recommended Mitigation Steps

The correct implementation of `previewDeposit()` should look like below:

```
    function previewDeposit(uint256 _assets) public pure returns (uint256) {
        // shares represent how many assets an account has deposited, so they are 1:1 on deposit
        return convertToShares(_assets);
    }
```


## Assessed type

ERC4626
Yield fee is permanently lost if fee claimer doesn't claim all the accumulated yield
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
The protocol provides the ability to set yield fee and yield fee recipient when registering an Yield Vault in the Prize Vault Factory. The yield fee is accumulated when a user decides to transfer tokens out of the contract as seen in `PrizeVault::transferTokensOut`
```javascript
...
        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;
        }
...
```

The accumulated yield can then be withdrawn by the yield fee recipient via `PrizeVault::claimYieldFeeShares`. The function takes in one parameter specifying the amount of accumulated yield shares to mint. 
However instead of subtracting the amount of shares from the yield fee balance, the balance is subtracted from itself, effectively setting it to zero loosing the   `yildFeeBalance - _shares` amount. 

Note that this issue is not present if `_shares` == `_yieldFeeBalance`.

## Proof of Concept
Add the following test to `Liquidate.t.sol` and run with `forge test --mt testClaimYieldFeeShares_YieldDeleted -vvv`

```javascript
    function testClaimYieldFeeShares_YieldDeleted() public {
        vault.setYieldFeePercentage(1e8); // 10% fee
        vault.setYieldFeeRecipient(bob);
        vault.setLiquidationPair(address(this));

        // liquidate some yield
        underlyingAsset.mint(address(vault), 1e18);
        uint256 amountOut = vault.liquidatableBalanceOf(address(underlyingAsset));
        assertGt(amountOut, 0);

        vault.transferTokensOut(address(0), alice, address(underlyingAsset), amountOut);
        uint256 yieldFeeBalance = vault.yieldFeeBalance();

        console.log("yieldFeeBalance before: ", yieldFeeBalance);

        vm.prank(bob);
        vault.claimYieldFeeShares(yieldFeeBalance / 3);

        assertEq(vault.balanceOf(bob), yieldFeeBalance / 3);

        console.log("yieldFeeBalance after: ", vault.yieldFeeBalance());
        console.log("Yieeld Fee Bob balance: ", vault.balanceOf(bob));
    }
```

## Tools Used
Manual Review, Foundry

## Recommended Mitigation Steps
```diff
    f
User will still be able to deposit despite yield being depleted incurring losses due to rounding errors
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#L874


# Vulnerability details

## Impact
According to the doc, the project uses 2 strategies to prevent rounding errors; the dust and yield buffer. In the yield buffer, it states 

"....This buffer should never run dry during normal operating conditions and
expected yield rates. If the yield buffer is ever depleted, new deposits will be prevented and the prize vault will enter a lossy withdrawal state where depositors will incur the rounding errors on withdraw"

However, this is not the case as the depositor can still deposit even if yield is ever depleted incurring losses due to rounding errors


## Proof of Concept

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

In the _depositAndMint, there is a check "if (totalAssets() < totalDebt()) revert LossyDeposit(totalAssets(), totalDebt()); ", which is meant to revert if deposit will results in loss of funds

However, it is placed at the bottom of the function, meaning the depositor would be able to deposit and get their shares with "_mint(_receiver, _shares);" before the check. In this case, the users will be able to deposit despite the yield being depleted incurring losses due to rounding errors


## Tools Used

Manual Review

## Recommended Mitigation Steps

Make sure the check "if (totalAssets() < totalDebt()) revert LossyDeposit(totalAssets(), totalDebt())" is right after the Zero checks before "_mint(_receiver, _shares);"


## Assessed type

Other
`claimYieldFeeShares` deducts the incorrect amount of value from `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
Incorrect amount of value will be decreased for `yieldFeeBalance`, and can potentially cause lose of share a recipient can get.

## Proof of Concept
In function `claimYieldFeeShares`, fee recipient claims the yield fees and will be minted for equal amount of shares asked for:

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

But, in the function, yield balance is not decreased by the shares claimed for, instead, it's deducted by `_yieldFeeBalance`, which is essentially itself as:

```solidity
uint256 _yieldFeeBalance = yieldFeeBalance;
```

This will clear out all yield balance, despite shares claimed may not be all of the yield fees. This will cause fee recipient receive less shares than expected.

## Tools Used
Manual review.

## Recommended Mitigation Steps
Change the deduction to: 

```solidity
yieldFeeBalance -= _shares;
```



## Assessed type

Context
claimYieldFeeShares Wrong Amount Reduction for 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#L617


# Vulnerability details

## Summary

The claimYieldFeeShares function in the PrizeVault contract incorrectly reduces the yieldFeeBalance, leading to potential discrepancies in fee distribution.

## Vulnerability Details

In the PrizeVault contract, the claimYieldFeeShares function is intended to reduce the yieldFeeBalance by the amount of yield fee shares being claimed. However, due to a typo, it incorrectly reduces the yieldFeeBalance by _yieldFeeBalance instead of_shares.

The affected line of code is as follows:

```solidity
yieldFeeBalance -= _yieldFeeBalance; // Incorrect
```

The correct implementation should be:

```solidity
yieldFeeBalance -= _shares; // Correct
```

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

## Impact

The incorrect reduction of yieldFeeBalance can lead to an inaccurate accounting of yield fees within the contract. This discrepancy can result in unfair distribution of yield fees among participants, potentially disadvantaging some users while unfairly benefiting others. Over time, this could undermine trust in the platform and affect the overall integrity and fairness of fee distribution.

## Tools Used

Manual review

## Recommendations

To resolve this issue, the following code change is recommended:

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


## Assessed type

Math
PrizeVault::transferTokensOut incorrect implementation of formula to calculate _yeildFee
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#L673-L675


# Vulnerability details

## Impact
There will always an error in calculation of which can result in unexpected less _yeildFee from expected

## Proof of Concept
- The yeildFee calculted in transferTokenOut in done as following
```
        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;
        }
```
- From the given equation if we will try to calculate yeildFee it will be as follows
### Derivation of Yield Fee Calculation

We start with the equation:

```
total = amountOut + yieldFee
```

and

```
yieldFee / total = yieldFeePercentage
```

From the above equations, we can derive:

```
yieldFee / (amountOut + yieldFee) = yieldFeePercentage
yieldFee = (amountOut + yieldFee) * yieldFeePercentage
yieldFee * (1 - yieldFeePercentage) = amountOut * yieldFeePercentage
yieldFee = (amountOut * yieldFeePercentage) / (1 - yieldFeePercentage)
```

Therefore, the expression for yield fee is:

```
yieldFee = (amountOut * yieldFeePercentage) / (1 - yieldFeePercentage)
```
### Now if we try to evaluate what is being implemented
```
_yieldFee = (_amountOut * FEE_PRECISION) / (FEE_PRECISION - _yieldFeePercentage) - _amountOut;

// Simplified steps:
// _yieldFee = ((amountOut * FEE_PRECISION) - amountOut(Fee_Precesion - yieldFeePercentage)) / (Fee_Precesion - yieldFeePercentage)
// _yieldFee = (amountOut * FEE_PRECISION - amountOut*FEE_PRECISION + amountOut*yieldFeePercentage) / (FEE_PRECISION - yieldFeePercentage)
// _yieldFee = (amountOut*yieldFeePercentage) / (FEE_PRECISION - yieldFeePercentage)
```

- As we can clearly see the difference in both the equations intended should be divided by `(1-yeildFeePercentage)` but instead it is divided by `(FEE_PRECISION - yeildFeePercentage)`


## Tools Used
Manual Review

## Recommended Mitigation Steps
- So for dividing by `(1-_yeildFeePercentage)` we can divide by `(FEE_PRECISION - _yeildFeePercentage)/FEE_PRECISION`
- This is also implemented in [liquidatableBalanceOf](https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L644-L649) in same fashion.
- So _correctedYeildFee can be calculated as :
```
_yieldFee = (_amountOut * FEE_PRECISION) / (FEE_PRECISION - _yieldFeePercentage) - _amountOut;
_correctedYeildFee = _yeildFee * FEE_PRECISION;
```


## Assessed type

Error
Yield fee can be generated from thin air, which causes potential DoS and unlimited claim of fee 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#L685
https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L938


# Vulnerability details

## Impact
When recipient in `_withdraw` is set to vault itself, yield fees can be generated out of nothing, and cause potential DoS. A malicious fee recipient can also abuse this to claim unlimited amount of fee shares. 

## Proof of Concept
In the function `_withdraw`, the last few lines check if recipient is the vault. If not, the vault transfers asset token to the recipient.

```solidity
    function _withdraw(address _receiver, uint256 _assets) internal {
        // ... 

        if (_receiver != address(this)) {
            _asset.transfer(_receiver, _assets);
        }
    }
```

So, when recipient is set to the vault itself, the function essentially would do nothing, as supposed so. `_withdraw` is used in `_burnAndWithdraw` and `transferTokensOut`. In the later function, it lets user to claim excessed amount yield fee and convert the amount to shares or withdraw them from assets.

```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 LiquidationTokenOutNotSupported(_tokenOut);
        }
        // ...
    }

```

And we see, in the function, when `tokenOut` is set to asset, `_withdraw` is called. Before those logic, yield fees are calculated and added to the total fee balance `yieldFeeBalance`. However, when recipi
PrizeVault::claimYieldFeeShares has a wrong calculation 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#L617


# Vulnerability details

## Area of concern
```
    function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient {
        if (_shares == 0) revert MintZeroShares();

        uint256 _yieldFeeBalance = yieldFeeBalance;
        if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance);
->      //@audit if shares is less than  or equal to the yieldFeeBalance, then the yieldFeeBalance should be decremented by shares
->      //But here we are making it 0
        yieldFeeBalance -= _yieldFeeBalance;

        _mint(msg.sender, _shares);

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

## Tools Used
Manual review

## 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;
+       yeildFeeBalance -= _shares;

        _mint(msg.sender, _shares);

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


## Assessed type

Error
claimYieldFeeShares() will always reset the yield fee balance to 0, regardless of amount of shares claimed.
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
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L617


# Vulnerability details

## Impact
A yield fee recipient calls the `claimYieldFeeShares()` function to claim yield liquidation fees from the PrizeVault. The recipient specifies as input the number of shares he wants to claim, however regardless of the amount specified the function always resets the yield fee balance to 0. So, unless the yield fee recipient always calls the function with the exact amount he will lose fees.

## Proof of Concept
Yield fees are accumulated on token liquidations in the prize vault. Each liquidation contributes a percentage to the `yieldFeeBalance`.

When enough fees are accumulated, the yield fee recipient can withdraw them. He does so through the `claimYieldFeeShares()` function:

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L611
```js
    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 we can see the receiver speicifies a `_shares` input parameter for the amount of shares he wants to receive. These shares are minted to the `msg.sender`. However regardless of this amount the yieldFeeBalance is reset to 0:

```js
        yieldFeeBalance -= _yieldFeeBalance;
```

This means that input parameter `_shares` must always match yieldFeeBalance to prevent loss of funds. Even if the recipient tries to match variable, liquidation transactions that frontrun the claimYieldFeeShares() transaction will cause loss of funds.
## Tools Used
Manual Review

## Recommended Mitigation Steps
The correct calucation should be:

```js
        yieldFeeBalance -= _shares;
```
This will accurately reflect the shares minted to the user.


## Assessed type

Math
User can't withdraw assets when underlying Vault loses assets 
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#L906-L907


# Vulnerability details

## Impact
When underlying vault loses assets then totalAssets < totalDebt , when user withdraws same no.of assets then the user need to burn more number of shares than user  has. when minting assets the user receives same number of shares but when underlying Vault lost some assets the number of shares are calculated using 
 _assets.mulDiv(totalDebt_, _totalAssets, Math.Rounding.Up);
returns _shares which are more than _assets because totalAssets < totalDebt.
when user calls the _burnAndWithdraw(), it burns the _shares which are more than the available, so it reverts with  ERC20InsufficientBalance(from, fromBalance, value);

## Proof of Concept
function test_PrizeVault_Withdraw() public{

        // address(1) deposits 1000 assets
        vm.prank(address(1));
        uint shares=_prizeVault.deposit(1000);
        require(shares=1000);

        //underlying asset losses assets
        vm.prank(address(_underlyingVault));
        _asset.tranfer(address(2),100);

        //address(1) tries to withdraw 1000 assets
        vm.startPrank(address(1));
        vm.expectRevert();
        _prizeVault.withdraw(1000);
        vm.stopPrank();
    }
https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L906-L907
## Tools Used
Foundry

## Recommended Mitigation Steps
burn the same number of shares as assets so as this cant exceed the current balance of the user 





## Assessed type

ERC4626