harb/onchain/test/ReplayProfitableScenario.t.sol

240 lines
9.3 KiB
Solidity
Raw Permalink Normal View History

// 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)));
}
}