xanoutas/solidity-security-findings huggingface dataset:
Attacker can steal vault funds through the deposit function.
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In the deposit function, a check is made to see if the amount of assets being deposited by the user is greater than the amount of assets the vault currently holds. The vault then transfers the difference between the assets being deposited and the vault’s assets if the condition is true, otherwise it simply transfers the assets to the Yield Vault. However, the assets transferred in this situation come from the vault and not the attacker. An attacker could use this to get the vault to deposit assets and mint shares to them without contributing a single asset to the vault. The attacker could then withdraw their minted shares from the vault and profit from the exploit. 

## Proof of Concept
https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L942-#L960



## Tools Used
Manual review and Foundry for tests

## Recommended Mitigation Steps
Getting rid of the conditional statement is advised. The deposit function should always make a transfer of funds from the user before a deposit to the Yield Vault is made.


## Assessed type

Token-Transfer
No access control on mintYieldFee
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Lack of access-control allows anyone to mint the yield fee to himself. 

## Proof of Concept

`Vault.mintYieldFee` does not restrict the `_recipient` yield fees:

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

## Tools Used

Manual Review

## Recommended Mitigation Steps

Dont use an extra parameter for the recipient and use the contracts `_yieldFeeRecipient` instead.


## Assessed type

Access Control
The `_currentExchangeRate` of the Vault contract can't increase, and always be lower than or equal to `_assetUnit`
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `_currentExchangeRate` of the Vault contract can not increase, and always be lower than or equal to `_assetUnit`. Therefore, when the vault is undercollateralized (`_currentExchangeRate` < `_assetUnit`), it can't be further collateralized.
## Proof of concept
```solidity=
function _currentExchangeRate() internal view returns (uint256) {
    uint256 _totalSupplyAmount = _totalSupply();
    uint256 _totalSupplyToAssets = _convertToAssets(
      _totalSupplyAmount,
      _lastRecordedExchangeRate,
      Math.Rounding.Down
    );

    uint256 _withdrawableAssets = _yieldVault.maxWithdraw(address(this));

    if (_withdrawableAssets > _totalSupplyToAssets) {
      _withdrawableAssets = _withdrawableAssets - (_withdrawableAssets - _totalSupplyToAssets);
    }

    if (_totalSupplyAmount != 0 && _withdrawableAssets != 0) {
      return _withdrawableAssets.mulDiv(_assetUnit, _totalSupplyAmount, Math.Rounding.Down);
    }

    return _assetUnit;
  }
```
In case `_totalSupplyAmount != 0 && _withdrawableAssets != 0`, `_currentExchangeRate` function will return a value `_withdrawableAssets * _assetUnit / _totalSupplyAmount`.

However `_withdrawableAssets` can not exceed `_totalSupplyToAssets`, which is equal to `_totalSupplyAmount * _lastRecordedExchangeRate / _assetUnit`.

Therefore, `_currentExchangeRate` always be lower than or equal to `_lastRecordedExchangeRate`.


**Testing**: 
Add this assert line and run `forge test` , all tests will passed.
```solidity=
if (_totalSupplyAmount != 0 && _withdrawableAssets != 0) {
  assert(_withdrawableAssets.mulDiv(_assetUnit, _totalSupplyAmount, Math.Rounding.Down) <= _assetUnit);
  return _withdrawableAssets.mulDiv(_assetUnit, _totalSupplyAmount, Math.Rounding.Down);
}
```


## Tool used

Manual Review

## Recommended Mitigation Steps
Remove these lines of code that limit the `_withdrawableAssets`
```solidity=
if (_withdrawableAssets > _totalSupplyToAssets) {
  _withdrawableAssets = _withdrawableAssets - (_withdrawableAssets - _totalSupplyToAssets);
}
```



## Assessed type

Context
Malicious user can steal other user's deposits from Vault.sol
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

When the `Vault.withdraw()` function is called, a maximum of type(uint96).max shares are being burnt subsequently: `Vault.withdraw()`-> `Vault._withdraw()`-> `Vault._burn` burns uint96(_shares), see Vault.sol line 1139.

A malicious user can exploit this in the following way:

1. A malicious user deposits for example two times a value of type(uint96).max underlying assets into the Vault, calling two times the function `Vault.deposit()`. They can't deposit more in a single transaction because type(uint96).max is the maximum value to deposit.
1. Then the malicious user calls `Vault.withdraw()` with a higher value of assets to withdraw than type(uint96).max, for example they withdraw (2 * type(uint96).max) which is the total amount of assets they depositted before.
1. Now what happens is that the Vault.sol contract only burns type(uint96).max shares for the user, but transfers 2 * type(uint96).max underlying assets to the malicious user, which is the total amount they depositted before.

1. This happens because `Vault._burn()` only burns `uint96(shares)` shares of the malicious users - see Vault.sol line 1155.

1. Now the malicious user has still vault shares left but they withdrew the total amount of their depositted assets.

1. Now the vault transferred the total amount of the malicious user's assets back to them, and the malicious user has still shares left to withdraw even more assets that are now being stolen from assets depositted by other users.

1. Or if the malicious user was the first depositor, they wait until another user deposits and the malicious user can now withdraw the other users depositted assets since the malicious user still has Vault shares left.

1. Or if the malicious user is not the first depositor, they use a flashLoan or flashMint to deposit multiple times type(uint96).max assets into the vault, then withdraw their deposit, pay back the flashLoan or flashMint and they will still have enough vault shares left to steal all other users assets by withdrawing them.

In this way, other user's depositted assets can be stolen, as explained above.


## Proof of Concept

Here is a POC, where the problem is illustrated:

https://gist.github.com/zzzitron/397790302ca95aa3fbf05694ae1497ab

## Tools Used

Manual review

## Recommended Mitigation Steps

Consider adjusting the `Vault._burn` function to not convert from uint256 to uint96 when burning shares.


## Assessed type

Math
`_amountOut` is representing assets and shares at the same time in the `liquidate` function
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

In the `liquidate` function from the `Vault` contract, the input argument `_amountOut` is used as if it was representing a value of asset amount and share amount at the same time which is impossible a there a conversion rate between them, this error will make `liquidate` function behave in an expected manner, not the one that was intended.

## Proof of Concept

The issue is occurring in the `liquidate` function below :

```solidity
function liquidate(
    address _account,
    address _tokenIn,
    uint256 _amountIn,
    address _tokenOut,
    uint256 _amountOut
) public virtual override returns (bool) {
    _requireVaultCollateralized();
    if (msg.sender != address(_liquidationPair))
      revert LiquidationCallerNotLP(msg.sender, address(_liquidationPair));
    if (_tokenIn != address(_prizePool.prizeToken()))
      revert LiquidationTokenInNotPrizeToken(_tokenIn, address(_prizePool.prizeToken()));
    if (_tokenOut != address(this))
      revert LiquidationTokenOutNotVaultShare(_tokenOut, address(this));
    if (_amountOut == 0) revert LiquidationAmountOutZero();
    
    uint256 _liquidableYield = _liquidatableBalanceOf(_tokenOut);
    
    // @audit _amountOut compared with _liquidableYield which represents an asset amount
    // @audit _amountOut is considered as an asset amount
    if (_amountOut > _liquidableYield)
      revert LiquidationAmountOutGTYield(_amountOut, _liquidableYield);
    
    _prizePool.contributePrizeTokens(address(this), _amountIn);
    
    if (_yieldFeePercentage != 0) {
        // @audit _amountOut used to increase fee shares so considered as representing a share amount
        _increaseYieldFeeBalance(
            (_amountOut * FEE_PRECISION) / (FEE_PRECISION - _yieldFeePercentage) - _amountOut
        );
    }
    
    uint256 _vaultAssets = IERC20(asset()).balanceOf(address(this));
    
    // @audit _amountOut compared with _vaultAssets which represents an asset amount
    // @audit _amountOut is considered as an asset amount
    if (_vaultAssets != 0 && _amountOut >= _vaultAssets) {
      _yieldVault.deposit(_vaultAssets, address(this));
    }
    
    // @audit _amountOut representing a share amount minted to the _account
    _mint(_account, _amountOut);
    
    return true;
}
```

As you can see from the code above, the value of the argument `_amountOut` is used multiple times in the function logic and each time it is representing either an asset amount or a share amount which is impossible as there a conversion formula used to transform asset amount into share amount (and inversely) with the function `_convertToShares` (or `_convertToAssets`). 

From the function comments i couldn't figure out what the value of `_amountOut` actually represents, but because there is also another argument given to the `liquidate` function which is `_t
`Vault.sponsor` may take away the prize chance from the receiver.
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/55dcd65de4436d50967819c2ac313c3910b6f5f3/src/Vault.sol#L480-L482
https://github.com/generationsoftware/pt-v5-twab-controller/blob/1cdf78e87a3d9127f85a3755024f143664643c5e/src/TwabController.sol#L500-L502
https://github.com/generationsoftware/pt-v5-twab-controller/blob/1cdf78e87a3d9127f85a3755024f143664643c5e/src/TwabController.sol#L656-L661
https://github.com/generationsoftware/pt-v5-twab-controller/blob/1cdf78e87a3d9127f85a3755024f143664643c5e/src/TwabController.sol#L24


# Vulnerability details

## Impact
`TwabController.delegateBalance` is related to the probability to get the prize, and `Vault.sponsor` can make the others' delegateBalance to 0.
A malicious user can send a small amount of assets to every depositor and be the only prize taker. 

## Proof of Concept
Paste this test in `VaultDepositTest` below this line.
https://github.com/GenerationSoftware/pt-v5-vault/blob/55dcd65de4436d50967819c2ac313c3910b6f5f3/test/unit/Vault/Deposit.t.sol#L401
I commented the purpose of the code.
```solidity
function testSponsor_AliceTakesAwayPrizeChanceFromBob() external {
    // @audit bob deposits $5000
    vm.startPrank(bob);

    uint256 _amount1 = 5000e18;
    underlyingAsset.mint(bob, _amount1);
    underlyingAsset.approve(address(vault), type(uint256).max);
    vault.deposit(_amount1, bob);

    assertEq(vault.balanceOf(bob), _amount1);
    assertEq(twabController.delegateBalanceOf(address(vault), bob), _amount1);

    vm.stopPrank();

    // @audit alice sponsors bob with little assets
    vm.startPrank(alice);
    uint256 _amount2 = 1;
    underlyingAsset.mint(alice, _amount2);
    underlyingAsset.approve(address(vault), type(uint256).max);

    vault.sponsor(_amount2, bob);
    vm.stopPrank();

    // @audit bob has no chance to get the prize. This should not pass.
    assertEq(twabController.delegateBalanceOf(address(vault), bob), 0);
}
```

Then run these commands.
```sh
cd vault
forge test --match-test testSponsor_AliceTakesAwayPrizeChanceFromBob
```

This changes the probability of getting the prize, because `TwabLib.AccountDetails.delegateBalance` is the actual balance recorded in the `TwabLib.Account.observations` mapping. 
https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/1cdf78e87a3d9127f85a3755024f143664643c5e/src/libraries/TwabLib.sol#L203
https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/1cdf78e87a3d9127f85a3755024f143664643c5e/src/libraries/TwabLib.sol#L119

The observations mapping is used in `PrizePool._isWinner` to calculate the winning zone based on `_userTwab`.
https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L864
https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L928
https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculat
Yield fee can be stolen
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `_yieldFeeRecipient` can claim some of the fees earned in the vault by calling `mintYieldFee`. The function has no access control so anyone can call the function and claim the yield fee for themselves.

## Proof of Concept

In the below test an attacker  (`Alice`) mints the yield fee share to an controlled address (0xf) without being authorized to call the function.

```solidity

function testStealYieldFee() external {

// setup
_setLiquidationPair();
vault.setYieldFeePercentage(YIELD_FEE_PERCENTAGE);
vault.setYieldFeeRecipient(bob);
underlyingAsset.mint(address(this), 1000e18);
_sponsor(underlyingAsset, vault, 1000e18, address(this));
_accrueYield(underlyingAsset, yieldVault, 10e18);
vm.startPrank(alice);
prizeToken.mint(alice, 1000e18);
uint256 _liquidatedYield = vault.liquidatableBalanceOf(address(vault));
_liquidate(liquidationRouter, liquidationPair, prizeToken, _liquidatedYield, alice);
vm.stopPrank();

  

// pre checks
assertNotEq(vault.yieldFeeRecipient(), alice);
uint256 _yieldFeeShares = _getYieldFeeShares(_liquidatedYield, YIELD_FEE_PERCENTAGE);


// alice steal funds
vm.startPrank(alice);
vm.expectEmit();
emit MintYieldFee(alice, address(0xf), _yieldFeeShares);
vault.mintYieldFee(_yieldFeeShares, address(0xf));


// post checks
assertEq(vault.balanceOf(address(0xf)), _yieldFeeShares);
}
```

## Tools Used

Manual review, VSCode

## Recommended Mitigation Steps


Add access control so that only the fee recipient can call the function:

```diff
function mintYieldFee(uint256 _shares, address _recipient) external {
_requireVaultCollateralized();
+require(msg.sender == _yieldFeeRecipient, 'Wrong caller');
}
```





## Assessed type

Access Control
FIRST DEPOSITOR ATTACK IS PRESENT IN THE `Vault.sol` CONTRACT
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

The `Vault.deposit()` function is vulnerable to first depositor attack. It can be described as follows:

A malicious early depositor can deposit() with `1 wei` of asset token as the first depositor of the `Vault`, and get 1 wei of shares.

Then the first depositor can send 10000e18 - 1 of asset tokens directly to the contract 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 (from 19999e18 * 1 / 10000e18) 1 share token.

They will immediately lose 9999e18 or half of their deposits if they redeem() right after the deposit().

## Proof of Concept

```solidity
  function deposit(uint256 _assets, address _receiver) public virtual override returns (uint256) {
    if (_assets > maxDeposit(_receiver))
      revert DepositMoreThanMax(_receiver, _assets, maxDeposit(_receiver));

    uint256 _shares = _convertToShares(_assets, Math.Rounding.Down);
    _deposit(msg.sender, _receiver, _assets, _shares);

    return _shares;
  }
```

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

## Tools Used
Manual Review and VSCode

## Recommended Mitigation Steps

Consider requiring a minimal amount of share tokens to be minted for the first minter, and send a part of the initial mints as a reserve to a DAO like reserve or burn, so that the price per share can be more resistant to manipulation.


## Assessed type

Other
Missing access control in mintYieldFee allowing everybody to mint the available YieldFee to himself
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Everybody can call the mintYieldFee function in the Vault, when there is _yieldFeeTotalSupply available and mint shares to himself for free, which latter results in stealing funds form the Vault. (if this is a desired behavior, which it shouldn't based on the docs, the function can still be frontrun, and result in a malicious actor minting shares for free)

## Proof of Concept
Everybody can call the mintYieldFee function in the Vault, when there is _yieldFeeTotalSupply which is increased in the 
liquidate function at https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L550-L587. The liquidator is out of scope for this contest, this is why the _yieldFeeTotalSupply was increased manually. 
Example: 
After executing liquidate we have _yieldFeeTotalSupply = 5 * 1e9;

```
function testMintYieldFee() public {
    vault.setYieldFeeRecipient(alice);
    // vault._increaseYieldFeeBalance(5 * 1e9); <== simulates the result of liquidate function call

    vm.startPrank(alice);
    console.log('Balance of bob before: ', vault.balanceOf(bob));
    console.log('Yield fee total supply: ', vault.yieldFeeTotalSupply());
    console.log('Yield Fee Recipient: ', vault.yieldFeeRecipient());
    console.log('Bob address: ', bob);
    vault.mintYieldFee(5000000000, bob);
    console.log('Yield fee total after: ', vault.yieldFeeTotalSupply());
    console.log('Balance of bob after: ', vault.balanceOf(bob));
    console.log('Balance of alice after: ', vault.balanceOf(alice));
    vm.stopPrank();
  }

```

The result will be: 

```
Logs:
  Balance of bob before:  0
  Yield fee total supply:  5000000000
  Yield Fee Recipient:  0xBf0b5A4099F0bf6c8bC4252eBeC548Bae95602Ea
  Bob address:  0x4dBa461cA9342F4A6Cf942aBd7eacf8AE259108C
  Yield fee total after:  0
  Balance of bob after:  5000000000
  Balance of alice after:  0
```
## Tools Used
Manual Review

## Recommended Mitigation Steps
Add a modifier in the mintYieldFee function that requires the caller or the receiver to be the yieldFeeReceipient_


## Assessed type

Access Control
`Vault.mintYieldFee` FUNCTION CAN BE CALLED BY ANYONE TO MINT `Vault Shares` TO ANY RECIPIENT ADDRESS
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `Vault.mintYieldFee` external function is used to mint `Vault shares` to the yield fee `_recipient`. The function is an external function and can be called by anyone since there is no access control. The function will revert only under following two conditions:

1. if the Vault is undercollateralized
2. if the `_shares` are greater than the accrued `_yieldFeeTotalSupply`.

The issue with this function is it allows the caller to set the `_recipient` (Address of the yield fee recipient). It does not use the `_yieldFeeRecipient` state variable which was set in the `Vault.constructor` as the `yield fee recipient`.

Which means any one can steal the available `yield fee` from this vault (As long as above two revert conditions are not satisfied) by `minting shares` to his own address or to any address of his choice. 

## Proof of Concept

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

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

## Tools Used
Manual Review and VSCode

## Recommended Mitigation Steps

Hence it is recommended to use the `_yieldFeeRecipient` state variable value as the `yield fee recipient` inside the `Vault.mintYieldFee` external function and to remove the input parameter `address _recipient` from the `Vault.mintYieldFee` function. So that the caller will not be able to mint shares to any arbitory address of his choice and steal the yield fee of the protocol.

The updated function should be as follows:

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

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

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


## Assessed type

Other