Vanguard

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

The `_resetPerAddressTracking` function that is called in `_beforeSwap` never resets address tracking variables, causing penalty fees for users.

Root + Impact

Description

  • The _resetPerAddressTracking function is supposed to reset address tracking addressSwappedAmount and addressLastSwapBlock when the currentPhase changes in order to reset previous phase stats.

  • However, these variables are never reset to 0, as _resetPerAddressTracking only resets values for address(0).

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
.
.
.
if (newPhase != currentPhase) {
@> _resetPerAddressTracking();
currentPhase = newPhase;
lastPhaseUpdateBlock = block.number;
}
.
.
.
}
@> function _resetPerAddressTracking() internal {
addressSwappedAmount[address(0)] = 0;
addressLastSwapBlock[address(0)] = 0;
}

Risk

Likelihood:

  • Likelihood is high as there are no specific steps required for this issue to occur.

  • Users swap within the normal flow, performing swaps during various phases.

Impact:

  • Users reach the limits of the next phase sooner than expected, based on stats from the previous phase.

  • Users are penalized for normal operation due to a flaw in the hook logic.

  • Causes denial of service, loss of users as it becomes less interesting for users or potentially more expensive than using other pools.

Proof of Concept

This POC shows that addressSwappedAmount did not reset to 0 after the phase changed.
Add this snippet of code to test/TokenLaunchHookUnit.t.sol

function test_PhaseTransition_Phase1ToPhase2_limitsNotReseted_POC() public {
assertEq(antiBotHook.getCurrentPhase(), 1, "Should start in phase 1");
vm.deal(address(this), 1 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});
swapRouter.swap{value: 0.001 ether}(key, params, testSettings, ZERO_BYTES);
// Check that swapRouter has swapped (since sender is the router)
uint256 addressSwappedAmountPhase1 = antiBotHook.addressSwappedAmount(address(swapRouter));
vm.roll(block.number + phase1Duration + 1);
uint256 currentPhase = antiBotHook.getCurrentPhase();
uint256 addressSwappedAmountPhase2 = antiBotHook.addressSwappedAmount(address(swapRouter));
assertEq(currentPhase, 2, "Should be in phase 2");
assertEq(
addressSwappedAmountPhase2,
addressSwappedAmountPhase1,
"SwappedAmount is equal, proves reset on phase change did not happen"
);
}

Recommended Mitigation

  1. Add sender address input parameter to _resetPerAddressTracking, and replace address(0) with this new input parameter.

  2. Replace _resetPerAddressTracking(); with _resetPerAddressTracking(sender); in the _beforeSwap function.

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
.
.
.
if (newPhase != currentPhase) {
- _resetPerAddressTracking();
+ _resetPerAddressTracking(sender);
currentPhase = newPhase;
lastPhaseUpdateBlock = block.number;
}
.
.
.
}
- function _resetPerAddressTracking() internal {
- addressSwappedAmount[address(0)] = 0;
- addressLastSwapBlock[address(0)] = 0;
+ function _resetPerAddressTracking(address sender) internal {
+ addressSwappedAmount[sender] = 0;
+ addressLastSwapBlock[sender] = 0;
}
Updates

Appeal created

chaossr Lead Judge 17 days 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!