Capricorn
Cantina Security Report
Organization
- @capricorn-exchange
Engagement Type
Cantina Solo
Period
-
Researchers
Findings
Informational
2 findings
2 fixed
0 acknowledged
Gas Optimizations
1 findings
0 fixed
1 acknowledged
Informational2 findings
Initial IncreaseObservationCardinalityNext emit does not indicate completed observation initialization
Severity
- Severity: Informational
Submitted by
slowfi
Description
The pool initialization logic from contract
CapricornCLPoolemits theIncreaseObservationCardinalityNextevent withINITIAL_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
IncreaseObservationCardinalityNextevent only occurs after an explicitincreaseObservationCardinalityNextcall (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
IncreaseObservationCardinalityNextemit 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.
Semantically clearer use of cardinalityNext in observations.grow
Description
Found by Capricorn team.
The function
initializefrom contractCapricornCLPoolpre-allocates oracle slots by callingobservations.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, bothcardinalityandcardinalityNexthold the same value, so the two calls are functionally equivalent in this context. The change tocardinalityNextaligns better with the intended semantics, since subsequent logic and events (such asIncreaseObservationCardinalityNext) conceptually work withcardinalityNextas 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.growand the rest of the observation configuration logic.Recommendation
Consider to:
- Keep using
cardinalityNextin theobservations.growcall at initialization, as it better reflects the intended target cardinality while preserving the same behavior as the previous implementation.
- Keep using
Gas Optimizations1 finding
Redundant conditional on cardinalityNext during observation initialization
State
- Acknowledged
Severity
- Severity: Gas optimization
Submitted by
slowfi
Description
The function
initializefrom contractCapricornCLPoolcontains a conditional around theIncreaseObservationCardinalityNextevent:if (cardinalityNext != INITIAL_OBSERVATION_CARDINALITY) { emit IncreaseObservationCardinalityNext(cardinalityNext, INITIAL_OBSERVATION_CARDINALITY);}Given the current initialization flow,
observations.initializesets bothcardinalityandcardinalityNextto1, whileINITIAL_OBSERVATION_CARDINALITYis set to3. Under these parameters, the conditioncardinalityNext != INITIAL_OBSERVATION_CARDINALITYis always satisfied at initialization, so theifstatement 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
IncreaseObservationCardinalityNextunconditionally during initialization, or - Explicitly document that the conditional is only relevant for alternative configurations where
INITIAL_OBSERVATION_CARDINALITYmight equal the initialcardinalityNext.
Capricorn: This was done to be rigorous, since
INITIAL_OBSERVATION_CARDINALITYwas used as a configuration file and could be 1. It is possible to remove this in this case thatINITIAL_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.