xanoutas/solidity-security-findings huggingface dataset:
Incorrect Premium Calculation for Non-Long Legs in `_calculateAccumulatedPremia` Function
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details



## Impact

The `_calculateAccumulatedPremia` function calculates the accumulated premia for a user's option positions. However, when handling short positions and excluding pending premium, the function incorrectly converts the signed premia to an unsigned value before passing it to `_getAvailablePremium`. This incorrect conversion can lead to logical errors in the calculation of available premium, potentially affecting the accuracy of the user's financial position.

The incorrect premium calculation in _calculateAccumulatedPremia affects the call to `_getAvailablePremium` function, which is responsible for determining the available premium for settlement. Incorrect premium values passed to this function can result in incorrect premium settlements and Inaccurate premium values can distort the valuation of user portfolios.

## Proof of Concept
The issue is in the `_calculateAccumulatedPremia` function where it processes each leg of the option position. Specifically, when a leg is not long and `includePendingPremium` is `false`, the function attempts to calculate the available premium:

```solidity
// Incorrect premium calculation for non-long legs
if (tokenId.isLong(leg) == 0 && !includePendingPremium) {
    bytes32 chunkKey = keccak256(
        abi.encodePacked(
            tokenId.strike(leg),
            tokenId.width(leg),
            tokenId.tokenType(leg)
        )
    );

    // Incorrect argument passed for premium calculation
    LeftRightUnsigned availablePremium = _getAvailablePremium(
        _getTotalLiquidity(tokenId, leg),
        s_settledTokens[chunkKey],
        s_grossPremiumLast[chunkKey],
        LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))), // Incorrect argument here
        premiumAccumulatorsByLeg[leg]
    );
    portfolioPremium = portfolioPremium.add(
        LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium)))
    );
}
```

Here, `LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg])))` is used to convert the signed premia (`premiaByLeg[leg]`) to an unsigned value. This conversion is unnecessary and could lead to incorrect results due to the way signed and unsigned integers are handled in Solidity. The unwrap function is used to extract the signed value from a LeftRightSigned object, and then it's immediately converted to an unsigned integer and wrapped back into a LeftRightUnsigned object. This conversion does not change the sign of the value but rather its representation.


## Tools Used
Manual

## Recommended Mitigation Steps

To address this issue, ensure that the correct value is passed for premium calculation for non-long legs in the `_calculateAccumulatedPremia` function. Update the argument passed to `_getAvailablePremium` to use `premiumAccumulatorsByLeg[leg]` instead of `premiaB
inadequate Handling of Receiver Logic in safeTransferFrom
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/tokens/ERC1155Minimal.sol#L112-L121


# Vulnerability details

## Impact
the bug is can disrupt user experience and contract integrations, where legitimate contracts might intend to receive tokens but haven't implemented the necessary receiver interface, thus causing all transfers to these contracts to fail.
## root of the bug and summary 
the contract checks if to is a contract inn this line to.code.length != 0 and then immediately calls onERC1155Received on to.
The logical assumption here is that any contract address will correctly implement ERC1155Holder or, at least, a compliant onERC1155Received function if to is a contract but does not implement ERC1155Holder or the expected interface, this will cause a call to a non-existent function, leading to a failed transaction here :
```solidity
if (to.code.length != 0) {
    if (
        ERC1155Holder(to).onERC1155Received(msg.sender, from, id, amount, data) !=
        ERC1155Holder.onERC1155Received.selector
    ) {
        revert UnsafeRecipient();
    }
}
```
the root of this vulnerability lies in the assumption that any contract address in the to field will properly implement the required ERC-1155 token receiver interface. There's no preliminary check to ensure that to indeed supports the interface, which could lead to transaction failures if to is a contract that doesn't support handling ERC-1155 tokens correctly.
## Tools Used
manual review 
## Recommended Mitigation Steps
need that Before making the call to onERC1155Received, use ERC-165 standard checks to verify if the contract implements the ERC-1155 token receiver interface


## Assessed type

Other
`PanopticFactory` uses spot price when deploying new pools, resulting in liquidity manipulation when minting
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticFactory.sol#L341-L374


# Vulnerability details

## Impact
When `deployNewPool` is called it uses the spot price of the pool, which can be manipulated through a flashloan and thus could return a highly inaccurate result.

The price is used when deciding how much liquidity should be minted for each token, so this can result in an unbalanced pool.

In other parts of the code, this is not an issue as there are oracles that prevent price manipulations, but in case there aren't any checks to avoid so.

## Proof of Concept
The spot price is used to calculate the range liquidity for each token:

```solidity
@>	(uint160 currentSqrtPriceX96, , , , , , ) = v3Pool.slot0();

	// For full range: L = Δx * sqrt(P) = Δy / sqrt(P)
	// We start with fixed token amounts and apply this equation to calculate the liquidity
	// Note that for pools with a tickSpacing that is not a power of 2 or greater than 8 (887272 % ts != 0),
	// a position at the maximum and minimum allowable ticks will be wide, but not necessarily full-range.
	// In this case, the `fullRangeLiquidity` will always be an underestimate in respect to the token amounts required to mint.
	uint128 fullRangeLiquidity;
	unchecked {
	    // Since we know one of the tokens is WETH, we simply add 0.1 ETH + worth in tokens
	    if (token0 == WETH) {
	        fullRangeLiquidity = uint128(
@>	            Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_WETH, currentSqrtPriceX96)
	        );
	    } else if (token1 == WETH) {
	        fullRangeLiquidity = uint128(
	            Math.mulDivRoundingUp(
	                FULL_RANGE_LIQUIDITY_AMOUNT_WETH,
	                Constants.FP96,
@>	                currentSqrtPriceX96
	            )
	        );
	    } else {
	        // Find the resulting liquidity for providing 1e6 of both tokens
	        uint128 liquidity0 = uint128(
@>	            Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN, currentSqrtPriceX96)
	        );
	        uint128 liquidity1 = uint128(
	            Math.mulDivRoundingUp(
	                FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN,
	                Constants.FP96,
@>	                currentSqrtPriceX96
	            )
	        );

	        // Pick the greater of the liquidities - i.e the more "expensive" option
	        // This ensures that the liquidity added is sufficiently large
	        fullRangeLiquidity = liquidity0 > liquidity1 ? liquidity0 : liquidity1;
	    }
	}
```
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticFactory.sol#L341-L374

But unlike other parts of the code, the `PanopticFactory` doesn't have any checks against the price (it doesn't use any oracles nor the TWAP), so each token liquidity is manipulable through flash loans.

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider using the TWAP price instead of the spot pri
There is no msg.value check while making deposit.
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/CollateralTracker.sol#L417-L429
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/libraries/SafeTransferLib.sol#L21-L46


# Vulnerability details

### **Summary**

The `SafeTransferLib.safeTransferFrom` does not check if the actual value being sent with the transaction is the specified amount

### **Detailed Description**
The `SafeTransferLib` makes a low level call to send a transaction but there is no check to ascertain the value sent is equal to the actual amount. 


### **Impact:**

This could lead to lose of funds.

### **Proof of Code**


Run the following test in the projects test suit using the same setup to validate the bug
```javascript
    function deposit(uint256 assets, address receiver) external returns (uint256 shares) {
        if (assets > type(uint104).max) revert Errors.DepositTooLarge();

        shares = previewDeposit(assets);

        // @audit-issue there is no msg.value check
        SafeTransferLib.safeTransferFrom(
            s_underlyingToken,
            msg.sender,
            address(s_panopticPool),
            assets
        );

        _mint(receiver, shares);

        // update tracked asset balance
        unchecked {
            s_poolAssets += uint128(assets);
        }

        emit Deposit(msg.sender, receiver, assets, shares);
    }
    
```



### **Tool used:**

Manual Review

### **Recommended Mitigation:**

The protocol should add a check in order to make sure only the specified amount of deposit actually gets deposited.



## Assessed type

Token-Transfer
The FullMath library is unable to handle intermediate overflows due to overflow that's desired but never reached
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The FullMath library doesn't correctly handle the case when an intermediate value overflows 256 bits. This happens because an overflow is desired in this case but it's never reached.
## Proof of Concept
The FullMath library was taken from Uniswap v3-core. However, the original solidity version that was used was < 0.8.0, meaning that the execution didn't revert wan an overflow was reached. This effectively means that when a phantom overflow (a multiplication and division where an intermediate value overflows 256 bits) occurs the execution will revert and the correct result won't be returned. The original library was designed in a way that could handle intermediate overflows. The FullMath library is used in the MathLib library in order to format an 18-decimal number to a FixedPoint 96.Q96 number.
## impact
The correct result isn't returned in this case and the execution gets reverted when a phantom overflows occurs.





## Assessed type

Under/Overflow
Token Sorting Vulnerability in SemiFungiblePositionManager.sol
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L350-L391


# Vulnerability details

## Impact
The absence of token sorting in the `initializeAMMPool()` function can lead to inconsistencies in Automated Market Maker (AMM) pools, with the order of `token0` and `token1` varying depending on the chain. This inconsistency can cause issues in subsequent operations involving these tokens, affecting trading, liquidity provision, and other pool-related activities. If pools are initialized incorrectly, it could lead to miscalculations or improper behavior within the protocol.

## Proof of Concept
- The code from [SemiFungiblePositionManager.sol](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L350-L391) that initializes AMM pools without sorting tokens can lead to the order of `token0` and `token1` being unpredictable.
- This issue may cause complications when pools are initialized from a Panoptic pool or directly by calling `initializeAMMPool()`.
- The inconsistency between token ordering might not be immediately visible but could result in errors when interacting with these pools in the future.

```solidity
    function initializeAMMPool(address token0, address token1, uint24 fee) external {
        // compute the address of the Uniswap v3 pool for the given token0, token1, and fee tier
        address univ3pool = FACTORY.getPool(token0, token1, fee);

        // reverts if the Uni v3 pool has not been initialized
        if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized();

        // return if the pool has already been initialized in SFPM
        // @dev pools can be initialized from a Panoptic pool or by calling initializeAMMPool directly, reverting
        // would prevent any PanopticPool from being deployed
        // @dev some pools may not be deployable if the poolId has a collision (since we take only 8 bytes)
        // if poolId == 0, we have a bit on the left set if it was initialized, so this will still return properly
        if (s_AddrToPoolIdData[univ3pool] != 0) return;

        // The base poolId is composed as follows:
        // [tickSpacing][pool pattern]
        // [16 bit tickSpacing][most significant 48 bits of the pool address]
        uint64 poolId = PanopticMath.getPoolId(univ3pool);

        // There are 281,474,976,710,655 possible pool patterns.
        // A modern GPU can generate a collision such a space relatively quickly,
        // so if a collision is detected increment the pool pattern until a unique poolId is found
        while (address(s_poolContext[poolId].pool) != address(0)) {
            poolId = PanopticMath.incrementPoolPattern(poolId);
        }

        // store the poolId => UniswapV3Pool information in a mapping
        // `locked` being initialized to false is gas-efficient because the pool address makes the slot nonzero
        // note: we preserve the state of `lock
`ValidateExerciseable` function logic leads to forceExercising ineligible positions
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/types/TokenId.sol#L578


# Vulnerability details

## Impact
Nonexercisable positions can be exercisable

## Proof of Concept
The protocol checks if a position is exercisable, i.e. at least one of its legs is outside of predefined width, by `currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown` condition.

However, it also includes the case when the tick is right at the width, which should be nonexercisable.

As per the validation of exercisable position logic, the position should be 1 tick out of the range of the current tick:
```solidity
Contract: PanopticPool.sol

1218:         // on forced exercise, the price *must* be outside the position's range for at least 1 leg
1219:         touchedId[0].validateIsExercisable(twapTick);
```
And `validateIsExercisable` checks the issue as :

```solidity
Contract: TokenId.sol

578:     function validateIsExercisable(TokenId self, int24 currentTick) internal pure {
579:         unchecked {
580:             uint256 numLegs = self.countLegs();
581:             for (uint256 i = 0; i < numLegs; ++i) {
582:                 (int24 rangeDown, int24 rangeUp) = PanopticMath.getRangesFromStrike(
583:                     self.width(i),
584:                     self.tickSpacing()
585:                 );
586: 
587:                 int24 _strike = self.strike(i);
588:                 // check if the price is outside this chunk
589:                 if ((currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown)) { 
590:                     // if this leg is long and the price beyond the leg's range:
591:                     // this exercised ID, `self`, appears valid
592:                     if (self.isLong(i) == 1) return; // validated
593:                 }
594:             }
595:         }
596: 
597:         // Fail if position has no legs that is far-out-of-the-money
598:         revert Errors.NoLegsExercisable();
599:     }
```
Looking at L:589, it also checks the equivalency - not 1 tick difference only which means that the positions that don´t have this difference can also be exercisable.

In addition, `rangeup` calculation is based on rounding up - which we can say that it´s really being forced to be inside the ticks even if it might not be:

```solidity
Contract: PanopticMath.sol

371:     function getRangesFromStrike(
372:         int24 width,
373:         int24 tickSpacing
374:     ) internal pure returns (int24, int24) {
375:         return (
376:             (width * tickSpacing) / 2,
377:             int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) //@audit rounding up
378:         );
379:     }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
We recommend refactoring the logic to below:
```diff
-  if ((currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown)) { 
+  if ((currentTick > _strike + r
Inaccurate MEV Tax Calculation
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The calculation of the MEV tax in the takeCommissionAddData function does not accurately account for the intrinsic value of ITM positions. This could lead to incorrect fee calculations and potential losses for the protocol.


## Proof of Concept
https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/CollateralTracker.sol#L995


## Tools Used
Manuel Review

## Recommended Mitigation Steps
Revise the calculation of the MEV tax in the takeCommissionAddData function to accurately account for the intrinsic value of premium positions. This may involve adjusting the formula used to calculate the tax or ensuring that the intrinsic value is correctly factored into the calculation.


## Assessed type

Other
Using delegatecall inside a loop. When calling delegatecall the same msg.value amount will be accredited multiple times.
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The use of `delegatecall` within a loop in a payable function can lead to security vulnerabilities. This pattern can cause the same `msg.value` amount to be accredited multiple times if used improperly, leading to incorrect accounting and potential exploits. It becomes especially dangerous if a future refactor introduces additional logic that relies on `msg.value`.

## Proof of Concept
- The code with the loop containing `delegatecall` is located in [Multicall.sol](https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/multicall/Multicall.sol#L15).
- This issue has similarities with a known vulnerability reported in `vault-v2/contracts/Ladle.sol`. The code snippet from this case demonstrates the use of `delegatecall` inside a loop, posing a high-severity risk:
  ```solidity
  function batch(bytes[] calldata calls) external payable returns (bytes[] memory results) {
      results = new bytes[](calls.length);
      for (uint256 i; i < calls.length; i++) {
          (bool success, bytes memory result) = address(this).delegatecall(calls[i]);
          if (!success) revert(RevertMsgExtractor.getRevertMsg(result));
          results[i] = result;
      }
  }
  ```
- In scenarios where `msg.value` impacts system arithmetic, an attacker can exploit this to manipulate balances without actually transferring corresponding funds.

## Tools Used
The following tools were used for identifying and documenting the bug:
- GitHub repository links for code inspection.
- Reports of known vulnerabilities with similar patterns.

## Recommended Mitigation Steps
- **Short-term**: Document the risks associated with `delegatecall` within loops and ensure developers understand the potential attack vectors. This documentation should be incorporated into developer onboarding and training.
- **Long-term**: Include security implications for all functions in the code documentation. Ensure future refactors or code updates do not introduce exploitable conditions by leveraging `msg.value` inappropriately.



## Assessed type

Context
no verification of open positions before withdrawal of assets
Audit: 2024-04-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The contract does not check if a user has any open positions before allowing them to withdraw assets. This is a direct violation of the invariant listed in the documentation `Users should not be allowed to withdraw collateral if they have open positions`.

## Proof of Concept
https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/CollateralTracker.sol#L531

https://code4rena.com/audits/2024-04-panoptic#top:~:text=Users%20should%20not%20be%20allowed%20to%20withdraw%20collateral%20if%20they%20have%20open%20positions

## Tools Used
Manual Review

## Recommended Mitigation Steps

Implement a check in the withdraw function to ensure that a user does not have any open positions before allowing them to withdraw assets. This can be done by querying the s_panopticPool.numberOfPositions(owner) and reverting the transaction if it returns a non-zero value.


## Assessed type

Other