Vanguard

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

[H-02] Sender Address is Router Contract, Not End User

Author Revealed upon completion

Root + Impact

Description

The hook tracks the sender parameter which is the router contract address, not the actual end user. This means all users share the same limits and cooldowns.

Location: src/TokenLaunchHook.sol:125-172

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
// sender is the router (e.g., PoolSwapTest), NOT the actual user!
// ...
addressSwappedAmount[sender] += swapAmount; // All users share this!
addressLastSwapBlock[sender] = block.number; // All users share this!

In Uniswap V4's architecture:

  1. User calls Router contract (e.g., PoolSwapTest.swap())

  2. Router calls PoolManager.unlock()

  3. In callback, Router calls PoolManager.swap()

  4. sender in hook = Router address

Risk

Likelihood:

  • Every swap through the standard Uniswap V4 router triggers this behavior

  • 100% of swaps will track the router address, not the actual user

  • This is the default and only code path for swap tracking

Impact:

  • All users share the same swap limit (whoever hits limit first blocks everyone)

  • All users share the same cooldown (one swap triggers cooldown for all)

  • Anti-bot protection is completely ineffective

  • Legitimate users penalized by other users' activity

  • A single bot can trigger penalties for all subsequent legitimate users

Proof of Concept

The test file confirms this behavior at line 300:

// Check that swapRouter has swapped (since sender is the router)
assertGt(antiBotHook.addressSwappedAmount(address(swapRouter)), 0, "SwapRouter should have swapped amount");

Test demonstrating shared limits:

function test_allUsersShareLimits() public {
vm.deal(user1, 10 ether);
vm.deal(user2, 10 ether);
// User1 swaps
vm.prank(user1);
swapRouter.swap{value: 0.005 ether}(...);
// User2's limit is now reduced because they share router address
uint256 user2Limit = antiBotHook.getUserRemainingLimit(address(swapRouter));
// user2Limit is reduced by user1's swap!
}

Recommendations

Uniswap Recommended - Trusted Router with msgSender() interface:

Per Uniswap V4 documentation, the recommended approach is to maintain a list of trusted routers that implement a msgSender() view function.

interface IMsgSender {
function msgSender() external view returns (address);
}
contract TokenLaunchHook is BaseHook {
mapping(address swapRouter => bool approved) public verifiedRouters;
function addRouter(address _router) external onlyOwner {
verifiedRouters[_router] = true;
}
function removeRouter(address _router) external onlyOwner {
verifiedRouters[_router] = false;
}
function _beforeSwap(address sender, ...) internal override returns (...) {
address user;
if (verifiedRouters[sender]) {
user = IMsgSender(sender).msgSender();
} else {
revert("Router not verified");
}
addressSwappedAmount[user] += swapAmount;
addressLastSwapBlock[user] = block.number;
}
}

Support

FAQs

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

Give us feedback!