HIGH severity: Missing access control on unlockCallback(). The unlockCallback() functions in LikwidLendPosition.sol, LikwidMarginPosition.sol, and LikwidPairPosition.sol lack access control, allowing any external caller to invoke them with arbitrary data. This occurs because the contracts do not inherit from Uniswap v4's SafeCallback.sol, which is designed...

Vulnerable code:
// SPDX-License-Identifier: BUSL-1.1
// Likwid Contracts
pragma solidity ^0.8.26;

// Local
import {Math} from "./libraries/Math.sol";
import {BasePositionManager} from "./base/BasePositionManager.sol";
import {PoolKey} from "./types/PoolKey.sol";
import {PoolId} from "./types/PoolId.sol";
import {Currency} from "./types/Currency.sol";
import {BalanceDelta} from "./types/BalanceDelta.sol";
import {IVault} from "./interfaces/IVault.sol";
import {ILendPositionManager} from "./interfaces/ILendPositionManager.sol";
import {LendPosition} from "./libraries/LendPosition.sol";
import {StateLibrary} from "./libraries/StateLibrary.sol";
import {CustomRevert} from "./libraries/CustomRevert.sol";
import {CurrencyPoolLibrary} from "./libraries/CurrencyPoolLibrary.sol";
import {SafeCast} from "./libraries/SafeCast.sol";

contract LikwidLendPosition is ILendPositionManager, BasePositionManager {
    using CurrencyPoolLibrary for Currency;
    using CustomRevert for bytes4;
    using SafeCast for *;

    mapping(uint256 tokenId => bool lendForOne) public lendDirections;

    constructor(address initialOwner, IVault _vault)
        BasePositionManager("LIKWIDLendPositionManager", "LLPM", initialOwner, _vault)
    {}

    enum Actions {
        DEPOSIT,
        WITHDRAW,
        SWAP
    }

    function unlockCallback(bytes calldata data) external override returns (bytes memory) {
        (Actions action, bytes memory params) = abi.decode(data, (Actions, bytes));

        if (action == Actions.DEPOSIT) {
            return handleLend(params);
        } else if (action == Actions.WITHDRAW) {
            return handleLend(params);
        } else if (action == Actions.SWAP) {
            return handleSwap(params);
        } else {
            InvalidCallback.selector.revertWith();
        }
    }

    /// @inheritdoc ILendPositionManager
    function getPositionState(uint256 tokenId) external view returns (LendPosition.State memory position) {
        bytes32 salt = bytes32(tokenId);
        PoolId poolId = poolIds[tokenId];
        bool lendForOne = lendDirections[tokenId];
        (,, uint256 deposit0CumulativeLast, uint256 deposit1CumulativeLast) =
            StateLibrary.getBorrowDepositCumulative(vault, poolId);
        uint256 depositCumulativeLast = lendForOne ? deposit1CumulativeLast : deposit0CumulativeLast;
        position = StateLibrary.getLendPositionState(vault, poolId, address(this), lendForOne, salt);
        position.lendAmount = Math.mulDiv(
            position.lendAmount,
            depositCumulativeLast,
            position.depositCumulativeLast == 0 ? depositCumulativeLast : position.depositCumulativeLast
        ).toUint128();
        position.depositCumulativeLast = depositCumulativeLast;
    }

    /// @inheritdoc ILendPositionManager
    function addLending(PoolKey memory key, bool lendForOne, address recipient, uint256 amount)
        external
        payable
        returns (uint256 tokenId)
    {
        tokenId = _mintPosition(key, recipient);
        lendDirections[tokenId] = lendForOne;
        if (amount > 0) {
            _deposit(msg.sender, recipient, tokenId, amount);
        }
    }

    function _deposit(address sender, address tokenOwner, uint256 tokenId, uint256 amount) internal {
        _requireAuth(tokenOwner, tokenId);
        PoolId poolId = poolIds[tokenId];
        bool lendForOne = lendDirections[tokenId];
        PoolKey memory key = poolKeys[poolId];

        IVault.LendParams memory params =
            IVault.LendParams({lendForOne: lendForOne, lendAmount: -amount.toInt128(), salt: bytes32(tokenId)});

        bytes memory callbackData = abi.encode(sender, key, params);
        bytes memory data = abi.encode(Actions.DEPOSIT, callbackData);

        vault.unlock(data);
        Currency currency = lendForOne ? key.currency1 : key.currency0;
        emit Deposit(poolId, currency, sender, tokenId, tokenOwner, amount);
    }

    /// @inheritdoc ILendPositionManager
    function deposit(uint256 tokenId, uint256 amount) external payable {
        _deposit(msg.sender, msg.sender, tokenId, amount);
    }

    /// @inheritdoc ILendPositionManager
    function withdraw(uint256 tokenId, uint256 amount) external {
        _requireAuth(msg.sender, tokenId);
        PoolId poolId = poolIds[tokenId];
        bool lendForOne = lendDirections[tokenId];
        PoolKey memory key = poolKeys[poolId];

        IVault.LendParams memory params =
            IVault.LendParams({lendForOne: lendForOne, lendAmount: amount.toInt128(), salt: bytes32(tokenId)});

        bytes memory callbackData = abi.encode(msg.sender, key, params);
        bytes memory data = abi.encode(Actions.WITHDRAW, callbackData);

        vault.unlock(data);
        Currency currency = lendForOne ? key.currency1 : key.currency0;
        emit Withdraw(poolId, currency, msg.sender, tokenId, msg.sender, amount);
    }

    function handleLend(bytes memory _data) internal returns (bytes memory) {
        (address sender, PoolKey memory key, IVault.LendParams memory params) =
            abi.decode(_data, (address, PoolKey, IVault.LendParams));

        BalanceDelta delta = vault.lend(key, params);

        (uint256 amount0, uint256 amount1) = _processDelta(sender, key, delta, 0, 0, 0, 0);

        return abi.encode(amount0, amount1);
    }

    /// @inheritdoc ILendPositionManager
    function exactInput(SwapInputParams calldata params)
        external
        payable
        ensure(params.deadline)
        returns (uint24 swapFee, uint256 feeAmount, uint256 amountOut)
    {
        _requireAuth(msg.sender, params.tokenId);
        PoolKey memory key = poolKeys[params.poolId];
        if (params.zeroForOne != lendDirections[params.tokenId]) {
            InvalidCurrency.selector.revertWith();
        }
        int256 amountSpecified = -int256(params.amountIn);
        IVault.SwapParams memory swapParams = IVault.SwapParams({
            zeroForOne: params.zeroForOne,
            amountSpecified: amountSpecified,
            useMirror: true,
            salt: bytes32(params.tokenId)
        });
        uint256 amount0Min = params.zeroForOne ? 0 : params.amountOutMin;
        uint256 amount1Min = params.zeroForOne ? params.amountOutMin : 0;
        bytes memory callbackData = abi.encode(msg.sender, key, swapParams, amount0Min, amount1Min, 0, 0);
        bytes memory data = abi.encode(Actions.SWAP, callbackData);

        bytes memory result = vault.unlock(data);
        uint256 amount0;
        uint256 amount1;
        (swapFee, feeAmount, amount0, amount1) = abi.decode(result, (uint24, uint256, uint256, uint256));
        amountOut = params.zeroForOne ? amount1 : amount0;
    }

    /// @inheritdoc ILendPositionManager
    function exactOutput(SwapOutputParams calldata params)
        external
        payable
        ensure(params.deadline)
        returns (uint24 swapFee, uint256 feeAmount, uint256 amountIn)
    {
        _requireAuth(msg.sender, params.tokenId);
        PoolKey memory key = poolKeys[params.poolId];
        if (params.zeroForOne != lendDirections[params.tokenId]) {
            InvalidCurrency.selector.revertWith();
        }
        int256 amountSpecified = int256(params.amountOut);
        IVault.SwapParams memory swapParams = IVault.SwapParams({
            zeroForOne: params.zeroForOne,
            amountSpecified: amountSpecified,
            useMirror: true,
            salt: bytes32(params.tokenId)
        });
        uint256 amount0Max = params.zeroForOne ? params.amountInMax : 0;
        uint256 amount1Max = params.zeroForOne ? 0 : params.amountInMax;
        bytes memory callbackData = abi.encode(msg.sender, key, swapParams, 0, 0, amount0Max, amount1Max);
        bytes memory data = abi.encode(Actions.SWAP, callbackData);

        bytes memory result = vault.unlock(data);
        uint256 amount0;
        uint256 amount1;
        (swapFee, feeAmount, amount0, amount1) = abi.decode(result, (uint24, uint256, uint256, uint256));
        amountIn = params.zeroForOne ? amount0 : amount1;
    }

    function handleSwap(bytes memory _data) internal returns (bytes memory) {
        (
            address sender,
            PoolKey memory key,
            IVault.SwapParams memory params,
            uint256 amount0Min,
            uint256 amount1Min,
            uint256 amount0Max,
            uint256 amount1Max
        ) = abi.decode(_data, (address, PoolKey, IVault.SwapParams, uint256, uint256, uint256, uint256));

        (BalanceDelta delta, uint24 swapFee, uint256 feeAmount) = vault.swap(key, params);

        uint256 amount0;
        uint256 amount1;

        if (params.zeroForOne) {
            if (delta.amount0() < 0) {
                amount0 = uint256(-int256(delta.amount0()));
                if ((amount0Min > 0 && amount0 < amount0Min) || (amount0Max > 0 && amount0 > amount0Max)) {
                    PriceSlippageTooHigh.selector.revertWith();
                }
                key.currency0.settle(vault, sender, amount0, false);
            } else if (delta.amount0() > 0) {
                amount0 = uint256(int256(delta.amount0()));
                if ((amount0Min > 0 && amount0 < amount0Min) || (amount0Max > 0 && amount0 > amount0Max)) {
                    PriceSlippageTooHigh.selector.revertWith();
                }
                key.currency0.take(vault, sender, amount0, false);
            }
            if (delta.amount1() < 0) {
                amount1 = uint256(-int256(delta.amount1()));
                if ((amount1Min > 0 && amount1 < amount1Min) || (amount1Max > 0 && amount1 > amount1Max)) {
                    PriceSlippageTooHigh.selector.revertWith();
                }
            } else if (delta.amount1() > 0) {
                amount1 = uint256(int256(delta.amount1()));
                if ((amount1Min > 0 && amount1 < amount1Min) || (amount1Max > 0 && amount1 > amount1Max)) {
                    PriceSlippageTooHigh.selector.revertWith();
                }
            }
        } else {
            if (delta.amount0() < 0) {
                amount0 = uint256(-int256(delta.amount0()));
                if ((amount0Min > 0 && amount0 < amount0Min) || (amount0Max > 0 && amount0 > amount0Max)) {
                    PriceSlippageTooHigh.selector.revertWith();
                }
            } else if (delta.amount0() > 0) {
                amount0 = uint256(int256(delta.amount0()));
                if ((amount0Min > 0 && amount0 < amount0Min) || (amount0Max > 0 && amount0 > amount0Max)) {
                    PriceSlippageTooHigh.selector.revertWith();
                }
            }
            if (delta.amount1() < 0) {
                amount1 = uint256(-int256(delta.amount1()));
                if ((amount1Min > 0 && amount1 < amount1Min) || (amount1Max > 0 && amount1 > amount1Max)) {
                    PriceSlippageTooHigh.selector.revertWith();
                }
                key.currency1.settle(vault, sender, amount1, false);
            } else if (delta.amount1() > 0) {
                amount1 = uint256(int256(delta.amount1()));
                if ((amount1Min > 0 && amount1 < amount1Min) || (amount1Max > 0 && amount1 > amount1Max)) {
                    PriceSlippageTooHigh.selector.revertWith();
                }
                key.currency1.take(vault, sender, amount1, false);
            }
        }
        _clearNative(sender);

        return abi.encode(swapFee, feeAmount, amount0, amount1);
    }
}

// SPDX-License-Identifier: BUSL-1.1
// Likwid Contracts
pragma solidity ^0.8.26;

// Local
import {BasePositionManager} from "./base/BasePositionManager.sol";
import {IMarginPositionManager} from "./interfaces/IMarginPositionManager.sol";
import {IProtocolFees} from "./interfaces/IProtocolFees.sol";
import {IVault} from "./interfaces/IVault.sol";
import {CurrencyPoolLibrary} from "./libraries/CurrencyPoolLibrary.sol";
import {CustomRevert} from "./libraries/CustomRevert.sol";
import {FeeLibrary} from "./libraries/FeeLibrary.sol";
import {MarginPosition} from "./libraries/MarginPosition.sol";
import {Math} from "./libraries/Math.sol";
import {PerLibrary} from "./libraries/PerLibrary.sol";
import {PositionLibrary} from "./libraries/PositionLibrary.sol";
import {SafeCast} from "./libraries/SafeCast.sol";
import {StateLibrary} from "./libraries/StateLibrary.sol";
import {TimeLibrary} from "./libraries/TimeLibrary.sol";
import {SwapMath} from "./libraries/SwapMath.sol";
import {MarginActions} from "./types/MarginActions.sol";
import {BalanceDelta, toBalanceDelta} from "./types/BalanceDelta.sol";
import {Currency, CurrencyLibrary} from "./types/Currency.sol";
import {MarginLevels, MarginLevelsLibrary} from "./types/MarginLevels.sol";
import {PoolId} from "./types/PoolId.sol";
import {PoolKey} from "./types/PoolKey.sol";
import {Reserves} from "./types/Reserves.sol";
import {PoolState} from "./types/PoolState.sol";
import {MarginBalanceDelta} from "./types/MarginBalanceDelta.sol";
// Solmate

contract LikwidMarginPosition is IMarginPositionManager, BasePositionManager {
    using SafeCast for *;
    using CurrencyLibrary for Currency;
    using CurrencyPoolLibrary for Currency;
    using PerLibrary for uint256;
    using FeeLibrary for uint24;
    using CustomRevert for bytes4;
    using PositionLibrary for address;
    using MarginLevelsLibrary for MarginLevels;
    using TimeLibrary for uint32;
    using MarginPosition for MarginPosition.State;

    uint8 constant MAX_LEVERAGE = 5; // 5x

    mapping(uint256 tokenId => MarginPosition.State positionInfo) private positionInfos;
    MarginLevels public marginLevels;
    uint24 public defaultMarginFee = 3000; // 0.3%

    constructor(address initialOwner, IVault _vault)
        BasePositionManager("LIKWIDMarginPositionManager", "LMPM", initialOwner, _vault)
    {
        MarginLevels _marginLevels;
        _marginLevels = _marginLevels.setMinMarginLevel(1170000);
        _marginLevels = _marginLevels.setMinBorrowLevel(1400000);
        _marginLevels = _marginLevels.setLiquidateLevel(1100000);
        _marginLevels = _marginLevels.setLiquidationRatio(950000);
        _marginLevels = _marginLevels.setCallerProfit(10000);
        _marginLevels = _marginLevels.setProtocolProfit(5000);
        marginLevels = _marginLevels;
    }

    /// @notice Callback function for the vault to execute margin-related actions.
    /// @param data The encoded action and parameters.
    /// @return The result of the action.
    function unlockCallback(bytes calldata data) external override returns (bytes memory) {
        (MarginActions action, bytes memory params) = abi.decode(data, (MarginActions, bytes));

        if (action == MarginActions.LIQUIDATE_BURN) {
            return handleLiquidateBurn(params);
        } else {
            return handleMargin(params);
        }
    }

    function _getPoolState(PoolId poolId) internal view returns (PoolState memory state) {
        state = StateLibrary.getCurrentState(vault, poolId);
    }

    /// @dev Gets the last cumulative borrow and deposit values for a given position.
    /// @param poolState The current state of the pool.
    /// @param marginForOne Whether the margin is on token1.
    /// @return borrowCumulativeLast The last cumulative borrow value.
    /// @return depositCumulativeLast The last cumulative deposit value.
    function _getPoolCumulativeValues(PoolState memory poolState, bool marginForOne)
        private
        pure
        returns (uint256 borrowCumulativeLast, uint256 depositCumulativeLast)
    {
        if (marginForOne) {
            borrowCumulativeLast = poolState.borrow0CumulativeLast;
            depositCumulativeLast = poolState.deposit1CumulativeLast;
        } else {
            borrowCumulativeLast = poolState.borrow1CumulativeLast;
            depositCumulativeLast = poolState.deposit0CumulativeLast;
        }
    }

    function getPositionState(uint256 tokenId) external view returns (MarginPosition.State memory position) {
        PoolId poolId = poolIds[tokenId];
        position = positionInfos[tokenId];
        PoolState memory state = _getPoolState(poolId);
        (uint256 borrowCumulativeLast, uint256 depositCumulativeLast) =
            _getPoolCumulativeValues(state, position.marginForOne);

        position.marginAmount =
            Math.mulDiv(position.marginAmount, depositCumulativeLast, position.depositCumulativeLast).toUint128();
        position.marginTotal =
            Math.mulDiv(position.marginTotal, depositCumulativeLast, position.depositCumulativeLast).toUint128();
        position.debtAmount =
            Math.mulDiv(position.debtAmount, borrowCumulativeLast, position.borrowCumulativeLast).toUint128();

        position.depositCumulativeLast = depositCumulativeLast;
        position.borrowCumulativeLast = borrowCumulativeLast;
    }

    function checkLiquidate(uint256 tokenId)
        external
        view
        returns (bool liquidated, uint256 marginAmount, uint256 marginTotal, uint256 debtAmount)
    {
        PoolId poolId = poolIds[tokenId];
        MarginPosition.State memory position = positionInfos[tokenId];
        PoolState memory state = _getPoolState(poolId);
        (liquidated, marginAmount, marginTotal, debtAmount) = _checkLiquidate(state, position);
    }

    function _checkLiquidate(PoolState memory state, MarginPosition.State memory position)
        internal
        view
        returns (bool liquidated, uint256 marginAmount, uint256 marginTotal, uint256 debtAmount)
    {
        (uint256 borrowCumulativeLast, uint256 depositCumulativeLast) =
            _getPoolCumulativeValues(state, position.marginForOne);
        MarginLevels _marginLevels = marginLevels;
        // use truncatedReserves
        uint256 level = position.marginLevel(state.truncatedReserves, borrowCumulativeLast, depositCumulativeLast);
        liquidated = level < _marginLevels.liquidateLevel();
        if (liquidated) {
            marginAmount = Math.mulDiv(position.marginAmount, depositCumulativeLast, position.depositCumulativeLast);
            marginTotal = Math.mulDiv(position.marginTotal, depositCumulativeLast, position.depositCumulativeLast);
            debtAmount = Math.mulDiv(position.debtAmount, borrowCumulativeLast, position.borrowCumulativeLast);
        }
    }

    function _checkMinLevel(
        Reserves pairReserves,
        uint256 borrowCumulativeLast,
        uint256 depositCumulativeLast,
        MarginPosition.State memory position,
        uint256 minLevel
    ) internal pure {
        uint256 level = position.marginLevel(pairReserves, borrowCumulativeLast, depositCumulativeLast);
        if (level < minLevel) {
            InvalidLevel.selector.revertWith();
        }
    }

    function _processLost(PoolState memory state, MarginPosition.State memory position, uint256 lostAmount)
        internal
        pure
        returns (uint256 lendLostAmount, uint256 debtDepositCumulativeLast)
    {
        debtDepositCumulativeLast = position.marginForOne ? state.deposit0CumulativeLast : state.deposit1CumulativeLast;
        if (lostAmount > 0) {
            (uint128 pairReserve0, uint128 pairReserve1) = state.pairReserves.reserves();
            (uint128 lendReserve0, uint128 lendReserve1) = state.lendReserves.reserves();
            uint256 pairReserve = position.marginForOne ? pairReserve0 : pairReserve1;
            uint256 lendReserve = position.marginForOne ? lendReserve0 : lendReserve1;
            lendLostAmount = Math.mulDiv(lostAmount, lendReserve, pairReserve + lendReserve);
            if (lendReserve > 0) {
                debtDepositCumulativeLast =
                    Math.mulDiv(debtDepositCumulativeLast, lendReserve - lendLostAmount, lendReserve);
            }
        }
    }

    /// @inheritdoc IMarginPositionManager
    function addMargin(PoolKey memory key, IMarginPositionManager.CreateParams calldata params)
        external
        payable
        ensure(params.deadline)
        returns (uint256 tokenId, uint256 borrowAmount, uint256 swapFeeAmount)
    {
        tokenId = _mintPosition(key, params.recipient);
        positionInfos[tokenId].marginForOne = params.marginForOne;
        (borrowAmount, swapFeeAmount) = _margin(
            msg.sender,
            params.recipient,
            IMarginPositionManager.MarginParams({
                tokenId: tokenId,
                leverage: params.leverage,
                marginAmount: params.marginAmount,
                borrowAmount: params.borrowAmount,
                borrowAmountMax: params.borrowAmountMax,
                deadline: params.deadline
            })
        );
    }

    function _margin(address sender, address tokenOwner, IMarginPositionManager.MarginParams memory params)
        internal
        returns (uint256 borrowAmount, uint256 swapFeeAmount)
    {
        _requireAuth(tokenOwner, params.tokenId);
        PoolId poolId = poolIds[params.tokenId];
        PoolState memory poolState = _getPoolState(poolId);
        if (poolState.lpFee < 3000) revert LowFeePoolMarginBanned();
        PoolKey memory key = poolKeys[poolId];
        MarginPosition.State storage position = positionInfos[params.tokenId];

        (uint256 borrowCumulativeLast, uint256 depositCumulativeLast) =
            _getPoolCumulativeValues(poolState, position.marginForOne);

        MarginBalanceDelta memory delta;
        delta.action = MarginActions.MARGIN;
        delta.marginForOne = position.marginForOne;
        uint256 minLevel;
        if (params.leverage > 0) {
            minLevel = marginLevels.minMarginLevel();
            (borrowAmount, swapFeeAmount) = _executeAddLeverage(params, poolState, position, delta);
        } else {
            minLevel = marginLevels.minBorrowLevel();
            borrowAmount = _executeAddCollateralAndBorrow(params, poolState, position, delta);
        }
        delta.swapFeeAmount = swapFeeAmount;
        _checkMinLevel(poolState.pairReserves, borrowCumulativeLast, depositCumulativeLast, position, minLevel);
        bytes memory callbackData = abi.encode(sender, key, delta);
        bytes memory data = abi.encode(delta.action, callbackData);

        vault.unlock(data);
        emit Margin(
            key.toId(),
            sender,
            params.tokenId,
            position.marginAmount,
            position.marginTotal,
            position.debtAmount,
            position.marginForOne
        );
    }

    /// @inheritdoc IMarginPositionManager
    function margin(IMarginPositionManager.MarginParams memory params)
        external
        payable
        ensure(params.deadline)
        returns (uint256 borrowAmount, uint256 swapFeeAmount)
    {
        (borrowAmount, swapFeeAmount) = _margin(msg.sender, msg.sender, params);
    }

    function _executeAddLeverage(
        IMarginPositionManager.MarginParams memory params,
        PoolState memory poolState,
        MarginPosition.State storage position,
        MarginBalanceDelta memory delta
    ) internal returns (uint256 borrowAmount, uint256 swapFeeAmount) {
        uint256 borrowMirrorReserves = poolState.mirrorReserves.reserve01(!position.marginForOne);
        uint256 borrowRealReserves = poolState.realReserves.reserve01(!position.marginForOne);
        if (Math.mulDiv(borrowMirrorReserves, 100, borrowRealReserves + borrowMirrorReserves) > 90) {
            MirrorTooMuch.selector.revertWith();
        }

        uint256 marginReserves = poolState.realReserves.reserve01(position.marginForOne);
        uint256 marginTotal = params.marginAmount * params.leverage;
        if (marginTotal > marginReserves) ReservesNotEnough.selector.revertWith();

        delta.marginTotal = marginTotal.toUint128();
        delta.marginFee = poolState.marginFee == 0 ? defaultMarginFee : poolState.marginFee;
        (uint256 marginWithoutFee,) = delta.marginFee.deduct(marginTotal);
        (borrowAmount,, swapFeeAmount) = SwapMath.getAmountIn(
            poolState.pairReserves, poolState.truncatedReserves, poolState.lpFee, position.marginForOne, marginTotal
        );
        params.borrowAmount = borrowAmount.toUint128();

        uint256 borrowCumulativeLast;
        uint256 depositCumulativeLast;
        if (position.marginForOne) {
            borrowCumulativeLast = poolState.borrow0CumulativeLast;
            depositCumulativeLast = poolState.deposit1CumulativeLast;
        } else {
            borrowCumulativeLast = poolState.borrow1CumulativeLast;
            depositCumulativeLast = poolState.deposit0CumulativeLast;
        }

        position.update(
            borrowCumulativeLast,
            depositCumulativeLast,
            params.marginAmount.toInt128(),
            marginWithoutFee,
            params.borrowAmount,
            0
        );

        int128 amount0Delta;
        int128 amount1Delta;
        int128 amount = -params.marginAmount.toInt128();
        int128 lendAmount = amount - marginWithoutFee.toInt128();

        if (position.marginForOne) {
            amount1Delta = amount;
            delta.pairDelta = toBalanceDelta(-borrowAmount.toInt128(), marginWithoutFee.toInt128());
            delta.lendDelta = toBalanceDelta(0, lendAmount);
            delta.mirrorDelta = toBalanceDelta(-borrowAmount.toInt128(), 0);
        } else {
            amount0Delta = amount;
            delta.pairDelta = toBalanceDelta(marginWithoutFee.toInt128(), -borrowAmount.toInt128());
            delta.lendDelta = toBalanceDelta(lendAmount, 0);
            delta.mirrorDelta = toBalanceDelta(0, -borrowAmount.toInt128());
        }
        delta.marginDelta = toBalanceDelta(amount0Delta, amount1Delta);
    }

    function _executeAddCollateralAndBorrow(
        IMarginPositionManager.MarginParams memory params,
        PoolState memory poolState,
        MarginPosition.State storage position,
        MarginBalanceDelta memory delta
    ) internal returns (uint256 borrowAmount) {
        uint256 borrowRealReserves = poolState.realReserves.reserve01(!position.marginForOne);
        (uint256 borrowMaxAmount,) =
            SwapMath.getAmountOut(poolState.pairReserves, poolState.lpFee, !position.marginForOne, params.marginAmount);
        borrowMaxAmount = Math.min(borrowMaxAmount, borrowRealReserves * 20 / 100);
        if (params.borrowAmount > borrowMaxAmount) BorrowTooMuch.selector.revertWith();
        if (params.borrowAmount == 0) params.borrowAmount = borrowMaxAmount.toUint128();
        borrowAmount = params.borrowAmount;
        uint256 borrowCumulativeLast;
        uint256 depositCumulativeLast;
        if (position.marginForOne) {
            borrowCumulativeLast = poolState.borrow0CumulativeLast;
            depositCumulativeLast = poolState.deposit1CumulativeLast;
        } else {
            borrowCumulativeLast = poolState.borrow1CumulativeLast;
            depositCumulativeLast = poolState.deposit0CumulativeLast;
        }

        position.update(
            borrowCumulativeLast, depositCumulativeLast, params.marginAmount.toInt128(), 0, params.borrowAmount, 0
        );

        int128 amount0Delta;
        int128 amount1Delta;
        int128 amount = -params.marginAmount.toInt128();

        if (position.marginForOne) {
            amount1Delta = amount;
            amount0Delta = borrowAmount.toInt128();
            delta.lendDelta = toBalanceDelta(0, amount);
            delta.mirrorDelta = toBalanceDelta(-borrowAmount.toInt128(), 0);
        } else {
            amount0Delta = amount;
            amount1Delta = borrowAmount.toInt128();
            delta.lendDelta = toBalanceDelta(amount, 0);
            delta.mirrorDelta = toBalanceDelta(0, -borrowAmount.toInt128());
        }
        delta.marginDelta = toBalanceDelta(amount0Delta, amount1Delta);
    }

    /// @inheritdoc IMarginPositionManager
    function repay(uint256 tokenId, uint256 repayAmount, uint256 deadline) external payable ensure(deadline) {
        _requireAuth(msg.sender, tokenId);
        PoolId poolId = poolIds[tokenId];
        PoolKey memory key = poolKeys[poolId];
        PoolState memory poolState = _getPoolState(poolId);
        MarginPosition.State storage position = positionInfos[tokenId];
        MarginBalanceDelta memory delta;

        (uint256 borrowCumulativeLast, uint256 depositCumulativeLast) =
            _getPoolCumulativeValues(poolState, position.marginForOne);

        (uint256 releaseAmount, uint256 realRepayAmount) =
            position.update(borrowCumulativeLast, depositCumulativeLast, 0, 0, 0, repayAmount);

        MarginLevels _marginLevels = marginLevels;
        _checkMinLevel(
            poolState.pairReserves,
            borrowCumulativeLast,
            depositCumulativeLast,
            position,
            _marginLevels.liquidateLevel()
        );

        int128 amount0Delta;
        int128 amount1Delta;
        if (position.marginForOne) {
            amount0Delta = -realRepayAmount.toInt128();
            amount1Delta = releaseAmount.toInt128();
            delta.lendDelta = toBalanceDelta(0, amount1Delta);
            delta.mirrorDelta = toBalanceDelta(realRepayAmount.toInt128(), 0);
        } else {
            amount0Delta = releaseAmount.toInt128();
            amount1Delta = -realRepayAmount.toInt128();
            delta.lendDelta = toBalanceDelta(amount0Delta, 0);
            delta.mirrorDelta = toBalanceDelta(0, realRepayAmount.toInt128());
        }
        delta.action = MarginActions.REPAY;
        delta.marginForOne = position.marginForOne;
        delta.marginDelta = toBalanceDelta(amount0Delta, amount1Delta);

        bytes memory callbackData = abi.encode(msg.sender, key, delta);
        bytes memory data = abi.encode(delta.action, callbackData);

        vault.unlock(data);

        emit Repay(
            key.toId(),
            msg.sender,
            tokenId,
            position.marginAmount,
            position.marginTotal,
            position.debtAmount,
            releaseAmount,
            realRepayAmount
        );
    }

    /// @inheritdoc IMarginPositionManager
    function close(uint256 tokenId, uint24 closeMillionth, uint256 closeAmountMin, uint256 deadline)
        external
        ensure(deadline)
    {
        _requireAuth(msg.sender, tokenId);
        PoolId poolId = poolIds[tokenId];
        PoolKey memory key = poolKeys[poolId];
        PoolState memory poolState = _getPoolState(poolId);
        MarginPosition.State storage position = positionInfos[tokenId];
        MarginBalanceDelta memory delta;

        (uint256 borrowCumulativeLast, uint256 depositCumulativeLast) =
            _getPoolCumulativeValues(poolState, position.marginForOne);

        (uint256 releaseAmount, uint256 repayAmount, uint256 closeAmount, uint256 lostAmount, uint256 swapFeeAmount) =
        position.close(
            poolState.pairReserves, poolState.lpFee, borrowCumulativeLast, depositCumulativeLast, 0, closeMillionth
        );
        if (lostAmount > 0 || (closeAmountMin > 0 && closeAmount < closeAmountMin)) {
            InsufficientCloseReceived.selector.revertWith();
        }
        MarginLevels _marginLevels = marginLevels;
        _checkMinLevel(
            poolState.pairReserves,
            borrowCumulativeLast,
            depositCumulativeLast,
            position,
            _marginLevels.liquidateLevel()
        );

        int128 amount0Delta;
        int128 amount1Delta;
        if (position.marginForOne) {
            amount1Delta = closeAmount.toInt128();
            delta.lendDelta = toBalanceDelta(0, releaseAmount.toInt128());
            delta.mirrorDelta = toBalanceDelta(repayAmount.toInt128(), 0);
            delta.pairDelta = toBalanceDelta(repayAmount.toInt128(), -(releaseAmount - closeAmount).toInt128());
        } else {
            amount0Delta = closeAmount.toInt128();
            delta.lendDelta = toBalanceDelta(releaseAmount.toInt128(), 0);
            delta.mirrorDelta = toBalanceDelta(0, repayAmount.toInt128());
            delta.pairDelta = toBalanceDelta(-(releaseAmount - closeAmount).toInt128(), repayAmount.toInt128());
        }
        delta.action = MarginActions.CLOSE;
        delta.swapFeeAmount = swapFeeAmount;
        delta.marginForOne = position.marginForOne;
        delta.marginDelta = toBalanceDelta(amount0Delta, amount1Delta);

        bytes memory callbackData = abi.encode(msg.sender, key, delta);
        bytes memory data = abi.encode(delta.action, callbackData);

        vault.unlock(data);
        emit Close(
            key.toId(),
            msg.sender,
            tokenId,
            position.marginAmount,
            position.marginTotal,
            position.debtAmount,
            releaseAmount,
            repayAmount,
            closeAmount
        );
    }

    /// @inheritdoc IMarginPositionManager
    function liquidateBurn(uint256 tokenId) external returns (uint256 profit) {
        PoolId poolId = poolIds[tokenId];
        PoolState memory poolState = _getPoolState(poolId);
        MarginPosition.State storage position = positionInfos[tokenId];
        (bool liquidated, uint256 marginAmount, uint256 marginTotal, uint256 debtAmount) =
            _checkLiquidate(poolState, position);
        if (!liquidated) {
            PositionNotLiquidated.selector.revertWith();
        }
        PoolKey memory key = poolKeys[poolId];
        MarginBalanceDelta memory delta;
        MarginLevels _marginLevels = marginLevels;
        uint256 assetsAmount = marginAmount + marginTotal;
        profit = assetsAmount.mulDivMillion(_marginLevels.callerProfit());
        uint256 protocolProfitAmount = assetsAmount.mulDivMillion(_marginLevels.protocolProfit());

        (uint256 borrowCumulativeLast, uint256 depositCumulativeLast) =
            _getPoolCumulativeValues(poolState, position.marginForOne);
        uint256 rewardAmount = profit + protocolProfitAmount;
        (uint256 releaseAmount, uint256 repayAmount,, uint256 lostAmount, uint256 swapFeeAmount) = position.close(
            poolState.pairReserves,
            poolState.lpFee,
            borrowCumulativeLast,
            depositCumulativeLast,
            rewardAmount,
            uint24(PerLibrary.ONE_MILLION)
        );
        (uint256 lendLostAmount, uint256 debtDepositCumulativeLast) = _processLost(poolState, position, lostAmount);
        delta.swapFeeAmount = swapFeeAmount;
        delta.debtDepositCumulativeLast = debtDepositCumulativeLast;

        int128 amount0Delta;
        int128 amount1Delta;
        if (position.marginForOne) {
            amount1Delta = rewardAmount.toInt128();
            delta.lendDelta = toBalanceDelta(lendLostAmount.toInt128(), releaseAmount.toInt128());
            delta.mirrorDelta = toBalanceDelta(repayAmount.toInt128(), 0);
            delta.pairDelta =
                toBalanceDelta((repayAmount - lendLostAmount).toInt128(), -(releaseAmount - rewardAmount).toInt128());
        } else {
            amount0Delta = rewardAmount.toInt128();
            delta.lendDelta = toBalanceDelta(releaseAmount.toInt128(), lendLostAmount.toInt128());
            delta.mirrorDelta = toBalanceDelta(0, repayAmount.toInt128());
            delta.pairDelta =
                toBalanceDelta(-(releaseAmount - rewardAmount).toInt128(), (repayAmount - lendLostAmount).toInt128());
        }
        delta.action = MarginActions.LIQUIDATE_BURN;
        delta.marginForOne = position.marginForOne;
        delta.marginDelta = toBalanceDelta(amount0Delta, amount1Delta);

        bytes memory callbackData = abi.encode(msg.sender, key, delta, profit, protocolProfitAmount);
        bytes memory data = abi.encode(delta.action, callbackData);

        vault.unlock(data);

        emit LiquidateBurn(
            key.toId(),
            msg.sender,
            tokenId,
            marginAmount,
            marginTotal,
            debtAmount,
            Reserves.unwrap(poolState.truncatedReserves),
            Reserves.unwrap(poolState.pairReserves),
            releaseAmount,
            repayAmount,
            profit,
            lostAmount
        );
    }

    /// @inheritdoc IMarginPositionManager
    function liquidateCall(uint256 tokenId) external payable returns (uint256 profit, uint256 repayAmount) {
        PoolId poolId = poolIds[tokenId];
        PoolState memory poolState = _getPoolState(poolId);
        MarginPosition.State storage position = positionInfos[tokenId];
        (bool liquidated, uint256 marginAmount, uint256 marginTotal, uint256 debtAmount) =
            _checkLiquidate(poolState, position);
        if (!liquidated) {
            PositionNotLiquidated.selector.revertWith();
        }
        PoolKey memory key = poolKeys[poolId];
        MarginBalanceDelta memory delta;
        (uint128 reserve0, uint128 reserve1) = poolState.pairReserves.reserves();
        (uint256 reserveBorrow, uint256 reserveMargin) =
            position.marginForOne ? (reserve0, reserve1) : (reserve1, reserve0);

        MarginLevels _marginLevels = marginLevels;
        uint24 _liquidationRatio = _marginLevels.liquidationRatio();
        uint256 assetsAmount = marginAmount + marginTotal;
        repayAmount = Math.mulDiv(reserveBorrow, assetsAmount, reserveMargin);
        uint256 needPayAmount = repayAmount.mulDivMillion(_liquidationRatio);
        profit = assetsAmount;

        (uint256 borrowCumulativeLast, uint256 depositCumulativeLast) =
            _getPoolCumulativeValues(poolState, position.marginForOne);

        uint256 releaseAmount;
        (releaseAmount, repayAmount) = position.update(borrowCumulativeLast, depositCumulativeLast, 0, 0, 0, debtAmount);
        if (profit != releaseAmount) {
            InsufficientReceived.selector.revertWith();
        }
        uint256 realRepayAmount = Math.min(repayAmount, needPayAmount);

        uint256 lostAmount;
        if (debtAmount > realRepayAmount) {
            lostAmount = debtAmount - realRepayAmount;
        }
        (uint256 lendLostAmount, uint256 debtDepositCumulativeLast) = _processLost(poolState, position, lostAmount);
        delta.debtDepositCumulativeLast = debtDepositCumulativeLast;
        int128 amount0Delta;
        int128 amount1Delta;
        if (position.marginForOne) {
            amount0Delta = -realRepayAmount.toInt128();
            amount1Delta = releaseAmount.toInt128();
            delta.lendDelta = toBalanceDelta(lendLostAmount.toInt128(), amount1Delta);
            delta.pairDelta = toBalanceDelta((lostAmount - lendLostAmount).toInt128(), 0);
            delta.mirrorDelta = toBalanceDelta(debtAmount.toInt128(), 0);
        } else {
            amount0Delta = releaseAmount.toInt128();
            amount1Delta = -realRepayAmount.toInt128();
            delta.lendDelta = toBalanceDelta(amount0Delta, lendLostAmount.toInt128());
            delta.pairDelta = toBalanceDelta(0, (lostAmount - lendLostAmount).toInt128());
            delta.mirrorDelta = toBalanceDelta(0, debtAmount.toInt128());
        }
        delta.action = MarginActions.LIQUIDATE_CALL;
        delta.marginForOne = position.marginForOne;
        delta.marginDelta = toBalanceDelta(amount0Delta, amount1Delta);

        bytes memory callbackData = abi.encode(msg.sender, key, delta);
        bytes memory data = abi.encode(delta.action, callbackData);

        vault.unlock(data);

        emit LiquidateCall(
            key.toId(),
            msg.sender,
            tokenId,
            marginAmount,
            marginTotal,
            debtAmount,
            Reserves.unwrap(poolState.truncatedReserves),
            Reserves.unwrap(poolState.pairReserves),
            releaseAmount,
            repayAmount,
            needPayAmount,
            lostAmount
        );
    }

    /// @inheritdoc IMarginPositionManager
    function modify(uint256 tokenId, int128 changeAmount) external payable {
        _requireAuth(msg.sender, tokenId);
        PoolId poolId = poolIds[tokenId];
        PoolKey memory key = poolKeys[poolId];
        PoolState memory poolState = _getPoolState(poolId);
        MarginPosition.State storage position = positionInfos[tokenId];
        MarginBalanceDelta memory delta;

        (uint256 borrowCumulativeLast, uint256 depositCumulativeLast) =
            _getPoolCumulativeValues(poolState, position.marginForOne);

        position.update(borrowCumulativeLast, depositCumulativeLast, changeAmount, 0, 0, 0);

        MarginLevels _marginLevels = marginLevels;
        _checkMinLevel(
            poolState.pairReserves,
            borrowCumulativeLast,
            depositCumulativeLast,
            position,
            _marginLevels.minBorrowLevel()
        );

        int128 amount0Delta;
        int128 amount1Delta;
        if (position.marginForOne) {
            amount1Delta = -changeAmount.toInt128();
            delta.lendDelta = toBalanceDelta(0, amount1Delta);
        } else {
            amount0Delta = -changeAmount.toInt128();
            delta.lendDelta = toBalanceDelta(amount0Delta, 0);
        }
        delta.action = MarginActions.MODIFY;
        delta.marginForOne = position.marginForOne;
        delta.marginDelta = toBalanceDelta(amount0Delta, amount1Delta);

        bytes memory callbackData = abi.encode(msg.sender, key, delta);
        bytes memory data = abi.encode(delta.action, callbackData);

        vault.unlock(data);
        emit Modify(
            key.toId(),
            msg.sender,
            tokenId,
            position.marginAmount,
            position.marginTotal,
            position.debtAmount,
            changeAmount
        );
    }

    function handleMargin(bytes memory _data) internal returns (bytes memory) {
        (address sender, PoolKey memory key, MarginBalanceDelta memory params) =
            abi.decode(_data, (address, PoolKey, MarginBalanceDelta));

        (BalanceDelta delta, uint256 feeAmount) = vault.marginBalance(key, params);

        _processDelta(sender, key, delta, 0, 0, 0, 0);

        return abi.encode(feeAmount);
    }

    function handleLiquidateBurn(bytes memory _data) internal returns (bytes memory) {
        (
            address sender,
            PoolKey memory key,
            MarginBalanceDelta memory params,
            uint256 callerProfitAmount,
            uint256 protocolProfitAmount
        ) = abi.decode(_data, (address, PoolKey, MarginBalanceDelta, uint256, uint256));

        vault.marginBalance(key, params);

        Currency marginCurrency = params.marginForOne ? key.currency1 : key.currency0;
        if (protocolProfitAmount > 0) {
            address feeTo = IProtocolFees(address(vault)).protocolFeeController();
            if (feeTo == address(0)) {
                feeTo = owner;
            }
            marginCurrency.take(vault, feeTo, protocolProfitAmount, false);
        }
        if (callerProfitAmount > 0) {
            marginCurrency.take(vault, sender, callerProfitAmount, false);
        }

        return abi.encode(callerProfitAmount + protocolProfitAmount);
    }

    // ******************** OWNER CALL ********************
    function setMarginLevel(bytes32 _marginLevel) external onlyOwner {
        MarginLevels newMarginLevels = MarginLevels.wrap(_marginLevel);
        if (!newMarginLevels.isValidMarginLevels()) InvalidLevel.selector.revertWith();
        bytes32 old = MarginLevels.unwrap(marginLevels);
        marginLevels = newMarginLevels;
        emit MarginLevelChanged(old, _marginLevel);
    }

    function setDefaultMarginFee(uint24 newMarginFee) external onlyOwner {
        uint24 old = defaultMarginFee;
        defaultMarginFee = newMarginFee;
        emit MarginFeeChanged(old, newMarginFee);
    }
}

// SPDX-License-Identifier: BUSL-1.1
// Likwid Contracts
pragma solidity ^0.8.26;

// Local
import {BasePositionManager} from "./base/BasePositionManager.sol";
import {PoolKey} from "./types/PoolKey.sol";
import {PoolId} from "./types/PoolId.sol";
import {Currency} from "./types/Currency.sol";
import {BalanceDelta} from "./types/BalanceDelta.sol";
import {IVault} from "./interfaces/IVault.sol";
import {IPairPositionManager} from "./interfaces/IPairPositionManager.sol";
import {PairPosition} from "./libraries/PairPosition.sol";
import {StateLibrary} from "./libraries/StateLibrary.sol";
import {CustomRevert} from "./libraries/CustomRevert.sol";
import {CurrencyPoolLibrary} from "./libraries/CurrencyPoolLibrary.sol";

contract LikwidPairPosition is IPairPositionManager, BasePositionManager {
    using CurrencyPoolLibrary for Currency;
    using CustomRevert for bytes4;

    constructor(address initialOwner, IVault _vault)
        BasePositionManager("LIKWIDPairPositionManager", "LPPM", initialOwner, _vault)
    {}

    enum Actions {
        MODIFY_LIQUIDITY,
        SWAP
    }

    function unlockCallback(bytes calldata data) external override returns (bytes memory) {
        (Actions action, bytes memory params) = abi.decode(data, (Actions, bytes));

        if (action == Actions.MODIFY_LIQUIDITY) {
            return handleModifyLiquidity(params);
        } else if (action == Actions.SWAP) {
            return handleSwap(params);
        } else {
            InvalidCallback.selector.revertWith();
        }
    }

    /// @inheritdoc IPairPositionManager
    function getPositionState(uint256 tokenId) external view returns (PairPosition.State memory) {
        bytes32 salt = bytes32(tokenId);
        PoolId poolId = poolIds[tokenId];
        return StateLibrary.getPairPositionState(vault, poolId, address(this), salt);
    }

    /// @inheritdoc IPairPositionManager
    function addLiquidity(PoolKey memory key, uint256 amount0, uint256 amount1, uint256 amount0Min, uint256 amount1Min)
        external
        payable
        returns (uint256 tokenId, uint128 liquidity)
    {
        tokenId = _mintPosition(key, msg.sender);
        liquidity = _increaseLiquidity(msg.sender, msg.sender, tokenId, amount0, amount1, amount0Min, amount1Min);
    }

    function _increaseLiquidity(
        address sender,
        address tokenOwner,
        uint256 tokenId,
        uint256 amount0,
        uint256 amount1,
        uint256 amount0Min,
        uint256 amount1Min
    ) internal returns (uint128 liquidity) {
        _requireAuth(tokenOwner, tokenId);
        PoolId poolId = poolIds[tokenId];
        if (PoolId.unwrap(poolId) == 0) {
            revert("Invalid tokenId");
        }
        PoolKey memory key = poolKeys[poolId];

        IVault.ModifyLiquidityParams memory params = IVault.ModifyLiquidityParams({
            amount0: amount0,
            amount1: amount1,
            liquidityDelta: 0,
            salt: bytes32(tokenId)
        });

        bytes memory callbackData = abi.encode(sender, key, params, amount0Min, amount1Min);
        bytes memory data = abi.encode(Actions.MODIFY_LIQUIDITY, callbackData);

        bytes memory result = vault.unlock(data);
        (liquidity, amount0, amount1) = abi.decode(result, (uint128, uint256, uint256));
    }

    /// @inheritdoc IPairPositionManager
    function increaseLiquidity(
        uint256 tokenId,
        uint256 amount0,
        uint256 amount1,
        uint256 amount0Min,
        uint256 amount1Min
    ) external payable returns (uint128 liquidity) {
        liquidity = _increaseLiquidity(msg.sender, msg.sender, tokenId, amount0, amount1, amount0Min, amount1Min);
    }

    /// @inheritdoc IPairPositionManager
    function removeLiquidity(uint256 tokenId, uint128 liquidity, uint256 amount0Min, uint256 amount1Min)
        external
        returns (uint256 amount0, uint256 amount1)
    {
        _requireAuth(msg.sender, tokenId);
        PoolId poolId = poolIds[tokenId];
        if (PoolId.unwrap(poolId) == 0) {
            revert("Invalid tokenId");
        }
        PoolKey memory key = poolKeys[poolId];

        IVault.ModifyLiquidityParams memory params = IVault.ModifyLiquidityParams({
            amount0: 0,
            amount1: 0,
            liquidityDelta: -int128(liquidity),
            salt: bytes32(tokenId)
        });

        bytes memory callbackData = abi.encode(msg.sender, key, params, amount0Min, amount1Min);
        bytes memory data = abi.encode(Actions.MODIFY_LIQUIDITY, callbackData);

        bytes memory result = vault.unlock(data);
        (, amount0, amount1) = abi.decode(result, (int128, uint256, uint256));
    }

    function handleModifyLiquidity(bytes memory _data) internal returns (bytes memory) {
        (
            address sender,
            PoolKey memory key,
            IVault.ModifyLiquidityParams memory params,
            uint256 amount0Min,
            uint256 amount1Min
        ) = abi.decode(_data, (address, PoolKey, IVault.ModifyLiquidityParams, uint256, uint256));

        (BalanceDelta delta, int128 finalLiquidityDelta) = vault.modifyLiquidity(key, params);

        (uint256 amount0, uint256 amount1) =
            _processDelta(sender, key, delta, amount0Min, amount1Min, params.amount0, params.amount1);

        emit ModifyLiquidity(key.toId(), uint256(params.salt), sender, finalLiquidityDelta, amount0, amount1);
        return abi.encode(finalLiquidityDelta, amount0, amount1);
    }

    /// @inheritdoc IPairPositionManager
    function exactInput(SwapInputParams calldata params)
        external
        payable
        ensure(params.deadline)
        returns (uint24 swapFee, uint256 feeAmount, uint256 amountOut)
    {
        PoolKey memory key = poolKeys[params.poolId];
        int256 amountSpecified = -int256(params.amountIn);
        IVault.SwapParams memory swapParams = IVault.SwapParams({
            zeroForOne: params.zeroForOne,
            amountSpecified: amountSpecified,
            useMirror: false,
            salt: bytes32(0)
        });
        uint256 amount0Min = params.zeroForOne ? 0 : params.amountOutMin;
        uint256 amount1Min = params.zeroForOne ? params.amountOutMin : 0;
        bytes memory callbackData = abi.encode(msg.sender, key, swapParams, amount0Min, amount1Min, 0, 0);
        bytes memory data = abi.encode(Actions.SWAP, callbackData);

        bytes memory result = vault.unlock(data);
        uint256 amount0;
        uint256 amount1;
        (swapFee, feeAmount, amount0, amount1) = abi.decode(result, (uint24, uint256, uint256, uint256));
        amountOut = params.zeroForOne ? amount1 : amount0;
    }

    /// @inheritdoc IPairPositionManager
    function exactOutput(SwapOutputParams calldata params)
        external
        payable
        ensure(params.deadline)
        returns (uint24 swapFee, uint256 feeAmount, uint256 amountIn)
    {
        PoolKey memory key = poolKeys[params.poolId];
        int256 amountSpecified = int256(params.amountOut);
        IVault.SwapParams memory swapParams = IVault.SwapParams({
            zeroForOne: params.zeroForOne,
            amountSpecified: amountSpecified,
            useMirror: false,
            salt: bytes32(0)
        });
        uint256 amount0Max = params.zeroForOne ? params.amountInMax : 0;
        uint256 amount1Max = params.zeroForOne ? 0 : params.amountInMax;
        bytes memory callbackData = abi.encode(msg.sender, key, swapParams, 0, 0, amount0Max, amount1Max);
        bytes memory data = abi.encode(Actions.SWAP, callbackData);

        bytes memory result = vault.unlock(data);
        uint256 amount0;
        uint256 amount1;
        (swapFee, feeAmount, amount0, amount1) = abi.decode(result, (uint24, uint256, uint256, uint256));
        amountIn = params.zeroForOne ? amount0 : amount1;
    }

    function handleSwap(bytes memory _data) internal returns (bytes memory) {
        (
            address sender,
            PoolKey memory key,
            IVault.SwapParams memory params,
            uint256 amount0Min,
            uint256 amount1Min,
            uint256 amount0Max,
            uint256 amount1Max
        ) = abi.decode(_data, (address, PoolKey, IVault.SwapParams, uint256, uint256, uint256, uint256));

        (BalanceDelta delta, uint24 swapFee, uint256 feeAmount) = vault.swap(key, params);

        (uint256 amount0, uint256 amount1) =
            _processDelta(sender, key, delta, amount0Min, amount1Min, amount0Max, amount1Max);

        return abi.encode(swapFee, feeAmount, amount0, amount1);
    }
}


Vulnerability type: CWE-284|CWE-285|CWE-862