Capricorn Exchange

Capricorn

Cantina Security Report

Engagement Type

Cantina Solo

Period

-

Researchers


Findings

Informational

2 findings

2 fixed

0 acknowledged

Gas Optimizations

1 findings

0 fixed

1 acknowledged


Informational2 findings

  1. Initial IncreaseObservationCardinalityNext emit does not indicate completed observation initialization

    Severity

    Severity: Informational

    Submitted by

    slowfi


    Description

    The pool initialization logic from contract CapricornCLPool emits the IncreaseObservationCardinalityNext event with INITIAL_OBSERVATION_CARDINALITY (around line 283):

    emit IncreaseObservationCardinalityNext(cardinalityNext, INITIAL_OBSERVATION_CARDINALITY);

    However, at this point the observation history is still of length 1, because no in-range liquidity update or price movement has yet triggered observations.write. As a result, off-chain tooling that relies on this event as a signal that the TWAP observation buffer is “ready” may read and use TWAPs that are effectively based on a single observation.

    This behavior does not affect on-chain correctness, but it can cause off-chain indexers, oracles, or analytics systems that assume the first IncreaseObservationCardinalityNext event only occurs after an explicit increaseObservationCardinalityNext call (and after multiple observations exist) to misinterpret the state of the pool’s observation history.

    Recommendation

    Consider to make the semantics explicit for integrators by one or more of the following:

    • Consider to document that the initial IncreaseObservationCardinalityNext emit during pool initialization does not imply that the observation buffer already contains multiple timestamp-separated observations, and that off-chain components should wait for at least two distinct writes before trusting TWAP data.
    • Consider to add an inline comment near the emit clarifying that observation history remains length-1 until the first in-range liquidity update or price change triggers observations.write.
  2. Semantically clearer use of cardinalityNext in observations.grow

    State

    Fixed

    PR #1

    Severity

    Severity: Informational

    Submitted by

    slowfi


    Description

    Found by Capricorn team.

    The function initialize from contract CapricornCLPool pre-allocates oracle slots by calling observations.grow. The previous version used:

    // Pre-allocate oracle slots so the pool creator paysobservations.grow(cardinality, INITIAL_OBSERVATION_CARDINALITY);

    and this was updated to:

    // Pre-grow oracle slots.observations.grow(cardinalityNext, INITIAL_OBSERVATION_CARDINALITY);

    Immediately after observations.initialize, both cardinality and cardinalityNext hold the same value, so the two calls are functionally equivalent in this context. The change to cardinalityNext aligns better with the intended semantics, since subsequent logic and events (such as IncreaseObservationCardinalityNext) conceptually work with cardinalityNext as the target observation capacity.

    This is not a functional or security issue, but a small readability and consistency improvement between the variable used in observations.grow and the rest of the observation configuration logic.

    Recommendation

    Consider to:

    • Keep using cardinalityNext in the observations.grow call at initialization, as it better reflects the intended target cardinality while preserving the same behavior as the previous implementation.

Gas Optimizations1 finding

  1. Redundant conditional on cardinalityNext during observation initialization

    State

    Acknowledged

    Severity

    Severity: Gas optimization

    Submitted by

    slowfi


    Description

    The function initialize from contract CapricornCLPool contains a conditional around the IncreaseObservationCardinalityNext event:

    if (cardinalityNext != INITIAL_OBSERVATION_CARDINALITY) {    emit IncreaseObservationCardinalityNext(cardinalityNext, INITIAL_OBSERVATION_CARDINALITY);}

    Given the current initialization flow, observations.initialize sets both cardinality and cardinalityNext to 1, while INITIAL_OBSERVATION_CARDINALITY is set to 3. Under these parameters, the condition cardinalityNext != INITIAL_OBSERVATION_CARDINALITY is always satisfied at initialization, so the if statement does not change the behavior of the function.

    This pattern does not introduce a functional issue but adds a branch that is redundant for the current configuration and slightly reduces readability by suggesting configuration-dependent behavior that does not occur in practice.

    Recommendation

    Consider to:

    • Remove the conditional and emit IncreaseObservationCardinalityNext unconditionally during initialization, or
    • Explicitly document that the conditional is only relevant for alternative configurations where INITIAL_OBSERVATION_CARDINALITY might equal the initial cardinalityNext.

    Capricorn: This was done to be rigorous, since INITIAL_OBSERVATION_CARDINALITY was used as a configuration file and could be 1. It is possible to remove this in this case that INITIAL_OBSERVATION_CARDINALITY=3, but the team prefers to keep this here for safety/ease of reasoning, especially since we don't expect this to meaningfully affect gas.

    Cantina Managed: Acknowledged by Capricorn team.