xanoutas/solidity-security-findings huggingface dataset:
protocol will mint shares to option buyers instead of burning
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/CollateralTracker.sol#L1006-L1022
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/CollateralTracker.sol#L1058-L1079


# Vulnerability details

## Impact
protocol will mint shares to option buyers while it should be burned . This will cause to inflate the other user's shares and will lose funds.

## Proof of Concept
        function _createLegInAMM(
        IUniswapV3Pool univ3pool,
        TokenId tokenId,
        uint256 leg,
        LiquidityChunk liquidityChunk,
        bool isBurn
    )
      ...../
      
     moved = isLong == 0
                ? _mintLiquidity(liquidityChunk, univ3pool)
                : _burnLiquidity(liquidityChunk, univ3pool);

      ..../}



if option is long , protocol burn the liquidity from uniswap .

                function _burnLiquidity(
        LiquidityChunk liquidityChunk,
        IUniswapV3Pool univ3pool
    ) internal returns (LeftRightSigned movedAmounts) {
        // burn that option's liquidity in the Uniswap Pool.
        // This will send the underlying tokens back to the Panoptic Pool (msg.sender)
        (uint256 amount0, uint256 amount1) = univ3pool.burn(
            liquidityChunk.tickLower(),
            liquidityChunk.tickUpper(),
            liquidityChunk.liquidity()
        );

        // amount0 The amount of token0 that was sent back to the Panoptic Pool
        // amount1 The amount of token1 that was sent back to the Panoptic Pool
        // no need to safecast to int from uint here as the max position size is int128
        // decrement the amountsOut with burnt amounts. amountsOut = notional value of tokens moved
        unchecked {
    @=>           movedAmounts = LeftRightSigned.wrap(0).toRightSlot(-int128(int256(amount0))).toLeftSlot(
       @=>              -int128(int256(amount1))
            );
        }
    }


Protocol set to amount out to negative in burning liquidity .So minting sfpm or burning sfpm in long position will create negative total moved .




            function _mintInSFPMAndUpdateCollateral(
        TokenId tokenId,
        uint128 positionSize,
        int24 tickLimitLow,
        int24 tickLimitHigh
    ) internal returns (uint128) {

             ...../
          (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned 
         totalSwapped) = SFPM
            .mintTokenizedPosition(tokenId, positionSize, tickLimitLow, 
           tickLimitHigh);
       ..../
          uint128 poolUtilizations = _payCommissionAndWriteData(tokenId, 
       positionSize, 
    totalSwapped);



Total swapped from mint tokenized position is negative if option is in long position.



                       function takeCommissionAddData(
        address optionOwner,
        int128 longAmount,
        int128 shortAmount,
        int128 swappedAmount
    ) external onlyPanopticPool returns (int256 utilizati
Subsequent minters and depositors are worse and worse off
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/58dda1b3b74e48f4d924731ec5da14096043fde0/contracts/CollateralTracker.sol#L453-L468
https://github.com/code-423n4/2024-04-panoptic/blob/58dda1b3b74e48f4d924731ec5da14096043fde0/contracts/CollateralTracker.sol#L399-L408
https://github.com/code-423n4/2024-04-panoptic/blob/58dda1b3b74e48f4d924731ec5da14096043fde0/contracts/CollateralTracker.sol#L370-L374


# Vulnerability details

## Impact
The following `previewMint` and `previewDeposit` functions factor in `COMMISSION_FEE`. This means that the deposited assets include a part that is for the commission and the minted shares are only for the deposited assets minus the part for the commission.

https://github.com/code-423n4/2024-04-panoptic/blob/58dda1b3b74e48f4d924731ec5da14096043fde0/contracts/CollateralTracker.sol#L453-L468
```solidity
    function previewMint(uint256 shares) public view returns (uint256 assets) {
        // round up depositing assets to avoid protocol loss
        // This prevents minting of shares where the assets provided is rounded down to zero
        // compute the MEV tax, which is equal to a single payment of the commissionRate on the FINAL (post mev-tax) assets paid
        // finalAssets - convertedAssets = commissionRate * finalAssets
        // finalAssets - commissionRate * finalAssets = convertedAssets
        // finalAssets * (1 - commissionRate) = convertedAssets
        // finalAssets = convertedAssets / (1 - commissionRate)
        unchecked {
            assets = Math.mulDivRoundingUp(
                shares * DECIMALS,
                totalAssets(),
                totalSupply * (DECIMALS - COMMISSION_FEE)
            );
        }
    }
```

https://github.com/code-423n4/2024-04-panoptic/blob/58dda1b3b74e48f4d924731ec5da14096043fde0/contracts/CollateralTracker.sol#L399-L408
```solidity
    function previewDeposit(uint256 assets) public view returns (uint256 shares) {
        // compute the MEV tax, which is equal to a single payment of the commissionRate on the FINAL (post mev-tax) assets paid
        unchecked {
            shares = Math.mulDiv(
                assets * (DECIMALS - COMMISSION_FEE),
                totalSupply,
                totalAssets() * DECIMALS
            );
        }
    }
```

However, because the following `totalAssets` function does not remove the part of the deposited assets that is for the commission, calling the `mint` function, which calls the `previewMint` function, would cause a user who mints earlier to deposit less assets than a user who mints later for minting the same number of shares. In this case, the first minter can receive more assets than deposited and the subsequent minters can receive less and less assets than deposited when redeeming all of the minted shares; hence, the first minter gains more assets than entitled to while the subsequent minters lose more and more assets when they should not and are worse and worse off.

For the same reason, calli
`delegatecall` not handled correctly allowing the calling contract to manipulate the state of the called contract
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/multicall/Multicall.sol#L15


# Vulnerability details

## Description

`msg.value` is used within a loop to execute multiple calls using `delegatecall`. This approach can lead to the reuse of msg.value across iterations, which can have significant implications for the contract's behavior and security.

## Impact

Reuse of `msg.value` in a Loop

## Proof of Concept

- Initial Iteration: The first iteration of the loop will use all the `msg.value` sent with the transaction. This is because `msg.value` represents the amount of Ether sent with the transaction, and it remains constant throughout the execution of the function call.

- Following Iterations:  In subsequent iterations, if the contract attempts to use `msg.value` again, it will be 0 because the initial `msg.value` has already been used in the first iteration. This behavior is due to the fact that `msg.value` is reset to `0` after it has been used in a transaction.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Avoid Using `delegatecall` in Loops: To mitigate this risk  it's recommended to avoid using msg.value within loops. Instead, consider alternative approaches to handle multiple operations in a single transaction, such as using separate transactions for each operation or implementing a mechanism to track the amount of msg.value used


## Assessed type

call/delegatecall
A  Financial Integrity Issue in Premium Calculation 
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1934-L1945


# Vulnerability details

## Impact
this can  the state of premium tracking, potentially leading to financial discrepancies as incorrect premium payouts or accumulation

##  Root of the bug and summary 



the vulnerable part :
```solidity
(int256(
    grossPremiumLast.rightSlot() * totalLiquidityBefore
) - int256(
    _premiumAccumulatorsByLeg[_leg][0] * positionLiquidity
)) + int256(legPremia.rightSlot() * 2 ** 64),
```

The addition of $legPremia.rightSlot() * 2**64$ can lead to extremely large values that far exceed typical integer ranges, even in a 256-bit environment. 
This is because multiplying any non-trivial legPremia.rightSlot by $2^{64}$ especially in contracts dealing with financial calculations involving tokens with smaller decimals, could escalate to an overflow scenario.

## POC 
Here is an example Scenario:
let's say that we have :
- grossPremiumLast_rightSlot: 50,000  represents the last recorded premium
- totalLiquidityBefore: 100,000 Total liquidity before the position burn
- _premiumAccumulatorsByLeg_0: 25,000 Accumulated premium from a specific leg
- positionLiquidity: 5,000 The liquidity associated with the position being burned
- legPremia_rightSlot: 100 The premium associated with the right slot of a leg)
- totalLiquidity: 95,000 Total liquidity after the burn, less than totalLiquidityBefore

Calculation:
using this expression:
$
new\_Ln\_calculation=(totalLiquiditygrossPremiumLast\_rightSlot⋅totalLiquidityBefore−\_premiumAccumulatorsByLeg\_0⋅positionLiquidity+legPremia\_rightSlot⋅2^{64})​/totalLiquidity​ $

Using the given values, the formula becomes:
$
new\_Ln\_calculation=(50,000×100,000−25,000×5,000+100×2^{64}​)/95,000
$
$
=(5,000,000,000−125,000,000+1,844,674,407,370,955,200​​)/95,000 
$
$
=(1,844,674,382,370,955,200​​​)/95,000 =19,418,678,762,852.16
$

as result the term $100 * 2**64$ results in a number far exceeding typical financial ranges: $1,844,674,407,370,955,200$. 
When added to the premium calculation, it overwhelms the other terms, distorting the intended balance adjustments post-burn. 

## Tools Used
manual review 
## Recommended Mitigation Steps
the multiplication by 2**64 can be avoided or using a SafeMath to handle such large number operations can prevent overflow


## Assessed type

Under/Overflow
Option Buyers can earn premium instead of paying for it.
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1632-L1640


# Vulnerability details

## Impact
Loss of funds for PLPs because Option Buyers receive shares for the premia they owe instead of having their shares burnt to pay their debt.
This causes each share can claim fewer amounts of underlyingToken, thus, all the share's holders receive a haircut equivalent to the amount of premia that the Option Buyers should have paid.

## Proof of Concept
The [`PanopticPool.settleLongPremium() function`](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1587-L1659) is meant to be used by OptionSellers to settle premium owed by OptionSellers as long as they are solvent.

The process to determine the amount of premium to settle is by determining [the amount of `realizedPremia` that should be paid by the OptionBuyer](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1633-L1640). **The problem with the existing implementation is that the computed value of the `realizedPremia` is not inverted when is passed to the [`CollateralTracker.exercise() function`](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/CollateralTracker.sol#L1043-L1089)**. 
- Because the `realizedPremia` is received as a positive value on the [`CollateralTracker.exercise() function`](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/CollateralTracker.sol#L1043-L1089), the OptionBuyer will actually get shares minted instead of having his shares burnt to pay for the owedPremia.

This bug on the `settleLongPremium()` function can be abused by the same OptionBuyers, they can continuously call this function to receive free shares for the amount of premia they owe, apart from receiving free shares, they will also settle their owedPremia, meaning, they can hold long options and get paid for it, instead of they paying for the long options.
- Not only OptionBuyers get paid for holding long options (instead of paying for them), but the OptionSellers don't earn anything for selling options, and also the PLPs get the real value of their shares diluted.

### Code Walkthrough

<details>
<summary><b>Expand to see the Code Walkthrough</b></summary>
<br>

```
> PanopticPool.sol

function settleLongPremium(
    ...
) external {
    ...

    LeftRightUnsigned accumulatedPremium;
    {
        ...

        //@audit-info => If position is `long`, return the accumulated value as the `premiumOwed` for the whole chunk => Longs pays premia!
        (uint128 premiumAccumulator0, uint128 premiumAccumulator1) = SFPM.getAccountPremium(
            ...
        );

        //@audit-info => accumulatedPremia up to the current block. accumulated `premiumOwed` for the whole chunk!
        accumulatedPremium = LeftRightUnsigned
            .wrap(0)
            .toRightSlot(premiumAccumulator0)
            .toLeftSlot(premiumAccumulator1);

        ...

        //@audit-info
Option Buyers can cause Option Sellers to not be able to settle unpaid long premium
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1592


# Vulnerability details

## Impact
Option Buyers can DoS Option Sellers from settling all unpaid premium leading to Option Sellers potentially losing all their earned premia.

## Proof of Concept
Option Sellers can call the [`PanopticPool.settleLongPremium() function`](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1587-L1659) to settle all unpaid premium for longs the Option Buyer has. By settling the unpaid premia, the Option Sellers can close their short positions (if enough liquidity on the UniV3Pool) and receive the premia that was earned because of the short.

The current implementation to settle premia via this function is to pass a list of positions that represents the positions owned by the Option Buyer, and based on this list, the function will do the necessary checks and validations to verify that the list is indeed the user's positionList.

The way how the Options Seller specifies the position where the unpaid premium should be settled is by passing such position at the end of the input positionIdList.
**The problem is the offset that is used when the [`_validatePositionList() function`](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1367-L1395) is invoked.** ***The existing implementations pass a `0` as the offset to validate the positionIdList.*** 
- This is problematic because the offset 0 causes the `validatePositionList()` to iterate over all the `positionIdList` to generate a hash and validate if it matches the current position's hash of the Option Buyer.
  - This means, the `validatePositionList()` will iterate over all the received `positionIdList` (including the last position that is meant to be the position where the premia should be settled on). 

Option Buyers can easily DoS the Option Sellers from settling unpaid premium by minting a short option after they have minted all their long options. This will cause the last position on the Option Buyer's positionIdList to be a position for a short option.
- If an Option Seller would like to settle unpaid premium on one of the longs owned by the Option Buyer when calling the [`PanopticPool.settleLongPremium() function`](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1587-L1659), any potential combination for the `positionIdList` parameter will cause the execution to revert. 
  - The exact reason is that the offset passed to the `_validatePositionList() function` is set as 0, this causes the execution to iterate over the `positionIdList` exactly as how it is received without excluding the last position, which will ultimately cause the generated hash for the `positionIdList` to no match the current Option Buyer position's hash (s_positionsHash), this will end up causing the tx to revert and not been able to settle unpaid premia.

## Coded PoC
I have coded a PoC t
owners of long options can steal the premium they owe to short sellers
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1635


# Vulnerability details

## Impact
When `PanopticPool.settleLongPremium()` is called it supposed to change the accounting of the shares on the `CollateralTracker` so that the long holders` shares (based on the calculated premium) are burned. Instead due to incorrect sign they are minted. 

## Proof of Concept
The `settleLongPremium()` function calculates the would-be-accumulated premium for a long option based on the `premiumAccumulatorX` obtained through `SFPM.getAccountPremium()`. Those accumulators are then adjusted by the `s_options[owner][tokenId][legIndex]` value which holds the last used accumulators.
The difference is multiplied by the liquidity:
```solidity
            LeftRightSigned realizedPremia = LeftRightSigned
                .wrap(0)
                .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64)))
                .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));
```
This `realizedPremia` is then used to exercise is on each `CollateralToken` using `s_collateralTokenX.exercise()`.

The problem is that it is a positive value, and the positive value on `exercise()` will cause the shares to be minted instead of burned, because `exercise()` reverts the sign:
```solidity:
int256 tokenToPay = -realizedPremium;
```
Then the following happens:
```solidity
           } else if (tokenToPay < 0) {
                // if user must receive tokens, mint them
                uint256 sharesToMint = convertToShares(uint256(-tokenToPay));
                _mint(optionOwner, sharesToMint);
            }
```


## Tools Used
Manual Review

## Recommended Mitigation Steps
The `realizedPremium` should be coming to the `exercise()` as negative:

PanopticPool.sol:
```diff
-            s_collateralToken0.exercise(owner, 0, 0, 0, realizedPremia.rightSlot());
-            s_collateralToken1.exercise(owner, 0, 0, 0, realizedPremia.leftSlot());
+            s_collateralToken0.exercise(owner, 0, 0, 0, -realizedPremia.rightSlot());
+            s_collateralToken1.exercise(owner, 0, 0, 0, -realizedPremia.leftSlot());
```


## Assessed type

Math
Incorrect Oracle Selection in Solvency Validation
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L941-L946


# Vulnerability details

## Impact
In a volatile market, if the slow oracle lags significantly, using its data might underestimate the necessary collateral, exposing the protocol to higher default risks.


## Root of the Bug:
the _validateSolvency function is crucial as it validates the solvency of a user by checking their collateral status against price data provided by two oracles (fast and slow). The logic flaw occurs when selecting which oracle tick to use based on a static delta threshold (MAX_SLOW_FAST_DELTA). This threshold does not dynamically adjust based on market conditions or the actual time elapsed between updates from the oracles.

## Proof of Concept
- Let's say  a scenario where the market experiences a sharp downturn.
- The fast oracle updates quickly to reflect a significant price drop, while the slow oracle's data remains outdated due to its lower update frequency. 
- Suppose the difference between the fast and slow oracle ticks exceeds MAX_SLOW_FAST_DELTA, triggering the fallback to the slower and stale oracle data for solvency checks.
- This scenario can lead to underestimating collateral requirements, potentially resulting in insolvency under actual market conditions.
here is  test show the issue :
```python
import numpy as np
import matplotlib.pyplot as plt

# Simulation parameters
n_simulations = 1000
n_steps = 50
initial_price = 100
price_volatility = [0.01, 0.05]  # Low and high volatility scenarios
time_lag = [0, 5]  # No lag and lag of 5 steps
user_balances = [10000, 50000]  # Different user balance scenarios
user_positions = [100, 500]  # Different user position scenarios
collateral_levels = [0.1, 0.5]  # Different collateral level scenarios

# Oracle selection logic mock
def select_oracle(prices, volatility, time_since_last_update):
    """ Mock function to select the appropriate oracle based on price volatility and update lag.
        This is a placeholder logic that needs to be refined based on the actual contract conditions.
    """
    if volatility > 0.03 or time_since_last_update > 3:
        return 'slow'  # Select slow oracle in high volatility or high lag scenarios
    else:
        return 'fast'  # Select fast oracle otherwise

# Simulate price paths
price_paths = {}
for vol in price_volatility:
    price_paths[vol] = np.zeros((n_simulations, n_steps))
    price_paths[vol][:, 0] = initial_price
    for t in range(1, n_steps):
        price_paths[vol][:, t] = price_paths[vol][:, t-1] * np.exp(
            np.random.normal(0, vol, n_simulations)
        )

# Simulate using oracles with different update lags
oracle_selection_results = {}
for vol, prices in price_paths.items():
    for lag in time_lag:
        oracle_use = []
        for i in range(n_simulations):
            oracle_history = []
            for t in range(n_steps):
                # Simulate oracle