Vanguard

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

Hook Tracks SwapRouter Instead of Actual Users, Breaking All Per-User Limits

Author Revealed upon completion

Description

The hook should enforce per-user swap limits to prevent bots from dumping tokens.

The hook tracks the sender parameter in _beforeSwap(), which is the SwapRouter contract, not the end user. All users share the same tracking and limits.

function _beforeSwap(address sender, ...) { // @> sender = SwapRouter, not actual user
if (addressLastSwapBlock[sender] > 0) { // @> Tracks SwapRouter address
uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
if (blocksSinceLastSwap < phaseCooldown) {
applyPenalty = true;
}
}
if (!applyPenalty && addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
applyPenalty = true; // @> Checks SwapRouter's total, not individual users
}
addressSwappedAmount[sender] += swapAmount; // @> ALL users share same tracking
addressLastSwapBlock[sender] = block.number;
}

Risk

Likelihood:

  • Affects every single swap through the standard SwapRouter

  • Reproducible on all transactions

Impact:

  • Per-user limits completely broken - everyone shares one limit

  • First user exhausts the router's limit, blocking all subsequent users

  • Complete DoS of the pool for legitimate traders

Proof of Concept

Run: forge test --mt test_HookTracksRouterNotUsers -vv

function test_HookTracksRouterNotUsers() public {
// Trigger fallback to set initialLiquidity
vm.deal(user1, 1 ether);
vm.prank(user1);
swapRouter.swap{value: 0.0001 ether}(
key,
SwapParams({zeroForOne: true, amountSpecified: -0.0001 ether, sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}),
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),
ZERO_BYTES
);
// Alice swaps
address alice = makeAddr("alice");
token.mint(alice, 100 ether);
vm.deal(alice, 100 ether);
vm.startPrank(alice);
token.approve(address(swapRouter), type(uint256).max);
swapRouter.swap{value: 0.01 ether}(
key,
SwapParams({zeroForOne: true, amountSpecified: -0.01 ether, sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}),
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),
ZERO_BYTES
);
vm.stopPrank();
// Bob swaps (different user)
address bob = makeAddr("bob");
token.mint(bob, 100 ether);
vm.deal(bob, 100 ether);
vm.startPrank(bob);
token.approve(address(swapRouter), type(uint256).max);
vm.roll(block.number + phase1Cooldown + 1);
swapRouter.swap{value: 0.01 ether}(
key,
SwapParams({zeroForOne: true, amountSpecified: -0.01 ether, sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}),
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),
ZERO_BYTES
);
vm.stopPrank();
console.log("Alice's tracking:", antiBotHook.addressSwappedAmount(alice)); // 0
console.log("Bob's tracking:", antiBotHook.addressSwappedAmount(bob)); // 0
console.log("Router's tracking:", antiBotHook.addressSwappedAmount(address(swapRouter))); // Alice + Bob
assertEq(antiBotHook.addressSwappedAmount(alice), 0, "Alice not tracked");
assertEq(antiBotHook.addressSwappedAmount(bob), 0, "Bob not tracked");
}

Output:

Alice's tracking: 0
Bob's tracking: 0
Router's tracking: 20100000000000000 (Alice + Bob combined!)

Recommended Mitigation

Track actual users via tx.origin:

function _beforeSwap(address sender, ...) {
+ address actualUser = tx.origin;
- if (addressLastSwapBlock[sender] > 0) {
+ if (addressLastSwapBlock[actualUser] > 0) {
- uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
+ uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[actualUser];
if (blocksSinceLastSwap < phaseCooldown) {
applyPenalty = true;
}
}
- if (!applyPenalty && addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
+ if (!applyPenalty && addressSwappedAmount[actualUser] + swapAmount > maxSwapAmount) {
applyPenalty = true;
}
- addressSwappedAmount[sender] += swapAmount;
+ addressSwappedAmount[actualUser] += swapAmount;
- addressLastSwapBlock[sender] = block.number;
+ addressLastSwapBlock[actualUser] = block.number;
}

Support

FAQs

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

Give us feedback!