Vanguard

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

Sender Address in `_beforeSwap` Is Router Address, Breaking All Per-User Anti-Bot Protections

Author Revealed upon completion

Sender Address in _beforeSwap Is Router Address, Breaking All Per-User Anti-Bot Protections

Description

The TokenLaunchHook is designed to provide anti-bot protection during launches by tracking per-address swap amounts, enforcing cooldowns between swaps, and applying penalty fees when users exceed limits or violate cooldowns. Each user should have independent tracking of their swap activity.

In Uniswap V4, when a user swaps through a router contract, the sender parameter passed to _beforeSwap is the router's address, not the actual user's address. The hook uses this sender parameter for all per-address tracking, causing all users' swaps to be attributed to the router address instead of individual users. This completly breaks the anti-bot protection mechanism.

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
// ... phase logic ...
@> 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

  • All swaps in Uniswap V4 flow through router contracts. Direct calls to PoolManager are not the standard user flow.

  • The vulnerability is triggered on every single swap transaction, making it a certainty rather than an edge case.

Impact: High

  • Bots can swap unlimited amounts without any tracking since their EOA address is never recorded in the hook's mappings.

  • Per-address limits become meaningless - all users share the same limit through the router, turning individual limits into a global limit.

  • Cooldowns are shared across all users - one user's swap triggers cooldown penalties for all subsequent users using the same router.

  • The entire anti-bot protection mechanism is rendered completely ineffective.

Proof of Concept

function test_senderIsRouter() public {
vm.deal(user1, 10 ether);
// User 1 performs a swap
vm.startPrank(user1);
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});
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
// THE VULNERABILITY: Check that swap is attributed to router, NOT user1
uint256 user1SwapAmount = antiBotHook.addressSwappedAmount(user1);
uint256 routerSwapAmount = antiBotHook.addressSwappedAmount(address(swapRouter));
assertEq(user1SwapAmount, 0, "VULNERABILITY: User1 should have 0 recorded (swap attributed to router)");
assertGt(routerSwapAmount, 0, "Router has the swap amount recorded instead of user");
}

Recommended Mitigation

The following changes in the flow are suggested:

  • Maintain a list of trusted routers

  • Verify the sender address against the trusted routers

  • Use a method exposed from the router to read the user address

Support

FAQs

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

Give us feedback!