Vanguard

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

_resetPerAddressTracking() Only Resets address(0) - User Tracking Persists Across Phase Transitions

Author Revealed upon completion

Root + Impact

Description

The _resetPerAddressTracking() function is intended to clear per-user tracking data when phases transition (Phase 1→2 or Phase 2→3). However, the function only resets mappings for address(0) instead of actual user addresses, causing user swap limits and cooldowns to incorrectly persist across phases.

Expected behavior: When the protocol transitions between phases, each user should receive fresh tracking: reset swap amounts and cooldown timers appropriate for the new phase.

Actual behavior: User tracking data (addressSwappedAmount, addressLastSwapBlock) accumulates indefinitely across all phases, defeating the phased anti-bot protection mechanism.

// @> BUG: Only resets address(0), not actual users
function _resetPerAddressTracking() internal {
addressSwappedAmount[address(0)] = 0; // ❌ Only resets address(0)
addressLastSwapBlock[address(0)] = 0; // ❌ Actual users NOT reset!
}

This function is called in _beforeSwap() during phase transitions:

if (newPhase != currentPhase) {
_resetPerAddressTracking(); // @> Called but doesn't reset real users
currentPhase = newPhase;
lastPhaseUpdateBlock = block.number;
}

Risk

Likelihood:

  • Phase transitions occur deterministically at phase1Duration and phase1Duration + phase2Duration blocks after launch - this is guaranteed to happen

  • Every user who swaps in Phase 1 will have their data incorrectly carried into Phase 2

  • The bug affects 100% of active participants

Impact:

  • Cumulative Limit Exhaustion: Users who use their Phase 1 limit (e.g., 1%) have that counted against their Phase 2 limit (e.g., 5%), receiving only 4% instead of the intended 5%

  • Cross-Phase Cooldown Penalties: Users may be incorrectly penalized in Phase 2 based on Phase 1 swap timing

  • Anti-Bot Mechanism Defeated: Bots using fresh addresses per phase get full limits, while legitimate early participants are penalized - inverting the intended security model

  • Unfair User Treatment: The protocol's core promise of "fresh limits per phase" is broken

Proof of Concept

// Scenario demonstrating the bug
function testStateResetFailure() public {
// Setup: phase1LimitBps = 100 (1%), phase2LimitBps = 500 (5%)
// PHASE 1: Alice swaps her full 1% limit
vm.prank(alice);
// swap 1% of liquidity
// Result: addressSwappedAmount[alice] = 1%
// PHASE TRANSITION: Fast forward past Phase 1
vm.roll(block.number + phase1Duration + 1);
// PHASE 2: Check Alice's remaining limit
uint256 remaining = hook.getUserRemainingLimit(alice);
// BUG DEMONSTRATED:
// Expected: 5% (full Phase 2 limit)
// Actual: 4% (5% - 1% carryover from Phase 1)
// Meanwhile, bot with fresh address gets full 5%
assertLt(remaining, hook.getUserRemainingLimit(address(0xB07)));
}

Recommended Fix

Use epoch-based tracking (gas efficient):

uint256 public currentEpoch;
mapping(address => mapping(uint256 => uint256)) public addressSwappedAmountByEpoch;
function _resetPerAddressTracking() internal {
currentEpoch++; // All previous epoch data becomes irrelevant
}
// In _beforeSwap, use currentEpoch for lookups
uint256 swapped = addressSwappedAmountByEpoch[sender][currentEpoch];

Support

FAQs

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

Give us feedback!