Vanguard

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

_resetPerAddressTracking() Only Resets address(0), Breaking Phase Transition User Limit Reset

Author Revealed upon completion

Root + Impact

Description

Normal Behavior: When the protocol transitions between phases (Phase 1 → Phase 2), the _resetPerAddressTracking() function should reset ALL users' swap tracking, giving everyone fresh limits based on the new phase's relaxed parameters.

The Bug: The _resetPerAddressTracking() function only resets address(0), which is never used as a swap sender. Real users' tracking persists across phase transitions.

// @> BUG: Only resets address(0), not actual users!
function _resetPerAddressTracking() internal {
addressSwappedAmount[address(0)] = 0; // @> Useless - nobody uses address(0)
addressLastSwapBlock[address(0)] = 0; // @> Real users' data persists!
}

Risk

Likelihood: HIGH

  • This occurs on EVERY phase transition (Phase 1 → Phase 2 and Phase 2 → Phase 3)

  • Any user who swapped in Phase 1 will be affected when Phase 2 begins

  • The function is called automatically when newPhase != currentPhase (line 147)

Impact: HIGH

  • Users who hit their Phase 1 limit (1% of liquidity) remain penalized in Phase 2

  • Phase 2's "relaxed limits" (5% of liquidity) never take effect for returning users

  • Users pay penalty fees (up to 10%) when they shouldn't

  • The fair launch mechanism is completely broken

  • Direct financial loss for affected users

Proof of Concept

function test_EXPLOIT_TrackingPersistsAcrossPhases() public {
// Phase 1: User swaps and accumulates tracking
vm.startPrank(user);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
uint256 trackingAfterPhase1 = hook.addressSwappedAmount(address(swapRouter));
assertGt(trackingAfterPhase1, 0, "Should have tracking");
// Transition to Phase 2
vm.roll(hook.launchStartBlock() + phase1Duration + 1);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
// BUG: Tracking persists instead of resetting!
uint256 trackingAfterTransition = hook.addressSwappedAmount(address(swapRouter));
assertGt(trackingAfterTransition, trackingAfterPhase1,
"BUG CONFIRMED: Tracking accumulated instead of reset!");
}

Recommended Mitigation

Implement an epoch-based tracking system:

uint256 public currentEpoch;
mapping(address => mapping(uint256 => uint256)) public epochSwappedAmount;
function _resetPerAddressTracking() internal {
currentEpoch++; // Simply increment - old tracking becomes irrelevant
}
function _beforeSwap(...) internal override {
// Use current epoch for all tracking
epochSwappedAmount[sender][currentEpoch] += swapAmount;
// Check limits against current epoch only
}

This achieves O(1) reset by incrementing the epoch counter, automatically invalidating all prior tracking while maintaining gas efficiency.

Support

FAQs

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

Give us feedback!