xanoutas/solidity-security-findings huggingface dataset:
Incorrect Values Returned by sub(int256,int256) Function of LeftRight Library on Specific Edge Cases
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/types/LeftRight.sol#L177-L189


# Vulnerability details

## Impact
The sub(int256,int256) function in the LeftRight library is designed to return the difference of two int256 numbers. However, due to incorrect implementation, it returns erroneous values in specific edge cases, resulting in precision loss.

This function is used multiple times in the SemiFungiblePositionManager contract, and its incorrect behavior can cause heavy financial losses.

## Proof of Concept
- when both x and y are negative values and x is less than y (i.e., x is more negative or further away from 0 than y), the function returns unexpected results.
  **Example:** 
   LeftRight.sub(-2, -1) returns => 340282366920938463463374607431768211455
which is clearly disasterous it should have returned -1 instead.


- Similarly, when y is negative but x is greater or equal to zero, the subtraction function always returns incorrect values.
**Example**
LeftRight.sub(0,-1) -> 340282366920938463463374607431768211457
LeftRight.sub(49,-8) -> 340282366920938463463374607431768211513

Here is a Foundry POC to demonstrate this critical issue
place inside test/foundry and run using command: **forge test --match-test kyberswap -vvv**
```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {LeftRight} from "@types/LeftRight.sol";


import "forge-std/Test.sol";

contract LRTest is Test {
using LeftRight for int256;
using LeftRight for int128;

// No bug condition therefore should return perfect values
function test_should_Be_normal_kyberswap() public {
 
  console.logInt(int256(-33).sub(int256(33)));//normal
  console.logInt(int256(-1).sub(int256(2))); // normal
  console.logInt(int256(-1).sub(int256(-1))); // normal
  console.logInt(int256(-1).sub(int256(-2))); //normal

}

// bug: when y < 0 and x >= 0 
function test_Ynegative_x_zeroOR_More_kyberswap() public {
  console.logInt(int256(200).sub(int256(-100)));
  console.logInt(int256(0).sub(int256(-1)));
  console.logInt(int256(49).sub(int256(-8)));
  
// returns
//   340282366920938463463374607431768211756
//   340282366920938463463374607431768211457
//   340282366920938463463374607431768211513
  
}

// bug: when x & y < 0  and y > x
function test_Both_xyNegative_YgreaterX_kyberswap() public {
   
   console.logInt(int256(-2).sub(int256(-1)));
   console.logInt(int256(-7).sub(int256(-3)));
//   returns
//   340282366920938463463374607431768211455
//   340282366920938463463374607431768211452

}
 
}
```

## Tools Used
Echidna, Foundry

## Recommended Mitigation Steps
Avoid sub() function.


## Assessed type

Math
Overflow Bypass
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/types/TokenId.sol#L469


# Vulnerability details

The bit shift calculation `64 + 48 * i` could overflow for high `i` values, causing the contiguity check to read incorrect areas of the token ID.

An attacker could force an overflow to also bypass the check.

**Suggestion:** Use SafeMath and validate `i` cannot overflow.



## Assessed type

Under/Overflow
Incorrect Liquidity Accounting
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L1129


# Vulnerability details

## Impact

The function updates the liquidity state after minting, but it may not account for all scenarios, such as partial mints.

## Proof of Concept

Consider a situation where the requested liquidity to be minted is greater than what's available due to external constraints or a sudden reduction in token availability. The code might look something like this:

function createPartialMint(IUniswapV3Pool univ3pool, uint256 liquidityChunk) internal {
    bytes memory mintdata = abi.encode(
        CallbackLib.CallbackData({
            poolFeatures: CallbackLib.PoolFeatures({
                token0: univ3pool.token0(),
                token1: univ3pool.token1(),
                fee: univ3pool.fee()
            }),
            payer: msg.sender
        })
    );

    // For the sake of the PoC, assuming liquidityChunk requested is more than available
    // This can be simulated by setting liquidityChunk higher than the available liquidity

    // Assume a situation where the available liquidity is less than liquidityChunk
    uint256 availableLiquidity = univ3pool.totalLiquidity();
    require(liquidityChunk > availableLiquidity, "Requested liquidity is more than available");

    // Attempt to mint more liquidity than available
    (uint256 amount0, uint256 amount1) = univ3pool.mint(
        address(this),
        liquidityChunk.tickLower(),
        liquidityChunk.tickUpper(),
        liquidityChunk.liquidity(),
        mintdata
    );

    // Assume 'amount0' and 'amount1' are the amounts actually minted
    // There should be a logic to handle the situation where these amounts are less than 'liquidityChunk'
    // Due to partial minting, adjust the liquidity state accordingly
    // This part could be missing or insufficient in the original function
    // For instance, the adjustment logic might update state variables that track liquidity without accounting for the partial mint scenario.
    // An appropriate solution might involve adjusting the liquidity state based on the actual amounts minted.
    // However, without specific details of the contract, it's hard to provide an exact fix.
}


## Tools Used

## Recommended Mitigation Steps

A potential code solution involves improving the function logic to handle partial mints:

function createPartialMint(IUniswapV3Pool univ3pool, uint256 liquidityChunk) internal {
    // ...

    // Mint liquidity
    (uint256 amount0, uint256 amount1) = univ3pool.mint(
        address(this),
        liquidityChunk.tickLower(),
        liquidityChunk.tickUpper(),
        liquidityChunk.liquidity(),
        mintdata
    );

    // Logic to handle partial minting
    if (amount0 < liquidityChunk || amount1 < liquidityChunk) {
        // Handle the scenario where the minted amounts are less than the requested liquidityChunk
        // Adjust the liquidity state or tak
Input Manipulation
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/types/TokenId.sol#L463


# Vulnerability details

An attacker could construct a token ID with higher order bits enabled to bypass the check:

```
Token ID: 0x00...00 | OPTION_RATIO_1 | 00..00 | OPTION_RATIO_3 | 00

Validate will NOT detect gap between leg 1 and 3.
```

This input manipulation gets around the contiguous leg assumption.

**Suggestion:** Use bit masking instead of bit shift check.



## Assessed type

Invalid Validation
Lack of clarity and Complexity in Type Conversions
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L1097


# Vulnerability details

## Impact
Although the code includes comments explaining the type conversion, it remains complex and challenging to decipher. 


## 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

## Recommended Mitigation Steps.
Even with comments, nested conversions can remain convoluted. Consider breaking down these conversions further.
Utilize descriptive variable names to clarify the purpose of intermediate conversions.
Create functions or separate steps for better readability and understanding.

Simplify the code by decomposing complex expressions into smaller, more manageable parts.
Incorporate functions or variables with descriptive names to encapsulate intermediary steps.
Enhance readability by avoiding deeply nested conversions.

Proposal: 

// Calculate feeGrowthInside0 and feeGrowthInside1 separately
int256 feeGrowthInside0 = Math.mulDiv128(feeGrowthInside0LastX128, liquidity);
int256 feeGrowthInside1 = Math.mulDiv128(feeGrowthInside1LastX128, liquidity);

// Convert feeGrowthInside0 and feeGrowthInside1 to int128 separately
int128 feeGrowthInside0Int128 = int128(int256(feeGrowthInside0));
int128 feeGrowthInside1Int128 = int128(int256(feeGrowthInside1));

// Perform individual type conversions using toRightSlot and toLeftSlot functions
int256 feesBaseRightSlot = int256(0).toRightSlot(feeGrowthInside0Int128);
int256 feesBaseLeftSlot = int256(0).toLeftSlot(feeGrowthInside1Int128);

// Combine the calculated values to generate feesBase
feesBase = feesBaseRightSlot.toLeftSlot(feesBaseLeftSlot);



## Assessed type

Other
Unchecked Arithmetic Operations
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L936


# Vulnerability details

## Impact
Existing mathematical operations look safe, but I still flag for not using SafeMath for uint256 since the the function performs arithmetic operations on liquidity amounts without using SafeMath or similar checks. 

## Proof of Concept
uint128 startingLiquidity = currentLiquidity.rightSlot();
uint128 removedLiquidity = currentLiquidity.leftSlot();
uint128 updatedLiquidity = startingLiquidity + chunkLiquidity;
updatedLiquidity = startingLiquidity - chunkLiquidity;

## Tools Used

## Recommended Mitigation Steps
using SafeMath for uint256;



## Assessed type

Math
Possible Gas Limit DoS on balanceOfBatch
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/tokens/ERC1155Minimal.sol#L187


# Vulnerability details

The balanceOfBatch function performs unbounded looping based on the length of the input arrays:

```
for (uint256 i = 0; i < owners.length; ++i) {

  balances[i] = balanceOf[owners[i]][ids[i]];  
}
```

An attacker could specify very long input arrays to try and cause the transaction to hit the block gas limit and revert.

**Suggestion** 

Impose a reasonable limit on the maximum input array sizes:

```
require(owners.length <= 50, "Too many inputs");
```

This prevents excessive computation.

**Severity**: Medium  
**Risk**: Transaction could be forced to fail by exceeding block gas limit. User denied service.



## Assessed type

DoS
Incorrect Position Size Handling
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L848


# Vulnerability details

## Impact
The function assumes that the position size is valid and does not perform checks for extreme values or edge cases.

If the position size is not correctly handled, it could lead to incorrect liquidity being minted or burned, potentially resulting in financial loss.


## Proof of Concept
// This PoC assumes 'positionSize' is assigned an extreme value that exceeds the token amount limits.
// Please adjust values to simulate different scenarios.

function maliciousCall() {
    // Call the function with an extreme value for position size
    uint256 extremeValue = 2**129; // An example exceeding the int128 max value

    // Call the vulnerable function with the extreme value
    _createPositionInAMM(someUniv3Pool, someTokenId, extremeValue, false);
}


## Tools Used

## Recommended Mitigation Steps

Add checks to ensure the position size is within valid bounds and handle edge cases appropriately.

require(positionSize > 0 && positionSize <= MAX_POSITION_SIZE, 'Invalid position size');


## Assessed type

Error
Incorrect Swap Amount Calculation
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L743


# Vulnerability details

## Impact

Incorrect calculation of swap amounts could lead to suboptimal execution and potential financial loss.

The function calculates the swap amount based on the ITM amounts but may not handle all edge cases correctly.

## Proof of Concept
simplified scenario: 

// Assuming tokens in the Uniswap pool
int128 itm0 = 100;  // Surplus of token0
int128 itm1 = -80;  // Shortage of token1

// Simplified version of the function's logic
int256 net0 = itm0 - PanopticMath.convert1to0(itm1, sqrtPriceX96);
bool zeroForOne = net0 < 0;
int256 swapAmount = -net0;

// Performing the swap based on computed values
_univ3pool.swap(msg.sender, zeroForOne, swapAmount, /*...other parameters...*/);

## Tools Used

## Recommended Mitigation Steps

Please have further a) validity Check for Swap Amounts b) handle edge case scenarios more

function swapInAMM(
    //... existing function parameters ...
) internal returns (int256 totalSwapped) {
    // Existing function logic...

    // To Mitigate Risk: Ensure swap amount is non-zero and handle edge cases gracefully
    if (swapAmount == 0) {
        // Handle scenario when swapAmount is zero (edge case)
        // Return or revert with an appropriate error message
        revert("Zero swap amount: No swap operation needed.");
    }

    // Existing function logic...
}



## Assessed type

Error
Signature Replay Vulnerability
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/tokens/ERC1155Minimal.sol#L165


# Vulnerability details

Transfers rely on calling back to the recipient without a unique nonce:

```
ERC1155Holder(to).onERC1155Received(...)
```

This signature can be replayed.

**Suggestion**: Add a nonce parameter that is incremented per transfer.

**Severity**: Medium
**Risk**: Tokens could be stolen by replaying signed payloads.



## Assessed type

Invalid Validation
Usage of Slot0 is susceptible to Price manipulation
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L775


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

The function swapInAMM() in SemiFungiblePositionManager.sol uses UniswapV3.slot0 to obtain the value of sqrtPriceX96. This value is then calculated and utilised to execute the swap. Uniswap.slot0, the most recent data point, is where the sqrtPriceX96 is derived from, though, and it is easily manipulated by MEV bots sandwich attacks, When associated with uniswap.swap function will result in loss of funds.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L775

## Tools Used
Manual

## Recommended Mitigation Steps
Instead of Slot0 use TWAP function to get value of sqrtPriceX96.


## Assessed type

MEV
Incorrect Slippage Limit Handling
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L710


# Vulnerability details

## Impact
Improper handling of slippage limits could result in transactions failing unexpectedly or not providing the intended price protection.


The function assumes that slippage limits are always provided in a specific order and does not handle cases where they might be reversed or equal.


Current check doesn't directly address the scenario where the slippage limits are reversed (tickLimitLow is greater than tickLimitHigh) or equal. If the limits are provided in an unexpected order or are equal, the current implementation might revert transactions erroneously or fail to revert despite the price falling outside the intended range.


## 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

## Recommended Mitigation Steps
Implement robust handling of slippage limit parameters to ensure they are used correctly.
To rectify this, you could integrate the additional check for reversed or equal slippage limits within the existing validation logic. 


// Check if slippage limits are reversed or equal
if (tickLimitLow >= tickLimitHigh) {
    revert("Invalid slippage limits");
}

// Check slippage against provided limits
if ((newTick >= tickLimitHigh) || (newTick <= tickLimitLow)) {
    revert("PriceBoundFail");
}


## Assessed type

Other
Insufficient Position Size Validation
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L663


# Vulnerability details

## Impact
Executing operations with a position size of zero could lead to unnecessary gas expenditure and potential logical errors.

The function checks for a position size of zero but does not account for other invalid or edge case values.


## Proof of Concept
function testFunction() external {
        handlePosition(0); // Simulating a zero position size

        // Simulating an unexpected or invalid value (e.g., negative)
        handlePosition(uint128(-1));
    }
## Tools Used

## Recommended Mitigation Steps

Enhance input validation to handle edge cases and ensure position size is within valid bounds.

require(positionSize > 0 && positionSize <= MAX_POSITION_SIZE, 'Invalid position size');


## Assessed type

Invalid Validation
Transferring to Address with Existing Position
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L578


# Vulnerability details

## Impact
The function does not prevent transfers to addresses that already hold the same position.

Could lead to incorrect liquidity accounting or inconsistent state within the system.

## 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

## Recommended Mitigation Steps

Add a validation step to check if the recipient address already holds a similar position.

Prevent transfers to addresses with existing positions.

require(s_accountLiquidity[positionKey_to] == 0, 'Recipient already has a position');


## Assessed type

Token-Transfer
Approval Check Missing in Token Transfers (Mint and Swap Callbacks)
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/SemiFungiblePositionManager.sol#L407
https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/SemiFungiblePositionManager.sol#L441


# Vulnerability details

## Impact
Without the required approval, the token transfers will fail leading to unexpected behavior and potential transaction reverts. 

## Proof of Concept
The `uniswapV3MintCallback` and `uniswapV3SwapCallback` functions attempt to transfer tokens using `SafeTransferLib.safeTransferFrom`, but both lack a check to ensure that the contract has the necessary approval from the token owner (payer) to spend the tokens.
https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/SemiFungiblePositionManager.sol#L407

https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/SemiFungiblePositionManager.sol#L441

## Tools Used
Manual analysis
## Recommended Mitigation Steps
Call safeTransfer(to, amount) instead of safeTrasferFrom


## Assessed type

ERC20
Unbounded loops can lead to denial of service 
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/tokens/ERC1155Minimal.sol#L159


# Vulnerability details


## Impact
The ```safeBatchTransferFrom``` function relies on an unbounded loop. An attacker could disrupt swap operations by forcing the loop to go through too many operations, potentially trapping the ```safeBatchTransferFrom``` due to a lack of gas.

## Proof of Concept
Alice as an  attacker  could  carry out  thousands  of  transfer  to  make  the  system  very  costly and  potentially  prevent  transfer operations.

```
function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) public virtual {
        if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized();

        // Storing these outside the loop saves ~15 gas per iteration.
        uint256 id;
        uint256 amount;

        for (uint256 i = 0; i < ids.length; ) {
            id = ids[i];
            amount = amounts[i];

            balanceOf[from][id] -= amount;

            // balance will never overflow
            unchecked {
                balanceOf[to][id] += amount;
            }

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        afterTokenTransfer(from, to, ids, amounts);

        emit TransferBatch(msg.sender, from, to, ids, amounts);

        if (to.code.length != 0) {
            if (
                ERC1155Holder(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) !=
                ERC1155Holder.onERC1155BatchReceived.selector
            ) {
                revert UnsafeRecipient();
            }
        }
    }
```

Alice have  to  pay  large gas  to  carryout the transfer and the  system  deployed  on  a  layer  2  solution  (e.g., arbitrum, polygon), the  attacker’s  gas  payment would  be  signiﬁcantly  lower.

## Tools Used
Manual Review

## Recommended Mitigation Steps
The recommendation is made to limit number of transfer for each iteration to avoid any potential denial of service issue or attack.

```diff
function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) public virtual {
        if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized();

        // Storing these outside the loop saves ~15 gas per iteration.
        uint256 id;
        uint256 amount;

        for (uint256 i = 0; i < ids.length; ) {
+	require(i < 50, "Exceeded maximum allowed iterations");
            id = ids[i];
            amount = amounts[i];

            balanceOf[from][id] -= amount;

            // balance will never overflow
            unchecked {
                balanceOf[to][id] += amount;
            }
Insufficient Slippage Validation
Audit: 2023-11-panoptic-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L526


# Vulnerability details

## Impact
The function may not adequately enforce the slippage limits provided by the user, especially in volatile market conditions.

## 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

## Recommended Mitigation Steps

Implement robust slippage checks to ensure that the user's specified limits are respected.
require(newTick >= slippageTickLimitLow && newTick <= slippageTickLimitHigh, 'Slippage limits breached');


## Assessed type

Invalid Validation