// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import {TickMath} from "@aperture/uni-v3-lib/TickMath.sol"; import {LiquidityAmounts} from "@aperture/uni-v3-lib/LiquidityAmounts.sol"; import {ThreePositionStrategy} from "../../src/abstracts/ThreePositionStrategy.sol"; /** * @title LiquidityBoundaryHelper * @notice Helper library for calculating safe trade sizes within liquidity boundaries * @dev Prevents trades that would exceed available liquidity and cause SPL errors */ library LiquidityBoundaryHelper { /** * @notice Calculates the maximum ETH amount that can be traded (buy HARB) without exceeding position liquidity limits * @param pool The Uniswap V3 pool * @param liquidityManager The liquidity manager contract * @param token0isWeth Whether token0 is WETH * @return maxEthAmount Maximum ETH that can be safely traded */ function calculateBuyLimit( IUniswapV3Pool pool, ThreePositionStrategy liquidityManager, bool token0isWeth ) internal view returns (uint256 maxEthAmount) { (, int24 currentTick,,,,,) = pool.slot0(); // Get position data (uint128 anchorLiquidity, int24 anchorLower, int24 anchorUpper) = liquidityManager.positions(ThreePositionStrategy.Stage.ANCHOR); (uint128 discoveryLiquidity, int24 discoveryLower, int24 discoveryUpper) = liquidityManager.positions(ThreePositionStrategy.Stage.DISCOVERY); // If no positions exist, return 0 (no safe limit) if (anchorLiquidity == 0 && discoveryLiquidity == 0) { return 0; } uint256 maxEth = 0; // Check anchor position if (currentTick >= anchorLower && currentTick < anchorUpper && anchorLiquidity > 0) { uint160 currentSqrtPrice = TickMath.getSqrtRatioAtTick(currentTick); uint160 upperSqrtPrice = TickMath.getSqrtRatioAtTick(anchorUpper); if (token0isWeth) { maxEth = LiquidityAmounts.getAmount0ForLiquidity(currentSqrtPrice, upperSqrtPrice, anchorLiquidity); } else { maxEth = LiquidityAmounts.getAmount1ForLiquidity(currentSqrtPrice, upperSqrtPrice, anchorLiquidity); } } // Check discovery position else if (currentTick >= discoveryLower && currentTick < discoveryUpper && discoveryLiquidity > 0) { uint160 currentSqrtPrice = TickMath.getSqrtRatioAtTick(currentTick); uint160 upperSqrtPrice = TickMath.getSqrtRatioAtTick(discoveryUpper); if (token0isWeth) { maxEth = LiquidityAmounts.getAmount0ForLiquidity(currentSqrtPrice, upperSqrtPrice, discoveryLiquidity); } else { maxEth = LiquidityAmounts.getAmount1ForLiquidity(currentSqrtPrice, upperSqrtPrice, discoveryLiquidity); } } // Apply safety margin (90% of calculated max) return (maxEth * 9) / 10; } /** * @notice Calculates the maximum HARB amount that can be traded (sell HARB) without exceeding position liquidity limits * @param pool The Uniswap V3 pool * @param liquidityManager The liquidity manager contract * @param token0isWeth Whether token0 is WETH * @return maxHarbAmount Maximum HARB that can be safely traded */ function calculateSellLimit( IUniswapV3Pool pool, ThreePositionStrategy liquidityManager, bool token0isWeth ) internal view returns (uint256 maxHarbAmount) { (, int24 currentTick,,,,,) = pool.slot0(); // Get position data (uint128 anchorLiquidity, int24 anchorLower, int24 anchorUpper) = liquidityManager.positions(ThreePositionStrategy.Stage.ANCHOR); (uint128 floorLiquidity, int24 floorLower, int24 floorUpper) = liquidityManager.positions(ThreePositionStrategy.Stage.FLOOR); // If no positions exist, return 0 (no safe limit) if (anchorLiquidity == 0 && floorLiquidity == 0) { return 0; } uint256 maxHarb = 0; // Check anchor position if (currentTick >= anchorLower && currentTick < anchorUpper && anchorLiquidity > 0) { uint160 currentSqrtPrice = TickMath.getSqrtRatioAtTick(currentTick); uint160 lowerSqrtPrice = TickMath.getSqrtRatioAtTick(anchorLower); if (token0isWeth) { maxHarb = LiquidityAmounts.getAmount1ForLiquidity(lowerSqrtPrice, currentSqrtPrice, anchorLiquidity); } else { maxHarb = LiquidityAmounts.getAmount0ForLiquidity(lowerSqrtPrice, currentSqrtPrice, anchorLiquidity); } } // Check floor position else if (currentTick >= floorLower && currentTick < floorUpper && floorLiquidity > 0) { uint160 currentSqrtPrice = TickMath.getSqrtRatioAtTick(currentTick); uint160 lowerSqrtPrice = TickMath.getSqrtRatioAtTick(floorLower); if (token0isWeth) { maxHarb = LiquidityAmounts.getAmount1ForLiquidity(lowerSqrtPrice, currentSqrtPrice, floorLiquidity); } else { maxHarb = LiquidityAmounts.getAmount0ForLiquidity(lowerSqrtPrice, currentSqrtPrice, floorLiquidity); } } // Apply safety margin (90% of calculated max) return (maxHarb * 9) / 10; } }