Pre-auditThree days of senior audit time for $1,500, credited in full toward your Zealynx audit.3 audit days, credited toward your auditSee how →
L-03·

The Chainlink adapter rejects every Chronicle answer (startedAt is always 0), so Chronicle-priced assets cannot be filled or are silently repriced

Fixedrwarfqoracles
TL;DR

OctarineChainlinkOracle is the AggregatorV3 adapter for "Chainlink, RedStone, Chronicle, Superstate" (src/shared/README.md L11). getPrice treats a zero startedAt as an invalid round (L45):

Severity
LOW
Method
MManual review
CAT.
02Section · Affected files

Affected files

src/shared/oracles/OctarineChainlinkOracle.sol#L45

03Section · Description

Description

OctarineChainlinkOracle is the AggregatorV3 adapter for "Chainlink, RedStone, Chronicle, Superstate" (src/shared/README.md L11). getPrice treats a zero startedAt as an invalid round (L45):

solidity
if (roundId == 0 || answeredInRound < roundId || startedAt == 0 || updatedAt == 0) return (0, updatedAt);

Chronicle's Scribe implements latestRoundData() with startedAt hard-coded to zero. In src/Scribe.sol L289-L307, it sets roundId = 1, startedAt = 0, updatedAt to the poke age, and answeredInRound = roundId. Every Chronicle answer, however fresh, therefore returns price 0 from getPrice, while latestPriceUnchecked (which has no startedAt check) serves the same answer. What happens next depends on how the adapter is registered in OctarineRFQ:

  • As the asset's leg to the reference asset: _unitPrice reverts with NoPriceAvailable, so no fill on that asset can settle.
  • As a direct pair: _tryOracle treats the feed as down, and the fill settles on the reference-asset route instead of the Chronicle price, without a revert or any other signal.

The other checks already cover what startedAt == 0 was meant to catch: updatedAt == 0 rejects a round that never completed, and both staleness bounds reject an old one.

04Section · Recommendations

Recommendations

Drop the startedAt clause (and its now-unused binding):

diff
@@ -38,11 +38,10 @@ contract OctarineChainlinkOracle is OctarineOracleBase, Ownable {
{
_checkPair(_base, _quote);
- (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) =
- feed.latestRoundData();
+ (uint80 roundId, int256 answer,, uint256 updatedAt, uint80 answeredInRound) = feed.latestRoundData();
lastUpdated = updatedAt;
- if (roundId == 0 || answeredInRound < roundId || startedAt == 0 || updatedAt == 0) return (0, updatedAt);
+ if (roundId == 0 || answeredInRound < roundId || updatedAt == 0) return (0, updatedAt);
if (answer <= 0) return (0, updatedAt);
if (updatedAt > block.timestamp || block.timestamp - updatedAt > maxStaleness) return (0, updatedAt);

With this change the full test suite passes (706 passed, 1 skipped).

05Section · Remediation

Remediation

Fixed in 413a466, as recommended. The Chronicle PoC's harm tests now fail, and the lifecycle test passes.

A runnable Foundry proof of concept for this finding is in the corresponding issue on the project tracker.

06Section · Report provenance

Report provenance

Source: the final report dated 28 October 2026. Original finding identifiers are retained, including M-03 and M-04, which were regraded Low. Final dispositions and residual conditions are preserved in this write-up. Read the full report.

Status
Fixed
L-03