Vanguard

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

View Function Returns Incorrect Phase at Boundary Blocks, Leading to Unexpected Penalties

Author Revealed upon completion

ROLE + IMPACT

## Description

The `getCurrentPhase` function is intended to report the current status of the launch (Phase 1, Phase 2, or Phase 3) to external callers, such as frontends or other smart contracts. This data is typically used to estimate fees and limits before a user submits a transaction.

However, there is a logic inconsistency between how `getCurrentPhase` calculates the phase and how the core logic `_beforeSwap` calculates it.

* **`_beforeSwap` (Execution):** Uses inclusive comparison (`<=`).

* Logic: `if (blocksSinceLaunch <= phase1Duration)`

\

* **`getCurrentPhase` (View):** Uses strict inequality (`<`).

* Logic: `if (blocksSinceLaunch < phase1Duration)`


\

At the exact boundary block where blocksSinceLaunch == phase1Duration, the view function reports that the protocol has moved to the **next phase**, while the execution logic still enforces the **previous phase**.

This mismatch applies to both the transition from Phase 1 to 2, and Phase 2 to 3.

```solidity

// TokenLaunchHook.sol

function getCurrentPhase() public view returns (uint256) {

// ...

uint256 blocksSinceLaunch = block.number - launchStartBlock;

// @audit Uses '<' instead of '<='

if (blocksSinceLaunch < phase1Duration) {

return 1;

} else if (blocksSinceLaunch < phase1Duration + phase2Duration) { // @audit Same error here

return 2;

} else {

return 3;

}

}

```

## Risk

**Likelihood**: Medium

* This occurs exactly on the specific blocks that mark the end of a phase. While infrequent, in a high-volume launch, it is statistically probable that transactions will be included in these blocks.

**Impact**: High

* **Financial Discrepancy:**

* **Phase 1 -> 2:** User sees Phase 2 (lower fee/penalty). User submits tx. User pays Phase 1 (higher fee/penalty).

* **Phase 2 -> 3:** User sees Phase 3 (Standard 0.3% Fee). User submits tx. User pays Phase 2 (e.g., 2% Penalty).

\

* **Failed Transactions:** If the Phase 1 limit is stricter than Phase 2, a user might submit a swap size valid for Phase 2 (as reported by the view) which reverts under Phase 1 rules.

## Proof of Concept

**Scenario:**

* `launchStartBlock` = 1000.

* `phase1Duration` = 100 blocks.

* `phase1Penalty` = 5%.

* `phase2Penalty` = 2%.

**At Block 1100:**

  1. **Calculation:** `blocksSinceLaunch` = `1100 - 1000` = `100`.

  2. **Frontend Check (`getCurrentPhase`):**

* `100 < 100` evaluates to **FALSE**.

* Function returns **Phase 2**.

* **User Expectation:** "I will pay 2% penalty."

\

  1. **Execution (`_beforeSwap`):**

* `100 <= 100` evaluates to **TRUE**.

* Function enforces **Phase 1**.

* **Actual Result:** User pays **5% penalty**.


\

## Recommended Mitigation

Update the comparison operators in `getCurrentPhase` to use `<=` to strictly match the logic in `_beforeSwap`.

```diff

function getCurrentPhase() public view returns (uint256) {

if (launchStartBlock == 0) return 0;

uint256 blocksSinceLaunch = block.number - launchStartBlock;

- if (blocksSinceLaunch < phase1Duration) {

+ if (blocksSinceLaunch <= phase1Duration) {

return 1;

- } else if (blocksSinceLaunch < phase1Duration + phase2Duration) {

+ } else if (blocksSinceLaunch <= phase1Duration + phase2Duration) {

return 2;

} else {

return 3;

}

}

```

Support

FAQs

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

Give us feedback!