Vanguard

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

`_resetPerAddressTracking` Only Resets Zero Address, Failing to Clear User Tracking on Phase Transitions

Author Revealed upon completion

_resetPerAddressTracking Only Resets Zero Address, Failing to Clear User Tracking on Phase Transitions

Description

The TokenLaunchHook is designed to reset per-address tracking when transitioning between phases. This allows users who may have hit their limits or accumulated penalties in Phase 1 to start fresh in Pahse 2 with new, more relaxed parameters.

The _resetPerAddressTracking function is called during phase transitions but only resets the tracking for address(0), leaving all actual user addresses with their accumulated swap amounts and penalty counts intact. Users who reached their limits in Phase 1 remain restricted in Phase 2 despite the intended fresh start.

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
/// ...
if (newPhase != currentPhase) {
@> _resetPerAddressTracking();
currentPhase = newPhase;
lastPhaseUpdateBlock = block.number;
}
// ...
}

Risk

Likelihood: High

  • Phase transitions occur automatically based on block numbers, triggering this faulty reset logic every time.

  • Every user who swapped during Phase 1 will be affected when Phase 2 begins.

Impact: Medium

  • Users who accumulated swap amounts in Phase 1 carry that balance into Phase 2, potentially exceeding Phase 2 limits immediately.

  • Users penalized in Phase 1 retain their penalty history, affecting their treatment in Phase 2.

  • The phase transition benefit of "fresh start" is completely non-functional.

  • Legitimate users may be unfairly restricted while expecting relaxed Phase 2 conditions.

Proof of Concept

function test_resetZeroAddress() public {
vm.deal(user1, 10 ether);
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});
// User swaps in Phase 1
vm.prank(user1);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
uint256 routerAmountPhase1 = antiBotHook.addressSwappedAmount(address(swapRouter));
assertGt(routerAmountPhase1, 0, "Router should have swap amount recorded");
// Transition to Phase 2
vm.roll(block.number + PHASE2_DURATION + 1);
vm.prank(user1);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
// VULNERABILITY: Router's accumulated amount was NOT reset
uint256 routerAmountPhase2 = antiBotHook.addressSwappedAmount(address(swapRouter));
// Amount should have been reset to just the new swap, but instead it accumulated
assertGt(routerAmountPhase2, routerAmountPhase1, "Amount accumulated instead of resetting");
}

Recommended Mitigation

Track active addresses and reset them properly.

- function _resetPerAddressTracking() internal {
+ function _resetPerAddressTracking(address user) internal {
- addressSwappedAmount[address(0)] = 0;
+ addressSwappedAmount[user] = 0;
- addressLastSwapBlock[address(0)] = 0;
+ addressLastSwapBlock[user] = 0;
}

Support

FAQs

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

Give us feedback!