// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; import {TickMath} from "@aperture/uni-v3-lib/TickMath.sol"; import "../../src/interfaces/IWETH9.sol"; import {Kraiken} from "../../src/Kraiken.sol"; /** * @title UniswapTestBase * @dev Base contract for Uniswap V3 testing, providing reusable swap logic. */ abstract contract UniswapTestBase is Test { address account = makeAddr("alice"); IUniswapV3Pool public pool; IWETH9 public weth; Kraiken public harberg; bool public token0isWeth; /** * @dev Performs a swap in the Uniswap V3 pool. * @param amount The amount to swap. * @param isBuy True if buying WETH, false if selling. */ function performSwap(uint256 amount, bool isBuy) public { uint160 limit; // Determine the swap direction bool zeroForOne = isBuy ? token0isWeth : !token0isWeth; if (isBuy) { vm.prank(account); weth.transfer(address(this), amount); } else { vm.prank(account); harberg.approve(address(this), amount); } // Set the sqrtPriceLimitX96 based on the swap direction // Get current price to set appropriate limits (uint160 currentSqrtPrice,,,,,,) = pool.slot0(); if (zeroForOne) { // Swapping token0 for token1 - price goes down // sqrtPriceLimitX96 must be less than current price but greater than MIN_SQRT_RATIO uint160 minAllowedLimit = TickMath.MIN_SQRT_RATIO + 1; // Safety check: ensure we have enough room to set a valid limit if (currentSqrtPrice <= minAllowedLimit + 1) { // Emergency fallback: current price is at or very close to minimum // We can't safely set a limit, so use the minimum possible limit = minAllowedLimit; } else { // Calculate a safe limit that's 90% of the way from min to current // This ensures we don't hit the boundaries uint160 range = currentSqrtPrice - minAllowedLimit; uint160 calculatedLimit = minAllowedLimit + (range * 9) / 10; // Final validation if (calculatedLimit >= currentSqrtPrice) { limit = currentSqrtPrice - 1; } else if (calculatedLimit <= minAllowedLimit) { limit = minAllowedLimit; } else { limit = calculatedLimit; } } } else { // Swapping token1 for token0 - price goes up // sqrtPriceLimitX96 must be greater than current price but less than MAX_SQRT_RATIO uint160 maxAllowedLimit = TickMath.MAX_SQRT_RATIO - 1; // Safety check: ensure we have enough room to set a valid limit if (currentSqrtPrice >= maxAllowedLimit - 1) { // Emergency fallback: current price is at or very close to maximum // We can't safely set a limit, so use the maximum possible limit = maxAllowedLimit; } else { // Calculate a safe limit that's 10% of the way from current to max // This ensures we don't hit the boundaries uint160 range = maxAllowedLimit - currentSqrtPrice; uint160 calculatedLimit = currentSqrtPrice + (range * 1) / 10; // Final validation if (calculatedLimit <= currentSqrtPrice) { limit = currentSqrtPrice + 1; } else if (calculatedLimit >= maxAllowedLimit) { limit = maxAllowedLimit; } else { limit = calculatedLimit; } } } pool.swap(account, zeroForOne, int256(amount), limit, abi.encode(account, int256(amount), isBuy)); } /** * @notice Performs a swap with aggressive price limits for extreme price normalization * @param amount The amount to swap * @param isBuy True if buying HARB, false if selling HARB */ function performSwapWithAggressiveLimits(uint256 amount, bool isBuy) internal { uint160 limit; // Determine the swap direction bool zeroForOne = isBuy ? token0isWeth : !token0isWeth; if (isBuy) { vm.prank(account); weth.transfer(address(this), amount); } else { vm.prank(account); harberg.approve(address(this), amount); } // Set aggressive price limits that allow price to move to liquidity ranges if (zeroForOne) { // Swapping token0 for token1 - price goes down // Use very aggressive limit close to MIN_SQRT_RATIO limit = TickMath.MIN_SQRT_RATIO + 1; } else { // Swapping token1 for token0 - price goes up // Use very aggressive limit close to MAX_SQRT_RATIO limit = TickMath.MAX_SQRT_RATIO - 1; } pool.swap(account, zeroForOne, int256(amount), limit, abi.encode(account, int256(amount), isBuy)); } /** * @dev The Uniswap V3 swap callback. */ function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) external { // Handle the case where no swap occurred (both deltas are 0) if (amount0Delta == 0 && amount1Delta == 0) { return; } require(amount0Delta > 0 || amount1Delta > 0); (address seller,, bool isBuy) = abi.decode(_data, (address, uint256, bool)); (, uint256 amountToPay) = amount0Delta > 0 ? (!token0isWeth, uint256(amount0Delta)) : (token0isWeth, uint256(amount1Delta)); if (isBuy) { weth.transfer(msg.sender, amountToPay); } else { require(harberg.transferFrom(seller, msg.sender, amountToPay), "Transfer failed"); } } /// @notice Callback function that Uniswap V3 calls for liquidity actions requiring minting or burning of tokens. /// @param amount0Owed The amount of token0 owed for the liquidity provision. /// @param amount1Owed The amount of token1 owed for the liquidity provision. /// @dev This function mints Kraiken tokens as needed and handles WETH deposits for ETH conversions during liquidity interactions. function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external { // CallbackValidation.verifyCallback(factory, poolKey); // take care of harb uint256 harbPulled = token0isWeth ? amount1Owed : amount0Owed; if (harbPulled > 0) { harberg.mint(harbPulled); harberg.transfer(msg.sender, harbPulled); } // pack ETH uint256 ethOwed = token0isWeth ? amount0Owed : amount1Owed; if (weth.balanceOf(address(this)) < ethOwed) { weth.deposit{value: address(this).balance}(); } if (ethOwed > 0) { weth.transfer(msg.sender, amount1Owed); } } // ======================================== // EXTREME PRICE HANDLING // ======================================== // Safety margin to prevent tick boundary violations (conservative approach) int24 constant TICK_BOUNDARY_SAFETY_MARGIN = 15000; // Price normalization constants uint256 constant NORMALIZATION_HARB_PERCENTAGE = 100; // 1% of HARB balance uint256 constant NORMALIZATION_ETH_AMOUNT = 0.01 ether; // Fixed ETH amount for normalization uint256 constant MAX_NORMALIZATION_ATTEMPTS = 3; // Prevent infinite loops uint256 constant PRICE_LIMIT_BUFFER = 1000; // Buffer from sqrt price limits /** * @notice Handles extreme price conditions by executing normalizing trades * @dev This function should be called before any recenter operation to ensure * the price is within safe boundaries for liquidity position creation */ function handleExtremePrice() internal { uint256 attempts = 0; while (attempts < MAX_NORMALIZATION_ATTEMPTS) { (, int24 currentTick,,,,,) = pool.slot0(); if (currentTick >= TickMath.MAX_TICK - TICK_BOUNDARY_SAFETY_MARGIN) { _executeNormalizingTrade(true); // Move price down attempts++; } else if (currentTick <= TickMath.MIN_TICK + TICK_BOUNDARY_SAFETY_MARGIN) { _executeNormalizingTrade(false); // Move price up attempts++; } else { // Price is now safe, exit loop break; } } } /** * @notice Executes a small trade to move price away from tick boundaries * @param moveDown True to move price down (sell HARB), false to move price up (buy HARB) */ function _executeNormalizingTrade(bool moveDown) internal { if (moveDown) { // Need to move price DOWN (reduce HARB price) // This means: sell HARB for ETH (increase HARB supply in pool) uint256 harbBalance = harberg.balanceOf(account); if (harbBalance > 0) { // Use 1% of account's HARB balance (conservative approach like original) uint256 harbToSell = harbBalance / NORMALIZATION_HARB_PERCENTAGE; if (harbToSell == 0) harbToSell = 1; vm.prank(account); harberg.transfer(address(this), harbToSell); harberg.approve(address(pool), harbToSell); // Sell HARB for ETH with aggressive price limits for normalization performSwapWithAggressiveLimits(harbToSell, false); } } else { // Need to move price UP (increase HARB price) // This means: buy HARB with ETH (reduce HARB supply in pool) uint256 ethBalance = weth.balanceOf(account); if (ethBalance > 0) { // Use small amount for normalization (like original) uint256 ethToBuy = NORMALIZATION_ETH_AMOUNT; if (ethToBuy > ethBalance) ethToBuy = ethBalance; // Buy HARB with ETH with aggressive price limits for normalization performSwapWithAggressiveLimits(ethToBuy, true); } } } }