xanoutas/solidity-security-findings huggingface dataset:
One-Step Ownership Transfer Vulnerability
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The transferOwnership function in the Panoptic Factory contract allows for a one-step transfer of ownership without any additional checks or confirmation steps.

This design allows the current owner of the Panoptic Factory to directly transfer ownership to a new address without any confirmation or authorization from the new owner. It can lead to unauthorized ownership transfers and potential loss of control over the contract.

## Proof of Concept
 Exploit:
  - Deploy the Panoptic Factory contract.
  - Call the testOwnershipTransfer function with the address of the desired new owner as the parameter.
  - Ownership of the contract is immediately transferred to the provided address without any confirmation from the new owner.
## Tools Used
Manual Review

## Recommended Mitigation Steps
- Implement a multi-step ownership transfer process that requires confirmation from both the current and new owners, such as a two-step authorization process or a timelock mechanism. This ensures that ownership transfers are authorized by both parties and reduces the risk of unauthorized ownership changes. Additionally, consider adding access control mechanisms to restrict access to critical functions based on the current owner's address.


## Assessed type

Other
Lack of Correct Handling of Negative Utilization
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The _sellCollateralRatio function in the smart contract incorrectly handles negative utilization by simply negating the value, which could lead to unintended behaviour and incorrect results.

If negative utilization values occur due to underutilization or specific conditions, the incorrect handling can lead to inaccurate collateral ratio calculations. This could potentially result in incorrect risk assessments and financial losses for users.
 
## Tools Used
Manual Review

## Recommended Mitigation Steps
- Ensure correct handling of negative utilization by taking the absolute value of the utilization before further processing. This can be achieved using the abs function or by casting the value to an unsigned integer (uint256) after converting it to a signed integer (int256).
 ```
utilization = uint256(int256(utilization));
```


## Assessed type

Math
`SettleLongPremium` is incorrectly implemented: premium should be deducted instead of added
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Impact

`SettleLongPremium` is the function intended to settle premiums for long option holders. When called, it should deduct the premium from the option owner's account, but the current implementation adds the premium instead.

## Bug Description

Lets see the code for premium calculation. We can see that `accumulatedPremium` and `s_options[owner][tokenId][legIndex]` are premium accumulators for calculating the owed amount of premium, and that `accumulatedPremium` is a LeftRightUnsigned type, which means it must be positive.

The `realizedPremia` is also positive, because it is calculated by `accumulatedPremium * liquidity`.

The issue occurs when calling `s_collateralToken.exercise()`. The `realizedPremia` that is passed inside should be negative instead of positive, because negative means user pays premia, and positive means user receives premia. The current implementation is incorrect.

PanopticPool.sol
```solidity
            accumulatedPremium = LeftRightUnsigned
                .wrap(0)
                .toRightSlot(premiumAccumulator0)
                .toLeftSlot(premiumAccumulator1);

            // update the premium accumulator for the long position to the latest value
            // (the entire premia delta will be settled)
            LeftRightUnsigned premiumAccumulatorsLast = s_options[owner][tokenId][legIndex];
            s_options[owner][tokenId][legIndex] = accumulatedPremium;

>           accumulatedPremium = accumulatedPremium.sub(premiumAccumulatorsLast);
        }

        uint256 liquidity = PanopticMath
            .getLiquidityChunk(tokenId, legIndex, s_positionBalance[owner][tokenId].rightSlot())
            .liquidity();

        unchecked {
            // update the realized premia
>           LeftRightSigned realizedPremia = LeftRightSigned
>               .wrap(0)
>               .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64)))
>               .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));

            // deduct the paid premium tokens from the owner's balance and add them to the cumulative settled token delta
            s_collateralToken0.exercise(owner, 0, 0, 0, realizedPremia.rightSlot());
            s_collateralToken1.exercise(owner, 0, 0, 0, realizedPremia.leftSlot());
```

CollateralTracker.sol
```solidity
   function exercise(
        address optionOwner,
        int128 longAmount,
        int128 shortAmount,
        int128 swappedAmount,
        int128 realizedPremium
    ) external onlyPanopticPool returns (int128) {
        unchecked {
            // current available assets belonging to PLPs (updated after settlement) excluding any premium paid
            int256 updatedAssets = int256(uint2
Lack of Input Validation in Token Transfer Function
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
 The transfer function in the ERC20 token contract lacks input validation for the recipient address and transfer amount. This absence of validation opens up potential vulnerabilities, allowing for the transfer of tokens to the zero address and the execution of zero-amount transfers. These issues can lead to loss of tokens, unexpected behavior, and security risks.
 
 - Tokens Lost to Zero Address: If a user mistakenly sends tokens to the zero address (address(0)), those tokens will effectively be burned and become irrecoverable.

 - Zero-Amount Transfers: Allowing zero-amount transfers means users can execute transactions with no effect on token balances. This could be exploited for spamming transactions or circumventing certain checks.
 
## Tools Used
Manual Review

## Recommended Mitigation Steps
- Consider implementing input validation in the transfer function to ensure that the recipient address is not the zero address and the transfer amount is greater than zero. This will prevent potential vulnerabilities and ensure the safety and integrity of the token contract.


## Assessed type

Invalid Validation
Unable to burn tokenId with more than 2 legs inside
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability detail
In `PanopticPool#_getPremia()` function, `premiumAccumulatorsByLeg` variable is initalized as below:

        uint256[2][4] memory premiumAccumulatorsByLeg

Which mean that it only can handle maximum 2 legs:

        for (uint256 leg = 0; leg < numLegs; ) {   //<---
            uint256 isLong = tokenId.isLong(leg);
            if ((isLong == 1) || computeAllPremia) { //long là nó deposit vào
                LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk(
                    tokenId,
                    leg,
                    positionSize
                );
                uint256 tokenType = tokenId.tokenType(leg);

                (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM.getAccountPremium(   //<----

From documentation at [here](https://panoptic.xyz/research/reasons-bullish-financial-nfts#reason-4-the-token-id-of-an-erc1155-is-a-uint256-that-can-encode-information), one TokenId can have maximum 4 legs inside, so if the tokenId have 3 or 4 legs, this function will be reverted, which will make some action cant be done like burning (because `_updateSettlementPostBurn()` call `_getPremia()` function)

## Impact
TokenId cant be burned. Which mean it can not be liquidated or burn by owner

## Tools Used
Manual review

## Recommended Mitigation Steps
Upgrade this variable to:

            uint256[4][2] memory premiumAccumulatorsByLeg


## Assessed type

Other
In the swapInAMM the malicious actor could manipulate the sqrtPriceX96 in the slot0 which can cause an incorrect calculation.
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/SemiFungiblePositionManager.sol#L787-L845


# Vulnerability details

# Summary 
In the SFPM.sol the swapInAMM function is triggered when the position minted/burned in ITM, and the tokens need to be swapped (because user receive only one token). However the problem is that during the swap calculation the (uint160 sqrtPriceX96, , , , , , ) = _univ3pool.slot0(); is used. 
Since the sqrtPriceX96 is easily manipulated by the MEV bots / Flashloans the user funds could be lost due to manipulation.

# Vulnerability details
Let's desing the flow of the attack. 

1. When the position is minted/burned the _validateAndForwardToAMM function is triggered. In this function the burning/minting mechanism in the UniswapV3 is triggered through the _createLegInAMM function. Eventually, this function gather the itmAmounts based on the amount moved out/from the Uniswap, this itm amount is used further to execute the swap
2. Then, the condition in _validateAndForwardToAMM is checked, the tickLimitLow should be higher than tickLimintHigh (tickLimitLow > tickLimitHigh). If yes -> swapInAmm function is triggered
3. In the swapInAmm the itmAmounts are retrieved and if both of them > than 0, the swap occured.

The exact problem here is that during the swap the (uint160 sqrtPriceX96, , , , , , ) = _univ3pool.slot0(); is taken which could cause the swap to not work as intended.

```
negative ITM amounts denote a surplus of tokens (burning liquidity), while positive amounts denote a shortage of tokens (minting liquidity)
```

int256 net0 = itm0 - PanopticMath.convert1to0(itm1, sqrtPriceX96); 
And we can see that the convert1to0 basically does this --> return Math.mulDiv(amount, 2 ** 192, uint256(sqrtPriceX96) ** 2);

As we can see here, the malicious user could inflate the sqrtPriceX96, thus the prices will be calculated not correctly and user will suffer from the incorrect conversions.

# Tools Used
Manual Review

# Recommended Mitigation Steps
Since it is a well know vulnerability that is was previosly found many times, i suggest to use the TWAP prices instead:
https://solodit.xyz/issues/h-02-use-of-slot0-to-get-sqrtpricelimitx96-can-lead-to-price-manipulation-code4rena-maia-dao-ecosystem-maia-dao-ecosystem-git



## Assessed type

Uniswap
Lack of Check on Equal Length of Owners and IDs (Potential Mismatch)
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
 The balanceOfBatch function in the ERC1155 token contract lacks a check to ensure that the lengths of the owners and ids arrays are equal before proceeding with the balance retrieval process.

This omission could lead to unexpected behavior or runtime errors if the owners and ids arrays have different lengths. If the arrays are of unequal length, the function may attempt to access indices beyond the bounds of one of the arrays, resulting in potential out-of-bounds errors, data corruption, or unintended behavior.
 
## Tools Used
Manual Review

## Recommended Mitigation Steps
 - Add a check at the beginning of the balanceOfBatch function to ensure that the lengths of the owners and ids arrays are equal.
 - If the lengths are not equal, revert the transaction with an appropriate error message indicating the mismatch.
    


## Assessed type

Other
Attacker could manipulate the observationCardinality array and force computeInternalMedian/computeMedianObservedPrice return 0
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/libraries/PanopticMath.sol#L135-L151


# Vulnerability details

# Summary 

The function which computes the medium price, relies on the univ3pool.observations(). However, the observations in the Uniswap V3 are stored in the finite array(max 65,535 observations) - this array called observationCardinality, anyone could increase the .length by calling 'increaseObservationCardinalityNext' directly from the Uniswap. 
The problem is: observations are expected to overflow: if a new observation doesn’t fit into the array, writing continues starting at index 0, i.e. oldest observations get overwritten.

You could explore in more details how the observations work in the Uniswap: https://uniswapv3book.com/milestone_5/price-oracle.html

It worth to mention that when the pool is created the observations length is 1.

# Vulnerability details / PoC

The block.timestamp and tickCumulutative are retrieved based on the observation index via this formula
```
(timestamps[i], tickCumulatives[i], , ) = univ3pool.observations( uint256((int256(observationIndex) - int256(i * period)) + int256(observationCardinality)) % observationCardinality);
```

However the attack could easily hijack this calculation and retrieve incorrect prices.

Let's take for example USDC/WETH pool, where the current observationCardinality is 723.
1. Attacker waits till the observationCardinality array would be reset to 0.
2. Attacker calls 'increaseObservationCardinalityNext' directly from the UniswapV3, and increase the .length to 800
3. Attacker calls computeMedianObservedPrice, this function takes the new observationCardinality (800 in our case)
4. We increased the array to 800, however the last observations length was 723, so every number above 723 are uninitialized (set to 0)
5. In the computeMedianObservedPrice the calculation is triggered
```
univ3pool.observations((observationIndex - (i * period) + observationCardinality) % observationCardinality)
```
Here the vulnerability occurs. Let's see how
- observationIndex is 0, since array overflowed and we started from the beggining.
- i: it is cardinality (3 for the fast oracle, 7 for the slow oracle)
- period: (1 for the fast oracle, 5 for the slow oracle)
- observationCardinality: new, updated length of the array = 800.

The equation will run in a loop and return such values:
For the Fast Oracle:
0, 799, 798, 797

For the Slow Oracle:
0, 795, 790, 785, 780, 775, 770, 765

This number are new, they didn't exists in the previous observationCardinality array, since we have just updated the observationCardinality, and consequently these numbers will return uninitialized variables (0)

It means that tickCumulatives[], and timestamps[] will return 0.

Further along, these numbers will play a role in the tick calculation and returning median of it 
```js 
return int24(Math.sort(ticks)[cardinality / 2]);
```
Also, these parameters w
Potential DoS Attack via Large Input Arrays (Vector Length DoS)
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The safeBatchTransferFrom function accepts arrays ids and amounts as parameters, iterating over each element in the ids array using a for loop. An attacker could exploit this by crafting a malicious transaction with exceptionally large arrays of ids and amounts, leading to excessive gas consumption and potential DoS.

If successful, the attacker could disrupt the normal operation of the contract, causing legitimate transactions to fail due to gas exhaustion. This could result in delays, congestion, and increased transaction fees for users of the Ethereum network.

 
## Tools Used
Manual review
## Recommended Mitigation Steps
 - Implement gas-efficient algorithms to process input arrays.
 - Enforce limits on the size of input arrays to prevent excessive gas consumption.
 - Utilize gas limits, rate limiting, and transaction prioritization mechanisms to mitigate DoS attacks by restricting computational resources and prioritizing legitimate transactions.


## Assessed type

DoS
Incorrect validation in force-exercising allows ITM positions be force-exercised
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
By allowing ITM positions to be force-exercised, it causes loss for option buyers and option sellers can avoid loss.

## Proof of Concept
```Solidity
if ((currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown)) {
    // if this leg is long and the price beyond the leg's range:
    // this exercised ID, `self`, appears valid
    if (self.isLong(i) == 1) return; // validated
}
```
In `validateIsExercisable` function of `TokenId.sol`,  it checks if current tick is out of the range, if it does, it considers the option OTM.
This assumption is wrong, because for PUT options, when current tick is above `tickHigh`, it means ITM and the option is earning money, and for CALL options, when current tick is below `tickLow`, it means ITM and the option is earning money.

As a result, it allows ITM positions to be force-exercised which causes loss for option buyers.

## Tools Used
Manual Review

## Recommended Mitigation Steps
When checking OTM, it should check if current leg is PUT or CALL and apply appropriate comparison based on the option type.


## Assessed type

Context
The position could be minted in the SFPM directly, bypassing important checks in the PanopticPool.
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/SemiFungiblePositionManager.sol#L504-L527


# Vulnerability details

# Summary 
I suppose it is small mistake that wasn't taked into an account, however the consequences could be enormous. The position in the Panoptic protocol is minted/burned via SFPM.sol which is the core contract. However, as i understand, that the user flow should begin in the PanopticPool where the mint/burn functionality is started. However, the user could call the mint/burnTokenizedPosition from the SFPM contract and bypass important security checks


# Vulnerability details

***Additionaly, it worth to mention that if we call the mint funciton directly it would mint the ERC1155 shares to us (msg.sender) and we could receive the flow via onERC1155Received, which is dangerous, that could result in the unintended behaviour***

Let's take a look which security checks could be bypassed if we would call mintTokenizedPosition directly.
1. _validatePositionList is skipped. Which ensures the corectness of the position hash
2. _getSlippageLimits is skipped. Which verifies that tick low/max are correct
3. Check that verifies the corectness of the pool -> If tokenId.poolId() != SFPM.getPoolId(address(s_univ3pool)) revert
4. Check that verifies that position isn't minted yet If ((s_positionBalance[msg.sender][tokenId]) != 0) revert
5. Also, the _addUserOption function is skipped, which stores option data in the pool contract
6. Solvency validation (_validateSolvency) is bypassed

Since important security checks are bypassed which break the overall functionality of the protocol the vulnerability is high


# Recommended Mitigation Steps
Ensure that the onlyPool modifier is set to the mint/burn function in the SFPM


## Assessed type

Access Control
settleLongPremium() buyer should pay Premium
Audit: 2024-04-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability details
`settleLongPremium()` is use for `Settle all unpaid premium for long legs`
The code implementation is as follows:
```solidity
    function settleLongPremium(
        TokenId[] calldata positionIdList,
        address owner,
        uint256 legIndex
    ) external {
        _validatePositionList(owner, positionIdList, 0);

        TokenId tokenId = positionIdList[positionIdList.length - 1];

        if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg();

        (, int24 currentTick, , , , , ) = s_univ3pool.slot0();

        LeftRightUnsigned accumulatedPremium;
        {
            (int24 tickLower, int24 tickUpper) = tokenId.asTicks(legIndex);

            uint256 tokenType = tokenId.tokenType(legIndex);
            (uint128 premiumAccumulator0, uint128 premiumAccumulator1) = SFPM.getAccountPremium(
                address(s_univ3pool),
                address(this),
                tokenType,
                tickLower,
                tickUpper,
                currentTick,
                1
            );
            accumulatedPremium = LeftRightUnsigned
                .wrap(0)
                .toRightSlot(premiumAccumulator0)
                .toLeftSlot(premiumAccumulator1);

            // update the premium accumulator for the long position to the latest value
            // (the entire premia delta will be settled)
            LeftRightUnsigned premiumAccumulatorsLast = s_options[owner][tokenId][legIndex];
            s_options[owner][tokenId][legIndex] = accumulatedPremium;

            accumulatedPremium = accumulatedPremium.sub(premiumAccumulatorsLast);
        }

        uint256 liquidity = PanopticMath
            .getLiquidityChunk(tokenId, legIndex, s_positionBalance[owner][tokenId].rightSlot())
            .liquidity();

        unchecked {
            // update the realized premia
            LeftRightSigned realizedPremia = LeftRightSigned
                .wrap(0)
                .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64)))
                .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));

            // deduct the paid premium tokens from the owner's balance and add them to the cumulative settled token delta
@>          s_collateralToken0.exercise(owner, 0, 0, 0, realizedPremia.rightSlot());
@>          s_collateralToken1.exercise(owner, 0, 0, 0, realizedPremia.leftSlot());

            bytes32 chunkKey = keccak256(
                abi.encodePacked(
                    tokenId.strike(legIndex),
                    tokenId.width(legIndex),
                    tokenId.tokenType(legIndex)
                )
            );
            // commit the delta in settled tokens (all of the premium paid by long chunks in the tokenIds list) to