xanoutas/solidity-security-findings huggingface dataset:
Incorrect Fee Collection Logic
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The function's logic for calculating the amount of fees to collect may not accurately reflect the fees accrued in the Uniswap V3 pool.

## Proof of Concept
// Simplified example to illustrate potential discrepancy in fee collection

// Mock UniswapV3Pool for demonstration purposes
contract MockUniswapV3Pool {
    // Simulated fee calculation function in the actual UniswapV3 pool
    function getFeesBase(uint128 liquidityChunk) external pure returns (int256 feesBase) {
        // Simulated fee calculation logic (example values)
        feesBase = int256(liquidityChunk * 5); // Assuming a fee of 5 times liquidity for demonstration
    }

    // Function to simulate collect method in UniswapV3 pool
    function collect(
        address sender,
        uint256 tickLower,
        uint256 tickUpper,
        uint128 amount0,
        uint128 amount1
    ) external pure returns (uint128, uint128) {
        // Simulated collect function (echo back the amounts)
        return (amount0, amount1);
    }
}

contract YourContract {
    MockUniswapV3Pool public univ3pool;

    // Mock liquidity chunk
    uint128 public liquidityChunk = 100;

    // Assuming this function mimics the _collectAndWritePositionData logic in your contract
    function simulateFeeCollection(uint256 currentLiquidity) external returns (int256 collectedOut) {
        uint128 startingLiquidity = uint128(currentLiquidity); // Mock current liquidity for simplicity

        int256 amountToCollect = univ3pool.getFeesBase(liquidityChunk); // Get fees as per assumed logic

        // Rest of the logic for collecting fees (similar to _collectAndWritePositionData)

        // Illustrative example (not real fee calculation)
        uint128 receivedAmount0 = 50; // Example received amount from pool
        uint128 receivedAmount1 = 60; // Example received amount from pool

        // Assuming discrepancy due to different fee calculation logic
        uint128 collected0 = receivedAmount0 - uint128(-amountToCollect.rightSlot()); // Simulated discrepancy
        uint128 collected1 = receivedAmount1 - uint128(-amountToCollect.leftSlot()); // Simulated discrepancy

        collectedOut = int256(0).toRightSlot(collected0).toLeftSlot(collected1);
    }
}


## Tools Used

## Recommended Mitigation Steps

Review and correct the fee collection logic to ensure it accurately calculates the correct amount of fees to collect.


## Assessed type

Other
Risk of Insufficient Liquidity Check
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The function assumes that liquidity exists for a given chunk range without verifying its sufficiency before attempting to modify it.

May result in reverting transactions if the required liquidity isn't available, leading to failed operations.

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

function _createLegInAMM(
    IUniswapV3Pool _univ3pool,
    uint256 _tokenId,
    uint256 _leg,
    uint256 _liquidityChunk,
    bool _isBurn
) internal returns (int256 _moved, int256 _itmAmounts, int256 _totalCollected) {
    // ... existing code ...

    // Check for sufficient liquidity before modifications
    if (isLong == 0 && startingLiquidity < chunkLiquidity) {
        revert Errors.NotEnoughLiquidity();
    }

    // ... existing code ...
}



## Assessed type

Invalid Validation
Unauthorized Position Modification
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The function does not verify that the caller is authorized to modify the position associated with the given `tokenId`.

Without proper access controls, an unauthorized user could potentially modify positions, leading to unauthorized liquidity changes


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

require(isAuthorized(msg.sender, tokenId), 'Caller not authorized'); 
// Continue with existing logic


## Assessed type

Access Control
Missing Approval Check in Token Transfers Cripples Position Creation and Update
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Users are unable to create or update their desired positions

## Proof of Concept
1.User attempts creating/updating a position using the contract.
  The uniswapV3MintCallback or uniswapV3SwapCallback function is triggered upon token 
  transfer initiation.
2.Vulnerability: The `uniswapV3MintCallback` and `uniswapV3SwapCallback` functions lack a crucial check to ensure the contract has the necessary approval from the user (payer) to spend their tokens.
3.Consequently, the token transfer within the callback fails due to insufficient 
  approval.
4.Since position creation/update relies on successful token transfer, the operation is 
  incomplete, and no position is created or updated.
## Tools Used
Manual Analysis
## Recommended Mitigation Steps
Integrate a robust approval check within both `uniswapV3MintCallback` and `uniswapV3SwapCallback` functions.


## Assessed type

ERC20
Access Controls Missing 
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

The internal `_mint` and `_burn` functions do not have access restrictions:

```solidity
function _mint(address to, uint256 id, uint256 amount) internal {

  // Anyone can mint tokens! 
  balanceOf[to][id] += amount;

}
```

**Suggestion**: Add an `onlyOwner` modifier to restrict access:

```solidity 
function _mint(address to, uint256 id, uint256 amount) internal onlyOwner {

  // Only owner can mint now
  balanceOf[to][id] += amount;  
}
```

**Severity**: High  
**Risk**: Arbitrary token minting can lead to total loss of trust in token supply.



## Assessed type

Access Control
Unverified External Call to Uniswap Pool
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Calling an external contract without proper validation could lead to interaction with malicious contracts.

The function retrieves a Uniswap pool address from a mapping but does not verify its authenticity.



## Proof of Concept


contract UniswapPoolRegistry {
    mapping(uint256 => address) public poolAddresses;

    // Function to set Uniswap pool addresses
    function setPoolAddress(uint256 poolId, address poolAddress) external {
        poolAddresses[poolId] = poolAddress;
    }
}

contract VulnerableContract {
    UniswapPoolRegistry public registry;

    constructor(address registryAddress) {
        registry = UniswapPoolRegistry(registryAddress);
    }

    function executeWithPool(uint256 tokenId) external {
        address poolAddress = registry.poolAddresses[tokenId];

        // Assuming poolAddress retrieved from mapping is a Uniswap pool
        // Perform interactions with this assumed Uniswap pool
        // ...
    }
}

## Tools Used

## Recommended Mitigation Steps

Verify the Uniswap pool address before interacting with it.

IUniswapV3Pool univ3pool = s_poolContext[tokenId.univ3pool()].pool;
require(address(univ3pool) != address(0), 'Invalid pool address');
require(FACTORY.getPool(univ3pool.token0(), univ3pool.token1(), univ3pool.fee()) == address(univ3pool), 'Unverified pool address');


## Assessed type

Other
Access Controls Missing
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

**Access Controls Missing Poc**  

```solidity
function mintTokenizedPosition(uint256 tokenId) external {
 
  // Mint logic
  
}

function burnTokenizedPosition(uint256 tokenId) external {

  // Burn logic
  
}
```
## Impact
Anyone can call sensitive functions like mint and burn.

**Suggestion**:

```solidity
modifier onlyAuthorized() {

  require(msg.sender == authorizedAddress, "Not authorized");
  _;

}

function mintTokenizedPosition(uint256 tokenId) external onlyAuthorized {
 
  // Mint logic
  
}
```
## Mitigation
Use modifiers to restrict access.



## Assessed type

Access Control
Incomplete Operations Handling
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
May result in partial execution of operations without proper handling for failure scenarios, leaving inconsistent state changes.


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

Enhance 'registerTokenTransfer' function to handle failures gracefully. Implement appropriate rollback mechanisms or use try-catch blocks to handle exceptions.

Implement try-catch blocks or error handling within 'registerTokenTransfer' function.


## Assessed type

Error
Unauthorized Transfer of Partial Positio
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

May allow partial transfer of a position, leading to inconsistent state and potential disputes between users.

The function does not enforce that the entire balance of a position must be transferred, which could result in fragmented positions.


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

Enforce full balance transfer to maintain position integrity.

require(amount == balanceOf(from, id), 'Must transfer full balance')




## Assessed type

Token-Transfer
Out-of-Bounds Access
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Assumption of arrays 'ids' and 'amounts' having matching lengths; accessing one array might lead to out-of-bounds access for the other.

May result in runtime errors, such as accessing elements beyond array bounds, causing unexpected behavior or crashes.

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

Check that 'ids' and 'amounts' arrays have the same length before iterating and accessing elements from both arrays to prevent out-of-bounds access.

if (ids.length != amounts.length) { /* ... handle mismatch ... */ }


## Assessed type

Error
Pool can be created with unfair fee rate
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Anyone can initialize a pool and set unfair fee which can lead to charging more fees to the user.

## Proof of Concept
```initializeAMMPool``` function allows anyone to initialize a pool and set high fee which will be charged for every swap by users.

```
    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 (address(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;


        // Set the base poolId as last 8 bytes of the address (the first 16 hex characters)
        // @dev in the unlikely case that there is a collision between the first 8 bytes of two different Uni v3 pools
        // @dev increase the poolId by a pseudo-random number
        uint64 poolId = PanopticMath.getPoolId(univ3pool);

        while (address(s_poolContext[poolId].pool) != address(0)) {
            poolId = PanopticMath.getFinalPoolId(poolId, token0, token1, fee);
        }
        // store the poolId => UniswapV3Pool information in a mapping
        // `locked` can be initialized to false because the pool address makes the slot nonzero
        s_poolContext[poolId] = PoolAddressAndLock({
            pool: IUniswapV3Pool(univ3pool),
            locked: false
        });

        // store the UniswapV3Pool => poolId information in a mapping
        // add a bit on the end to indicate that the pool is initialized
        // (this is for the case that poolId == 0, so we can make a distinction between zero and uninitialized)
        unchecked {
            s_AddrToPoolIdData[univ3pool] = uint256(poolId) + 2 ** 255;
        }
        emit PoolInitialized(univ3pool);

        return;

        // this disables `memoryguard` when compiling this contract via IR
        // it is classed as a potentially unsafe assembly block by the compiler, but is in fact safe
        // we need this because enabling `memoryguard` and therefore StackLimitEvader increases the size of the contract significantly beyond the size limit
        assembly {
            mstore(0, 0xFA20F71C)
        }
    }
```

## Tools Used
Manual code Review / Solodit
Unverified External Call to Uniswap Pool
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Calling an external contract without proper validation could lead to interaction with malicious contracts.

The function retrieves a Uniswap pool address from a mapping but does not verify its authenticity


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

Verify the Uniswap pool address before interacting with it.

IUniswapV3Pool univ3pool = s_poolContext[tokenId.univ3pool()].pool;
require(address(univ3pool) != address(0), 'Invalid pool address');
require(FACTORY.getPool(univ3pool.token0(), univ3pool.token1(), univ3pool.fee()) == address(univ3pool), 'Unverified pool address');




## Assessed type

Other
Rounding error in `liquidityChunk()` can cause a DOS of ERC1155 transfers
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/libraries/PanopticMath.sol#L120-L129


# Vulnerability details

## Impact

Due to a rounding error in the liquidity chunk calculation, ERC1155 transfers may be DOS.

This can happen if the same account adds liquidity in the same `tokenId` twice.

For example, if Alice adds `amount0` and `amount1` to a leg within two transactions, the liquidity value will not be the same as if she added `amount0 + amount1` in a single transaction. Since transferring tokens requires sending the entire liquidity, the transfer will revert at this line: [SemiFungiblePositionManager.sol#L621](https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/SemiFungiblePositionManager.sol#L621).

```solidity
liq0 = Math.getLiquidityForAmount0(..., amount0); 
liq1 = Math.getLiquidityForAmount0(..., amount1);
liqTot = Math.getLiquidityForAmount0(..., amount0 + amount1);

assert(liq1 + liq0 == liqTot): // not always true
```

## POC

- Evidence that a rounding error can occur
    
    ```solidity
    function test_beirao4() public {
        uint256 amount0 = 1e18;
        uint256 amount1 = 1e18 + 1;
        int24 tickLowerr = 199248;
        int24 tickUpperr = 199250;
    
        uint128 legLiquidity0 = Math.getLiquidityForAmount1(
            uint256(0).addTickLower(tickLowerr).addTickUpper(tickUpperr),
            amount0
        );
        uint128 legLiquidity1 = Math.getLiquidityForAmount1(
            uint256(0).addTickLower(tickLowerr).addTickUpper(tickUpperr),
            amount1
        );
    
        uint128 legLiquidityTot = Math.getLiquidityForAmount1(
            uint256(0).addTickLower(tickLowerr).addTickUpper(tickUpperr),
            amount0 + amount1
        );
        console.log("legLiquidityTot ", legLiquidityTot);
        console.log("legLiquidity0 + legLiquidity1 ", legLiquidity0 + legLiquidity1);
        assert(legLiquidityTot == legLiquidity0 + legLiquidity1);
    }
    
    function test_beirao3() public {
        uint256 amount0 = 1e18;
        uint256 amount1 = 1e18;
        int24 tickLowerr = 199248;
        int24 tickUpperr = 199250;
    
        uint128 legLiquidity0 = Math.getLiquidityForAmount0(
            uint256(0).addTickLower(tickLowerr).addTickUpper(tickUpperr),
            amount0
        );
        uint128 legLiquidity1 = Math.getLiquidityForAmount0(
            uint256(0).addTickLower(tickLowerr).addTickUpper(tickUpperr),
            amount1
        );
    
        uint128 legLiquidityTot = Math.getLiquidityForAmount0(
            uint256(0).addTickLower(tickLowerr).addTickUpper(tickUpperr),
            amount0 + amount1
        );
        console.log("legLiquidityTot ", legLiquidityTot);
        console.log("legLiquidity0 + legLiquidity1 ", legLiquidity0 + legLiquidity1);
        assert(legLiquidityTot == legLiquidity0 + legLiquidity1);
    }
    ```
    
- Fuzzing test for in
Incorrect Position Size Handling
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Minting a position with an incorrect size could lead to financial discrepancies or enable unintended arbitrage opportunities.

The function may not properly handle cases where the `positionSize` does not align with the expected liquidity amounts.


## 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 checks to ensure the position size is valid and aligns with liquidity requirements.

require(positionSize > 0, 'Position size must be greater than zero')
Internally done _validateAndForwardToAMM may not be enough given it only does null check. 


## Assessed type

Other
Unauthorized Burning of Positions
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If proper authorization checks are not in place, an unauthorized user could burn positions they do not own.

The function does not verify that the caller is the owner of the `tokenId` or has been approved to burn it.

## 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 authorization checks to ensure only the owner or approved users can burn positions.

require(_isApprovedOrOwner(_msgSender(), tokenId), 'Caller is not owner nor approved')


## Assessed type

Access Control
Cannot Transfer The Multiple Legs Token Position
Audit: 2023-11-panoptic-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `liquidityChunk` at [L586](https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L586-L591) of the `registerTokenTransfer()` is computed based on the position amount and each leg's option ratio. However, the `s_accountLiquidity` and `s_accountFeesBase` can be accumulated from the same `positionKey` hash. 

**As a result, the `require` statement at [L621](https://github.com/code-423n4/2023-11-panoptic/blob/main/contracts/SemiFungiblePositionManager.sol#L621) will consistently revert when transferring the multiple legs token.**

## Vulnerability Detail
The `liquidityChunk` at L586 within the `registerTokenTransfer()` function is calculated based on the position amount and the option ratio of each leg:
[PanopticMath::getLiquidityChunk()](https://gist.github.com/assets/32016865/16058ee8-4002-49b2-b090-e8c6751bde11)

However, the `s_accountLiquidity` and `s_accountFeesBase` can be accumulated from the same `positionKey` hash as it is computed based on the following properties
[positionKey hashing in the _createLegInAMM()](https://gist.github.com/assets/32016865/86d58fc2-8629-46de-819c-fa57fca46f39)

so the legs that have the same `_tokenType`, `_liquidityChunk` will return the same `positionKey` that used to track the `s_accountLiquidity` and `s_accountFeesBase`
[Updating the s_accountLiquidity](https://gist.github.com/assets/32016865/1fabe93f-4e42-4ba2-b369-f16948d769d7)

Consequently, it is inappropriate to validate the liquidity of each leg's liquidityChunk using the accumulated s_accountLiquidity, and this can lead to a transfer revert.

## Proof of Concept
- **Alice mints a position** with some `positionSize` amount. The `tokenId` contains two **same** `ShortPut` leg positions.
    - Leg properties
        - `Leg1`:
        ```solidity
        legIndex: 0, 
        _optionRatio: 1, //same as Leg 2
        _asset: 1, //same as Leg 2
        _isLong: 0, //same as Leg 2
        _tokenType: 1, //same as Leg 2
        _riskPartner: 0, 
        _strike: strike, //same as Leg 2
        _width: width //same as Leg 2
        ```
        - `Leg2`:
        ```solidity
        legIndex: 1,
        _optionRatio: 1, //same as Leg 1
        _asset: 1, //same as Leg 1
        _isLong: 0, //same as Leg 1
        _tokenType: 1, //same as Leg 1
        _riskPartner: 1,
        _strike: strike, //same as Leg 1
        _width: width //same as Leg 1
        ```
        
- During the minting process, Alice's `s_accountLiquidity` at the `positionKey` gets updated twice because the `positionKey` for both legs is the same.
    - To illustrate: Assuming `LiquidityChunk` for `Leg1` and `Leg2` is 100 (same tick boundary, same ratio), and starting liquidity in AMM is 0.
    - Loop Round 1 (`Leg1`)
    `s_accountLiquidity[positionKey].rightSlot() += 100 = 100`
    - Lo