Vanguard

First Flight #56
Beginner FriendlyDeFiFoundry
0 EXP
Submission Details
Impact: high
Likelihood: high

Per-Address Swap Tracking Tracks Router

Author Revealed upon completion

Description

  • The TokenLaunchHook is designed to track individual user swap amounts and enforce cooldowns to prevent bot activity during token launches. Each user should have their own addressSwappedAmount and addressLastSwapBlock entries.

  • The _beforeSwap function receives sender as a parameter, but in Uniswap V4's architecture, this sender is the swap router contract address, not the actual user initiating the transaction. All swap tracking is therefore accumulated against the router address.

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
// ... phase logic ...
// @> sender is the swap router, NOT the actual user (msg.sender's origin)
addressSwappedAmount[sender] += swapAmount;
addressLastSwapBlock[sender] = block.number;
// @> Cooldown check uses router address
if (addressLastSwapBlock[sender] > 0) {
uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
if (blocksSinceLastSwap < phaseCooldown) {
applyPenalty = true;
}
}
// ...
}

Risk

Likelihood:

  • Every swap through the standard Uniswap V4 swap router will track against the router's address rather than individual users

  • This occurs on 100% of swaps - no special conditions required

Impact:

  • All users share the same swap limit bucket (the router's), meaning the collective activity triggers penalties instead of individual limits

  • Cooldowns apply to the router, so if any user swaps, ALL subsequent users must wait the cooldown period

  • Bots can bypass completely by using direct pool access or different router contracts

  • The anti-bot protection mechanism is completely non-functional

Proof of Concept

Two users swap through the same router. Both users' tracked amounts are 0, while the router accumulates both swaps - proving per-user tracking is broken.

function test_SenderIsRouterNotUser() public {
vm.deal(user1, 10 ether);
vm.deal(user2, 10 ether);
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});
// User1 swaps
vm.startPrank(user1);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
// Check: swap is tracked against router, not user1
assertEq(antiBotHook.addressSwappedAmount(user1), 0, "User1 should have 0 tracked");
assertGt(antiBotHook.addressSwappedAmount(address(swapRouter)), 0, "Router has the tracked amount");
// User2 swaps - will inherit cooldown from user1's swap
vm.startPrank(user2);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
// Both users' swaps accumulated on router
assertEq(antiBotHook.addressSwappedAmount(user2), 0, "User2 should have 0 tracked");
}

Recommended Mitigation

Use tx.origin instead of sender to track the actual user. Alternatively, pass user address via hookData.

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
+ // Decode the actual user from hookData or use tx.origin
+ address actualUser = tx.origin; // Or decode from hookData if passed by router
+
// ... phase logic ...
- addressSwappedAmount[sender] += swapAmount;
- addressLastSwapBlock[sender] = block.number;
+ addressSwappedAmount[actualUser] += swapAmount;
+ addressLastSwapBlock[actualUser] = block.number;
- if (addressLastSwapBlock[sender] > 0) {
+ if (addressLastSwapBlock[actualUser] > 0) {
- uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
+ uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[actualUser];
// ...
}
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!