## Description
The `TokenLaunchHook` is designed to reset user swap limits when the protocol transitions from Phase 1 to Phase 2. This is intended to give users a "fresh start" with the new phase's limits.
However, the `_resetPerAddressTracking` function is implemented incorrectly. In Solidity, deleting a key in a mapping (like `addressSwappedAmount[address(0)] = 0`) does **not** clear the entire mapping. It only clears that specific key. Consequently, all user swap history from Phase 1 persists into Phase 2.
**Code at Fault:**
```solidity
function _resetPerAddressTracking() internal {
// @audit Only clears the zero address. All actual user data remains.
addressSwappedAmount[address(0)] = 0;
addressLastSwapBlock[address(0)] = 0;
}
```
## Risk
**Likelihood**: High (Certainty)
* This logic runs on every phase transition. The bug is intrinsic to how Solidity mappings work.
**Impact**: High
* **Denial of Service:** Users active in Phase 1 will enter Phase 2 with their limits already partially or fully consumed.
* **Broken Core Logic:** The concept of "Phased Limits" is fundamentally broken as usage accumulates globally rather than per-phase.
## Proof of Concept
Add this function to your `TestTokenLaunchHook` contract. It uses the exact same variable naming and struct initialization as your working tests.
```solidity
function test_PoC_BrokenLimitReset_UserUsageCarriesOver() public {
// 1. SETUP: Give user1 funds
vm.deal(user1, 10 ether);
// Define swap params (Selling 0.05 ETH)
// Phase 1 Limit is 1% of 10 ETH = 0.1 ETH. We use 50% of it.
uint256 swapAmountPhase1 = 0.05 ether;
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: -int256(swapAmountPhase1),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
// 2. ACTION: User1 swaps in Phase 1
vm.startPrank(user1);
swapRouter.swap{value: swapAmountPhase1}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
// Check Phase 1 usage is recorded
// Note: Checking 'swapRouter' address due to the known Router DoS bug
uint256 usageAfterPhase1 = antiBotHook.addressSwappedAmount(address(swapRouter));
assertEq(usageAfterPhase1, swapAmountPhase1, "Phase 1 usage should be recorded");
// 3. TRANSITION: Move to Phase 2
// Phase 1 duration is 100 blocks. We roll past it.
vm.roll(block.number + phase1Duration + 1);
// 4. ACTION: User1 swaps in Phase 2
// This triggers _beforeSwap -> newPhase detected -> _resetPerAddressTracking called
uint256 swapAmountPhase2 = 0.01 ether;
// Update params for the new amount
params.amountSpecified = -int256(swapAmountPhase2);
vm.startPrank(user1);
swapRouter.swap{value: swapAmountPhase2}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
// 5. ASSERTION: Check if usage was reset
uint256 usageTotal = antiBotHook.addressSwappedAmount(address(swapRouter));
// EXPECTED (If Reset worked): usageTotal == swapAmountPhase2 (0.01)
// ACTUAL (Bug): usageTotal == swapAmountPhase1 + swapAmountPhase2 (0.06)
console.log("Expected Usage if Reset worked:", swapAmountPhase2);
console.log("Actual Usage (Accumulated): ", usageTotal);
assertGt(usageTotal, swapAmountPhase2, "FAIL: Phase 1 usage was not cleared!");
assertEq(usageTotal, swapAmountPhase1 + swapAmountPhase2, "FAIL: Limits are cumulative across phases");
}
```
## Recommended Mitigation
Since mappings cannot be cleared in O(1) time in Solidity, you must include the **Phase ID** in the mapping key. This effectively creates a new, empty limits bucket for every phase.
**1. Update the Mapping:**
```solidity
// Change mapping(address => uint256) to:
mapping(uint256 => mapping(address => uint256)) public phaseAddressSwappedAmount;
```
**2. Update `_beforeSwap`:**
```solidity
// Remove _resetPerAddressTracking() call entirely.
// access data using currentPhase
if (!applyPenalty && phaseAddressSwappedAmount[currentPhase][sender] + swapAmount > maxSwapAmount) {
// ...
}
phaseAddressSwappedAmount[currentPhase][sender] += swapAmount;
```
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.