xanoutas/solidity-security-findings huggingface dataset:
Missing return values in `assertMinCollateralValues` function causes difficulty in slippage checks
Audit: 2024-06-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-panoptic/blob/153f0d82440b7e63075d55b0659706531431145f/contracts/PanopticPool.sol#L329-L336


# Vulnerability details

## Impact

The function `assertMinCollateralValues` is designed to check if the user's collateral balances for two different tokens meet certain minimum requirements specified by minValue0 and minValue1.
However, if `minValue0` and `minValue1` are both set to 0, the function will always assert true, even if the user has zero collateral balances or non-zero collateral balances. This function will not return any return values on asserting true, Causing a difficulty in slippage check.Mainly when using multiCall to check the slippage as this fuction will not return any values, causing the return values always be 0 bytes on success and we cannot decode any values from it.

## Proof of Concept

Impact scenario:

1. The `assertMinCollateralValues` function is called with `minValue0` and `minValue1` set to 0.
2. The function will always assert true, even if the user has zero collateral balances or non-zero collateral balances.
3. When a multicall is used to check the slippage, the function will not return any values, causing the return values always be 0 bytes on success and we cannot decode any values from it.
4. This will cause a difficulty in checking the slippage.

```javascript
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

import {Test, console} from "forge-std/Test.sol";
import {FactoryNFT} from "@base/FactoryNFT.sol";
import {CollateralTrackerTest} from "./core/CollateralTracker.t.sol";
import {CollateralTracker} from "@contracts/CollateralTracker.sol";
import {IERC20Partial} from "@tokens/interfaces/IERC20Partial.sol";
import {ERC20Mock} from "@openzeppelin/contracts/mocks/ERC20Mock.sol";
import {PanopticFactoryTest} from "./core/PanopticFactory.t.sol";
import {PanopticPool} from "@contracts/PanopticPool.sol";
import {PanopticPoolTest} from "./core/PanopticPool.t.sol";
import {SemiFungiblePositionManager} from "@contracts/SemiFungiblePositionManager.sol";

contract BugTest_CollateralTracker is CollateralTrackerTest {
    FactoryNFT public factory;
    ERC20Mock public mock1;
    ERC20Mock public mock2;
    address public mockAddr1;
    address public mockAddr2;
    address public ctAddr;
    address public ppAddr;
    CollateralTracker public ct;
    PanopticPool public pp;
    SemiFungiblePositionManager public sf;

    function setUp() public override(CollateralTrackerTest) {
        sf = new SemiFungiblePositionManager(V3FACTORY);
        mock1 = new ERC20Mock("Mock1", "MCK1", address(this), 1e18);
        mock2 = new ERC20Mock("Mock2", "MCK2", address(this), 1e18);
        ct = new CollateralTracker(10, 2_000, 1_000, -1_024, 5_000, 9_000, 20_000);
        pp = new PanopticPool(sf);
        mock1.approveInternal(address(this), address(ct), type(uint256).max);
        mock2.approveInternal(address(this), address(ct), type(uint256).max);
        mockAddr1 = addr
Unhandled return value of transferFrom in contracts/CollateralTracker.sol
Audit: 2024-06-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/CollateralTracker.sol?plain=1#L333


# Vulnerability details

## Impact
Not all IERC20 implementations revert() when there's a failure in transfer()/transferFrom(). The function signature has a boolean return value, and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment. Failure to properly handle transfer errors can be exploited by malicious actors to disrupt token transfers, potentially affecting the integrity and trust of the entire token ecosystem.
```solidity
  function transfer(
        address recipient,
        uint256 amount
    ) public override(ERC20Minimal) returns (bool) {
        // make sure the caller does not have any open option positions
        // if they do: we don't want them sending panoptic pool shares to others
        // as this would reduce their amount of collateral against the opened positions

        if (s_panopticPool.numberOfPositions(msg.sender) != 0) revert Errors.PositionCountNotZero();

        return ERC20Minimal.transfer(recipient, amount);
    }
```

## Proof of Concept
Scenario:
Alice attempts to transfer 100 tokens to Bob.
The ERC20Minimal.transfer call fails and returns false.
Since the original function does not check the return value, it returns true, indicating a successful transfer.
Bob never receives the tokens, and Alice's token balance remains unchanged, but Alice believes the transfer was successful.
I have added a similar issue below as ref reference:
https://consensys.io/diligence/audits/2020/09/aave-protocol-v2/#unhandled-return-values-of-transfer-and-transferfrom
https://github.com/Uniswap/solidity-lib/blob/c01640b0f0f1d8a85cba8de378cc48469fcfd9a6/contracts/libraries/TransferHelper.sol#L33-L45
## Tools Used
Manual review
## Recommended Mitigation Steps
Ensure that the return value of the ERC20Minimal.transfer call is checked and handled appropriately. If the transfer fails, revert the transaction.
Additionally, you could make use of OpenZeppelin's SafeERC20 wrapper functions which automatically handle return values and revert on failure.

https://github.com/Uniswap/solidity-lib/blob/c01640b0f0f1d8a85cba8de378cc48469fcfd9a6/contracts/libraries/TransferHelper.sol#L33-L45


## Assessed type

ETH-Transfer
[M-01] Potential Division by Zero or Unintended Behavior Due to Close Asset Values in the `revoke` function
Audit: 2024-06-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-panoptic/blob/main/contracts/CollateralTracker.sol#L930-L986


# Vulnerability details

### Vulnerability Details

The `revoke` function in the smart contract has a medium severity issue related to the calculation of shares when revoking delegated shares. The issue arises from the calculation involving the potential division by zero or unintended behavior when the `totalAssets()` value is very close to or equal to the `assets` value being processed. Specifically, the problematic calculation is:

```solidity
Math.mulDiv(assets, totalSupply - delegateeBalance, uint256(Math.max(1, int256(totalAssets()) - int256(assets))))
```

If `totalAssets()` is close to or equal to `assets`, the calculation could lead to an unintended division by a very small number, potentially resulting in a large and incorrect number of shares being minted or causing the transaction to revert.

### Impact

The impact of this vulnerability includes:

- **Unexpected Reverts**: The transaction may unexpectedly revert due to a division by zero or near-zero value.
- **Incorrect Share Calculation**: If the calculation results in a large number due to a small denominator, it could lead to an incorrect number of shares being minted, potentially affecting the balance and integrity of the contract's operations.
- **Collateral Mismanagement**: This issue could lead to improper management of collateral, which could compromise the security and reliability of the financial operations of the smart contract.

### Proof of Concept

Here is the original function with the potential vulnerability:

```solidity
function revoke(
    address delegator,
    address delegatee,
    uint256 assets
) external onlyPanopticPool {
    uint256 shares = convertToShares(assets);

    // Get the delegatee balance and compare it later against the requested amount
    uint256 delegateeBalance = balanceOf[delegatee];

    if (shares > delegateeBalance) {
        // Transfer delegatee balance to delegator
        _transferFrom(delegatee, delegator, delegateeBalance);

        // Calculate the remaining shares to mint
        uint256 remainingShares = Math.mulDiv(
            assets,
            totalSupply - delegateeBalance,
            uint256(Math.max(1, int256(totalAssets()) - int256(assets)))
        );

        // Subtract delegatee balance from remaining shares since it was already transferred
        _mint(delegator, remainingShares - delegateeBalance);
    } else {
        // Transfer shares back if requested amount is less than delegatee balance
        _transferFrom(delegatee, delegator, shares);
    }
}
```

### Tools Used
- Manual Review

### Recommended Mitigation Steps

To mitigate the issue, ensure that the calculation handles edge cases properly and avoids division by zero or unintended behavior. Here is the revised function:

```solidity
function revoke(
    address delegator,
    address delegatee,
    uint256 assets
) external onlyPanopticP
Uninitialized Variable in _getRequiredCollateralSingleLegPartner Function May Lead to Incorrect Collateral Calculations
Audit: 2024-06-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `_getRequiredCollateralSingleLegPartner` function in the `CollateralTracker` contract has a logic path where the `required` variable may remain unassigned. Specifically, if `isLong != tokenId.isLong(partnerIndex)` is true and `isLong == 0`, the function does not assign a value to `required`. This can lead to uninitialized variables being used, causing unexpected behavior or vulnerabilities in the contract. Uninitialized variables can result in incorrect collateral calculations, potentially affecting the security and stability of the entire collateral management system.

## Proof of Concept
#### Line of Code:
```solidity
if (isLong != tokenId.isLong(partnerIndex)) {
    if (isLong == 1) {
        required = _computeSpread(
            tokenId,
            positionSize,
            index,
            partnerIndex,
            poolUtilization
        );
    }
} else {
    required = _computeStrangle(tokenId, index, positionSize, atTick, poolUtilization);
}
```

#### Proof:
Here's a scenario illustrating the issue:
1. `isLong` is `0`.
2. `tokenId.isLong(partnerIndex)` is `1`.
3. The condition `if (isLong != tokenId.isLong(partnerIndex))` is true.
4. Since `isLong == 0`, the inner `if (isLong == 1)` is false, leading to no assignment for `required`.

This results in `required` being uninitialized, which can cause erroneous behavior when this variable is later used.

## Tools Used
Manual

## Recommended Mitigation Steps
Ensure that all code paths in `_getRequiredCollateralSingleLegPartner` assign a value to the `required` variable.

```solidity
function _getRequiredCollateralSingleLegPartner(
    TokenId tokenId,
    uint256 index,
    uint128 positionSize,
    int24 atTick,
    uint128 poolUtilization
) internal view returns (uint256 required) {
    uint256 partnerIndex = tokenId.riskPartner(index);
    uint256 isLong = tokenId.isLong(index);

    if (isLong != tokenId.isLong(partnerIndex)) {
        if (isLong == 1) {
            required = _computeSpread(
                tokenId,
                positionSize,
                index,
                partnerIndex,
                poolUtilization
            );
        } else {
            // Handle the case where isLong == 0
            required = _computeSpread(
                tokenId,
                positionSize,
                partnerIndex,
                index,
                poolUtilization
            );
        }
    } else {
        required = _computeStrangle(tokenId, index, positionSize, atTick, poolUtilization);
    }
}
```

This ensures that the `required` variable is always assigned a value, preventing uninitialized variables from causing issues in the contract.



## Assessed type

Context
Division by Zero in _computeSpread Function Leads to Potential Runtime Errors and Incorrect Collateral Calculations
Audit: 2024-06-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `_computeSpread` function in the `CollateralTracker` contract contains a calculation that may result in a division by zero error if either `notional` or `notionalP` is zero. This issue can lead to runtime errors, causing the contract to terminate unexpectedly. Such errors can disrupt the collateral tracking process, potentially leading to incorrect collateral calculations and vulnerabilities in the contract.

## Proof of Concept

#### Line of Code:
```solidity
spreadRequirement = (notional < notionalP)
    ? Math.unsafeDivRoundingUp((notionalP - notional) * contracts, notional)
    : Math.unsafeDivRoundingUp((notional - notionalP) * contracts, notionalP);
```

If either `notional` or `notionalP` is zero, the division in the `Math.unsafeDivRoundingUp` function will result in a division by zero error, causing the contract execution to fail. This can be demonstrated by setting `notional` or `notionalP` to zero and observing the contract's behavior during execution.

## Tools Used
Manual

## Recommended Mitigation Steps
Check for zero values before performing the division.

```solidity
function _computeSpread(
    TokenId tokenId,
    uint128 positionSize,
    uint256 index,
    uint256 partnerIndex,
    uint128 poolUtilization
) internal view returns (uint256 spreadRequirement) {
    // Compute the total amount of funds moved for the position's current leg
    LeftRightUnsigned amountsMoved = PanopticMath.getAmountsMoved(tokenId, positionSize, index);

    // Compute the total amount of funds moved for the position's partner leg
    LeftRightUnsigned amountsMovedPartner = PanopticMath.getAmountsMoved(
        tokenId,
        positionSize,
        partnerIndex
    );

    // Amount moved is right slot if tokenType=0, left slot otherwise
    uint128 movedRight = amountsMoved.rightSlot();
    uint128 movedLeft = amountsMoved.leftSlot();

    // Amounts moved for partner
    uint128 movedPartnerRight = amountsMovedPartner.rightSlot();
    uint128 movedPartnerLeft = amountsMovedPartner.leftSlot();

    uint256 tokenType = tokenId.tokenType(index);

    // Compute the max loss of the spread
    uint256 notional;
    uint256 notionalP;
    uint128 contracts;

    if (tokenType == 1) {
        notional = movedRight;
        notionalP = movedPartnerRight;
        contracts = movedLeft;
    } else {
        notional = movedLeft;
        notionalP = movedPartnerLeft;
        contracts = movedRight;
    }

    // Check for zero values to prevent division by zero
    if (notional == 0 || notionalP == 0) {
        spreadRequirement = 0;
    } else {
        spreadRequirement = (notional < notionalP)
            ? Math.unsafeDivRoundingUp((notionalP - notional) * contracts, notional)
            : Math.unsafeDivRoundingUp((notional - notionalP) * contracts, notionalP);
    }
}
```



## Assessed type

Math
Inaccurate Collateral Calculation in _computeSpread Function Due to Insufficient Zero Difference Handling
Audit: 2024-06-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `_computeSpread` function in the `CollateralTracker` contract calculates the required collateral for spread positions. However, there is a critical flaw: the absolute difference calculation does not handle scenarios where `movedRight` or `movedLeft` might be very close to their respective partners, potentially resulting in a zero difference. This can lead to inaccurate collateral requirements, resulting in under-collateralization and increased risk of insolvency for the protocol.

## Proof of Concept
### LOC
```solidity
spreadRequirement = movedRight < movedPartnerRight
    ? movedPartnerRight - movedRight
    : movedRight - movedPartnerRight;
```

If `movedRight` is very close to `movedPartnerRight` or `movedLeft` is very close to `movedPartnerLeft`, the absolute difference calculation could result in a zero difference. This would not accurately reflect the risk and required collateral for the spread position.

## Tools Used
Manual

## Recommended Mitigation Steps
Add a small epsilon value to ensure that the difference is non-zero.

```solidity
spreadRequirement = movedRight < movedPartnerRight
    ? movedPartnerRight - movedRight + 1 // Ensure non-zero difference
    : movedRight - movedPartnerRight + 1; // Ensure non-zero difference
```


## Assessed type

Invalid Validation
Incorrect Validation in _updatePositionsHash Function Allows Exceeding Maximum Positions Limit by One
Audit: 2024-06-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The current implementation of the `_updatePositionsHash` function in the `PanopticPool` contract has an incorrect validation check for the maximum number of positions allowed per user. This oversight allows users to exceed the intended maximum positions limit by one. This could lead to unintended behavior and potential system vulnerabilities due to more positions being open than allowed.

## Proof of Concept

### Lines of Code
```solidity
function _updatePositionsHash(address account, TokenId tokenId, bool addFlag) internal {
    uint256 newHash = PanopticMath.updatePositionsHash(
        s_positionsHash[account],
        tokenId,
        addFlag
    );
    if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen();
    s_positionsHash[account] = newHash;
}
```

In this code, the check `(newHash >> 248) > MAX_POSITIONS` is intended to ensure that the number of positions does not exceed `MAX_POSITIONS`. However, this condition will only trigger if the number of positions exceeds `MAX_POSITIONS` by more than one.

### Issue with Current Condition:
- If `MAX_POSITIONS` is set to 32, and a user has exactly 32 positions, `newHash >> 248` will equal 32.
- The condition `(newHash >> 248) > MAX_POSITIONS` will not trigger because 32 is not greater than 32.
- Therefore, a user might be able to add one more position, making the total 33, which exceeds the intended limit.

### Proof:
Consider the following scenario with `MAX_POSITIONS` set to 32:
1. A user has 32 positions.
2. The `newHash` value after adding another position results in `newHash >> 248` equal to 33.
3. The condition `(newHash >> 248) > 32` will not revert, allowing the user to add the 33rd position.

## Tools Used
Manual

## Recommended Mitigation Steps
To strictly enforce the maximum number of positions, the condition should be changed to `>=`:

```solidity
function _updatePositionsHash(address account, TokenId tokenId, bool addFlag) internal {
    uint256 newHash = PanopticMath.updatePositionsHash(
        s_positionsHash[account],
        tokenId,
        addFlag
    );
    if ((newHash >> 248) >= MAX_POSITIONS) revert Errors.TooManyPositionsOpen();
    s_positionsHash[account] = newHash;
}
```


## Assessed type

Invalid Validation
Lack of Validation for positionIdList in mintOptions Function Can Lead to Errors and Potential Exploits
Audit: 2024-06-panoptic-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `mintOptions` function in the `PanopticPool` contract has a potential oversight related to the validation of the `positionIdList` parameter. If `positionIdList` is empty or contains invalid `TokenId`s, it could lead to unexpected behavior, errors, or even potential exploits. This oversight could allow users to bypass necessary checks, leading to incorrect minting of options and potentially compromising the integrity of the system.


## Proof of Concept
```solidity
function mintOptions(
    TokenId[] calldata positionIdList,
    uint128 positionSize,
    uint64 effectiveLiquidityLimitX32,
    int24 tickLimitLow,
    int24 tickLimitHigh
) external {
    _mintOptions(
        positionIdList,
        positionSize,
        effectiveLiquidityLimitX32,
        tickLimitLow,
        tickLimitHigh
    );
}

function _mintOptions(
    TokenId[] calldata positionIdList,
    uint128 positionSize,
    uint64 effectiveLiquidityLimitX32,
    int24 tickLimitLow,
    int24 tickLimitHigh
) internal {
    TokenId tokenId = positionIdList[positionIdList.length - 1];

    // Validate position list and user state
    _validatePositionList(msg.sender, positionIdList, 1);

    if (tokenId.poolId() != SFPM.getPoolId(address(s_univ3pool)))
        revert Errors.InvalidTokenIdParameter(0);

    // Disallow user to mint exact same position
    if (LeftRightUnsigned.unwrap(s_positionBalance[msg.sender][tokenId]) != 0)
        revert Errors.PositionAlreadyMinted();

    // Mint in the SFPM and update state of collateral
    uint128 poolUtilizations = _mintInSFPMAndUpdateCollateral(
        tokenId,
        positionSize,
        tickLimitLow,
        tickLimitHigh
    );

    _addUserOption(tokenId, effectiveLiquidityLimitX32);

    // Update user's position balance
    s_positionBalance[msg.sender][tokenId] = LeftRightUnsigned
        .wrap(0)
        .toLeftSlot(poolUtilizations)
        .toRightSlot(positionSize);

    // Check solvency after minting with a buffer
    uint256 medianData = _validateSolvency(msg.sender, positionIdList, BP_DECREASE_BUFFER);

    if (medianData != 0) s_miniMedian = medianData;

    emit OptionMinted(msg.sender, positionSize, tokenId, poolUtilizations);
}
```

### Issue with Current Validation:
- The function `_mintOptions` extracts the last `tokenId` from `positionIdList` without checking if the list is non-empty.
- If `positionIdList` is empty, attempting to access `positionIdList[positionIdList.length - 1]` will result in an out-of-bounds error, potentially causing the contract to revert unexpectedly.

### Proof:
Consider the following scenario:
1. A user calls `mintOptions` with an empty `positionIdList`.
2. The function `_mintOptions` attempts to access `positionIdList[positionIdList.length - 1]`.
3. This results in an out-of-bounds access error, causing the contract to revert.

## Tools Used
Man