Description
-
The README states the hook provides protection against "excessive selling" during token launches, implying sell-side protection to prevent dumps.
-
The implementation applies penalties to all swaps regardless of direction (zeroForOne). Both buys and sells are counted toward limits and trigger cooldowns.
function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
uint256 swapAmount =
params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);
uint256 maxSwapAmount = (initialLiquidity * phaseLimitBps) / 10000;
if (!applyPenalty && addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
applyPenalty = true;
}
}
Risk
Likelihood:
-
Occurs on every buy transaction, not just sells
-
Any user buying the token during launch phases faces the same restrictions as sellers
Impact:
-
Legitimate buyers are penalized and limited, reducing buying pressure
-
Discourages token accumulation during fair launch period
-
May reduce price discovery effectiveness as buyers are throttled
-
Contradicts stated goal of preventing "excessive selling"
Proof of Concept
Buyer executes multiple swaps - cooldowns and limits apply to buys the same as sells.
function test_BuysArePenalizedToo() public {
vm.deal(user1, 10 ether);
SwapParams memory buyParams = SwapParams({
zeroForOne: true,
amountSpecified: -int256(0.1 ether),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
vm.startPrank(user1);
swapRouter.swap{value: 0.1 ether}(key, buyParams, testSettings, ZERO_BYTES);
swapRouter.swap{value: 0.1 ether}(key, buyParams, testSettings, ZERO_BYTES);
vm.stopPrank();
uint256 cooldownEnd = antiBotHook.getUserCooldownEnd(address(swapRouter));
assertGt(cooldownEnd, block.number, "Buyers face cooldown too");
}
Recommended Mitigation
Check params.zeroForOne to determine swap direction and only apply limits to sells.
function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
// ... phase logic ...
+ // Determine if this is a sell (token -> ETH)
+ // Assuming token is currency1, sell is when zeroForOne = false
+ bool isSell = !params.zeroForOne;
+
+ // Only apply anti-bot limits to sells
+ if (!isSell) {
+ // This is a buy - no restrictions
+ return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, LPFeeLibrary.OVERRIDE_FEE_FLAG);
+ }
uint256 swapAmount =
params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);