Vanguard

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

[H-1] Swap Limits Are Never Enforced: Only Penalties Applied

Author Revealed upon completion

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

// Root cause in the codebase with @> Vanguard’s core anti-bot protection is functionally broken.
Although the protocol defines per-block swap limits during launch phases, these limits are never enforced.
When a user exceeds the allowed swap amount, the contract merely applies a penalty fee instead of reverting the transaction.
This allows bots or malicious actors to swap unlimited amounts simply by paying the penalty, completely bypassing the intended launch protections.

Risk

Likelihood:

  • Reason 1:

    During Phase 1 and Phase 2 launches, any trader exceeding the per-block swap limit will always be allowed to complete the swap because the hook never reverts on limit violations. This occurs on every launch using TokenLaunchHook making the issue consistently exploitable from the first block of trading.

  • Reason 2:

    The cost of exploitation is predictable and bounded (penalty fees), allowing bots and sophisticated actors to pre-calculate profitability and repeatedly execute oversized swaps. Because the behavior is deterministic and permissionless, automated trading bots can reliably exploit this weakness at scale during every protected launch.

Impact:

  • Impact 1

    - Complete bypass of anti-bot protections
    - Bots can trade unlimited amounts during Phase 1 and Phase 2
    - Large holders can dump immediately at launch
    - Unfair advantage
    - False security guarantees
    - The protocol claims “strict sell limits” that do not exist in practice

Proof of Concept

Run this test:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/TokenLaunchHook.sol";
contract ExploitLimitBypass is Test {
TokenLaunchHook hook;
function testLimitsNotEnforced() public {
// Setup: Deploy hook with Phase 1 limit of 1% (100 bps)
hook = new TokenLaunchHook(
poolManager,
100, // phase1Duration
100, // phase2Duration
100, // phase1LimitBps = 1%
200, // phase2LimitBps = 2%
5, // phase1Cooldown
3, // phase2Cooldown
3000, // phase1PenaltyBps = 30% fee
1500 // phase2PenaltyBps = 15% fee
);
// Initialize pool with 100,000 tokens liquidity
// This sets initialLiquidity = 100,000
// Phase 1 limit = 1% of 100,000 = 1,000 tokens max per address
// Attack: Bot tries to swap 50,000 tokens (50x the limit!)
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: -50000, // Selling 50,000 tokens
sqrtPriceLimitX96: 0
});
// Before swap
uint256 botLimit = hook.getUserRemainingLimit(botAddress);
assertEq(botLimit, 1000); // Bot should only be able to swap 1,000
// Execute swap - THIS SHOULD REVERT BUT IT DOESN'T
hook.beforeSwap(botAddress, poolKey, params, "");
// After swap - verify bot swapped 50,000 despite 1,000 limit
uint256 swappedAmount = hook.addressSwappedAmount(botAddress);
assertEq(swappedAmount, 50000); // Bot successfully swapped 50x limit!
// The only consequence is a penalty fee was charged
// But the swap was NOT blocked!
}
}
// This proof of concept demonstrates that swap limits are not enforced during launch phases.
// The test initializes the TokenLaunchHook with a Phase 1 swap limit of 1% of initial liquidity. Despite this restriction, a simulated bot address successfully executes a swap that is 50× larger than the configured limit in a single transaction.

Recommended Mitigation

- remove this code
if (addressLastSwapBlock[sender] > 0) {
uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
if (blocksSinceLastSwap < phaseCooldown) {
applyPenalty = true;
}
}
if (!applyPenalty && addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
applyPenalty = true;
}
uint24 feeOverride = 0;
if (applyPenalty) {
feeOverride = uint24((phasePenaltyBps * 100));
}
+ add this code
// Enforce cooldown strictly
if (addressLastSwapBlock[sender] > 0) {
uint256 blocksSinceLastSwap = block.number - addressLastSwapBlock[sender];
if (blocksSinceLastSwap < phaseCooldown) {
revert CooldownNotMet(addressLastSwapBlock[sender] + phaseCooldown);
}
}
// Enforce max swap amount strictly
if (addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
revert SwapLimitExceeded(
maxSwapAmount,
addressSwappedAmount[sender] + swapAmount
);
}
The recommended mitigation replaces the penalty-only enforcement model with hard limit enforcement.
Instead of allowing swaps to proceed with increased fees, the hook now reverts transactions that violate cooldown or per-block swap limits. This ensures that launch restrictions are enforced at the protocol level and cannot be bypassed by paying a fee.
By reverting on violations:
Oversized swaps are fully blocked
Bots cannot economically bypass protections

Support

FAQs

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

Give us feedback!