UniswapV3PositionManager

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/UniswapV3PositionManager.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma abicoder v2;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "lib/v3-core/contracts/libraries/TickMath.sol";
import "lib/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
import "src/IUniswapV3PositionManager.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";

contract UniswapV3PositionManager is IUniswapV3PositionManager, AccessControl {
    using Address for address;
    using SafeMath for uint256;
    struct CompoundState {
        uint256 amount0;
        uint256 amount1;
        uint256 maxAddAmount0;
        uint256 maxAddAmount1;
        uint256 priceX96;
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
    }

    struct FeeCalculationState {
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        int24 tickCurrent;
        uint128 liquidity;
        uint256 feeGrowthInside0LastX128;
        uint256 feeGrowthInside1LastX128;
        uint128 tokensOwed0;
        uint128 tokensOwed1;
        uint256 feeGrowthGlobal0X128;
        uint256 feeGrowthGlobal1X128;
        uint256 feeGrowthOutside0LowerX128;
        uint256 feeGrowthOutside1LowerX128;
        uint256 feeGrowthOutside0UpperX128;
        uint256 feeGrowthOutside1UpperX128;
        uint256 feeGrowthInside0X128;
        uint256 feeGrowthInside1X128;
    }

    struct NewPositionState {
        uint256 amount0;
        uint256 amount1;
        uint256 amount0Desired;
        uint256 amount1Desired;
    }

    struct SwapState {
        uint256 rewardAmount0;
        uint256 rewardAmount1;
        uint256 positionAmount0;
        uint256 positionAmount1;
        int24 tick;
        int24 otherTick;
        uint160 sqrtPriceX96;
        uint160 sqrtPriceX96Lower;
        uint160 sqrtPriceX96Upper;
        uint256 amountRatioX96;
        uint256 delta0;
        uint256 delta1;
        bool sell0;
        bool twapOk;
        uint256 totalReward0;
    }

    struct SwapParams {
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0;
        uint256 amount1;
        uint256 deadline;
        bool doSwap;
    }

    struct PositionConfig {
        uint32 maxTWAPTickDifference; // 4 bytes
        uint32 TWAPSeconds; // 4 bytes
        int24 defaultTickLower; // 3 bytes
        int24 defaultTickUpper; // 3 bytes
        // Total: 14 bytes in first slot (saves 18 bytes vs separate storage)
    }

    uint128 constant Q96 = 2 ** 96;
    bytes32 public constant MULTI_CALL_ROLE = keccak256("MULTI_CALL_ROLE");
    address public immutable override anchorToken;
    address public immutable override otherToken;
    address public immutable override token0;
    address public immutable override token1;
    uint24 public immutable override feeTier;
    IUniswapV3Pool public immutable pool;

    // Uniswap V3 Components
    IUniswapV3Factory public immutable override factory;
    INonfungiblePositionManager
        public immutable
        override nonfungiblePositionManager;
    ISwapRouter public immutable swapRouter;

    PositionConfig public config;
    // managing position
    uint256 public override positionTokenID;

    modifier onlyAdminOrMulticallRoles() {
        require(
            hasRole(DEFAULT_ADMIN_ROLE, msg.sender) ||
                hasRole(MULTI_CALL_ROLE, msg.sender),
            "Not admin or multicall"
        );
        _;
    }
    modifier onlyAdmin() {
        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not admin");
        _;
    }

    constructor(
        IUniswapV3Factory _factory,
        INonfungiblePositionManager _nonFungiblePositionManager,
        address admin,
        address multiCallContract,
        address anchorTokenAddress,
        address otherTokenAddress,
        uint24 _feeTier,
        ISwapRouter _swapRouter,
        int24 defaultLowerTick,
        int24 defaultUpperTick
    ) {
        require(address(_factory) != address(0), "Invalid factory address");
        require(
            address(_nonFungiblePositionManager) != address(0),
            "Invalid non-fungible position manager"
        );
        require(admin != address(0), "Invalid admin address");
        require(
            multiCallContract != address(0),
            "Invalid multicall contract address"
        );
        require(
            anchorTokenAddress != address(0),
            "Invalid anchor token address"
        );
        require(otherTokenAddress != address(0), "Invalid other token address");
        require(_feeTier != 0, "Invalid fee tier");
        require(
            address(_swapRouter) != address(0),
            "Invalid swap router address"
        );
        require(
            defaultUpperTick > defaultLowerTick &&
                defaultLowerTick != defaultUpperTick,
            "Invalid Upper Lower Tick"
        );

        config.defaultTickLower = defaultLowerTick;
        config.defaultTickUpper = defaultUpperTick;
        config.maxTWAPTickDifference = 100;
        config.TWAPSeconds = 0; // Disabled for testing

        factory = _factory;
        anchorToken = anchorTokenAddress;
        otherToken = otherTokenAddress;
        feeTier = _feeTier;

        address t0 = anchorTokenAddress < otherTokenAddress
            ? anchorTokenAddress
            : otherTokenAddress;
        address t1 = anchorTokenAddress < otherTokenAddress
            ? otherTokenAddress
            : anchorTokenAddress;
        token0 = t0;
        token1 = t1;
        address poolAddress = _factory.getPool(t0, t1, _feeTier);
        require(poolAddress != address(0), "Pool does not exist");
        pool = IUniswapV3Pool(poolAddress);
        nonfungiblePositionManager = _nonFungiblePositionManager;
        _setupRole(DEFAULT_ADMIN_ROLE, admin);
        _setupRole(MULTI_CALL_ROLE, multiCallContract);

        swapRouter = _swapRouter;
    }

    function setDefaultTicks(
        int24 _lower,
        int24 _upper
    ) external override onlyAdmin {
        config.defaultTickLower = _lower;
        config.defaultTickUpper = _upper;
    }

    function setTWAPSettings(
        uint32 _seconds,
        uint32 _maxTickDifference
    ) external override onlyAdmin {
        config.TWAPSeconds = _seconds;
        config.maxTWAPTickDifference = _maxTickDifference;
    }

    // Getter functions for config struct fields to maintain interface compatibility
    function defaultTickLower() external view override returns (int24) {
        return config.defaultTickLower;
    }

    function defaultTickUpper() external view override returns (int24) {
        return config.defaultTickUpper;
    }

    function TWAPSeconds() external view returns (uint32) {
        return config.TWAPSeconds;
    }

    function maxTWAPTickDifference() external view returns (uint32) {
        return config.maxTWAPTickDifference;
    }

    function maintenance() external override {
        // if we have balances of either token but not token id open a new position
        if (positionTokenID == 0) {
            uint256 amount0 = IERC20(token0).balanceOf(address(this));
            uint256 amount1 = IERC20(token1).balanceOf(address(this));
            if (amount0 == 0 && amount1 == 0) {
                return;
            }
            _openNewPosition(amount0, amount1);
        } else {
            // otherwise compound existing position
            _compoundPosition();
        }
    }

    /**
     * @notice Removes liquidity from the position based on a specified amount of the anchor token
     * @param amount the amount of token to remove in terms of the anchor token
     * @param tokenRecipient the address the tokens will go to
     * @param slippage the maximum slippage allowed in basis points (e.g., 100 = 1%)
     * @return anchorAmountReceived the amount of anchor token received
     */
    function removeLiquidityByAnchorTokenAmount(
        uint256 amount,
        address tokenRecipient,
        uint16 slippage
    )
        external
        override
        onlyAdminOrMulticallRoles
        returns (uint256 anchorAmountReceived)
    {
        require(positionTokenID != 0, "no position");
        if (tokenRecipient == address(0)) tokenRecipient = address(this);

        uint16 basisPoints = _calculateBasisPointsForAmount(amount);

        // remove liquidity by that percentage - call internal function
        (, , anchorAmountReceived) = _removeLiquidityByBasisPoints(
            basisPoints,
            tokenRecipient,
            slippage
        );
        return anchorAmountReceived;
    }

    /**
     * @notice Internal helper to calculate basis points for a given anchor token amount
     * @param amount The amount of anchor token to remove
     * @return basisPoints The basis points (0-10000) representing the percentage of liquidity to remove
     */
    function _calculateBasisPointsForAmount(
        uint256 amount
    ) internal view returns (uint16 basisPoints) {
        // get the position data
        (, , , , , , , uint128 liquidity, , , , ) = nonfungiblePositionManager
            .positions(positionTokenID);

        require(liquidity > 0, "no liq");

        // get the current price of the pool
        (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();

        // get the amounts claimable if we removed 100% (estimation at current price)
        (uint256 full0, uint256 full1) = LiquidityAmounts
            .getAmountsForLiquidity(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(config.defaultTickLower),
                TickMath.getSqrtRatioAtTick(config.defaultTickUpper),
                liquidity
            );

        // convert both amounts to anchor token terms
        uint256 priceX96 = uint256(sqrtPriceX96).mul(uint256(sqrtPriceX96)).div(
            Q96
        );
        uint256 fullAnchorAmount;
        if (token0 == anchorToken) {
            fullAnchorAmount = full0.add(full1.mul(priceX96).div(Q96));
        } else {
            fullAnchorAmount = full1.add(full0.mul(Q96).div(priceX96));
        }

        // calculate the percentage of liquidity to remove
        uint256 calculatedBasisPoints = amount.mul(10000).div(fullAnchorAmount);
        // Cap at 10000 (100%) if the requested amount exceeds position value
        basisPoints = uint16(
            calculatedBasisPoints > 10000 ? 10000 : calculatedBasisPoints
        );
    }

    /**
     * @notice Removes a percentage of liquidity from the position based on basis points
     * @dev Basis points: 10000 = 100%, 5000 = 50%, 2500 = 25%, etc.
     * @param basisPoints The percentage of liquidity to remove in basis points (0-10000)
     * @return amount0 The amount of token0 received
     * @return amount1 The amount of token1 received
     */
    function removeLiquidityByBasisPoints(
        uint16 basisPoints,
        address tokenRecipient,
        uint16 slippage
    )
        external
        override
        onlyAdminOrMulticallRoles
        returns (uint256 amount0, uint256 amount1, uint256 anchorAmountOut)
    {
        return
            _removeLiquidityByBasisPoints(
                basisPoints,
                tokenRecipient,
                slippage
            );
    }

    /**
     * @notice Internal function to remove liquidity by basis points
     * @dev Basis points: 10000 = 100%, 5000 = 50%, 2500 = 25%, etc.
     * @param basisPoints The percentage of liquidity to remove in basis points (0-10000)
     * @return amount0 The amount of token0 received
     * @return amount1 The amount of token1 received
     * @return anchorAmountOut The amount of anchor token received after swapping
     */
    function _removeLiquidityByBasisPoints(
        uint16 basisPoints,
        address tokenRecipient,
        uint16 slippage
    )
        internal
        returns (uint256 amount0, uint256 amount1, uint256 anchorAmountOut)
    {
        require(positionTokenID != 0, "No position exists");
        if (tokenRecipient == address(0)) {
            tokenRecipient = address(this);
        }

        (, , , , , , , uint128 liquidity, , , , ) = nonfungiblePositionManager
            .positions(positionTokenID);

        uint256 liquidityToRemove = uint256(liquidity).mul(basisPoints).div(
            10000
        );

        // Get current pool price
        (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();

        // calculate expected output amounts from liquidity amounts
        // get balance of position
        (uint256 amount0Expected, uint256 amount1Expected) = LiquidityAmounts
            .getAmountsForLiquidity(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(config.defaultTickLower),
                TickMath.getSqrtRatioAtTick(config.defaultTickUpper),
                uint128(liquidityToRemove)
            );

        // decrease liquidity
        nonfungiblePositionManager.decreaseLiquidity(
            INonfungiblePositionManager.DecreaseLiquidityParams({
                tokenId: positionTokenID,
                liquidity: uint128(liquidityToRemove),
                amount0Min: amount0Expected,
                amount1Min: amount1Expected,
                deadline: type(uint256).max
            })
        );

        // Collect the tokens to this contract first (we need to swap)
        (amount0, amount1) = nonfungiblePositionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: positionTokenID,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );

        emit LiquidityRemoved(
            positionTokenID,
            uint128(liquidityToRemove),
            amount0,
            amount1
        );

        uint256 amountOut;
        uint256 minAmountOut;
        // swap the other token that we got for anchor token if needed
        if (token0 == anchorToken && amount1 > 0) {
            minAmountOut = amount1.mul(uint256(10000).sub(slippage)).div(10000);
            amountOut = _swap(
                abi.encodePacked(token1, feeTier, token0),
                amount1,
                minAmountOut
            );
            anchorAmountOut = amountOut.add(amount0);
        } else if (token1 == anchorToken && amount0 > 0) {
            minAmountOut = amount0.mul(uint256(10000).sub(slippage)).div(10000);
            amountOut = _swap(
                abi.encodePacked(token0, feeTier, token1),
                amount0,
                minAmountOut
            );
            anchorAmountOut = amountOut.add(amount1);
        }

        // Transfer the anchor token to the recipient
        if (tokenRecipient != address(this) && anchorAmountOut > 0) {
            _safeTransfer(anchorToken, tokenRecipient, anchorAmountOut);
        }
    }

    /**
     * @notice Transfer any ERC20 tokens held by this contract to a recipient
     * @param token The address of the ERC20 token to transfer
     * @param recipient The address to receive the tokens
     * @param amount The amount of tokens to transfer
     */
    function transferERC20(
        address token,
        address recipient,
        uint256 amount
    ) external override onlyAdmin {
        require(recipient != address(0), "Invalid recipient address");
        _safeTransfer(token, recipient, amount);
    }

    function delegate(
        address target,
        bytes calldata data
    ) external override onlyAdmin returns (bytes memory) {
        (bool success, bytes memory result) = target.delegatecall(data);
        require(success, "Delegatecall failed");
        return result;
    }

    /**
     * @notice Handles the receipt of an NFT
     */
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external view override returns (bytes4) {
        require(
            msg.sender == address(nonfungiblePositionManager),
            "Not Uniswap V3 Position Manager"
        );
        return this.onERC721Received.selector;
    }

    /// @notice Get the uncollected fee amounts for the current position
    /// @return amount0 The amount of token0 fees owed
    /// @return amount1 The amount of token1 fees owed
    function getPositionFees()
        external
        view
        override
        returns (uint128 amount0, uint128 amount1)
    {
        require(positionTokenID != 0, "No position exists");

        FeeCalculationState memory vars;

        // Get position info - split to avoid stack too deep
        (
            ,
            ,
            vars.token0,
            vars.token1,
            vars.fee,
            vars.tickLower,
            vars.tickUpper,
            vars.liquidity,
            ,
            ,
            ,

        ) = nonfungiblePositionManager.positions(positionTokenID);

        // If there's no liquidity, get tokens owed and return
        if (vars.liquidity == 0) {
            (
                ,
                ,
                ,
                ,
                ,
                ,
                ,
                ,
                ,
                ,
                vars.tokensOwed0,
                vars.tokensOwed1
            ) = nonfungiblePositionManager.positions(positionTokenID);
            return (vars.tokensOwed0, vars.tokensOwed1);
        }

        // Get fee growth tracking variables separately
        (
            ,
            ,
            ,
            ,
            ,
            ,
            ,
            ,
            vars.feeGrowthInside0LastX128,
            vars.feeGrowthInside1LastX128,
            vars.tokensOwed0,
            vars.tokensOwed1
        ) = nonfungiblePositionManager.positions(positionTokenID);

        // Get the pool to calculate current fee growth
        IUniswapV3Pool positionPool = IUniswapV3Pool(
            factory.getPool(vars.token0, vars.token1, vars.fee)
        );

        // Get current global fee growth
        vars.feeGrowthGlobal0X128 = positionPool.feeGrowthGlobal0X128();
        vars.feeGrowthGlobal1X128 = positionPool.feeGrowthGlobal1X128();

        // Get tick info for lower and upper ticks
        (
            ,
            ,
            vars.feeGrowthOutside0LowerX128,
            vars.feeGrowthOutside1LowerX128,
            ,
            ,
            ,

        ) = positionPool.ticks(vars.tickLower);

        (
            ,
            ,
            vars.feeGrowthOutside0UpperX128,
            vars.feeGrowthOutside1UpperX128,
            ,
            ,
            ,

        ) = positionPool.ticks(vars.tickUpper);

        // Get current tick
        (, vars.tickCurrent, , , , , ) = positionPool.slot0();

        // Calculate fee growth inside using the Uniswap V3 formula
        vars.feeGrowthInside0X128 = _getFeeGrowthInside(
            vars.feeGrowthGlobal0X128,
            vars.feeGrowthOutside0LowerX128,
            vars.feeGrowthOutside0UpperX128,
            vars.tickCurrent,
            vars.tickLower,
            vars.tickUpper
        );

        vars.feeGrowthInside1X128 = _getFeeGrowthInside(
            vars.feeGrowthGlobal1X128,
            vars.feeGrowthOutside1LowerX128,
            vars.feeGrowthOutside1UpperX128,
            vars.tickCurrent,
            vars.tickLower,
            vars.tickUpper
        );

        // Calculate uncollected fees
        amount0 =
            vars.tokensOwed0 +
            uint128(
                uint256(vars.liquidity)
                    .mul(
                        vars.feeGrowthInside0X128.sub(
                            vars.feeGrowthInside0LastX128
                        )
                    )
                    .div(2 ** 128)
            );
        amount1 =
            vars.tokensOwed1 +
            uint128(
                uint256(vars.liquidity)
                    .mul(
                        vars.feeGrowthInside1X128.sub(
                            vars.feeGrowthInside1LastX128
                        )
                    )
                    .div(2 ** 128)
            );
    }

    /// @dev Calculate fee growth inside a tick range
    function _getFeeGrowthInside(
        uint256 feeGrowthGlobalX128,
        uint256 feeGrowthOutsideLowerX128,
        uint256 feeGrowthOutsideUpperX128,
        int24 tickCurrent,
        int24 tickLower,
        int24 tickUpper
    ) private pure returns (uint256 feeGrowthInsideX128) {
        uint256 feeGrowthBelowX128;
        uint256 feeGrowthAboveX128;

        // Calculate fee growth below
        if (tickCurrent >= tickLower) {
            feeGrowthBelowX128 = feeGrowthOutsideLowerX128;
        } else {
            feeGrowthBelowX128 = feeGrowthGlobalX128.sub(
                feeGrowthOutsideLowerX128
            );
        }

        // Calculate fee growth above
        if (tickCurrent < tickUpper) {
            feeGrowthAboveX128 = feeGrowthOutsideUpperX128;
        } else {
            feeGrowthAboveX128 = feeGrowthGlobalX128.sub(
                feeGrowthOutsideUpperX128
            );
        }

        // Fee growth inside = global - below - above
        feeGrowthInsideX128 = feeGrowthGlobalX128.sub(feeGrowthBelowX128).sub(
            feeGrowthAboveX128
        );
    }

    function _ensureApprovals() internal {
        // Approve tokens for position manager
        _ensureTokenApproval(token0, address(nonfungiblePositionManager));
        _ensureTokenApproval(token1, address(nonfungiblePositionManager));

        // Approve tokens for swap router
        _ensureTokenApproval(token0, address(swapRouter));
        _ensureTokenApproval(token1, address(swapRouter));
    }

    function _ensureTokenApproval(address token, address spender) internal {
        try IERC20(token).allowance(address(this), spender) returns (
            uint256 currentAllowance
        ) {
            if (currentAllowance < type(uint256).max / 2) {
                // For USDT compatibility: reset to 0 first if non-zero
                if (currentAllowance > 0) {
                    _safeApprove(token, spender, 0);
                }
                _safeApprove(token, spender, type(uint256).max);
            }
        } catch {
            // If allowance check fails, reset to 0 first for USDT compatibility
            _safeApprove(token, spender, 0);
            _safeApprove(token, spender, type(uint256).max);
        }
    }

    function _openNewPosition(
        uint256 amount0,
        uint256 amount1
    ) internal returns (uint256 tokenId) {
        // Ensure approvals are set
        _ensureApprovals();

        // Swap if needed to get desired amounts of both tokens
        NewPositionState memory state;

        // Swap to optimal ratio and get max amounts to add
        uint256 priceX96;
        (state.amount0, state.amount1, priceX96) = _swapToPriceRatio(
            SwapParams({
                token0: token0,
                token1: token1,
                fee: feeTier,
                tickLower: config.defaultTickLower,
                tickUpper: config.defaultTickUpper,
                amount0: amount0,
                amount1: amount1,
                deadline: type(uint256).max,
                doSwap: true
            })
        );

        (tokenId, , , ) = nonfungiblePositionManager.mint(
            INonfungiblePositionManager.MintParams({
                token0: token0,
                token1: token1,
                fee: feeTier,
                tickLower: config.defaultTickLower,
                tickUpper: config.defaultTickUpper,
                amount0Desired: state.amount0,
                amount1Desired: state.amount1,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: type(uint256).max
            })
        );
        positionTokenID = tokenId;
    }

    function _compoundPosition() internal {
        CompoundState memory state;

        // collect fees
        nonfungiblePositionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: positionTokenID,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );

        // Ensure approvals are set
        _ensureApprovals();

        state.token0 = token0;
        state.token1 = token1;
        state.fee = feeTier;
        state.tickLower = config.defaultTickLower;
        state.tickUpper = config.defaultTickUpper;

        state.amount0 = IERC20(state.token0).balanceOf(address(this));
        state.amount1 = IERC20(state.token1).balanceOf(address(this));
        // if there are balances to work with start the compounding process
        if (state.amount0 > 0 || state.amount1 > 0) {
            // checks oracle for fair price - swaps to position ratio - calculates the max amount to be added
            (state.amount0, state.amount1, state.priceX96) = _swapToPriceRatio(
                SwapParams({
                    token0: state.token0,
                    token1: state.token1,
                    fee: state.fee,
                    tickLower: state.tickLower,
                    tickUpper: state.tickUpper,
                    amount0: state.amount0,
                    amount1: state.amount1,
                    deadline: type(uint256).max,
                    doSwap: true
                })
            );

            state.maxAddAmount0 = state.amount0;
            state.maxAddAmount1 = state.amount1;
        }

        // deposit liquidity into tokenId
        if (state.maxAddAmount0 > 0 || state.maxAddAmount1 > 0) {
            nonfungiblePositionManager.increaseLiquidity(
                INonfungiblePositionManager.IncreaseLiquidityParams({
                    tokenId: positionTokenID,
                    amount0Desired: state.maxAddAmount0,
                    amount1Desired: state.maxAddAmount1,
                    amount0Min: 0,
                    amount1Min: 0,
                    deadline: type(uint256).max
                })
            );
        }
    }

    function _getTWAPTick(
        uint32 twapPeriod
    ) internal view returns (int24 twapTick, bool ok) {
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = 0;
        secondsAgos[1] = twapPeriod;

        try pool.observe(secondsAgos) returns (
            int56[] memory tickCumulatives,
            uint160[] memory
        ) {
            return (
                int24(
                    (tickCumulatives[0] - tickCumulatives[1]) /
                        int56(uint56(twapPeriod))
                ),
                true
            );
        } catch {
            return (0, false);
        }
    }

    function _requireMaxTickDifference(
        int24 tick,
        int24 other,
        uint32 maxDifference
    ) internal pure {
        require(
            (other > tick && (uint48(other - tick) < maxDifference)) ||
                (other <= tick && (uint48(tick - other) < maxDifference)),
            "TWAP price deviation exceeds maximum allowed difference"
        );
    }

    function _swapToPriceRatio(
        SwapParams memory params
    ) internal returns (uint256 amount0, uint256 amount1, uint256 priceX96) {
        SwapState memory state;
        amount0 = params.amount0;
        amount1 = params.amount1;

        (state.sqrtPriceX96, state.tick, , , , , ) = pool.slot0();

        // seconds needed for TWAP
        uint32 tSecs = config.TWAPSeconds;
        if (tSecs > 0) {
            // check that price is not too far from TWAP
            (state.otherTick, state.twapOk) = _getTWAPTick(tSecs);
            if (state.twapOk) {
                _requireMaxTickDifference(
                    state.tick,
                    state.otherTick,
                    config.maxTWAPTickDifference
                );
            } else {
                params.doSwap = false;
            }
        }
        // (sqrtPriceX96 * sqrtPriceX96) / Q96 = priceX96
        priceX96 = uint256(state.sqrtPriceX96)
            .mul(uint256(state.sqrtPriceX96))
            .div(Q96);
        if (params.doSwap) {
            //calculate ideal position amounts
            state.sqrtPriceX96Lower = TickMath.getSqrtRatioAtTick(
                params.tickLower
            );
            state.sqrtPriceX96Upper = TickMath.getSqrtRatioAtTick(
                params.tickUpper
            );
            (state.positionAmount0, state.positionAmount1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    state.sqrtPriceX96,
                    state.sqrtPriceX96Lower,
                    state.sqrtPriceX96Upper,
                    Q96
                );

            // calculate amount needed to be converted to the other token
            if (state.positionAmount0 == 0) {
                state.delta0 = 0;
                state.sell0 = true;
            } else if (state.positionAmount1 == 0) {
                state.delta0 = amount1.mul(Q96).div(priceX96);
                state.sell0 = false;
            } else {
                state.amountRatioX96 = state.positionAmount0.mul(Q96).div(
                    state.positionAmount1
                );
                state.sell0 =
                    state.amountRatioX96.mul(amount1) < amount0.mul(Q96);
                if (state.sell0) {
                    state.delta0 = amount0
                        .mul(Q96)
                        .sub(state.amountRatioX96.mul(amount1))
                        .div(
                            state.amountRatioX96.mul(priceX96).div(Q96).add(Q96)
                        );
                } else {
                    state.delta0 = state
                        .amountRatioX96
                        .mul(amount1)
                        .sub(amount0.mul(Q96))
                        .div(
                            state.amountRatioX96.mul(priceX96).div(Q96).add(Q96)
                        );
                }
            }

            if (state.delta0 > 0) {
                if (state.sell0) {
                    uint256 amountOut = _swap(
                        abi.encodePacked(
                            params.token0,
                            params.fee,
                            params.token1
                        ),
                        state.delta0,
                        0
                    );
                    amount0 = amount0.sub(state.delta0);
                    amount1 = amount1.add(amountOut);
                } else {
                    state.delta1 = state.delta0.mul(priceX96).div(Q96);
                    if (state.delta1 > 0) {
                        uint256 amountOut = _swap(
                            abi.encodePacked(
                                params.token1,
                                params.fee,
                                params.token0
                            ),
                            state.delta1,
                            0
                        );
                        amount0 = amount0.add(amountOut);
                        amount1 = amount1.sub(state.delta1);
                    }
                }
            }
        }
    }

    function _swap(
        bytes memory swapPath,
        uint256 amount,
        uint256 amountOutMinimum
    ) internal returns (uint256 amountOut) {
        if (amount > 0) {
            amountOut = swapRouter.exactInput(
                ISwapRouter.ExactInputParams(
                    swapPath, //path
                    address(this), //recipient
                    type(uint256).max, //deadline
                    amount, //amountIn
                    amountOutMinimum //amountOutMinimum
                )
            );
        }
    }

    // Safe transfer for tokens like USDT that don't return bool
    function _safeTransfer(address token, address to, uint256 amount) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(IERC20.transfer.selector, to, amount)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "Transfer failed"
        );
    }

    // Safe approve for tokens like USDT that don't return bool
    function _safeApprove(
        address token,
        address spender,
        uint256 amount
    ) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(IERC20.approve.selector, spender, amount)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "Approve failed"
        );
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/AccessControl.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../utils/EnumerableSet.sol";
import "../utils/Address.sol";
import "../utils/Context.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it.
 */
abstract contract AccessControl is Context {
    using EnumerableSet for EnumerableSet.AddressSet;
    using Address for address;

    struct RoleData {
        EnumerableSet.AddressSet members;
        bytes32 adminRole;
    }

    mapping (bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view returns (bool) {
        return _roles[role].members.contains(account);
    }

    /**
     * @dev Returns the number of accounts that have `role`. Can be used
     * together with {getRoleMember} to enumerate all bearers of a role.
     */
    function getRoleMemberCount(bytes32 role) public view returns (uint256) {
        return _roles[role].members.length();
    }

    /**
     * @dev Returns one of the accounts that have `role`. `index` must be a
     * value between 0 and {getRoleMemberCount}, non-inclusive.
     *
     * Role bearers are not sorted in any particular way, and their ordering may
     * change at any point.
     *
     * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
     * you perform all queries on the same block. See the following
     * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
     * for more information.
     */
    function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
        return _roles[role].members.at(index);
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) public virtual {
        require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");

        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) public virtual {
        require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke");

        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) public virtual {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        emit RoleAdminChanged(role, _roles[role].adminRole, adminRole);
        _roles[role].adminRole = adminRole;
    }

    function _grantRole(bytes32 role, address account) private {
        if (_roles[role].members.add(account)) {
            emit RoleGranted(role, account, _msgSender());
        }
    }

    function _revokeRole(bytes32 role, address account) private {
        if (_roles[role].members.remove(account)) {
            emit RoleRevoked(role, account, _msgSender());
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
"
    },
    "lib/v3-core/contracts/libraries/TickMath.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0 <0.8.0;

/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
    int24 internal constant MAX_TICK = -MIN_TICK;

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
    /// at the given tick
    function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
        require(absTick <= uint256(MAX_TICK), 'T');

        uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
        if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
        if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
        if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
        if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
        if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
        if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
        if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
        if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
        if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
        if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
        if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
        if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
        if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
        if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
        if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
        if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
        if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
        if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
        if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

        if (tick > 0) ratio = type(uint256).max / ratio;

        // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
        // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
        // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
        sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
    }

    /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
    function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
        // second inequality must be < because the price can never reach the price at the max tick
        require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, 'R');
        uint256 ratio = uint256(sqrtPriceX96) << 32;

        uint256 r = ratio;
        uint256 msb = 0;

        assembly {
            let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(5, gt(r, 0xFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(4, gt(r, 0xFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(3, gt(r, 0xFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(2, gt(r, 0xF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(1, gt(r, 0x3))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := gt(r, 0x1)
            msb := or(msb, f)
        }

        if (msb >= 128) r = ratio >> (msb - 127);
        else r = ratio << (127 - msb);

        int256 log_2 = (int256(msb) - 128) << 64;

        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(63, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(62, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(61, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(60, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(59, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(58, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(57, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(56, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(55, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(54, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(53, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(52, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(51, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(50, f))
        }

        int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number

        int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
        int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

        tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
    }
}
"
    },
    "lib/v3-periphery/contracts/libraries/LiquidityAmounts.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import '@uniswap/v3-core/contracts/libraries/FullMath.sol';
import '@uniswap/v3-core/contracts/libraries/FixedPoint96.sol';

/// @title Liquidity amount functions
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
    /// @notice Downcasts uint256 to uint128
    /// @param x The uint258 to be downcasted
    /// @return y The passed value, downcasted to uint128
    function toUint128(uint256 x) private pure returns (uint128 y) {
        require((y = uint128(x)) == x);
    }

    /// @notice Computes the amount of liquidity received for a given amount of token0 and price range
    /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount0 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount0(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96);
        return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96));
    }

    /// @notice Computes the amount of liquidity received for a given amount of token1 and price range
    /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount1 The amount1 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount1(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96));
    }

    /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
    /// pool prices and the prices at the tick boundaries
    /// @param sqrtRatioX96 A sqrt price representing the current pool prices
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount of token0 being sent in
    /// @param amount1 The amount of token1 being sent in
    /// @return liquidity The maximum amount of liquidity received
    function getLiquidityForAmounts(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
            uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);

            liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
        } else {
            liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
        }
    }

    /// @notice Computes the amount of token0 for a given amount of liquidity and a price range
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount0 The amount of token0
    function getAmount0ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return
            FullMath.mulDiv(
                uint256(liquidity) << FixedPoint96.RESOLUTION,
                sqrtRatioBX96 - sqrtRatioAX96,
                sqrtRatioBX96
            ) / sqrtRatioAX96;
    }

    /// @notice Computes the amount of token1 for a given amount of liquidity and a price range
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount1 The amount of token1
    function getAmount1ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
    }

    /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current
    /// pool prices and the prices at the tick boundaries
    /// @param sqrtRatioX96 A sqrt price representing the current pool prices
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function getAmountsForLiquidity(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0, uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
            amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
        } else {
            amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        }
    }
}
"
    },
    "src/IUniswapV3PositionManager.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma abicoder v2;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "lib/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "lib/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import "lib/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
import "lib/v3-periphery/contracts/interfaces/ISwapRouter.sol";

interface IUniswapV3PositionManager is IERC721Receiver {
    /// @notice Emitted when liquidity is removed from a position
    event LiquidityRemoved(
        uint256 indexed tokenId,
        uint128 liquidityRemoved,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Returns the address of the anchor token
    function anchorToken() external view returns (address);

    /// @notice Returns the Uniswap V3 factory address
    function factory() external view returns (IUniswapV3Factory);

    /// @notice Returns the nonfungible position manager address
    function nonfungiblePositionManager()
        external
        view
        returns (INonfungiblePositionManager);

    /// @notice Returns the current position token ID (0 if no position)
    function positionTokenID() external view returns (uint256);

    /// @notice Returns the other token address (paired with anchor token)
    function otherToken() external view returns (address);

    /// @notice Returns token0 address (lower address of the pair)
    function token0() external view returns (address);

    /// @notice Returns token1 address (higher address of the pair)
    function token1() external view returns (address);

    /// @notice Returns the fee tier for the pool
    function feeTier() external view returns (uint24);

    /// @notice Returns the default lower tick for positions
    function defaultTickLower() external view returns (int24);

    /// @notice Returns the default upper tick for positions
    function defaultTickUpper() external view returns (int24);

    /// @notice Sets the default tick range for new positions
    /// @param _lower The lower tick boundary
    /// @param _upper The upper tick boundary
    function setDefaultTicks(int24 _lower, int24 _upper) external;

    /// @notice Sets TWAP validation settings
    /// @param _seconds The TWAP period in seconds (0 to disable)
    /// @param _maxTickDifference The maximum allowed tick difference
    function setTWAPSettings(
        uint32 _seconds,
        uint32 _maxTickDifference
    ) external;

    /// @notice Creates a new position or compounds an existing one
    function maintenance() external;

    function removeLiquidityByAnchorTokenAmount(
        uint256 anchorTokenAmount,
        address tokenRecipient,
        uint16 slippage
    ) external returns (uint256 anchorAmountOut);

    /// @notice Removes liquidity from the position by basis points
    /// @param basisPoints The percentage to remove (10000 = 100%)
    /// @param tokenRecipient The address to receive the tokens
    /// @param slippage Maximum slippage in basis points (e.g., 100 = 1%)
    /// @return amount0 The amount of token0 received
    /// @return amount1 The amount of token1 received
    /// @return anchorAmountOut The total amount received in anchor token terms
    function removeLiquidityByBasisPoints(
        uint16 basisPoints,
        address tokenRecipient,
        uint16 slippage
    )
        external
        returns (uint256 amount0, uint256 amount1, uint256 anchorAmountOut);

    /// @notice Transfers ERC20 tokens from the contract
    /// @param token The token address to transfer
    /// @param recipient The recipient address
    /// @param amount The amount to transfer
    function transferERC20(
        address token,
        address recipient,
        uint256 amount
    ) external;

    /// @notice Executes a delegatecall to another contract
    /// @param target The target contract address
    /// @param data The calldata to execute
    /// @return The return data from the delegatecall
    function delegate(
        address target,
        bytes calldata data
    ) external returns (bytes memory);

    /// @notice Gets the uncollected fees for the current position
    /// @return amount0 The amount of token0 fees owed
    /// @return amount1 The amount of token1 fees owed
    function getPositionFees()
        external
        view
        returns (uint128 amount0, uint128 amount1);
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Address.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity >=0.6.2 <0.8.0;

/**
 * @dev Collection of

Tags:
ERC20, ERC721, ERC165, Proxy, Mintable, Burnable, Non-Fungible, Swap, Liquidity, Upgradeable, Factory, Oracle|addr:0x049dd8e489b81fb93300b6434ce663f184ea50f9|verified:true|block:23706166|tx:0x21956fd5cad1bc1fa19f27a483b13faf6418d4e6daf7f9a5bc8e33c4d968bfca|first_check:1762024258

Submitted on: 2025-11-01 20:10:59

Comments

Log in to comment.

No comments yet.