xanoutas/solidity-security-findings huggingface dataset:
Some errors appear in the `PrincipalToken.sol#initialize` function because the previous ptRate is not kept after expiry.
Audit: 2024-02-spectra-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When the PT Contract expires, the Protocol initializer can reset state variables by calling the initialize function.
However, PT and YT holders may suffer losses by not storing the ptRate before expiration.

## 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
When the protocol's maturity expires, the protocol is stopped. Therefore, the protocol manager needs to initialize the protocol.
Therefore, the protocol initializer can call `PrincipalToken.sol#initialize` function to reset the protocol's state variables.

The `PrincipalToken.sol#initialize` function is as follows.

```solidity
function initialize(
        address _ibt,
        uint256 _duration,
        address _initialAuthority
    ) external initializer {
        if (_ibt == address(0) || _initialAuthority == address(0)) {
            revert AddressError();
        }
        if (IERC4626(_ibt).totalAssets() == 0) {
            revert RateError();
        }
        _asset = IERC4626(_ibt).asset();
        duration = _duration;
        expiry = _duration + block.timestamp;
        string memory _ibtSymbol = IERC4626(_ibt).symbol();
        string memory name = NamingUtil.genPTName(_ibtSymbol, expiry);
        __ERC20_init(name, NamingUtil.genPTSymbol(_ibtSymbol, expiry));
        __ERC20Permit_init(name);
        __Pausable_init();
        __ReentrancyGuard_init();
        __AccessManaged_init(_initialAuthority);
        _ibtDecimals = IERC4626(_ibt).decimals();
        _assetDecimals = PrincipalTokenUtil._tryGetTokenDecimals(_asset);
        if (
            _assetDecimals < MIN_DECIMALS ||
            _assetDecimals > _ibtDecimals ||
            _ibtDecimals > MAX_DECIMALS
        ) {
            revert InvalidDecimals();
        }
        ibt = _ibt;
        ibtUnit = 10 ** _ibtDecimals;
152:    ibtRate = IERC4626(ibt).previewRedeem(ibtUnit).toRay(_assetDecimals);
153:    ptRate = RayMath.RAY_UNIT;
        yt = _deployYT(
            NamingUtil.genYTName(_ibtSymbol, expiry),
            NamingUtil.genYTSymbol(_ibtSymbol, expiry)
        );
    }
```

As shown in the above code snippet, ptRate and ibtRate before expiration are reset without being stored.
However, PT holders who fail to repay PT before calling the initialize function and YT holders who do not receive interest income may suffer losses because ptRate and ibtRate is changed.
## Recommended Mitigation Steps
Save and use ptRate and ibtRate as mapping variables for PT and ibt tokens.


## Assessed type

Other
Upgraded Q -> 2 from #206 [1710118582077]
Audit: 2024-02-spectra-findings
Severity: MEDIUM

Judge has assessed an item in Issue #206 as 2 risk. The relevant finding follows:

 [L-01] Incorrect Return Value for previewDeposit()
The calculation path for previewDeposit() and deposit() is as follows:

previewDeposit() => _convertIBTsToSharesPreview() => _getPTandIBTRates(true)
deposit()　　　　=> _convertIBTsToShares()　　　　=>_getPTandIBTRates(false)
previewDeposit() should use the same rounding direction as deposit().

suggest:

    function _convertIBTsToSharesPreview(uint256 ibts) internal view returns (uint256 shares) {
-       (uint256 _ptRate, uint256 _ibtRate) = _getPTandIBTRates(true); // to round up the shares, the PT rate must round down
+       (uint256 _ptRate, uint256 _ibtRate) = _getPTandIBTRates(false); // to round up the shares, the PT rate must round down
        if (_ptRate == 0) {
            revert RateError();
        }
        shares = ibts.mulDiv(_ibtRate, _ptRate);
    }
Withdrawing or redeeming of tokens can be griefed
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
With the current implementation of Aloe Protocol interest does not accrue within the same block. And one is not disallowed to borrow and repay in the same block. These two properties open a grieﬁng attack that:
withdrawing liquidity can be blocked
Redeeming tokens by the suppliers can be blocked

To perform such attacks the attacker would only need to:

--pay for transaction gas fees

--have enough collateral to be able to borrow all the remaining liquidity

The attack works as follows assume T1 is the transaction the honest actor wants to submit to either borrow or withdraw liquidity, then the attacker frontruns T1 with T0 which is the transaction to remove/borrow all the liquidity by providing enough collateral and back runs with T2 which is the transaction that would repay the loan and withdraws the collateral. All through flashloan

Overall, the attacker would get all its collateral back but indeed would need to spend on gas and depending on the market state have enough liquidity to begin with.



## Proof of Concept
MorphoBlue Spearbit Audit considered as a high valid finding

## Tools Used

Manually

## Recommended Mitigation Steps

Borrowing and repaying in the same block (or even a small time interval) should be disincentived to prevent such attacks.




## Assessed type

Other
Share Inflation Attack
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
When totalSupply of underlying token == 0.
attacker goes ahead and executes the following steps:

They deposit 1 wei of the underlying token.
They will get back 1 wei of LPToken because totalSupply is zero
They transfer z underlying tokens directly to contract address
This leads to 1 wei of token worth z (+ some small amount)
Attacker won't have any problem making this z as big as possible as they have all the claim to it as a holder of 1 Wei of token
This attack has two implications:
The first deposit can be front-run and stolen
Let's assume there is a first user trying to mint some token in some reserve pool using their k*z underlying tokens.
An attacker can see this transaction and carry out the above-described attack making sure that k<1.
This leads to the first depositor getting zero LPToken for their k*z underlying tokens. Thus, 100% loss of funds(Now the underlying reserve of the LPToken contract is also increased with each deposit, so the upcoming depositor will have to deposit an even greater amount to mint some LPToken)
All upcoming depositors will face the same.
All the tokens are redeemable by the attacker using their 1 wei of LPToken.
Implicit minimum Amount and funds lost due to rounding errors
If an attacker is successful in making 1 wei of LPToken worth z underlying tokens and a user tries to mint LPToken using k* z underlying tokens, then
If k<1, then the user gets zero LPToken and the function would revert(mint shares >0).
This leads to an DOS of the protocol.
If k>1, then users still get some LPToken but they lose (k- floor(k)) * z) of underlying tokens, which get proportionally divided between LPToken holders due to rounding errors.
Attacker can keep 2 wei instead of 1 too. But that would require double the capital. But then the attack would be more powerful.
Impact
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

It is hard to put a number on the funds that can be affected because the value loss keeps happening until the detection of the bug.

## Proof of Concept
https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L171
https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L176

## Tools Used
Manual Review
## Recommended Mitigation Steps
Use openzeppelin Virtual share implementation.
Burn some tokens to dead address.


## Assessed type

ERC4626
Wrap around in `RayMath::fromRay()` , `RayMath::fromRay()` & `RayMath::toRay()` when max decimals are reached
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/libraries/RayMath.sol#L21
https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/libraries/RayMath.sol#L40
https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/libraries/RayMath.sol#L58


# Vulnerability details

The functions `RayMath::fromRay()` , `RayMath::fromRay()` & `RayMath::toRay()` all use a variable called `decimals_ratio`.

The variable `decimals_ratio` is denominated in `uint`. Also known as an `unsigned integer`.

One of the core functions of the `uint` variable is that it can never be negative. However, if you take an unsigned 0 and subtract 1 from it, the result wraps around, leaving a very large number.

In the variable `decimals_ratio` the decimals ratio gets calculated and part of this calculation requires a variable named `_decimals` which gets deducted from the 27 decimals which `Ray` is denominated in throughout the functions `RayMath::fromRay()` , `RayMath::fromRay()` & `RayMath::toRay()`.

However, if the `_decimals` variable is ever higher than 27, the `decimals ratio` will end up being a very big number and over-inflate the values of the respective calculations.

## Impact

The `decimals ratio` variable can wrap around and lead to over-inflated calculations if the `_decimals` variable is ever higher than the highest it can be, which is 27. 

## Proof of Concept

`Instance 01:`

```javascript
    function fromRay(uint256 _a, uint256 _decimals) internal pure returns (uint256 b) {
@>      uint256 decimals_ratio = 10 ** (27 - _decimals);
        assembly {
            b := div(_a, decimals_ratio)
        }
    }
```

`Instance 02:`

```javascript
function fromRay(
        uint256 _a,
        uint256 _decimals,
        bool _roundUp
    ) internal pure returns (uint256 b) {
@>      uint256 decimals_ratio = 10 ** (27 - _decimals);
        assembly {
            b := div(_a, decimals_ratio)

            if and(eq(_roundUp, 1), gt(mod(_a, decimals_ratio), 0)) {
                b := add(b, 1)
            }
        }
    }
```

`Instance 03:`

```javascript
function toRay(uint256 _a, uint256 _decimals) internal pure returns (uint256 b) {
@>      uint256 decimals_ratio = 10 ** (27 - _decimals);
        // to avoid overflow, b/decimals_ratio == _a
        assembly {
            b := mul(_a, decimals_ratio)

            if iszero(eq(div(b, decimals_ratio), _a)) {
                revert(0, 0)
            }
        }
    }
```

## Tools Used

Manual Review, VSCodium

## Recommended Mitigation Steps

Check if the `_decimals` variable is below the maximum decimal range.

`Instance 01:`

```diff
function fromRay(uint256 _a, uint256 _decimals) internal pure returns (uint256 b) {
++      require(_decimals < 27)
        uint256 decimals_ratio = 10 ** (27 - _decimals);
        assembly {
            b := div(_a, decimals_ratio)
        }
    }
```

`
Fee on transfer tokens can lead to incorrect approval
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The deposit function does not account for tokens with fee on transfer.

```solidity
    function deposit(
        uint256 assets,
        address ptReceiver,
        address ytReceiver
    ) public override returns (uint256 shares) {
        IERC20(_asset).safeTransferFrom(msg.sender, address(this), assets);
        IERC20(_asset).safeIncreaseAllowance(ibt, assets);
        uint256 ibts = IERC4626(ibt).deposit(assets, address(this));
        shares = _depositIBT(ibts, ptReceiver, ytReceiver);
    }
```

The function safeTransferFrom may not transfer exactly assets amount of tokens, for tokens with a fee on transfer. This means that the safeIncreaseAllowance call in the next line would be approving more tokens than what was received, leading to accounting issues.



## Tools Used
Manual Reveiw
## Recommended Mitigation Steps

It is recommended to find the balance of the current contract before and after the transferFrom to see how much tokens were received, and approve only what was received.




## Assessed type

Token-Transfer
PT and its YT may not have an equal supply at all times
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `PT and its YT should have an equal supply at all times` invariant can be easily broken because of the no restrictions on `burn()` function of the `YieldToken.sol` contract.

## Proof of Concept
Add the following test to the provided `PrincipalToken.t.sol` test suite:
```solidity
    function testNotEqualSupply() public {
        uint256 amount = 2e18;
        underlying.approve(address(principalToken), amount);
        principalToken.deposit(amount, address(this));
        
        //assure that totalSupplies are equal
        assertEq(principalToken.totalSupply(), yt.totalSupply()); 

        yt.burn(1e18);

        //assure that the amount of yt.totalSupply() is actually lower than the pt.totalSupply()
        assertEq(principalToken.totalSupply() > yt.totalSupply(), true);
    }
```
## Details
This invariant will also be broken after the expiration date and redeeming/withdrawing processed by users, since the YieldTokens are not burnt after the expiration date. 
## Tools Used
Manual Review, Foundry

## Recommended Mitigation Steps
Disallow arbitrary burn of tokens before the expiration date + if we want to preserve this invariant even after the expiration date it is still necessary to burn the YieldTokens together with PrincipalTokens.  





## Assessed type

Other
Flash Loan Griefing Leading to Gas Wastage and Congestion
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Attackers can drain gas and congest the PrincipalToken contract by taking recurring flash loans without actual usage.

## Proof of Concept

```solidity
File: PrincipalToken.sol
609:     function flashLoan(
610:         IERC3156FlashBorrower _receiver,
611:         address _token,
612:         uint256 _amount,
613:         bytes calldata _data
614:     ) external override returns (bool) {
615:         if (_amount > maxFlashLoan(_token)) revert FlashLoanExceedsMaxAmount();
616: 
617:         uint256 fee = flashFee(_token, _amount);
618:         _updateFees(fee);
619: 
620:         // Initiate the flash loan by lending the requested IBT amount
621:         IERC20(ibt).safeTransfer(address(_receiver), _amount);
622: 
623:         // Execute the flash loan
624:         if (_receiver.onFlashLoan(msg.sender, _token, _amount, fee, _data) != ON_FLASH_LOAN)
625:             revert FlashLoanCallbackFailed();
626: 
627:         // Repay the debt + fee
628:         IERC20(ibt).safeTransferFrom(address(_receiver), address(this), _amount + fee);
629: 
630:         return true;
631:     }

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

No mitigation for gas limits or recurrance caps when initiating flash loans.
 

## Tools Used

- Manual Review 

## Recommended Mitigation Steps

1. Put a max borrower limit per block 
2. Require minimum borrow amounts
3. Charge variable fee for repeat borrowers


## Assessed type

Other
Lack of slippage controls in PrincipalToken::deposit and PrincipalToken::withdraw
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `deposit` and `withdraw` functions do not check that users receive the expected number of shares based on previewed rates. This can lead to slippage and loss of value.

## Proof of Concept

In the `deposit` function, shares are minted based on current rates:

```solidity
File: PrincipalToken.sol
176:     function deposit(
177:         uint256 assets,
178:         address ptReceiver,
179:         address ytReceiver
180:     ) public override returns (uint256 shares) {
181:         IERC20(_asset).safeTransferFrom(msg.sender, address(this), assets);
182:         IERC20(_asset).safeIncreaseAllowance(ibt, assets);
183:         uint256 ibts = IERC4626(ibt).deposit(assets, address(this));
184:         shares = _depositIBT(ibts, ptReceiver, ytReceiver);
185:     }

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

There is no check that `shares` exceeds a minimum amount expected by the user.

Similarly in `withdraw`: 

```solidity
File: PrincipalToken.sol
278:     function withdraw(
279:         uint256 assets,
280:         address receiver,
281:         address owner
282:     ) public override returns (uint256 shares) {
283:         _beforeWithdraw(assets, owner);
284:         (uint256 _ptRate, uint256 _ibtRate) = _getPTandIBTRates(false);
285:         uint256 ibts = IERC4626(ibt).withdraw(assets, receiver, address(this));
286:         shares = _withdrawShares(ibts, receiver, owner, _ptRate, _ibtRate);
287:     }

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


## Scenario

1. Attacker manipulates market to change IBT/PT rates unfavorably

2. User calls `previewDeposit` and expects to receive 100 shares for 10 assets 

3. Attacker stops manipulation, allowing rates to return to normal

4. User deposits 10 assets, but only receives 90 shares due to updated rates

5. User lost value due to slippage between preview and execution

Similar for withdraw - user may receive fewer assets than expected.

## Tools

Manual Review

## Recommended Mitigation Steps

- Require minimum share amounts on `deposit`. Revert if received is less.

- Require maximum share amounts on `withdraw`. Revert if received is more.



## Assessed type

Invalid Validation
Sandwich attack possible in PrincipalToken::redeem function
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `redeem()` function relies on previewed rate from `previewRedeem()` which can be manipulated before actual redemption execution. This creates a sandwich attack vulnerability.

## Proof of Concept

The main issue is that `redeem()` uses the instantaneous rate from `previewRedeem()` to convert shares to assets:

```solidity
File: PrincipalToken.sol
229:     function redeem(
230:         uint256 shares,
231:         address receiver,
232:         address owner
233:     ) public override returns (uint256 assets) {
234:         _beforeRedeem(shares, owner);
235:         assets = IERC4626(ibt).redeem(_convertSharesToIBTs(shares, false), receiver, address(this));
236:         emit Redeem(owner, receiver, shares);
237:     }
```

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

```solidity
File: PrincipalToken.sol
659:     function _convertSharesToIBTs(
660:         uint256 _shares,
661:         bool _roundUp
662:     ) internal view returns (uint256 ibts) {
663:         (uint256 _ptRate, uint256 _ibtRate) = _getPTandIBTRates(false);
664:         if (_ibtRate == 0) {
665:             revert RateError();
666:         }
667:         ibts = _shares.mulDiv(
668:             _ptRate,
669:             _ibtRate,
670:             _roundUp ? Math.Rounding.Ceil : Math.Rounding.Floor
671:         );
672:     }
```

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

```solidity
File: PrincipalToken.sol
471:     function previewRedeem(uint256 shares) public view override returns (uint256) {
472:         return IERC4626(ibt).previewRedeem(previewRedeemForIBT(shares));
473:     }
```
[Line of Code](https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L471)

The `_getPTandIBTRates()` pricing can vary between `previewRedeem()` and `redeem()` based on market conditions. This allows manipulation between the preview and execution.

There is no limit on the allowed time between preview and execution.

## Scenario

1. Attacker calls `previewRedeem()` and sees current fair rate. 

2. Attacker takes actions to artificially decrease IBT price, like shorting or dumping tokens. 

3. Due to lower IBT price, `_getPTandIBTRates()` will now return a lower PT rate.

4. Victim calls `redeem()` based on initial preview.

5. `redeem()` uses manipulated lower rate due to attacker's actions.

6. Victim receives less assets than initially previewed.

7. Attacker stops manipulation, benefitting from victim's loss.

## Tools

Manual Review

## Recommended Mitigation Steps

- Use a commit-reveal pattern where user first commits to redeem and rate is locked.

- Add reasonable timeout on validity of previewed rate, requiring preview and redeem within limited block range.

- Utilize a pricing oracle that
`ibtRate` is vulnerable to price manipulation
Audit: 2024-02-spectra-findings
Severity: MEDIUM

# Lines of code

https://github.com/perspectivefi/spectra-core/blob/fec59dc6720fb4861b07b30845ef2c1a42f947bf/src/tokens/PrincipalToken.sol#L152


# Vulnerability details

## Impact
`ibtRate` initialization is vulnerable to price manipulation. This allows an attacker to increase or decrease the price to carry out various attacks against the protocol.

## Proof of Concept
Consider the following scenario.

```
        if (
            _assetDecimals < MIN_DECIMALS ||
            _assetDecimals > _ibtDecimals ||
            _ibtDecimals > MAX_DECIMALS
        ) {
            revert InvalidDecimals();
        }
        ibt = _ibt;
        ibtUnit = 10 ** _ibtDecimals;
        ibtRate = IERC4626(ibt).previewRedeem(ibtUnit).toRay(_assetDecimals);
        ptRate = RayMath.RAY_UNIT;
        yt = _deployYT(
            NamingUtil.genYTName(_ibtSymbol, expiry),
            NamingUtil.genYTSymbol(_ibtSymbol, expiry)
        );
```

An attacker sees that the PrincipalToken contract is about to be deployed and front runs the transaction by depositing a large amount of tokens into the specified ibt vault. The `ibtRate` is then set at a really low rate. The attacker can then withdraw from the vault and now the token has been deployed with a very small `ibtRate` affecting all future users and depositors of the Principal token. The attacker could also deflate the value returned by `previewRedeem` and cause the `ibtRate` to be very small therefore also benefiting from the price manipulation.

## Tools Used
Manual Review

## Recommended Mitigation Steps

Consider using a flashbots RPC to avoid any type of front running attacks or consider implementing TWAP to calculate the value of `ibtRate` though this may be a bit more difficult.


## Assessed type

Oracle