// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import {IWETH9} from "../../src/interfaces/IWETH9.sol"; import {Kraiken} from "../../src/Kraiken.sol"; import {TickMath} from "@aperture/uni-v3-lib/TickMath.sol"; import {LiquidityBoundaryHelper} from "../../test/helpers/LiquidityBoundaryHelper.sol"; import {ThreePositionStrategy} from "../../src/abstracts/ThreePositionStrategy.sol"; /** * @title SwapExecutor * @notice Helper contract to execute swaps on Uniswap V3 pools for analysis scripts * @dev Extracted from analysis scripts to avoid code duplication */ contract SwapExecutor { IUniswapV3Pool public pool; IWETH9 public weth; Kraiken public harberg; bool public token0isWeth; ThreePositionStrategy public liquidityManager; constructor(IUniswapV3Pool _pool, IWETH9 _weth, Kraiken _harberg, bool _token0isWeth, ThreePositionStrategy _liquidityManager) { pool = _pool; weth = _weth; harberg = _harberg; token0isWeth = _token0isWeth; liquidityManager = _liquidityManager; } function executeBuy(uint256 amount, address recipient) external returns (uint256) { // Calculate maximum safe buy amount based on liquidity uint256 maxBuyAmount = LiquidityBoundaryHelper.calculateBuyLimit(pool, liquidityManager, token0isWeth); // Cap the amount to the safe limit uint256 safeAmount = amount > maxBuyAmount ? maxBuyAmount : amount; // Skip if amount is zero if (safeAmount == 0) return 0; // For buying HARB with WETH, we're swapping in the direction that increases HARB price // zeroForOne = true if WETH is token0, false if WETH is token1 bool zeroForOne = token0isWeth; // Set appropriate price limit based on swap direction uint160 sqrtPriceLimitX96; if (zeroForOne) { // Price goes down (in terms of token0/token1 ratio) sqrtPriceLimitX96 = TickMath.MIN_SQRT_RATIO + 1; } else { // Price goes up sqrtPriceLimitX96 = TickMath.MAX_SQRT_RATIO - 1; } pool.swap( recipient, zeroForOne, int256(safeAmount), sqrtPriceLimitX96, "" ); return safeAmount; } function executeSell(uint256 amount, address recipient) external returns (uint256) { // Calculate maximum safe sell amount based on liquidity uint256 maxSellAmount = LiquidityBoundaryHelper.calculateSellLimit(pool, liquidityManager, token0isWeth); // Cap the amount to the safe limit uint256 safeAmount = amount > maxSellAmount ? maxSellAmount : amount; // Skip if amount is zero if (safeAmount == 0) return 0; // For selling HARB for WETH, we're swapping in the direction that decreases HARB price // zeroForOne = false if WETH is token0, true if WETH is token1 bool zeroForOne = !token0isWeth; // Set appropriate price limit based on swap direction uint160 sqrtPriceLimitX96; if (zeroForOne) { // Price goes down (in terms of token0/token1 ratio) sqrtPriceLimitX96 = TickMath.MIN_SQRT_RATIO + 1; } else { // Price goes up sqrtPriceLimitX96 = TickMath.MAX_SQRT_RATIO - 1; } pool.swap( recipient, zeroForOne, int256(safeAmount), sqrtPriceLimitX96, "" ); return safeAmount; } // Callback required for Uniswap V3 swaps function uniswapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata ) external { require(msg.sender == address(pool), "Unauthorized callback"); if (amount0Delta > 0) { IWETH9(pool.token0()).transfer(address(pool), uint256(amount0Delta)); } if (amount1Delta > 0) { IWETH9(pool.token1()).transfer(address(pool), uint256(amount1Delta)); } } }