Vanguard

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

User tracking metrics are not reset during phase transitions

Author Revealed upon completion

User tracking metrics are not reset during phase transitions

Description

The protocol tracks user swap amounts and cooldowns to enforce limits that vary by phase. The design intends for these limits to reset when the protocol transitions from Phase 1 to Phase 2.

The function _resetPerAddressTracking is called during a phase transition to achieve this. However, it incorrectly resets the mapping for address(0) instead of msg.sender or iterating through users (which is not possible in this architecture). As a result, a user's addressSwappedAmount accumulates across phases. A user who swapped 1% in Phase 1 will immediately be at 1% usage in Phase 2, potentially triggering penalties prematurely if they were expected to have a fresh limit.

// src/TokenLaunchHook.sol
function _resetPerAddressTracking() internal {
@> addressSwappedAmount[address(0)] = 0;
@> addressLastSwapBlock[address(0)] = 0;
}

Risk

Likelihood:

  • Occurs for every user active across the phase boundary.

Impact:

  • Users are unfairly penalized or restricted in Phase 2 based on their Phase 1 activity, contradicting the intended fresh start for the new phase.

Proof of Concept

function test_Bug_ResetTrackingFailed() public {
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: -0.001 ether,
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
// 1. Swap in Phase 1
vm.deal(user1, 1 ether);
vm.prank(user1);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
uint256 amountPhase1 = antiBotHook.addressSwappedAmount(address(swapRouter));
// 2. Move to Phase 2
vm.roll(block.number + phase1Duration + 1);
// 3. Next swap triggers phase transition
vm.prank(user1);
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
// The total amount is accumulated instead of being reset
uint256 amountPhase2 = antiBotHook.addressSwappedAmount(address(swapRouter));
assertEq(amountPhase2, amountPhase1 + 0.001 ether, "BUG: Address tracking not reset on phase transition");
}

Recommended Mitigation

Update state variables to map Phase ID -> User Address -> Data instead of reset.

- mapping(address => uint256) public addressSwappedAmount;
- mapping(address => uint256) public addressLastSwapBlock;
+ // Mapping: PhaseID => UserAddress => Data
+ mapping(uint256 => mapping(address => uint256)) public addressSwappedAmount;
+ mapping(uint256 => mapping(address => uint256)) public addressLastSwapBlock;
function _beforeSwap(...) {
// ... phase calculation ...
- uint256 currentAmount = addressSwappedAmount[sender];
+ uint256 currentAmount = addressSwappedAmount[currentPhase][sender];

Support

FAQs

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

Give us feedback!