harb/onchain/analysis/helpers/SwapExecutor.sol

83 lines
3.2 KiB
Solidity
Raw Normal View History

2025-08-09 18:03:31 +02:00
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;
import { Kraiken } from "../../src/Kraiken.sol";
import { ThreePositionStrategy } from "../../src/abstracts/ThreePositionStrategy.sol";
import { IWETH9 } from "../../src/interfaces/IWETH9.sol";
import { LiquidityBoundaryHelper } from "../../test/helpers/LiquidityBoundaryHelper.sol";
import { TickMath } from "@aperture/uni-v3-lib/TickMath.sol";
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
2025-08-09 18:03:31 +02:00
/**
* @title SwapExecutor
* @notice Helper contract to execute swaps on Uniswap V3 pools for analysis scripts.
* @dev Supports two modes:
* - Capped (default): trade sizes limited by LiquidityBoundaryHelper
* - Uncapped: trades go directly to pool with no size caps, matching production behavior
* Set uncapped=true via constructor to allow trades through position boundaries.
2025-08-09 18:03:31 +02:00
*/
contract SwapExecutor {
IUniswapV3Pool public pool;
IWETH9 public weth;
Kraiken public harberg;
bool public token0isWeth;
2025-08-23 16:10:05 +02:00
ThreePositionStrategy public liquidityManager;
bool public uncapped;
constructor(IUniswapV3Pool _pool, IWETH9 _weth, Kraiken _harberg, bool _token0isWeth, ThreePositionStrategy _liquidityManager, bool _uncapped) {
2025-08-09 18:03:31 +02:00
pool = _pool;
weth = _weth;
harberg = _harberg;
token0isWeth = _token0isWeth;
2025-08-23 16:10:05 +02:00
liquidityManager = _liquidityManager;
uncapped = _uncapped;
2025-08-09 18:03:31 +02:00
}
2025-08-23 22:32:41 +02:00
function executeBuy(uint256 amount, address recipient) external returns (uint256) {
uint256 safeAmount = amount;
if (!uncapped) {
uint256 maxBuyAmount = LiquidityBoundaryHelper.calculateBuyLimit(pool, liquidityManager, token0isWeth);
safeAmount = amount > maxBuyAmount ? maxBuyAmount : amount;
}
2025-08-23 22:32:41 +02:00
if (safeAmount == 0) return 0;
2025-08-09 18:03:31 +02:00
bool zeroForOne = token0isWeth;
uint160 sqrtPriceLimitX96 = zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
pool.swap(recipient, zeroForOne, int256(safeAmount), sqrtPriceLimitX96, "");
2025-08-23 22:32:41 +02:00
return safeAmount;
2025-08-09 18:03:31 +02:00
}
2025-08-23 22:32:41 +02:00
function executeSell(uint256 amount, address recipient) external returns (uint256) {
uint256 safeAmount = amount;
if (!uncapped) {
uint256 maxSellAmount = LiquidityBoundaryHelper.calculateSellLimit(pool, liquidityManager, token0isWeth);
safeAmount = amount > maxSellAmount ? maxSellAmount : amount;
}
2025-08-23 22:32:41 +02:00
if (safeAmount == 0) return 0;
2025-08-09 18:03:31 +02:00
bool zeroForOne = !token0isWeth;
uint160 sqrtPriceLimitX96 = zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
pool.swap(recipient, zeroForOne, int256(safeAmount), sqrtPriceLimitX96, "");
2025-08-23 22:32:41 +02:00
return safeAmount;
2025-08-09 18:03:31 +02:00
}
2025-08-09 18:03:31 +02:00
// Callback required for Uniswap V3 swaps
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external {
2025-08-09 18:03:31 +02:00
require(msg.sender == address(pool), "Unauthorized callback");
2025-08-09 18:03:31 +02:00
if (amount0Delta > 0) {
IWETH9(pool.token0()).transfer(address(pool), uint256(amount0Delta));
}
if (amount1Delta > 0) {
IWETH9(pool.token1()).transfer(address(pool), uint256(amount1Delta));
}
}
}