xanoutas/solidity-security-findings huggingface dataset:
Both `deposit`/`mint` use 1:1 ratio even if vault has less assets than debts
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L375-L377
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L441-L450
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L475-L486


# Vulnerability details

## Issue Description

When the underlying yield vault experiences a loss, the Prize Vault may have fewer total assets than the total minted debts. Consequently, the conversion rate from asset to shares will be the ratio `totalAssets/totalDebt`, but instead the vault still uses a 1:1 ratio for deposits.

In such circumstances, users should not be permitted to deposit (or mint) as they would directly incur losses on their tokens. This issue is highlighted in the `maxDeposit` function where the function returns 0:

```solidity
function maxDeposit(address) public view returns (uint256) {
    uint256 _totalSupply = totalSupply();
    uint256 totalDebt_ = _totalDebt(_totalSupply);
    if (totalAssets() < totalDebt_) return 0;

    ...
}
```

However, in reality, any user can deposit in this condition (loss of assets) and will also use the 1:1 ratio, as both `previewDeposit`/`previewMint` functions return a 1:1 conversion between asset and share amounts:

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

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

Consequently, users are allowed to deposit after a yield vault loss. If they attempt to withdraw afterward, they will lose some funds, as the `previewWithdraw`/`previewRedeem` functions use the current `totalAssets/totalDebt` ratio which will result in lower asset amount returned to the user.

## Impact

Both `deposit` and `mint` functions permit deposits when the yield vault has experienced a loss, resulting in users losing funds directly after the deposit.

## Tools Used

Manual review, VS Code

## Recommended Mitigation

Both `deposit` and `mint` functions should prohibit new deposits in the event of a yield vault loss (i.e. when `totalAssets < totalDebt`).


## Assessed type

Context
`TwabERC20.permit()` can be easily griefed by anyone
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/TwabERC20.sol#L5
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/TwabERC20.sol#L19
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/TwabERC20.sol#L46
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/bd325d56b4c62c9c5c1aff048c37c6bb18ac0290/contracts/token/ERC20/extensions/ERC20Permit.sol#L49-L68


# Vulnerability details

## Impact
The `TwabERC20.sol` contract implements `ERC20Permit`, this library provides permit functionality for ERC20s but is left behind by the new reliable `permit()` standard that many major protocols have migrated to. 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 `permit()` transaction.

Although I am aware that this cannot happen when using permit in the vault contract (with `depositWithPermit`), I think that `TwabERC20.sol` is an independent contract and therefore `permit()` in `TwabERC20.sol` should be overridden with the recommended.

## Proof of Concept
The inherited ERC20Permit's permit function:
```solidity
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        require(signer == owner, "ERC20Permit: invalid signature");

        _approve(owner, spender, value);
    }
```
For more information refer to the [blog post](https://www.trust-security.xyz/post/permission-denied) on Trust Security's website.

## Tools Used

Manual Review

## Recommended Mitigation Steps

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



## Assessed type

ERC20
`depositWithPermit` can only be called by the permit creator
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `depositWithPermit` function can only be called by the creator of the permit, essentially going against the purpose of the function. As the `depositWithPermit` reverts if the caller is different from the `_owner` parameter, no other user will be able to call the function on behalf of the signer.

## Proof of Concept

As we can see, the function fails if `msg.sender` is not `_owner`.

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L524-L546
```
    function depositWithPermit(
        uint256 _assets,
        address _owner,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external returns (uint256) {
        if (_owner != msg.sender) {
            revert PermitCallerNotOwner(msg.sender, _owner);
        }


        // Skip the permit call if the allowance has already been set to exactly what is needed. This prevents
        // griefing attacks where the signature is used by another actor to complete the permit before this
        // function is executed.
        if (_asset.allowance(_owner, address(this)) != _assets) {
            IERC20Permit(address(_asset)).permit(_owner, address(this), _assets, _deadline, _v, _r, _s);
        }


        uint256 _shares = previewDeposit(_assets);
        _depositAndMint(_owner, _owner, _assets, _shares);
        return _shares;
    }
```

This same issue has already been observed in a previous PoolTogether audit:
https://github.com/code-423n4/2023-07-pooltogether-findings/issues/113

## Tools Used
Manual review

## Recommended Mitigation Steps
Use a custom signature that has a `receiver` parameter.





## Assessed type

Other
# Potential reward manipulation
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/94b0c034c68b5318a25211a7b9f6d9ff6693e6ab/src/abstract/Claimable.sol#L76


# Vulnerability details


## Impact
I couldn't write down a functionnal POC but I think there would be a way to manipulate the reward amount.

## Proof of Concept
Once **winningRandomNumber** has been given by a contributor, the process continues and here https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/3344d3cbdcfe377e1218fae8ed791e4e2ffa219a/src/PrizePool.sol#L412 the function is executed `PrizePool.sol::awardDraw`:

```solidity
  /// @notice Allows the Manager to award a draw with the winning random number.
  /// @dev Updates the number of tiers, the winning random number and the prize pool reserve.
  /// @param winningRandomNumber_ The winning random number for the draw
  /// @return The ID of the awarded draw
  function awardDraw(uint256 winningRandomNumber_) external onlyDrawManager notShutdown returns (uint24) {
    // check winning random number
    if (winningRandomNumber_ == 0) {
      revert RandomNumberIsZero();
    }
    uint24 awardingDrawId = getDrawIdToAward();
    uint48 awardingDrawOpenedAt = drawOpensAt(awardingDrawId);
    uint48 awardingDrawClosedAt = awardingDrawOpenedAt + drawPeriodSeconds;
    if (block.timestamp < awardingDrawClosedAt) {
      revert AwardingDrawNotClosed(awardingDrawClosedAt);
    }

    uint24 lastAwardedDrawId_ = _lastAwardedDrawId;
    uint32 _claimCount = claimCount;
    uint8 _numTiers = numberOfTiers;
    uint8 _nextNumberOfTiers = _numTiers;

    _nextNumberOfTiers = computeNextNumberOfTiers(_claimCount);

    // If any draws were skipped from the last awarded draw to the one we are awarding, the contribution
    // from those skipped draws will be included in the new distributions.
    _awardDraw(
      awardingDrawId,
      _nextNumberOfTiers,
      getTotalContributedBetween(lastAwardedDrawId_ + 1, awardingDrawId)
    );

    _winningRandomNumber = winningRandomNumber_;
    if (_claimCount != 0) {
      claimCount = 0;
    }

    emit DrawAwarded(
      awardingDrawId,
      winningRandomNumber_,
      _numTiers,
      _nextNumberOfTiers,
      _reserve,
      prizeTokenPerShare,
      awardingDrawOpenedAt
    );

    return awardingDrawId;
  }
```

Once a winner in a draw has been set, then anyone (bots) can call ```Claimable.sol::claimPrize``` to send the reward to the winner.

```solidity
    /// @dev Also calls the before and after claim hooks if set by the winner.
    function claimPrize(
        address _winner,
        uint8 _tier,
        uint32 _prizeIndex,
        uint96 _reward,
        address _rewardRecipient
    ) external onlyClaimer returns (uint256) {
        address recipient;

        if (_hooks[_winner].useBeforeClaimPrize) {
            recipient = _hooks[_winner].implementation.beforeClaimPrize{ gas: HOOK_GAS }(
                _winner,
                _tier,
                _prizeIndex,
                _reward,
In times of negative yield, the faster withdrawers can prevent other people from withdrawing, resulting in stuck funds
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Users will not be able to withdraw their whole funds because of the earlier withdrawals

## Proof of Concept

When a user deposits, `previewDeposit` is called which returns the `_asset`. `_depositAndMint()` is then called. The user will transfer the asset tokens into the PrizeVault contract, and the price vault contract will call `yieldVault.mint` to convert the asset tokens into share tokens.

Assume the use of sDai as the underlying vault. Assume 1.1 sDAI is to 1 DAI. Alice deposits 110 DAI and gets 110 PrizeVault shares. The PrizeVault contract will hold 100 sDAI.

Withdrawal is the same concept, just opposite actions. A user will burn their PrizeVault shares and get back an equivalent amount of DAI. The PrizeVault will convert the underlying yield share to the asset and transfer it to the user.

```
    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));
        }
        if (_receiver != address(this)) {
            _asset.transfer(_receiver, _assets);
        }
    }
```

If Alice has 110 PrizeVault shares, she will burn them for 110 DAI. The PrizeVault contract will now hold 0 sDAI. (or some leftover dust)

The problem comes in when there is negative yield.

Assume 10 users deposits 110 DAI each and gets a total of 110 PrizeVault shares. The total PrizeVault shares minted is 1100. The PrizeVault contract will hold 1000 sDAI.

After some unfavourable circumstances, the ratio of sDAI to DAI drops to 1:1 extremely quickly

All 10 users attempts to withdraw. Since they have 110 shares, they are expected to get 110 DAI. Note that the PrizeVault only has 1000 sDAI. The first 9 users call withdraw and gets all their DAI back. 990 sDAI is burned, and there is 10 sDAI left in the contract. For the last user, he can only withdraw 10 DAI of his 110 DAI, effectively leaving 100 DAI stuck in the contract until the next user deposits into the contract.

The issue is that `previewDeposit` and `previewWithdraw` never calls convert to asset and convert to share
Lack of Slippage Protection in `withdraw`/`redeem` Functions of the Vault
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L454-L472
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L355-L366
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L489-L508


# Vulnerability details

## Issue Description

When a user who has deposited assets into the PrizeVault wishes to withdraw (or redeem) them, they can do so by calling either the `withdraw` or `redeem` functions.

Under normal conditions in the vault, users expect to receive their full deposited asset amount back, as there is a 1:1 exchange ratio between the asset amount and shares.

However, if the underlying yield vault experiences a loss, this exchange rate will decrease. This is highlighted in the `previewWithdraw` function (or `previewRedeem`/`convertToAssets` function):

```solidity
function previewWithdraw(uint256 _assets) public view returns (uint256) {
    uint256 _totalAssets = totalAssets();

    // No withdrawals can occur if the vault controls no assets.
    if (_totalAssets == 0) revert ZeroTotalAssets();

    uint256 totalDebt_ = totalDebt();
    if (_totalAssets >= totalDebt_) {
        return _assets;
    } else {
        // Follows the inverse conversion of `convertToAssets`
        return _assets.mulDiv(totalDebt_, _totalAssets, Math.Rounding.Up);
    }
}

function convertToAssets(uint256 _shares) public view returns (uint256) {
    uint256 totalDebt_ = totalDebt();
    uint256 _totalAssets = totalAssets();
    if (_totalAssets >= totalDebt_) {
        return _shares;
    } else {
        // If the vault controls fewer 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);
    }
}
```

```solidity
function totalAssets() public view returns (uint256) {
    return yieldVault.convertToAssets(yieldVault.balanceOf(address(this))) + _asset.balanceOf(address(this));
}
```

As shown, if the underlying yield vault experiences a loss, the total assets of the vault given by `totalAssets()` will decrease and might go below the total shares value `totalDebt()`. 

This will trigger the second `if` statement block, in which the calculated asset (or shares) amount will be converted using the ratio `totalAssets/totalDebt` (or its inverse for shares).

When a user redeems (or withdraws), if the yield vault experiences a loss while their transaction is pending (waiting in the mempool) and the total assets drop below the total vault debt, they will receive fewer assets after calling either the `withdraw`/`redeem` functions than they expected. Instead of a 1:1 ratio, they will use the current `totalAssets/totalDebt` ratio.

To illustrate this issue, consider the following scenario:

- In the vault, we
No slippage control in the `PrizeVault.sol#withdraw()` and `PrizeVault.sol#redeem()` functions. The lack of slippage control for `PrizeVault.sol#withdraw()`, `PrizeVault.sol#redeem()` function can lead to a loss of assets for the affected users.
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Loss of assets for the affected users.

## Proof of Concept
The `PrizeVault.sol#withdraw()` and `PrizeVault.sol#redeem()` functions call the `PrizeVault.sol#previewWithdraw()` and `PrizeVault.sol#previewRedeem()` functions to calculate the amount of stocks to be burned or assets to be redeemed.
When users deposit into PrizeVault they are really depositing into an ERC4626 vault that contributes its yield to the Prize Pool.
Therefore, when executing a transaction, the entire assets of PrizeVault can rise or fall at any time depending on the current onchain status according to the stock value of the yield vault.

```solidity
    function totalAssets() public view returns (uint256) {
        return yieldVault.convertToAssets(yieldVault.balanceOf(address(this))) + _asset.balanceOf(address(this));
    }
```

Therefore, if PrizeVault's total assets become less than totalDebt, slippage may occur in these functions(previewWithdraw(), previewRedeem()).
This slippage is more than what they can accept.
In summary, the `PrizeVault.sol#withdraw()`, `PrizeVault.sol#redeem()` functions lack slippage control that allows the user to revert if the amount of shares the user redeems is greater than expected or if the amount of assets the user receives is less than expected.
## Tools Used
Manual Review
## Recommended Mitigation Steps
Implement slip control in `PrizeVault.sol#withdraw()`, `PrizeVault.sol#redeem()` functions.


## Assessed type

Other
Liquidation of  `_availableYield` could lead to a temporary DoS due to unclaimed `YieldFees`
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Withdrawal of yield generated by underlying yield vault to prize pool would cause a significant drop in the `totalasset` of the prize vault which is
```
 function totalAssets() public view returns (uint256) {
        return yieldVault.convertToAssets(yieldVault.balanceOf(address(this))) + _asset.balanceOf(address(this));

```
this could potentially affect an internal accounting mechanism of the protocol which is `totalasset >= totaldebt` which is expected to hold true for a number of funtions in the yield vault to work but more importantly deposits and withdrwals.
This DoS stems from an increase in `accuredYeidfees` which is allocated in same function that transfers tokenOut in `prizevault:transferTokensOut`
```
        // Increase yield fee balance:
        if (_yieldFee > 0) {
            yieldFeeBalance += _yieldFee;
        }
```

This increase in YieldFees will cause an incease in totaldebt of the protocol
 ```
  function _totalDebt(uint256 _totalSupply) internal view returns (uint256) {
        return _totalSupply + yieldFeeBalance;
    }
 
``` 
If the amount of yield left in the Yield vault after the `Prizevault:transferTokensOut` is called is not more than yieldFeeBalance it will cause totaldebt > totaldeposit. leading a temporary DOS and stopping deposits(because max deposit ==0 at totaldebt > totalAsset) as well as affecting other functions. Until either new Yeild accures to offset this or till yieldFeeBalance is claimed by yield fee recipient. The current implementation consistently truncates the protocol's operation.

## Proof of Concept

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

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

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

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

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



## Tools Used
Manual

## Recommended Mitigation Steps
The `prizevault:transferTokensOut` function should call the `claimYieldFeeShares` function within it.
```

        // Increase yield fee balance:
        if (_yieldFee > 0) {
            yieldFeeBalance += _yieldFee;
+    uint256 Shares_of_Yield = convertToShares(yieldFeeBalance)
+   claimYieldFeeShares(Shares_of_Yield)

        }
```
for this to work though claimYieldFeeShares function has to be made a public function not external and not be called by onlyclaimer.

also a check can be put in place for ye