Vanguard

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

Use of block.number causes inconsistent phase durations across different EVM chains

Author Revealed upon completion

Root + Impact

Description

The project scope explicitly states support for "Any EVM-compatible chain." However, the contract relies on block.number to calculate the duration of protection phases.Block production times vary significantly across EVM networks:

  • Ethereum: ~12 seconds.

  • BSC/Polygon: ~2-3 seconds.

  • Optimism/Base: ~2 seconds.

  • Arbitrum: Returns L2 block number (different mechanism).



    Therefore, this results in different phase cycles on different chains.

// Root cause in the codebase with @> marks to highlight the relevant section
function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
if (launchStartBlock == 0) revert PoolNotInitialized();
if (initialLiquidity == 0) {
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity);
}
@> uint256 blocksSinceLaunch = block.number - launchStartBlock;
uint256 newPhase;
@> if (blocksSinceLaunch <= phase1Duration) {
newPhase = 1;
@> } else if (blocksSinceLaunch <= phase1Duration + phase2Duration) {
newPhase = 2;
} else {
newPhase = 3;
}
if (newPhase != currentPhase) {
_resetPerAddressTracking();
currentPhase = newPhase;
lastPhaseUpdateBlock = block.number;
}
//......
}

Risk

Likelihood:

The README explicitly requires support for multiple chains, and since each chain has a different block production interval, it easily causes inconsistencies in the cycle across chains.

Impact:

On Ethereum mainnet, if the first phase takes 10 minutes, on BSC/Polygon it might be under 2 minutes, in which case the protective effect becomes ineffective.

Proof of Concept

Use Foundry to simulate a fast-chain environment (e.g., Base with 2s block time) and compare the elapsed duration. Relying on block.number causes the protection phase to expire significantly earlier in wall-clock time than intended, effectively bypassing the protection mechanism

function test_Poc_blocknumberError() public {
uint256 startTimestamp = block.timestamp;
// then simulate Base/Optimism (2s per block)
// Ether 12S
vm.roll(block.number + phase1Duration + 1);
vm.warp(block.timestamp + (phase1Duration + 1) * 2);
assertEq(antiBotHook.getCurrentPhase(), 2);
assertLt(block.timestamp - startTimestamp, phase1Duration * 12);
}

Recommended Mitigation

Replace block.number with block.timestamp for all duration measurements. block.timestamp provides a consistent wall-clock time standard across all EVM-compatible chains.

- block.number
+ block.timestamp

Support

FAQs

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

Give us feedback!