The Vanguard hook is designed to prevent price manipulation during token launches by limiting swap amounts to a small percentage of pool liquidity. In Phase 1, swaps are restricted to 1% of the initial pool liquidity per transaction, with penalties applied to violators. This protection ensures that no single actor can dump large positions and crash the token price during the critical early trading period.
The hook records initialLiquidity once during pool initialization or the first swap and never updates this value thereafter. When liquidity is removed from the pool after initialization, the hook continues to calculate swap limits based on the stale initialLiquidity value rather than the actual current pool liquidity. This allows attackers to execute swaps that represent a massive percentage of the actual pool while the hook believes they are within the intended limits.
Likelihood:
An attacker or colluding liquidity provider removes liquidity after the launch begins. The hook's protection immediately becomes ineffective as limits are calculated from the outdated baseline.
Any legitimate liquidity provider who added significant liquidity can remove their position at any time. Even without malicious intent, this causes the protection mechanism to fail for all subsequent swaps.
An attacker adds initial liquidity, triggers the first swap to lock in initialLiquidity, removes most liquidity, then dumps tokens at the inflated limit.
Impact:
Attackers can execute swaps 19x larger than intended limits without penalties, completely bypassing the anti-bot protection mechanism.
Single swaps can crash token price by 17-21%, and the attack is repeatable every 5 blocks to drain the pool.
Any liquidity provider can unilaterally destroy protection for all users by withdrawing their position.
In the test file, add this code
then run `forge test --mt test_LiquidityRemovedAfterLaunch_LimitsTooLoose -vvv`
results of the test:
```
[⠒] Compiling...
[⠆] Compiling 1 files with Solc 0.8.26
[⠔] Solc 0.8.26 finished in 65.55s
Compiler run successful!
Ran 1 test for test/TokenLaunchHookUnit.t.sol:TestTokenLaunchHook
[PASS] test_LiquidityRemovedAfterLaunch_LimitsTooLoose() (gas: 777769)
Logs:
=== STEP 1: BASELINE RECORDED ===
Hook's initialLiquidity: 3338502497096994491347
Actual pool liquidity: 3338502497096994491347
Phase 1 limit (bps): 100
Max swap allowed: 33385024970969944913
Max swap as % of pool: 0 %
=== STEP 2: REMOVING LIQUIDITY ===
Current pool liquidity: 3338502497096994491347
Liquidity to remove (95%): 3305117472126024546433
Actual pool liquidity after removal: 33385024970969944914
Hook's initialLiquidity after removal: 3338502497096994491347
BUG CONFIRMED:
Actual pool liquidity: 33385024970969944914
Hook thinks liquidity is: 3338502497096994491347
Discrepancy: 9999 x
=== STEP 3: DISCREPANCY ANALYSIS ===
What max swap SHOULD be (1% of real pool): 333850249709699449
What hook ALLOWS (1% of stale value): 33385024970969944913
Difference: 33051174721260245464
Danger multiplier: 100 x
Hook allows swaps of: 99 % of ACTUAL pool
(Should only allow 1%)
=== STEP 4: EXECUTING EXPLOIT ===
Bot attempting to swap: 33385024970969944913
This is 0 % of what hook thinks is pool size
This is 99 % of ACTUAL pool size
Penalty fees before exploit: 0
Penalty fees after exploit: 0
Additional penalties charged: 0
EXPLOIT SUCCESSFUL:
Bot dumped 99 % of actual pool
Hook thought it was only 1 %
Zero penalty fees collected
Anti-bot protection DEFEATED
=== STEP 5: IMPACT ANALYSIS ===
DUMP STATISTICS:
Intended max dump: 1 % of pool
Actual dump achieved: 99 % of pool
Bypass multiplier: 99 x
PENALTY ANALYSIS:
Expected penalty (if caught): 10 %
Actual penalty collected: 0%
Attacker saved: ~ 3338502497096994491 tokens
PRICE IMPACT:
Dump size relative to pool: 99 %
SEVERE: Dump is >50% of pool
This would cause massive price impact
PROTECTION EFFECTIVENESS:
Intended protection: Limit dumps to 1 % to prevent manipulation
Actual protection: Bot dumped 99 % without penalty
Protection status: COMPLETELY BYPASSED
Vulnerability confirmed
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 686.69ms (11.85ms CPU time)
Ran 1 test suite in 729.01ms (686.69ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
```
Although the beforeswap checks if liquidity has been set, it does not remove the vulnerability at all.
To fix it, add this in the _beforeSwap func
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.