HIGH severity: Approvals of Non-Fungible Liquidity tokens are not restricted only to VioletID holders. Approvals of Non-Fungible Liquidity tokens are not restricted only to VioletID holders.

Vulnerable code:
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.8.20;
pragma abicoder v2;

import '@cryptoalgebra/core/contracts/interfaces/IAlgebraPool.sol';
import '@cryptoalgebra/core/contracts/interfaces/IAlgebraFactory.sol';
import '@cryptoalgebra/core/contracts/libraries/Constants.sol';
import '@cryptoalgebra/core/contracts/libraries/FullMath.sol';

import './interfaces/INonfungiblePositionManager.sol';
import './interfaces/INonfungibleTokenPositionDescriptor.sol';
import './interfaces/IPositionFollower.sol';
import './libraries/PositionKey.sol';
import './libraries/PoolInteraction.sol';
import './libraries/PoolAddress.sol';
import './base/LiquidityManagement.sol';
import './base/PeripheryImmutableState.sol';
import './base/Multicall.sol';
import './base/ERC721Permit.sol';
import './base/PeripheryValidation.sol';
import './base/SelfPermit.sol';
import './base/PoolInitializer.sol';

/// @title NFT positions
/// @notice Wraps Algebra  positions in the ERC721 non-fungible token interface
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-periphery
contract NonfungiblePositionManager is
    INonfungiblePositionManager,
    Multicall,
    ERC721Permit,
    PeripheryImmutableState,
    PoolInitializer,
    LiquidityManagement,
    PeripheryValidation,
    SelfPermit
{
    using PoolInteraction for IAlgebraPool;

    // details about the Algebra position
    struct Position {
        uint88 nonce; // the nonce for permits
        address operator; // the address that is approved for spending this token
        uint80 poolId; // the ID of the pool with which this token is connected
        int24 tickLower; // the tick range of the position
        int24 tickUpper;
        uint128 liquidity; // the liquidity of the position
        uint256 feeGrowthInside0LastX128; // the fee growth of the aggregate position as of the last action on the individual position
        uint256 feeGrowthInside1LastX128;
        uint128 tokensOwed0; // how many uncollected tokens are owed to the position, as of the last computation
        uint128 tokensOwed1;
    }

    /// @dev IDs of pools assigned by this contract
    mapping(address => uint80) private _poolIds;

    /// @dev Pool keys by pool ID, to save on SSTOREs for position data
    mapping(uint80 => PoolAddress.PoolKey) private _poolIdToPoolKey;

    /// @dev The token ID position data
    mapping(uint256 => Position) private _positions;

    /// @dev The ID of the next token that will be minted. Skips 0
    uint176 private _nextId = 1;
    /// @dev The ID of the next pool that is used for the first time. Skips 0
    uint80 private _nextPoolId = 1;

    /// @dev The address of the token descriptor contract, which handles generating token URIs for position tokens
    address private immutable _tokenDescriptor;

    /// @dev The address of the farming center contract, which handles farmings logic
    address public farmingCenter;

    /// @dev mapping tokenId => farmingCenter
    mapping(uint256 => address) public farmingApprovals;

    /// @dev mapping tokenId => farmingCenter
    mapping(uint256 => address) public tokenFarmedIn;

    bytes32 public constant NONFUNGIBLE_POSITION_MANAGER_ADMINISTRATOR_ROLE =
        keccak256('NONFUNGIBLE_POSITION_MANAGER_ADMINISTRATOR_ROLE');

    constructor(
        address _factory,
        address _WNativeToken,
        address _tokenDescriptor_,
        address _poolDeployer
    )
        ERC721Permit('Algebra Positions NFT-V2', 'ALGB-POS', '2')
        PeripheryImmutableState(_factory, _WNativeToken, _poolDeployer)
    {
        _tokenDescriptor = _tokenDescriptor_;
    }

    /// @inheritdoc INonfungiblePositionManager
    function positions(
        uint256 tokenId
    )
        external
        view
        override
        returns (
            uint88 nonce,
            address operator,
            address token0,
            address token1,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        )
    {
        Position memory position = _positions[tokenId];
        require(position.poolId != 0, 'Invalid token ID');
        PoolAddress.PoolKey storage poolKey = _poolIdToPoolKey[position.poolId];
        return (
            position.nonce,
            position.operator,
            poolKey.token0,
            poolKey.token1,
            position.tickLower,
            position.tickUpper,
            position.liquidity,
            position.feeGrowthInside0LastX128,
            position.feeGrowthInside1LastX128,
            position.tokensOwed0,
            position.tokensOwed1
        );
    }

    /// @dev Caches a pool key
    function cachePoolKey(address pool, PoolAddress.PoolKey memory poolKey) private returns (uint80 poolId) {
        if ((poolId = _poolIds[pool]) == 0) {
            unchecked {
                _poolIds[pool] = (poolId = _nextPoolId++);
            }
            _poolIdToPoolKey[poolId] = poolKey;
        }
    }

    function getPoolById(uint80 poolId) private view returns (address) {
        return PoolAddress.computeAddress(poolDeployer, _poolIdToPoolKey[poolId]);
    }

    /// @inheritdoc INonfungiblePositionManager
    function mint(
        MintParams calldata params
    )
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)
    {
        IAlgebraPool pool;
        uint256 actualLiquidity;
        (liquidity, actualLiquidity, amount0, amount1, pool) = addLiquidity(
            AddLiquidityParams({
                token0: params.token0,
                token1: params.token1,
                recipient: address(this),
                tickLower: params.tickLower,
                tickUpper: params.tickUpper,
                amount0Desired: params.amount0Desired,
                amount1Desired: params.amount1Desired,
                amount0Min: params.amount0Min,
                amount1Min: params.amount1Min
            })
        );
        unchecked {
            _mint(params.recipient, (tokenId = _nextId++));
        }

        (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool._getPositionInPool(
            address(this),
            params.tickLower,
            params.tickUpper
        );

        // idempotent set
        uint80 poolId = cachePoolKey(
            address(pool),
            PoolAddress.PoolKey({token0: params.token0, token1: params.token1})
        );

        _positions[tokenId] = Position({
            nonce: 0,
            operator: address(0),
            poolId: poolId,
            tickLower: params.tickLower,
            tickUpper: params.tickUpper,
            liquidity: uint128(actualLiquidity),
            feeGrowthInside0LastX128: feeGrowthInside0LastX128,
            feeGrowthInside1LastX128: feeGrowthInside1LastX128,
            tokensOwed0: 0,
            tokensOwed1: 0
        });

        emit IncreaseLiquidity(tokenId, liquidity, uint128(actualLiquidity), amount0, amount1, address(pool));
    }

    modifier isAuthorizedForToken(uint256 tokenId) {
        _checkAuthorizationForToken(tokenId);
        _;
    }

    function _checkAuthorizationForToken(uint256 tokenId) private view {
        require(_isApprovedOrOwner(msg.sender, tokenId), 'Not approved');
    }

    function tokenURI(uint256 tokenId) public view override(ERC721, IERC721Metadata) returns (string memory) {
        require(_exists(tokenId));
        return INonfungibleTokenPositionDescriptor(_tokenDescriptor).tokenURI(this, tokenId);
    }

    // save bytecode by removing implementation of unused method
    function baseURI() public pure returns (string memory) {}

    function _updateUncollectedFees(
        Position storage position,
        IAlgebraPool pool,
        address owner,
        int24 tickLower,
        int24 tickUpper,
        uint128 positionLiquidity
    ) private returns (uint128 tokensOwed0, uint128 tokensOwed1) {
        (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool._getPositionInPool(
            owner,
            tickLower,
            tickUpper
        );
        unchecked {
            tokensOwed0 = uint128(
                FullMath.mulDiv(
                    feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
                    positionLiquidity,
                    Constants.Q128
                )
            );
            tokensOwed1 = uint128(
                FullMath.mulDiv(
                    feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
                    positionLiquidity,
                    Constants.Q128
                )
            );
        }

        position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
        position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
    }

    /// @inheritdoc INonfungiblePositionManager
    function increaseLiquidity(
        IncreaseLiquidityParams calldata params
    )
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (uint128 liquidity, uint256 amount0, uint256 amount1)
    {
        Position storage position = _positions[params.tokenId];

        PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];

        IAlgebraPool pool;
        uint256 actualLiquidity;
        (liquidity, actualLiquidity, amount0, amount1, pool) = addLiquidity(
            AddLiquidityParams({
                token0: poolKey.token0,
                token1: poolKey.token1,
                tickLower: position.tickLower,
                tickUpper: position.tickUpper,
                amount0Desired: params.amount0Desired,
                amount1Desired: params.amount1Desired,
                amount0Min: params.amount0Min,
                amount1Min: params.amount1Min,
                recipient: address(this)
            })
        );

        // this is now updated to the current transaction
        uint128 positionLiquidity = position.liquidity;
        (uint128 tokensOwed0, uint128 tokensOwed1) = _updateUncollectedFees(
            position,
            pool,
            address(this),
            position.tickLower,
            position.tickUpper,
            positionLiquidity
        );

        unchecked {
            position.tokensOwed0 += tokensOwed0;
            position.tokensOwed1 += tokensOwed1;
            position.liquidity = positionLiquidity + uint128(actualLiquidity);
        }

        _applyLiquidityDeltaInFarming(params.tokenId, int256(actualLiquidity));

        emit IncreaseLiquidity(params.tokenId, liquidity, uint128(actualLiquidity), amount0, amount1, address(pool));
    }

    /// @inheritdoc INonfungiblePositionManager
    function decreaseLiquidity(
        DecreaseLiquidityParams calldata params
    )
        external
        payable
        override
        isAuthorizedForToken(params.tokenId)
        checkDeadline(params.deadline)
        returns (uint256 amount0, uint256 amount1)
    {
        require(params.liquidity > 0);
        Position storage position = _positions[params.tokenId];

        (uint80 poolId, int24 tickLower, int24 tickUpper, uint128 positionLiquidity) = (
            position.poolId,
            position.tickLower,
            position.tickUpper,
            position.liquidity
        );
        require(positionLiquidity >= params.liquidity);

        IAlgebraPool pool = IAlgebraPool(getPoolById(poolId));
        (amount0, amount1) = pool._burnPositionInPool(tickLower, tickUpper, params.liquidity);

        require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check');

        // scope to prevent stack-too-deep
        {
            // this is now updated to the current transaction
            (uint128 tokensOwed0, uint128 tokensOwed1) = _updateUncollectedFees(
                position,
                pool,
                address(this),
                tickLower,
                tickUpper,
                positionLiquidity
            );

            unchecked {
                position.tokensOwed0 += uint128(amount0) + tokensOwed0;
                position.tokensOwed1 += uint128(amount1) + tokensOwed1;

                // subtraction is safe because we checked positionLiquidity is gte params.liquidity
                position.liquidity = positionLiquidity - params.liquidity;
            }
        }

        _applyLiquidityDeltaInFarming(params.tokenId, -int256(uint256(params.liquidity)));

        emit DecreaseLiquidity(params.tokenId, params.liquidity, amount0, amount1);
    }

    /// @inheritdoc INonfungiblePositionManager
    function collect(
        CollectParams calldata params
    ) external payable override isAuthorizedForToken(params.tokenId) returns (uint256 amount0, uint256 amount1) {
        require(params.amount0Max > 0 || params.amount1Max > 0);
        // allow collecting to the nft position manager address with address 0
        address recipient = params.recipient == address(0) ? address(this) : params.recipient;

        Position storage position = _positions[params.tokenId];
        IAlgebraPool pool = IAlgebraPool(getPoolById(position.poolId));

        (uint128 tokensOwed0, uint128 tokensOwed1) = (position.tokensOwed0, position.tokensOwed1);

        // trigger an update of the position fees owed and fee growth snapshots if it has any liquidity
        (int24 tickLower, int24 tickUpper, uint128 positionLiquidity) = (
            position.tickLower,
            position.tickUpper,
            position.liquidity
        );
        if (positionLiquidity > 0) {
            pool._burnPositionInPool(tickLower, tickUpper, 0);
            (uint128 _tokensOwed0, uint128 _tokensOwed1) = _updateUncollectedFees(
                position,
                pool,
                address(this),
                tickLower,
                tickUpper,
                positionLiquidity
            );

            unchecked {
                tokensOwed0 += _tokensOwed0;
                tokensOwed1 += _tokensOwed1;
            }
        }

        // compute the arguments to give to the pool#collect method
        (uint128 amount0Collect, uint128 amount1Collect) = (
            params.amount0Max > tokensOwed0 ? tokensOwed0 : params.amount0Max,
            params.amount1Max > tokensOwed1 ? tokensOwed1 : params.amount1Max
        );

        // the actual amounts collected are returned
        (amount0, amount1) = pool.collect(recipient, tickLower, tickUpper, amount0Collect, amount1Collect);

        // sometimes there will be a few less wei than expected due to rounding down in core, but we just subtract the full amount expected
        // instead of the actual amount so we can burn the token
        unchecked {
            (position.tokensOwed0, position.tokensOwed1) = (tokensOwed0 - amount0Collect, tokensOwed1 - amount1Collect);
        }

        emit Collect(params.tokenId, recipient, amount0Collect, amount1Collect);
    }

    /// @inheritdoc INonfungiblePositionManager
    function burn(uint256 tokenId) external payable override isAuthorizedForToken(tokenId) {
        Position storage position = _positions[tokenId];
        require(position.liquidity | position.tokensOwed0 | position.tokensOwed1 == 0, 'Not cleared');

        delete _positions[tokenId];
        delete farmingApprovals[tokenId];
        delete tokenFarmedIn[tokenId];
        _burn(tokenId);
    }

    /// @inheritdoc INonfungiblePositionManager
    function approveForFarming(uint256 tokenId, bool approve) external payable override isAuthorizedForToken(tokenId) {
        farmingApprovals[tokenId] = approve ? farmingCenter : address(0);
    }

    /// @inheritdoc INonfungiblePositionManager
    function switchFarmingStatus(uint256 tokenId, bool toFarming) external override {
        address _farmingCenter = farmingCenter;
        bool accessAllowed = msg.sender == _farmingCenter;
        if (toFarming) {
            require(farmingApprovals[tokenId] == _farmingCenter, 'not approved for farming');
        } else {
            accessAllowed = accessAllowed || msg.sender == tokenFarmedIn[tokenId];
        }
        require(accessAllowed, 'only FarmingCenter');
        tokenFarmedIn[tokenId] = toFarming ? _farmingCenter : address(0);
    }

    /// @inheritdoc INonfungiblePositionManager
    function setFarmingCenter(address newFarmingCenter) external override {
        require(IAlgebraFactory(factory).hasRoleOrOwner(NONFUNGIBLE_POSITION_MANAGER_ADMINISTRATOR_ROLE, msg.sender));
        farmingCenter = newFarmingCenter;
    }

    /// @inheritdoc IERC721
    function getApproved(uint256 tokenId) public view override(ERC721, IERC721) returns (address) {
        require(_exists(tokenId), 'ERC721: approved query for nonexistent token');

        return _positions[tokenId].operator;
    }

    function _applyLiquidityDeltaInFarming(uint256 tokenId, int256 liquidityDelta) private {
        address _farmingCenter = farmingCenter;
        if (_farmingCenter == address(0)) return;

        if (tokenFarmedIn[tokenId] == _farmingCenter) {
            // errors without message will be propagated
            try IPositionFollower(_farmingCenter).applyLiquidityDelta(tokenId, liquidityDelta) {
                // do nothing
            } catch Panic(uint256) {
                emit FarmingFailed(tokenId);
            } catch Error(string memory) {
                emit FarmingFailed(tokenId);
            }
        }
    }

    function _getAndIncrementNonce(uint256 tokenId) internal override returns (uint256) {
        unchecked {
            return uint256(_positions[tokenId].nonce++);
        }
    }

    /// @dev Overrides _transfer to clear farming approval
    function _transfer(address from, address to, uint256 tokenId) internal override {
        delete farmingApprovals[tokenId];
        super._transfer(from, to, tokenId);
    }

    /// @dev Overrides _approve to use the operator in the position, which is packed with the position permit nonce
    function _approve(address to, uint256 tokenId) internal override(ERC721) {
        _positions[tokenId].operator = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
    }
}


Vulnerability type: CWE-284