xanoutas/solidity-security-findings huggingface dataset:
No slippage protection when withdrawing and redeeming
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#L493
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L504


# Vulnerability details

## Impact
The withdraw and redeem operations in PrizeVault are exposed to high slippage returns and could result in a loss for LPs of PrizeVault.

## Proof of Concept
When withdraw or redeem is called, they both work in similar ways in returning the amount to be burnt or received. When all assets are below the debt, the amount returned could vary. However, there is no slippage protection for these operations.

```solidity
function withdraw(
        uint256 _assets,
        address _receiver,
        address _owner
    ) external returns (uint256) {
        uint256 _shares = previewWithdraw(_assets);
        _burnAndWithdraw(msg.sender, _receiver, _owner, _shares, _assets);
        return _shares;
    }
```
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L489-L497

```solidity
    function redeem(
        uint256 _shares,
        address _receiver,
        address _owner
    ) external returns (uint256) {
        uint256 _assets = previewRedeem(_shares);
        _burnAndWithdraw(msg.sender, _receiver, _owner, _shares, _assets);
        return _assets;
    }
```
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L500-L508

However, there are no parameters for `amountOutMin` or `amountInMax`, which are used to prevent slippage. These parameters should be checked to create slippage protections.

https://docs.uniswap.org/contracts/v3/guides/providing-liquidity/decrease-liquidity

## Tools Used
Visual Studio Code

## Recommended Mitigation Steps
There are no parameters for `amountOutMin` or `amountInMax`, which are used to prevent slippage for varying return amount. These parameters should be checked to create slippage protections.

https://docs.uniswap.org/contracts/v3/guides/providing-liquidity/decrease-liquidity 


## Assessed type

MEV
Users can be eligable for prizes with little to no risk
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#L865-L866


# Vulnerability details

## Impact

After users deposit into the `PrizeVault` contract their deposit is then transferred to `yieldVault`, however if `yieldVault` has reached the maximum deposit limit, the transferred tokens will stay inside the `PrizeVault` contract risk-free and withdrawable at all time. The problem is that they will have shares of `PrizeVault` and according to the documentation - "In order for users to be eligible to win prizes, their balances must be tracked in the Twab Controller".
This means that users that didn't deposit into the yield vault will still be eligible for prizes because their balance in the Twab Controller is positive.

## Proof of Concept

Here is how the user can perform this:
(This depends on the implementation of the `yieldVault` and if it reverts on a zero deposit)
1. Let's look at the example in which the yield vault does allow a zero deposit
    - A user calls `yieldVault.maxDeposit` and sees that the vault reached the deposit limit
    - The user calls `PizeVault::deposit` with let's say 100e18 tokens 
    - `PrizeVault` tries to deposit the tokens into the yield vault but since the deposit limit is reached it does nothing and returns 0 as the deposit is not accepted
    - After this the user gets minted 100e18 shares of the `PrizeVault`, making him eligible for prizes

2. Let's look at the example in which the yield vault does NOT allow a zero deposit
    - A user calls `yieldVault.maxDeposit` and sees that the vault's max deposit is 10e18 (10e18 tokens are left until the limit is reached)
    - The user calls `PizeVault::deposit` with 100e18 tokens 
    - `PrizeVault` deposits only 10e18 tokens and the rest 90e18 tokens are stored in the `PrizeVault`. This way a user is only risking 10e18 tokens but eligible for larger rewards since he gets minted 100e18 shares

## Tools Used

Manual Review

## Recommended Mitigation Steps

Make it such that users cannot deposit more than `PrizeVault::maxDeposit`. Add the check in the deposit and mint function


## Assessed type

ERC4626
Incompatibility With Rebasing/Deflationary/Inflationary token
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#L854


# Vulnerability details

## Impact
safeTransferFrom does not verify whether the expected value was transferred. In cases where tokens incur a fee, the transferred amount may be less than expected. Additionally, the product contracts assume that the received amount equals the inputted amount. Consequently, the custodian contract may hold fewer tokens than anticipated, potentially leading to inaccuracies in product accounting.

Rebasing tokens, such as Aave aTokens, increment each holder's balanceOf() over time. In scenarios involving rebasing tokens, rewards accumulate in the contract holding the tokens and cannot be withdrawn by the original depositor. To mitigate this issue, it's crucial to track 'shares' deposited on a pro-rata basis. This approach allows shares to be redeemed for their proportion of the current balance at the time of withdrawal.
## Proof of Concept
https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L854C10-L859C1

        _asset.safeTransferFrom(
            _caller,
            address(this),
            _assets
        );
https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVaultFactory.sol#L118C1-L119C1

        IERC20(_vault.asset()).transferFrom(msg.sender, address(_vault), YIELD_BUFFER);





## Assessed type

Invalid Validation
`_yieldFee` wrongly rounded down in `PrizeVault:transferTokensOut`
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#L675


# Vulnerability details

## Impact
`_yieldFee` was wrongly rounded down as the intention of the protocol was to round down to Yield balance and round up on Yieldfee as stated here; 

```
/ The liquid yield is computed by taking the available yield balance and multiplying it
        // by (1 - yieldFeePercentage), rounding down, to ensure that enough yield is left for the
        // yield fee.

```
This will ultimately cause Loss of yield fees to Yield balance. 

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

## Tools Used
Manual

## Recommended Mitigation Steps
Round up on Yield fee






## Assessed type

Error
`previewDeposit` and `previewMint` return incorrect value when total assets is less than total debt.
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#L441-L450


# Vulnerability details

## Impact
When `_totalAssets < totalDebt_`, shares and assets are not 1:1, but `previewDeposit` and `previewMint` still work as they are 1:1, and result in incorrect values. The impact is that user's withdraws will revert, as it tends to burn more shares than minted.

## Proof of Concept
As shown in `previewDeposit` and `previewMint`, shares and assets are always 1:1.
```solidity
441:    function previewDeposit(uint256 _assets) public pure returns (uint256) {
442:        // shares represent how many assets an account has deposited, so they are 1:1 on deposit
443:=>      return _assets;
444:    }

447:    function previewMint(uint256 _shares) public pure returns (uint256) {
448:        // shares represent how many assets an account has deposited, so they are 1:1 on mint
449:=>      return _shares;
450:    }
```
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L441-L450


But according to `previewWithdraw`, shares and assets are not always 1:1. When `_totalAssets < totalDebt_`, one asset will return more shares.
```solidity
454:    function previewWithdraw(uint256 _assets) public view returns (uint256) {
455:        uint256 _totalAssets = totalAssets();
456:
457:        // No withdrawals can occur if the vault controls no assets.
458:        if (_totalAssets == 0) revert ZeroTotalAssets();
459:
460:        uint256 totalDebt_ = totalDebt();
461:        if (_totalAssets >= totalDebt_) {
462:            return _assets;
463:        } else {
464:            // Follows the inverse conversion of `convertToAssets`
465:=>          return _assets.mulDiv(totalDebt_, _totalAssets, Math.Rounding.Up);
466:        }
467:    }
```
https://github.com/code-423n4/2024-03-pooltogether/blob/main/pt-v5-vault/src/PrizeVault.sol#L454-L467


Let's assume that `_totalAssets < totalDebt_` has occurred, and assume `_totalAssets = 100`, `totalDebt_ = 110` for simplicity. Consider the following scenario:
1. Alice deposits 100 assets, and get 100 shares minted (1:1 according to `previewDeposit`)
2. Alice withdraws her 100 assets immediately afterwards (ignore the yield change), the shares needed to burn is 100*210/200=105 (according to `previewWithdraw`).
As long as the assets and shares is not 1:1, Alice cannot withdraw her all assets.

## Tools Used
VSCode

## Recommended Mitigation Steps
```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;
+        return convertToShares(_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;
+       return convertToAssets(_shares);
    }
```


## Asse
Function
Audit: 2024-03-pooltogether-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

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

## Tools Used

## Recommended Mitigation Steps


## Assessed type

Access Control
Calculations for `PrizeVault:totalAsset` incorrect and can lead to Dos in peculiar cases 
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#L336


# Vulnerability details

## Impact
In the protcol implementation of the `totalaAsset` is:
```
 function totalAssets() public view returns (uint256) {
        return yieldVault.convertToAssets(yieldVault.balanceOf(address(this))) + _asset.balanceOf(address(this));
    }

```
According to EIP4626 `convertToAssets` will always round down to 0. And due to this the `yieldVault.convertToAssets` may give us a slightly lower value due to rounding errors. the protocol is aware of this problem which was why yield buffer was implementent. However `Yieldbuffer` was not added to our `totalsupply`. Even though at initial deposit Totalasset == Totaldept. Due to these rounding error we could have totaldebt > totalasset till yield accrues which might take days for some yield vault. An error averted by adding yield buffer to total supply. 

## Proof of Concept

https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L336
```
 /// @notice The yield buffer that is reserved for covering rounding errors on withdrawals and deposits. 
    /// @dev The buffer prevents the entire yield balance from being liquidated, which would leave the vault
    /// in a state where a single rounding error could reduce the totalAssets to less than the totalSupply.
    /// 
    /// The yield buffer is expected to be of insignificant value and is used to cover rounding
    /// errors on deposits and withdrawals. Yield is expected to accrue faster than the yield buffer
    /// can be reasonably depleted.
    ///
    /// IT IS RECOMMENDED TO DONATE ASSETS DIRECTLY TO THE PRIZE VAULT AFTER DEPLOYMENT TO FILL THE YIELD
    /// BUFFER AND COVER ROUNDING ERRORS UNTIL THE DEPOSITS CAN GENERATE ENOUGH YIELD TO KEEP THE BUFFER 
    /// FULL WITHOUT ASSISTANCE.
    ///
    /// The yield buffer should be set as high as possible while still being considered insignificant
    /// for the underlying asset. For example, a reasonable yield buffer for USDC with 6 decimals might be
    /// 1e5 ($0.10), which will cover up to 100k rounding errors while still being an insignificant value.
    /// Some assets may be considered incompatible with the prize vault if the yield vault incurs rounding
    /// errors and the underlying asset has a low precision per dollar ratio.
    /// 
    /// Precision per dollar (PPD) can be calculated by: (10 ^ DECIMALS) / ($ value of 1 asset).
    /// For example, USDC has a PPD of (10 ^ 6) / ($1) = 10e6 p/$.
    /// 
    /// As a rule of thumb, assets with lower PPD than USDC should not be assumed to be compatible since
    /// the potential loss of a single unit rounding error is likely too high to be made up by yield at 
    /// a reasonable rate. Actual results may vary based on expected gas costs, asset fluctuation, and yield
    /// accrual rates. If t
the PrizeVault.sol doesnt check if yieldFeeRecipient_ is the zero address before setting it
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#L959


# Vulnerability details

## Impact
the protocol will be unable to call `function claimYieldFeeShares(uint256 _shares) external` because the function will always revert

## Proof of Concept
a malicious owner could set the `yieldFeeRecipient_` to the zero address to prevent the protocol from claiming yield fee shares

## Tools Used
Manual Audit

## Recommended Mitigation Steps
check in `function _setYieldFeeRecipient(address _yieldFeeRecipient)` that the `yieldFeeRecipient_` is not the zero address


## Assessed type

Invalid Validation
No slippage protection on withdrawals
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#L489-L497


# Vulnerability details

## Impact
The `withdraw` function of `PrizeVault.sol` allows a user to specify the amount of assets they want to withdraw. Furthermore, when the total assets are less than the total debt, one asset becomes more expensive than one share, causing more shares to need to be burned than the assets that are received. For example, if a user withdraws 100 assets they may have to burn 120 shares.

The issue is that there is no slippage protection on the amount of shares that the withdrawer is going to have to burn, which will likely cause an unexpected loss of funds. This problem will be further worsened during periods of high market volatility.

## Proof of Concept

As we can see in the `previewWithdraw` function, used in `withdraw` when there are fewer assets than debt, the shares that will be burned are more than the withdrawn assets. Furthermore, there is no limit on the amount of shares that the caller may need to burn. The lack of slippage protection can also be observed in the `redeem` function, where the redeemer may receive fewer assets than expected for the amount of shares that they have burned.

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

## Tools Used
Manual review

## Recommended Mitigation Steps
Include slippage protection on the number of shares burned in `withdraw` and the number of assets received in `redeem`.


## Assessed type

Other
Decimal mismatch prevents deposits, eroding trust and causing financial discrepancies.
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#L475-L479
https://github.com/code-423n4/2024-03-pooltogether/blob/480d58b9e8611c13587f28811864aea138a0021a/pt-v5-vault/src/PrizeVault.sol#L843-L876


# Vulnerability details

## Impact
The decimal mismatch in the `PrizeVault.sol` contract leads to users being unable to deposit assets, resulting in a trust deficit and potential financial inaccuracies within the protocol. The root of the issue is the contract's failure to align the decimals of the underlying asset with the `TwabERC20` minted shares, causing a critical deposit function to revert and disrupting the protocol's operations.

## Proof of Concept

User calls `deposit` function to deposit 1000 USDC (1000 * 1e6).
```javascript
    function deposit(uint256 _assets, address _receiver) external returns (uint256) {
        // this is nonstandard of erc4626 it will return the asset of that i have param in deposit function
        uint256 _shares = previewDeposit(_assets);
        _depositAndMint(msg.sender, _receiver, _assets, _shares);
        return _shares;
    }
```

`previewDeposit` is called and returns 1000 * 1e6 (since it assumes a 1:1 ratio for assets to shares).
`_depositAndMint` is called with 1000 * 1e6 for both _assets and _shares.

```javascript
 function _depositAndMint(address _caller, address _receiver, uint256 _assets, uint256 _shares) internal {

      // .......
        //@audit minting the twabERc20 with 1e18 decimal
        _mint(_receiver, _shares);

        //...........

        emit Deposit(_caller, _receiver, _assets, _shares);
    }
```

The contract mints shares (TWAB tokens) with 18 decimals, resulting in 1000 * 1e18 shares being minted.

`totalAssets()` returns 
```javascript    
function totalAssets() public view returns (uint256) {
        // suppose this will give the assets for example 200 steth for then share if the totalsupply is 2000 stETh
        return yieldVault.convertToAssets(yieldVault.balanceOf(address(this))) + _asset.balanceOf(address(this));
    }
```
represented as 1000 * 1e6.

`totalDebt()` calls `totalSupply()` from `TwabController`, which returns the total shares in 1e18 decimals, now showing 1000 * 1e18.

The contract compares `totalAssets() (1000 * 1e6)` with `totalDebt() (1000 * 1e18)` using if (totalAssets() < totalDebt()).
```javascript
    function _depositAndMint(address _caller, address _receiver, uint256 _assets, uint256 _shares) internal {

        //......
        // @audit mismatch decimal
        if (totalAssets() < totalDebt()) revert LossyDeposit(totalAssets(), totalDebt());

        emit Deposit(_caller, _receiver, _assets, _shares);
    }
```

The comparison fails because 1000 * 1e6 (total assets in USDC decimals) is less than 1000 * 1e18 (total debt in TWAB decimals).

The LossyDeposit error is triggered due to the faulty comparison.
The user's deposit transaction is reverted, and the
Failure of `PrizeVault::depositWithPermit` Function on Tokens Without EIP-2612 Permit Feature
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 will revert transactions for tokens like stETH that lack EIP-2612 permit functionality, limiting its current usability.

## Proof of Concept

User attempts to call `depositWithPermit` with stETH, providing necessary parameters including signature components (v, r, s) and _deadline.

The function attempts to execute IERC20Permit(address(_asset)).permit(...), expecting the asset to have a permit function as per EIP-2612.
stETH does not implement permit, so this call will not find the appropriate function to execute.
[stETH](https://etherscan.io/address/0x47ebab13b806773ec2a2d16873e2df770d130b50#code#F10#L50) fallback function is triggered due to the absence of the permit function.

stETH's fallback function is not designed to handle this type of call, resulting in a transaction revert.

[Same Issue](https://solodit.xyz/issues/non-existent-permit-function-openzeppelin-pods-finance-ethereum-volatility-vault-audit-1-markdown)

[Harvest finance gives a yield on stETH](https://app.harvest.finance/)

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider implementing a wrapper or adapter for stETH to provide permit functionality, or use wrapped stETH (wstETH) that already has it.


## Assessed type

Error
No check on ERC20  transfer 
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#L939


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
Here we are not checking the return of transfer function.we cannot know whether transfer has happened or not.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
 if (_receiver != address(this)) {
  @>          _asset.transfer(_receiver, _assets);
        }
## Tools Used

## Recommended Mitigation Steps
( success,)  _asset.transfer(_receiver, _assets);
require(success,"Token transfer);


## Assessed type

Token-Transfer