Description
The hook should track each user's swap amount individually to enforce per-user limits and cooldowns, ensuring fair token distribution during the launch phase.
The _beforeSwap function receives sender parameter which is the swap router contract address, not the actual user address. This causes all users to share the same limit and cooldown, completely breaking the anti-bot protection mechanism.
function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
uint256 maxSwapAmount = (initialLiquidity * phaseLimitBps) / 10000;
bool applyPenalty = false;
if (addressLastSwapBlock[sender] > 0) {
uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
if (blocksSinceLastSwap < phaseCooldown) {
applyPenalty = true;
}
}
if (!applyPenalty && addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
applyPenalty = true;
}
addressSwappedAmount[sender] += swapAmount;
addressLastSwapBlock[sender] = block.number;
}
Risk
Likelihood: High
-
Every swap goes through the swap router contract, so the sender parameter is always the router address
-
The vulnerability is present in 100% of transactions during Phase 1 and Phase 2
Impact: High
-
All users share a single global limit instead of having individual limits (Phase 1: 1% limit shared among all users instead of 1% per user)
-
First users to swap consume the shared limit, causing all subsequent users to receive penalties even if they individually haven't exceeded limits
-
Cooldown applies globally - if anyone swaps, all other users must wait the cooldown period before their next swap
Proof of Concept
Add this test to TokenLaunchHookUnit.t.sol, here it is shown how only the router address is tracked, not an individual user:
function test_PROOF_TracksRouterNotUsers() public {
address alice = makeAddr("alice");
address bob = makeAddr("bob");
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: -int256(0.001 ether),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
vm.deal(alice, 10 ether);
vm.prank(alice);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
vm.deal(bob, 10 ether);
vm.roll(block.number + 10);
vm.prank(bob);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
assertEq(antiBotHook.addressSwappedAmount(alice), 0, "Alice not tracked");
assertEq(antiBotHook.addressSwappedAmount(bob), 0, "Bob not tracked");
assertGt(antiBotHook.addressSwappedAmount(address(swapRouter)), 0.001 ether, "Only router tracked");
}
Recommended Mitigation
Uniswap V4 hooks don't receive the original user's address. The architecture needs to change to track actual users. Consider these options:
Option 1: Use tx.origin (simple but has limitations)
function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
+ address user = tx.origin; // Get actual user instead of router
// ... phase calculation ...
- if (addressLastSwapBlock[sender] > 0) {
+ if (addressLastSwapBlock[user] > 0) {
- uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
+ uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[user];
if (blocksSinceLastSwap < phaseCooldown) {
applyPenalty = true;
}
}
- if (!applyPenalty && addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
+ if (!applyPenalty && addressSwappedAmount[user] + swapAmount > maxSwapAmount) {
applyPenalty = true;
}
- addressSwappedAmount[sender] += swapAmount;
- addressLastSwapBlock[sender] = block.number;
+ addressSwappedAmount[user] += swapAmount;
+ addressLastSwapBlock[user] = block.number;
// ...
}
Note: Using tx.origin works for EOAs but may have issues with smart contract wallets and can be bypassed by contracts. Consider implementing additional validation or alternative tracking mechanisms based on your security requirements.