diff --git a/onchain/test/LiquidityManager.t.sol b/onchain/test/LiquidityManager.t.sol index d8a2d78..bd6727c 100644 --- a/onchain/test/LiquidityManager.t.sol +++ b/onchain/test/LiquidityManager.t.sol @@ -25,6 +25,7 @@ import {LiquidityManager} from "../src/LiquidityManager.sol"; import {ThreePositionStrategy} from "../src/abstracts/ThreePositionStrategy.sol"; import "../src/helpers/UniswapHelpers.sol"; import {UniswapTestBase} from "./helpers/UniswapTestBase.sol"; +import {LiquidityManagerSetupHelper} from "./helpers/TestBase.sol"; import "../src/Optimizer.sol"; import "../test/mocks/MockOptimizer.sol"; @@ -63,6 +64,8 @@ contract Dummy { } contract LiquidityManagerTest is UniswapTestBase { + // Constant address for recenter operations + address constant RECENTER_CALLER = address(0x7777); // Setup configuration bool constant DEFAULT_TOKEN0_IS_WETH = false; uint256 constant DEFAULT_ACCOUNT_BALANCE = 300 ether; @@ -75,7 +78,11 @@ contract LiquidityManagerTest is UniswapTestBase { IUniswapV3Factory factory; Stake stake; LiquidityManager lm; + Optimizer optimizer; address feeDestination = makeAddr("fees"); + + // Setup helper instance + LiquidityManagerSetupHelper setupHelper; struct Response { uint256 ethFloor; @@ -104,71 +111,32 @@ contract LiquidityManagerTest is UniswapTestBase { /// @notice Main setup function with custom token order /// @param token0shouldBeWeth Whether token0 should be WETH (affects pool pair ordering) function setUpCustomToken0(bool token0shouldBeWeth) public { - _deployFactory(); - _deployTokensWithOrder(token0shouldBeWeth); - _createAndInitializePool(); - _deployProtocolContracts(); - _configurePermissions(); - } - - /// @notice Deploys the Uniswap factory - function _deployFactory() internal { - factory = UniswapHelpers.deployUniswapFactory(); - } - - /// @notice Deploys tokens in the specified order - /// @param token0shouldBeWeth Whether token0 should be WETH - function _deployTokensWithOrder(bool token0shouldBeWeth) internal { - bool setupComplete = false; - uint256 retryCount = 0; - - while (!setupComplete && retryCount < 5) { - // Clean slate if retrying - if (retryCount > 0) { - deployDummies(1); // Deploy a dummy contract to shift addresses - } - - weth = IWETH9(address(new WETH())); - harberg = new Kraiken("HARB", "HARB"); - - // Check if the setup meets the required condition - if (token0shouldBeWeth == address(weth) < address(harberg)) { - setupComplete = true; - } else { - // Clear current instances for re-deployment - delete weth; - delete harberg; - retryCount++; - } + // Create setup helper if not already created + if (address(setupHelper) == address(0)) { + setupHelper = new LiquidityManagerSetupHelper(feeDestination); } - require(setupComplete, "Setup failed to meet the condition after several retries"); - } - - /// @notice Creates and initializes the Uniswap pool - function _createAndInitializePool() internal { - pool = IUniswapV3Pool(factory.createPool(address(weth), address(harberg), FEE)); - token0isWeth = address(weth) < address(harberg); - pool.initializePoolFor1Cent(token0isWeth); - } - - // Store optimizer reference for analysis - Optimizer public optimizer; - - /// @notice Deploys protocol contracts (Stake, Optimizer, LiquidityManager) - function _deployProtocolContracts() internal { - stake = new Stake(address(harberg), feeDestination); - optimizer = Optimizer(address(new MockOptimizer())); - optimizer.initialize(address(harberg), address(stake)); - lm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); - lm.setFeeDestination(feeDestination); - } - - /// @notice Configures permissions and initial funding - function _configurePermissions() internal { - harberg.setStakingPool(address(stake)); - vm.prank(feeDestination); - harberg.setLiquidityManager(address(lm)); - vm.deal(address(lm), INITIAL_LM_ETH_BALANCE); + + // Use helper to set up environment + ( + IUniswapV3Factory _factory, + IUniswapV3Pool _pool, + IWETH9 _weth, + Kraiken _harberg, + Stake _stake, + LiquidityManager _lm, + Optimizer _optimizer, + bool _token0isWeth + ) = setupHelper.setupEnvironment(token0shouldBeWeth, RECENTER_CALLER); + + // Assign to state variables + factory = _factory; + pool = _pool; + weth = _weth; + harberg = _harberg; + stake = _stake; + lm = _lm; + optimizer = _optimizer; + token0isWeth = _token0isWeth; } /// @notice Intelligent recenter function that handles extreme price conditions @@ -194,6 +162,7 @@ contract LiquidityManagerTest is UniswapTestBase { /// @notice Attempts the recenter operation with proper error handling /// @param last Whether this is the last attempt (affects error handling) function _attemptRecenter(bool last) internal { + vm.prank(RECENTER_CALLER); try lm.recenter() returns (bool isUp) { _validateRecenterResult(isUp); } catch Error(string memory reason) { @@ -414,7 +383,7 @@ contract LiquidityManagerTest is UniswapTestBase { /// @notice Grant recenter access for testing (commonly needed) function _grantRecenterAccess() internal { vm.prank(feeDestination); - lm.setRecenterAccess(address(this)); + lm.setRecenterAccess(RECENTER_CALLER); } /// @notice Setup with custom parameters but standard flow @@ -434,10 +403,6 @@ contract LiquidityManagerTest is UniswapTestBase { vm.prank(account); weth.deposit{value: accountBalance}(); - // Grant recenter access to bypass oracle checks - vm.prank(feeDestination); - lm.setRecenterAccess(address(this)); - // Setup initial liquidity recenter(false); } @@ -461,7 +426,7 @@ contract LiquidityManagerTest is UniswapTestBase { // Grant recenter access vm.prank(feeDestination); - lm.setRecenterAccess(address(this)); + lm.setRecenterAccess(RECENTER_CALLER); // Setup approvals without creating blocking positions vm.startPrank(account); diff --git a/onchain/test/helpers/TestBase.sol b/onchain/test/helpers/TestBase.sol index f3f2b9d..f6c0775 100644 --- a/onchain/test/helpers/TestBase.sol +++ b/onchain/test/helpers/TestBase.sol @@ -3,10 +3,26 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../../src/abstracts/ThreePositionStrategy.sol"; +import "@aperture/uni-v3-lib/TickMath.sol"; +import {WETH} from "solmate/tokens/WETH.sol"; +import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol"; +import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; +import "../../src/interfaces/IWETH9.sol"; +import {Kraiken} from "../../src/Kraiken.sol"; +import {Stake} from "../../src/Stake.sol"; +import {LiquidityManager} from "../../src/LiquidityManager.sol"; +import "../../src/helpers/UniswapHelpers.sol"; +import "../../src/Optimizer.sol"; +import "../../test/mocks/MockOptimizer.sol"; + +// Constants +uint24 constant FEE = uint24(10_000); // 1% fee +uint256 constant INITIAL_LM_ETH_BALANCE = 50 ether; +uint256 constant ORACLE_UPDATE_INTERVAL = 5 hours; /** * @title TestBase - * @notice Base contract providing shared utilities and default parameters for tests + * @notice Base contract providing shared utilities, default parameters, and setup functions for tests and scripts * @dev Consolidates common test functionality to reduce code duplication */ abstract contract TestUtilities is Test { @@ -41,4 +57,203 @@ abstract contract TestUtilities is Test { function denormTR(uint256 normalizedTaxRate) internal pure returns (uint256) { return normalizedTaxRate * 97; } +} + +/** + * @title LiquidityManagerSetupHelper + * @notice Helper contract that provides common setup functionality for LiquidityManager tests and scripts + * @dev Extracted from LiquidityManager.t.sol to enable reuse without inheritance + */ +contract LiquidityManagerSetupHelper is TestUtilities { + using UniswapHelpers for IUniswapV3Pool; + + // Core contracts + IUniswapV3Factory public factory; + IUniswapV3Pool public pool; + IWETH9 public weth; + Kraiken public harberg; + Stake public stake; + LiquidityManager public lm; + Optimizer public optimizer; + + // State variables + bool public token0isWeth; + address public feeDestination; + + constructor(address _feeDestination) { + feeDestination = _feeDestination; + } + + /** + * @notice Deploy all contracts and set up the environment + * @param token0shouldBeWeth Whether WETH should be token0 + * @param recenterCaller Address that will be granted recenter access + * @return _factory The deployed Uniswap factory + * @return _pool The created Uniswap pool + * @return _weth The WETH token contract + * @return _harberg The Kraiken token contract + * @return _stake The staking contract + * @return _lm The liquidity manager contract + * @return _optimizer The optimizer contract + * @return _token0isWeth Whether token0 is WETH + */ + function setupEnvironment(bool token0shouldBeWeth, address recenterCaller) external returns ( + IUniswapV3Factory _factory, + IUniswapV3Pool _pool, + IWETH9 _weth, + Kraiken _harberg, + Stake _stake, + LiquidityManager _lm, + Optimizer _optimizer, + bool _token0isWeth + ) { + // Deploy factory + factory = UniswapHelpers.deployUniswapFactory(); + + // Deploy tokens in correct order + _deployTokensWithOrder(token0shouldBeWeth); + + // Create and initialize pool + _createAndInitializePool(); + + // Deploy protocol contracts + _deployProtocolContracts(); + + // Configure permissions + _configurePermissions(); + + // Grant recenter access to specified caller + vm.prank(feeDestination); + lm.setRecenterAccess(recenterCaller); + + return (factory, pool, weth, harberg, stake, lm, optimizer, token0isWeth); + } + + /** + * @notice Deploy tokens ensuring the desired ordering + * @param token0shouldBeWeth Whether WETH should be token0 + */ + function _deployTokensWithOrder(bool token0shouldBeWeth) internal { + bool setupComplete = false; + uint256 retryCount = 0; + + while (!setupComplete && retryCount < 5) { + // Clean slate if retrying + if (retryCount > 0) { + // Deploy a dummy contract to shift addresses + new WETH(); // Use WETH as dummy to avoid declaring new contract + } + + weth = IWETH9(address(new WETH())); + harberg = new Kraiken("KRAIKEN", "HARB"); + + // Check if the setup meets the required condition + if (token0shouldBeWeth == (address(weth) < address(harberg))) { + setupComplete = true; + } else { + // Clear current instances for re-deployment + delete weth; + delete harberg; + retryCount++; + } + } + require(setupComplete, "Setup failed to meet the condition after several retries"); + } + + /** + * @notice Create and initialize the Uniswap pool + */ + function _createAndInitializePool() internal { + pool = IUniswapV3Pool(factory.createPool(address(weth), address(harberg), FEE)); + token0isWeth = address(weth) < address(harberg); + pool.initializePoolFor1Cent(token0isWeth); + } + + /** + * @notice Deploy protocol contracts (Stake, Optimizer, LiquidityManager) + */ + function _deployProtocolContracts() internal { + stake = new Stake(address(harberg), feeDestination); + optimizer = Optimizer(address(new MockOptimizer())); + optimizer.initialize(address(harberg), address(stake)); + lm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); + lm.setFeeDestination(feeDestination); + } + + /** + * @notice Configure permissions and initial funding + */ + function _configurePermissions() internal { + harberg.setStakingPool(address(stake)); + vm.prank(feeDestination); + harberg.setLiquidityManager(address(lm)); + vm.deal(address(lm), INITIAL_LM_ETH_BALANCE); + } + + /** + * @notice Setup environment with specific optimizer + * @param token0shouldBeWeth Whether WETH should be token0 + * @param recenterCaller Address that will be granted recenter access + * @param optimizerAddress Address of the optimizer to use + * @return _factory The deployed Uniswap factory + * @return _pool The created Uniswap pool + * @return _weth The WETH token contract + * @return _harberg The Kraiken token contract + * @return _stake The staking contract + * @return _lm The liquidity manager contract + * @return _optimizer The optimizer contract + * @return _token0isWeth Whether token0 is WETH + */ + function setupEnvironmentWithOptimizer( + bool token0shouldBeWeth, + address recenterCaller, + address optimizerAddress + ) external returns ( + IUniswapV3Factory _factory, + IUniswapV3Pool _pool, + IWETH9 _weth, + Kraiken _harberg, + Stake _stake, + LiquidityManager _lm, + Optimizer _optimizer, + bool _token0isWeth + ) { + // Deploy factory + factory = UniswapHelpers.deployUniswapFactory(); + + // Deploy tokens in correct order + _deployTokensWithOrder(token0shouldBeWeth); + + // Create and initialize pool + _createAndInitializePool(); + + // Deploy protocol contracts with custom optimizer + stake = new Stake(address(harberg), feeDestination); + optimizer = Optimizer(optimizerAddress); + lm = new LiquidityManager(address(factory), address(weth), address(harberg), optimizerAddress); + lm.setFeeDestination(feeDestination); + + // Configure permissions + _configurePermissions(); + + // Grant recenter access to specified caller + vm.prank(feeDestination); + lm.setRecenterAccess(recenterCaller); + + return (factory, pool, weth, harberg, stake, lm, optimizer, token0isWeth); + } + + /** + * @notice Perform recenter with proper time warp and oracle updates + * @param liquidityManager The LiquidityManager instance to recenter + * @param caller The address that will call recenter + */ + function performRecenter(LiquidityManager liquidityManager, address caller) external { + // Update oracle time + vm.warp(block.timestamp + ORACLE_UPDATE_INTERVAL); + + // Perform recenter + vm.prank(caller); + liquidityManager.recenter(); + } } \ No newline at end of file