Vanguard

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

Swap tracking is keyed on router address, not user, breaking all per-user limits

Author Revealed upon completion

Root + Impact

Description

  • The hook tracks sender to enforce per-user limits. In v4, sender is SwapRouter, not the user. All users share one limit.

// TokenLaunchHook.sol:171-172
addressSwappedAmount[sender] += swapAmount; // @> sender is router, not user
addressLastSwapBlock[sender] = block.number;

Risk

Likelihood:

  • Every swap through SwapRouter triggers this

  • Standard flow from first swap onward

Impact:

  • All users share one global limit

  • Attacker can max it out to grief everyone

Proof of Concept

function test_SenderIsRouter() public {
vm.warp(hook.launchTime() + 1);
vm.prank(userA);
router.swap(key, params, hookData);
vm.prank(userB);
router.swap(key, params, hookData);
// Per-user tracking is empty; all state is attributed to the router
assertEq(hook.addressSwappedAmount(userA), 0);
assertEq(hook.addressSwappedAmount(userB), 0);
assertGt(hook.addressSwappedAmount(address(router)), 0);
}

Recommended Mitigation

function _beforeSwap(
address,
PoolKey calldata,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) internal override returns (bytes4, BeforeSwapDelta, uint24) {
// Decode the actual user from hookData
address user = abi.decode(hookData, (address));
// amountSpecified is negative for exact-input swaps
uint256 amountIn = uint256(-params.amountSpecified);
addressSwappedAmount[user] += amountIn;
totalAddressSwaps[user] += 1;
return (this.beforeSwap.selector, BeforeSwapDelta(0, 0), 0);
}

Support

FAQs

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

Give us feedback!