harb/onchain/test/helpers/LiquidityBoundaryHelper.sol
2025-08-09 18:03:31 +02:00

118 lines
No EOL
5.4 KiB
Solidity

// 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;
}
}