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-172addressSwappedAmount[sender] += swapAmount; // @> sender is router, not useraddressLastSwapBlock[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 routerassertEq(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 hookDataaddress user = abi.decode(hookData, (address));// amountSpecified is negative for exact-input swapsuint256 amountIn = uint256(-params.amountSpecified);addressSwappedAmount[user] += amountIn;totalAddressSwaps[user] += 1;return (this.beforeSwap.selector, BeforeSwapDelta(0, 0), 0);}
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.