Skip to content

fix(oracles): UniswapV3Oracle quote-feed circuit breaker + setTwapPeriod cardinality recheck#187

Merged
drewstone merged 1 commit into
mainfrom
fix/uniswap-oracle-circuitbreaker-cardinality
Jun 19, 2026
Merged

fix(oracles): UniswapV3Oracle quote-feed circuit breaker + setTwapPeriod cardinality recheck#187
drewstone merged 1 commit into
mainfrom
fix/uniswap-oracle-circuitbreaker-cardinality

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Summary

Of the five reported oracle items, two are real and fixed here; three are non-issues (verified against the code).

# Finding Verdict
F1 UniswapV3Oracle quote feed missing min/maxAnswer circuit-breaker ✅ fixed
F2 setTwapPeriod skips observation-cardinality check ✅ fixed
F3 ChainlinkOracle sequencer feed: no staleness validation ❌ non-issue
F4 UniswapV3Oracle sequencer feed: no staleness validation ❌ non-issue
F5 ChainlinkOracle hardcodes native decimals = 18 ❌ non-issue

F1 (Medium) — quote-feed circuit breaker

ChainlinkOracle._getPriceData rejects an answer pinned to the aggregator's minAnswer/maxAnswer bound (a saturated, untrustworthy reading), but UniswapV3Oracle's quote-token Chainlink read (_getPriceData) only did sign/round/staleness checks. A saturated quote feed during an extreme dislocation would propagate a pinned floor/ceiling through the TWAP→USD conversion, under-/over-valuing the asset. Fix: added the same _checkAnswerBounds (try/catch with aggregator() resolution, direct-getter fallback) + AnswerOutOfBounds revert — parity with ChainlinkOracle.

F2 (Medium / low-ish — fail-closed) — setTwapPeriod cardinality

configurePool grows a pool's observation ring buffer to span the TWAP period in effect at config time. setTwapPeriod just reassigned the period, so raising it could outrun already-configured pools — observe() over the longer window then reverts (price unavailable; fail-closed, not mispricing, hence low-ish). Fix: track configured tokens and have setTwapPeriod re-ensure (and opportunistically grow) every configured pool for the new period, reverting InsufficientObservationCardinality if a pool can't be grown to support it (so an unsupportable period is rejected up front).

Non-issues (no change)

  • F3 / F4 (sequencer staleness): _requireSequencerUp already implements Chainlink's canonical L2 pattern — answer == 0 (up), block.timestamp - startedAt >= grace, plus a startedAt == 0 incomplete-round guard. Chainlink's own reference does not add an updatedAt staleness check on a status feed; the grace-period check is the correct equivalent.
  • F5 (native decimals = 18): correct for every EVM native gas token (ETH/MATIC/BNB…).

Tests (test/audit/medlow/Oracles.t.sol)

  • F1: quote answer at minAnswer/maxAnswerAnswerOutOfBounds; within-bounds prices normally.
  • F2: setTwapPeriod grows a growable configured pool to cover the raised window; reverts InsufficientObservationCardinality for a non-growable pool.
  • Full oracle suite green (Chainlink + Uniswap bounds/sequencer/cardinality/zero-price + adapter tests), no regressions.

Full ~1855-test suite not run in one shot (forge memory cap); ran the oracle subsystem. Changes are confined to UniswapV3Oracle.

…iod cardinality recheck

Two adapter/oracle hardening fixes; the other three reported items are non-issues
(see below).

F1 (Medium) — quote-feed min/maxAnswer circuit breaker:
  ChainlinkOracle rejects answers pinned to the aggregator's min/maxAnswer bound
  (saturated/untrustworthy), but UniswapV3Oracle's quote-token Chainlink read had
  no such check, so a saturated quote feed propagated a floor/ceiling value through
  the TWAP→USD conversion (under-/over-valuing the asset). Added the same
  _checkAnswerBounds (try/catch, aggregator() resolution) and AnswerOutOfBounds
  revert for parity.

F2 (Medium/low) — setTwapPeriod skipped the cardinality check:
  configurePool grows a pool's observation buffer to span the period in effect at
  config time, but setTwapPeriod just set the new period — a raised period could
  outrun already-configured pools (observe() reverts → price unavailable). Now
  setTwapPeriod re-ensures (and opportunistically grows) every configured pool for
  the new period, reverting InsufficientObservationCardinality if a pool cannot be
  grown to support it. Configured tokens are tracked in an appended array.

Not fixed (verified non-issues):
  - ChainlinkOracle / UniswapV3Oracle sequencer-uptime "staleness": _requireSequencerUp
    already implements Chainlink's canonical L2 pattern (answer==0 + startedAt!=0 +
    grace period); Chainlink's own reference does not staleness-check a status feed.
  - ChainlinkOracle native decimals hardcoded to 18: correct for every EVM native gas
    token (ETH/MATIC/BNB…).

Tests (test/audit/medlow/Oracles.t.sol): quote answer at min/max bound reverts
AnswerOutOfBounds (and within-bounds prices normally); setTwapPeriod grows a
growable pool and reverts for a non-growable one. Full oracle suite green.

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved PR — b52f5c50

Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-19T22:17:32Z

@drewstone drewstone merged commit e72d9ab into main Jun 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants