Vanguard

First Flight #56
Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

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

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.

Updates

Appeal created

chaossr Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

_resetPerAddressTracking() Only Resets address(0) Mappings

Support

FAQs

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

Give us feedback!