Vanguard

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

_resetPerAddressTracking() Only Resets address(0) Mappings

Author Revealed upon completion

Description

  • When phases transition (Phase 1 → Phase 2 → Phase 3), the contract calls _resetPerAddressTracking() to reset user swap amounts and cooldowns, allowing fresh limits in the new phase.

  • The implementation only resets the mappings for address(0), which is never used as a swap sender. All actual user/router tracking data persists across phase transitions.

function _resetPerAddressTracking() internal {
// @> Only resets address(0) - completely useless
addressSwappedAmount[address(0)] = 0;
addressLastSwapBlock[address(0)] = 0;
}

Called during phase transition:

if (newPhase != currentPhase) {
_resetPerAddressTracking(); // @> Does nothing useful
currentPhase = newPhase;
lastPhaseUpdateBlock = block.number;
}

Risk

Likelihood:

  • This occurs on every phase transition - guaranteed to happen during normal protocol operation

  • Any user who swaps during Phase 1 will have their accumulated amounts persist into Phase 2

Impact:

  • Users who reached their limit in Phase 1 will still be at/over limit in Phase 2

  • Users will be immediately penalized in Phase 2 based on Phase 1 activity

  • Phase transition feature is completely broken - no "fresh start" for new phases

  • Conflicts with documented behavior that phases should have separate tracking

Proof of Concept

Swap in Phase 1, then transition to Phase 2 and swap again. The tracked amount accumulates across phases instead of resetting.

function test_ResetTrackingBroken() 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});
// Swap in Phase 1
vm.startPrank(user1);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
uint256 phase1Amount = antiBotHook.addressSwappedAmount(address(swapRouter));
assertGt(phase1Amount, 0, "Should have tracked amount");
// Transition to Phase 2
vm.roll(block.number + phase1Duration + 1);
// Trigger phase update with a small swap
vm.startPrank(user1);
swapRouter.swap{value: 0.0001 ether}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
// BUG: Amount was NOT reset
uint256 phase2Amount = antiBotHook.addressSwappedAmount(address(swapRouter));
assertGt(phase2Amount, phase1Amount, "Amount accumulated, not reset");
// Expected behavior: phase2Amount should be only the new swap amount
}

Recommended Mitigation

Use phase-indexed mappings so each phase has separate tracking, or check lastPhaseUpdateBlock to auto-reset stale data.

Option 1: Track phase-specific amounts

+ mapping(address => mapping(uint256 => uint256)) public addressSwappedAmountByPhase;
+ mapping(address => mapping(uint256 => uint256)) public addressLastSwapBlockByPhase;
function _beforeSwap(...) internal override returns (...) {
// ... phase logic ...
- addressSwappedAmount[sender] += swapAmount;
- addressLastSwapBlock[sender] = block.number;
+ addressSwappedAmountByPhase[sender][currentPhase] += swapAmount;
+ addressLastSwapBlockByPhase[sender][currentPhase] = block.number;
- if (addressLastSwapBlock[sender] > 0) {
- uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
+ if (addressLastSwapBlockByPhase[sender][currentPhase] > 0) {
+ uint256 blocksSinceLastSwap = block.number - addressLastSwapBlockByPhase[sender][currentPhase];
// ...
}
}
- function _resetPerAddressTracking() internal {
- addressSwappedAmount[address(0)] = 0;
- addressLastSwapBlock[address(0)] = 0;
- }
+ // No reset needed - each phase has separate tracking automatically

Option 2: Use phase start block for expiration

function _beforeSwap(...) internal override returns (...) {
// ... phase transition ...
+ // Check if user's tracking is from a previous phase
+ if (addressLastSwapBlock[sender] < lastPhaseUpdateBlock) {
+ // Reset for this user since it's a new phase
+ addressSwappedAmount[sender] = 0;
+ }
// Continue with normal logic...
}

Support

FAQs

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

Give us feedback!