The contract maintains two independent phase determination mechanisms, which are expected to represent the same logical state but are not guaranteed to remain synchronized.
This architectural inconsistency causes the enforcement logic used during swaps to diverge from the phase information exposed to users and off-chain systems, resulting in incorrect application of limits and penalties.
The contract determines the current phase in two different ways:
A mutable storage variable currentPhase, which is updated during _beforeSwap
A derived phase calculation via getCurrentPhase(), which computes the phase dynamically based on block.number
The hook’s enforcement logic relies on the stored currentPhase, while view and helper functions rely on getCurrentPhase().
Because currentPhase is only updated when a swap occurs, it may lag behind the actual phase determined by block height. If no swap happens at the exact block where a phase transition occurs, the stored phase becomes stale.
This introduces a state divergence between execution-time enforcement and user-visible phase information.
Likelihood:
Reason 1: Phase transitions are based solely on block height and do not require a swap to occur at the transition boundary
Reason 2: currentPhase is only updated inside _beforeSwap, allowing it to remain outdated until the next swap
Impact:
Impact 1: Users may be penalized under stricter rules even though the protocol has already entered a later phase
Impact 2: Frontends and off-chain systems may display incorrect limits, cooldowns, or penalties compared to actual enforcement
This issue occurs naturally during normal operation and does not require a malicious actor.
The pool is initialized at block N
Phase 1 is configured to last X blocks and should end at block N + X
No swap is executed at block N + X
At block N + X + 1:
getCurrentPhase() returns Phase 2
currentPhase remains Phase 1
The next swap is executed and enforcement logic applies Phase 1 limits and penalties instead of Phase 2
This demonstrates that enforcement logic can lag behind the actual phase state.
Phase is a derived state that can be deterministically calculated from launchStartBlock and block.number. Storing it in contract state introduces unnecessary complexity and creates opportunities for state desynchronization.
To ensure consistent behavior, the contract should rely on a single phase calculation mechanism that is evaluated at execution time.
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.