Vanguard

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

Incorrect implementation of function `TokenLaunchHook.sol::_resetPerAddressTracking`.

Author Revealed upon completion

Root +Impact

Description

  • The function TokenLaunchHook::_resetPerAddressTracking is used in TokenLaunchHook::_beforeSwap to reset addressSwappedAmount and addressLastSwappedBlock when switching to the next phase of the token launch ( specifically between phase1 and 2).

if (newPhase != currentPhase) {
@> _resetPerAddressTracking();
currentPhase = newPhase;
lastPhaseUpdateBlock = block.number;
}
if (currentPhase == 3) {
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, LPFeeLibrary.OVERRIDE_FEE_FLAG);
}
  • The function is incorrect since it resets the amounts of address(0) instead of dynamically resetting the address for the caller of the swap ( the swap router in this case).

function _resetPerAddressTracking() internal {
addressSwappedAmount[address(0)] = 0;
addressLastSwapBlock[address(0)] = 0;
}

Risk

Likelihood

  • High: addressSwappedAmount and addressLastSwapBlock will always persist from phase 1 to phase 2.

  • Note: this will not happen when moving to phase 3 since the function then stops tracking the addressSwappedAmount and addressLastSwapBlock variables.

Impact

  • High: Swaps in phase 2 will accumulate addressSwappedAmount from phase 1 and might run into penalties earlier than expected

  • High: users that want to perform swaps in phase 2 might be forced to wait for the PHASE2_COOLDOWN to be over to avoid penalties since addressLastSwapBlock isn't reset correctly.

Proof of concept

Add the following test to the test suite, which demonstrates the persistence of addressSwappedAmount and addressLastSwapBlock when transitioning from phase 1 to phase 2.

function test_Incorrect_resetPerAddressTracking() public {
assertEq(antiBotHook.getCurrentPhase(), 1, "Should start in phase 1");
// Add liquidity
modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(
key,
ModifyLiquidityParams({
tickLower: -60,
tickUpper: 60,
liquidityDelta: int256(uint256(10 ether)),
salt: bytes32(0)
}),
ZERO_BYTES
);
// First swap sets initial liquidity and records per-address tracking
uint256 swapAmount = 0.001 ether;
vm.deal(user1, 1 ether);
vm.startPrank(user1);
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: -int256(swapAmount),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
swapRouter.swap{value: swapAmount}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
// Reach phase duration limit
vm.roll(block.number + phase1Duration - 1);
// Make swap near phase transition and swap limit to record swapped amount and last swap block
uint256 startingRemainingSwapLimit = antiBotHook.getUserRemainingLimit(address(swapRouter));
vm.deal(user1, startingRemainingSwapLimit);
vm.startPrank(user1);
params = SwapParams({
zeroForOne: true,
amountSpecified: -int256(startingRemainingSwapLimit - 0.0001 ether),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
swapRouter.swap{value: startingRemainingSwapLimit - 0.0001 ether}(key, params, testSettings, ZERO_BYTES);
vm.stopPrank();
// Record swapped amount and last swap block before phase transition
uint256 swappedAmountBeforeTransition = antiBotHook.addressSwappedAmount(address(swapRouter));
uint256 lastSwapBlockBeforeTransition = antiBotHook.addressLastSwapBlock(address(swapRouter));
console.log("Swapped Amount Before Transition:", swappedAmountBeforeTransition);
console.log("Last Swap Block Before Transition:", lastSwapBlockBeforeTransition);
// Move to phase 2
vm.roll(block.number + 5); // Move past phase1Duration
assertEq(antiBotHook.getCurrentPhase(), 2, "Should be in phase 2");
// addressSwappedAmount and addressLastSwapBlock haven't been reset
console.log(
"Swapped Amount After Transition:",
antiBotHook.addressSwappedAmount(address(swapRouter))
);
console.log("Last Swap Block After Transition:", antiBotHook.addressLastSwapBlock(address(swapRouter)));
assertEq(
antiBotHook.addressSwappedAmount(address(swapRouter)),
swappedAmountBeforeTransition,
"Swapped amount after phase transition should persist due to incorrect reset"
);
assertEq(
antiBotHook.addressLastSwapBlock(address(swapRouter)),
lastSwapBlockBeforeTransition,
"Last swap block after phase transition should persist due to incorrect reset"
);
}

Recommended mitigation

_resetPerAddressTracking should take as argument the msg.sender ( the swap router ) and reset accordingly

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;
+ }

Support

FAQs

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

Give us feedback!