xanoutas/solidity-security-findings huggingface dataset:
An Attacker can manipulate the IBTRate on PrincpleToken Contract resulting bad PTRate 
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L912-L916


# Vulnerability details

## Impact
An attacker can decrease PTRate drastically and cause users loss of funds

## Proof of Concept
[ibtRate](https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L152) is being calculated relying on `previewRedeem()` of the Vault.

Let's say we have C Vault and it has a big amount of asset deposited, this amount will affect `ibtRate` as expected on PT contract initialize which is expected, however an attacker can front-run this initialize and deposit a huge amount in C Vault.

Since `ibtRate` calculated as high rate it It could become bigger than [ptRate = RayMath.RAY_UNIT](https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L153) (ptRate = 1e17), `ptRate` is set fixed as 1e27 but `ibtRate` is dynamic and it's value calcualted according to C vaualt assets amount.

What could happen? Bob waits for other users to deposit then he **redeem** his big deposit from C vault then makes a small deposit into PT contract, [_getCurrentPTandIBTRates](https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L906) will be called, the `currentIBTRate` will be calculated according to `previewRedeem()` but it will be small value since Bob redeemed his big deposit from C vault, since `currentIBTRate` will be smaller than `ibtRate` this block will be executed [L912-L916](https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L912-L916)
```
            ? ptRate.mulDiv(
                currentIBTRate,
                ibtRate,
                roundUpPTRate ? Math.Rounding.Ceil : Math.Rounding.Floor
            )
```
because currentIBTRate < ibtRate will be true, and `ptRate` will be calculated and decreases to very low value unfortunatly.

Attack Scenario:

1. Bob will make big deposit in C Vault and front-running PT contract initialize.
2. PT contract initialized and ibtRate is very high rate.
3. Bob will wait for other users to deposit
4. Redeem his big deposit from C Vault
5. Deposit in PT contract small amount of assets just to update the rate.
6. PTRate will be too low.
7. Users try to redeem their assets but PTRate is very low and affected their redeemed amount causing loss of funds.

## Tools Used
Manual Review

## Recommended Mitigation Steps
If ibtRate is higher than ptRate revert. We know that there's a rate inflation or
a suggestion ptRate should equal always to ibtRate on initialize.



## Assessed type

Other
Asymmetry in calculating yield, when IBTRate has first dropped and then increased
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/main/src/libraries/PrincipalTokenUtil.sol#L55-L129


# Vulnerability details

## Impact
The [_computeYield()](https://github.com/code-423n4/2024-02-spectra/blob/main/src/libraries/PrincipalTokenUtil.sol#L55-L129) function is responsible for calculating the yield that a certain user has generated since depositing IBT tokens into the ``PrincipaToken.sol`` contract. In the begging the ``PTRate`` = 1 and ``IBTRAte`` = 1. The the ``IBTRate`` drops to 0.8, and ``PTRate`` drops to 0.8 as well, based on the calculations in the [_getCurrentPTandIBTRates()](https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L901-L914) function. Afterwards ``IBTRate`` goes to 0.9 as the strategy used by the underlying ERC4626 Vault has generated some profits, after first accruing some loses, but the loses are still bigger than the profits. The ``PTRate`` will stay at 0.8. Now the ``PTRate`` = 0.8 and the ``IBTRate`` = 0.9 .Consider the following scenario, decimals are not taken into account in order to simplify the example. Alice deposits in the beginning when ``PTRate`` = 1 and ``IBTRAte`` = 1. Then she doesn't interact with her tokens at all, and when the maturity period has passed she decides to withdraw. The The [_computeYield()](https://github.com/code-423n4/2024-02-spectra/blob/main/src/libraries/PrincipalTokenUtil.sol#L55-L129)function will enter the following statement
```solidity
// either both negative and positive yield happened, more negative
                    // or only negative yield happened
                    uint256 actualNegativeYieldInAssetRay = _convertToAssetsWithRate(
                        userYTBalanceInRay,
                        _oldPTRate - _ptRate,
                        RayMath.RAY_UNIT,
                        Math.Rounding.Floor
                    );
                    uint256 expectedNegativeYieldInAssetRay = Math.ceilDiv(
                        ibtOfPTInRay * (_oldIBTRate - _ibtRate),
                        RayMath.RAY_UNIT
                    );
                    yieldInAssetRay = expectedNegativeYieldInAssetRay >
                        actualNegativeYieldInAssetRay
                        ? 0
                        : actualNegativeYieldInAssetRay - expectedNegativeYieldInAssetRay;
                    yieldInAssetRay = yieldInAssetRay.fromRay(
                        IERC4626(IPrincipalToken(IYieldToken(_yt).getPT()).underlying()).decimals()
                    ) < SAFETY_BOUND /// INFO: this is set to 100, it is a constant that is hardcoded
                        ? 0
                        : yieldInAssetRay;
                }
                newYieldInIBTRay = _convertToSharesWithRate(
                    yieldInAssetRay,
                    _ibtRate,
                    RayMath.RAY_UNIT,
                    Math.Rounding.Floor
                );
```

This part 
```solidity
yieldInAssetRay = expectedNegativeYieldInAssetRay
Yield token holders can drain value from principal token holders
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L906-L912


# Vulnerability details

## Impact

Yield token holders can drain value from principal token holders by manipulating ibt token exchange rate.

## Proof of Concept

Exchange rate of ERC-4626 [can be manipulated](https://www.euler.finance/blog/exchange-rate-manipulation-in-erc4626-vaults) and it can be done for profit by yt token holders.

On each yt token balance change ibtRate and ptRate are updated. ptRate can only [decrease](https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L906-L912) and it happens in cases if currect exchange rate of ERC-4626 lower than stored rate, e.i. yield is negative. This condition can easily be triggered by anyone, naturally or on purpose. 

After that principal token amount [returned](https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L667-L671) after maturity reduced permanently for principal token holders independent from future ibt rates.

PT token holders' ibt tokens doesn't disappear completely, they goes to yt token holders. So there is incentives for them to manipulate ibt exchange rate to theft principal from pt token holders.

```
// test/PrincipalToken/PrincipalToken.t.sol
function test_poc1() public {
    address alice = makeAddr("alice");

    // user deposits ibt tokens and sells yield to alice
    uint256 amountToDeposit = 1000e18;
    underlying.approve(address(principalToken), amountToDeposit);
    principalToken.deposit(amountToDeposit, address(this), alice);

    // alice manipulates exchange rate locally
    ibt.setPricePerFullShare(0.95e18);
    principalToken.updateYield(address(this));

    // ...but in the big picture everything is going well
    ibt.setPricePerFullShare(1.1e18);
    principalToken.updateYield(address(this));

    vm.warp(principalToken.maturity() + 1);

    // after maturity pt holder should get back his principal = 1000 tokens
    // but in reality he can only withdraw 950
    assertApproxEqAbs(
        principalToken.maxWithdraw(address(this)),
        950e18,
        1000
    );
    
    // for alice expected yield = 1000 * (1.1 - 10) = 100 underlying tokens
    // but in reality she stealed additional 50 from pt holder
    assertApproxEqAbs(
        principalToken.getCurrentYieldOfUserInIBT(alice) * 11/10, // convert IBT to underlying
        150e18,
        1000
    );
}
```

## Tools Used

Manual review

## Recommended Mitigation Steps

I can only recommend redesign of yield stripping system to handle cases when ibtRate can drop and then recover: pt holders shoudn't lose principal amount.


## Assessed type

Math
The ratesAtExpiryStored flag is not protected by access control. Anyone can call storeRatesAtExpiry() after expiry 
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L409-L417


# Vulnerability details

## Impact
Anyone can lock in potentially arbitrary rates for redeeming principal tokens after expiry by calling storeRatesAtExpiry(). This could allow attackers to manipulate redemptions to their advantage.
The severity is high because it impacts a critical part of the contract's functionality for redeeming tokens after expiry. Allowing anyone to determine the conversion rates means the rates could potentially be set to completely arbitrary values, enabling theft of funds or making redemptions impossible if rates are set to zero.


## Proof of Concept
The `ratesAtExpiryStored` flag is not protected by access control. This means that anyone can call the `storeRatesAtExpiry()` function after expiry.
Here is how it works:
The `storeRatesAtExpiry()` function is marked with the `afterExpiry` modifier, which checks that the block timestamp is greater than or equal to the expiry time:

       modifier afterExpiry() virtual {
           if (block.timestamp < expiry) {
               revert PTNotExpired();
          }
           _;
       }
This means that anyone can call the `storeRatesAtExpiry()` function after the expiry time.
Inside the function, it simply sets the `ratesAtExpiryStored` flag to true:

       function storeRatesAtExpiry() public override afterExpiry {
           if (ratesAtExpiryStored) {
               revert RatesAtExpiryAlreadyStored(); 
           }
           ratesAtExpiryStored = true;
    
           // update rates
    
           emit RatesStoredAtExpiry(...);
      }
There is no access control check here to restrict who can call this.
The impact is that anyone can trigger the rate storage after expiry. This could be used by an attacker to manipulate the rates stored if the actual market rates have moved significantly.
For example, an attacker could:
1. Wait until expiry
2. Call `storeRatesAtExpiry()` with the old rates if rates have dropped a lot
3. Redeem PT for less IBT than they should get


## Tools Used
Manual
## Recommended Mitigation Steps
The `storeRatesAtExpiry()` function should be restricted to an authorized role, for example:

       function storeRatesAtExpiry() public override afterExpiry restricted {
         // add restricted modifier
       }

This would prevent unauthorized actors from manipulating the stored rates.



## Assessed type

Other
Initial Deposit Rate Exploitation can Lead to Unfair Advantage and Future Deposit Dilution
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L750


# Vulnerability details

## Impact

The PrincipalToken allows the first depositor to arbitrarily set the initial PT and IBT rates by making the first deposit. This could be exploited to dilute future depositors. 

## Proof of Concept

The `ibtRate` and `ptRate` state variables are initialized to some defaults. But the first depositor can arbitrarily set them by choosing the deposit amount `_ibts`. 

```solidity
File: PrincipalToken.sol
152:         ibtRate = IERC4626(ibt).previewRedeem(ibtUnit).toRay(_assetDecimals);
153:         ptRate = RayMath.RAY_UNIT;
```
[Lines of Code](https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L152C9-L153C34)

In `_depositIBT()` function first depositor can set arbitrary rates by deposit amount in this line:

```solidity
shares = _convertIBTsToShares(_ibts - tokenizationFee, false);
```
[Lines of Code](https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L750)


```solidity
File: PrincipalToken.sol
750:     function _depositIBT(
751:         uint256 _ibts,
752:         address _ptReceiver,
753:         address _ytReceiver
754:     ) internal notExpired nonReentrant whenNotPaused returns (uint256 shares) {
755:         updateYield(_ytReceiver);
756:         uint256 tokenizationFee = PrincipalTokenUtil._computeTokenizationFee(
757:             _ibts,
758:             address(this),
759:             registry
760:         );
761:         _updateFees(tokenizationFee);
762:         shares = _convertIBTsToShares(_ibts - tokenizationFee, false);
763:         if (shares == 0) {
764:             revert RateError();
765:         }
766:         _mint(_ptReceiver, shares);
767:         emit Mint(msg.sender, _ptReceiver, shares);
768:         IYieldToken(yt).mint(_ytReceiver, shares);
769:     }
```

[Lines of Code](https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L750)


Later depositors get diluted shares based on manipulated rates

This exploits the `_convertIBTsToShares()` conversion formula:

```solidity
File: PrincipalToken.sol
680:     function _convertIBTsToShares(
681:         uint256 _ibts,
682:         bool _roundUp
683:     ) internal view returns (uint256 shares) {
684:         (uint256 _ptRate, uint256 _ibtRate) = _getPTandIBTRates(false);
685:         if (_ptRate == 0) {
686:             revert RateError();
687:         }
688:         shares = _ibts.mulDiv(
689:             _ibtRate,
690:             _ptRate,
691:             _roundUp ? Math.Rounding.Ceil : Math.Rounding.Floor
692:         );
693:     }
```

[Lines of Code](https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L680)

Malicious user can manipulate the ratio between `_ibts` and `_ptRate` by setting the rates in their favor before others deposit.

## Scenario

1. Deploy a new PrincipalToken contract.

2. Ca
There is no reentrancy protection for principal token flashloan function, allowing attackers to drain IBT
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L609
https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L624


# Vulnerability details

## Impact
The main impact is that a malicious user can drain all interest bearing token locked in the principal token contract.

## Proof of Concept

In order to reproduce a malicious user has to deploy a malicious receiver contract

```
pragma solidity ^0.8.19;

contract MaliciousReceiver {
    count = 0

  function onFlashLoan(address,address _token,uint256 _amount,uint256,bytes calldata _dat) returns bytes32 {

    if (count < 10) {
      address principalAddress = msg.sender;
      principalAddress.flashLoan(address(this),_token,_amount,_dat);

      count += 1;
      }
      return keccak256("ERC3156FlashBorrower.onFlashLoan");

  }


}
```

Then the attacker proceeds to call the function [flashLoan](https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L609C14-L609C23) by supplying their malicious contract as the receiver address.

Once this call chain is executed it is clear to see that funds will be moved from the principal token address multiple times due to the line 

```
IERC20(ibt).safeTransfer(address(_receiver), _amount);

``` 

and the reentrancy is triggered on the line

```
if (_receiver.onFlashLoan(msg.sender, _token, _amount, fee, _data) != ON_FLASH_LOAN)
            revert FlashLoanCallbackFailed();
```

However the flash loan will only be re-payed once after above on the line

```
IERC20(ibt).safeTransferFrom(address(_receiver), address(this), _amount + fee);

```

## Tools Used

Manual Review

## Recommended Mitigation Steps

I would recommend that a reentrancy guard be added to the flashloan function.


## Assessed type

Reentrancy
Post-Expiry Rate Manipulation in PrincipalToken due to Lack of Access Control
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L409


# Vulnerability details

## Impact

Attackers can exploit the reliance on calling `storeRatesAtExpiry()` to lock rates after expiry to extract additional value or manipulate user redemptions.

## Proof of Concept

```
File: PrincipalToken.sol
409:     function storeRatesAtExpiry() public override afterExpiry {
410:         if (ratesAtExpiryStored) {
411:             revert RatesAtExpiryAlreadyStored();
412:         }
413:         ratesAtExpiryStored = true;
414:         // PT rate not rounded up here
415:         (ptRate, ibtRate) = _getCurrentPTandIBTRates(false);
416:         emit RatesStoredAtExpiry(ibtRate, ptRate);
417:     }

```
[Lines of code](https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L409)

The contract relies on this being called to set rates after expiry but there is no control on when it can be called.

An attacker could wait until favourable rate moves after expiry and then call `storeRatesAtExpiry()` to lock in rates benefiting themselves or causing maximum damage.

## Recommended Mitigation Steps

1. Add onlyOwner access control to `storeRatesAtExpiry()`
2. Emit event on expiry block timestamp passed without valid rate store
3. Compute rate derivation from fixed oracle sources rather than calling contract states  


## Assessed type

Access Control
Rounding in favour of the user in the _convertIBTsToShares() function
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/tokens/PrincipalToken.sol#L684


# Vulnerability details

## Impact
Rounding in favour of the user in the `_convertIBTsToShares()` function may lead to minting more shares than it was intended by the protocol. Minted amount of shares may exceed the amount that was supposed to be minted in case of rounding down in favour of the protocol, thus leading to loss of funds for the protocol when redeeming/withdrawing. 

## Proof of Concept
First of all, let's take a look at two invariants stated for this protocol:
1. Decimals imprecisions should always benefit the protocol and no user should be able to extract extra value;
2. `previewDeposit ≤ deposit` : the preview of shares minted upon depositing should be less than or equal to the actual shares minted.

Let's start from the second invariant and see its realisation in the code. This invariant is secured by the difference between the `_convertIBTsToShares()` and the `_convertIBTsToSharesPreview()` functions. The first difference is during the call to `_getPTandIBTRates()`, where `_convertIBTsToShares()` passes `false`, and the preview function passes `true`:
```solidity
function _convertIBTsToShares(
        uint256 _ibts,
        bool _roundUp
    ) internal view returns (uint256 shares) {
        (uint256 _ptRate, uint256 _ibtRate) = _getPTandIBTRates(false);

   function _convertIBTsToSharesPreview(uint256 ibts) internal view returns (uint256 shares) {
        (uint256 _ptRate, uint256 _ibtRate) = _getPTandIBTRates(true);
```
In the code commentaries there is important note, stating that `to round up the shares, the PT rate must round down`. This statement is then once again found in the `_getPTandIBTRates()` function: `roundUpPTRate true if the PTRate result from mulDiv computation in case of negative rate should be rounded up`.
Thus, in order to roundUp the `shares`, it is required to roundDown the `_ptRate`. By setting `false` for the `_convertIBTsToShares()` and `true` for `_convertIBTsToSharesPreview()` we definitely secure, that the amount of `shares` for preview will likely be less than the amount for the actual `deposit()` function. However, by securing this in the outlined way, we are actually giving a field for rounding not in favour of the protocol, but in favour of the user, as it is outlined in the natspec comments and as is it showed in the code itself, since the amount of shares is calculated in the following way: `shares = ibts * ibtRate / ptRate`. The higher the `ptRate`, the lower the amount of `shares` will be, the lower the `ptRate`, the higher the amount of `shares` will be.
So, by passing the `false` bool-parameter to the `_convertIBTsToShares`, which function is actually used in the `deposit` flow, we are allowing for rounding in favour of the user, since the amount of `shares` will be rounded up, even though the `previewDeposit ≤ deposit` invariant is secured.
## De