Skip to content

fix(settlement): reject stale oracle prices with conservative fallback#43

Merged
elizabetheonoja-art merged 2 commits into
Utility-Protocol:mainfrom
real-venus:fix/oracle-staleness-settlement
Jun 25, 2026
Merged

fix(settlement): reject stale oracle prices with conservative fallback#43
elizabetheonoja-art merged 2 commits into
Utility-Protocol:mainfrom
real-venus:fix/oracle-staleness-settlement

Conversation

@real-venus

Copy link
Copy Markdown
Contributor

fix(settlement): reject stale oracle prices with conservative fallback

Closes #7

  • constants.rs
    • MAX_ORACLE_AGE = 600 (epoch-seconds), compile-time enforced to be within
      [300, 3600] via a const _: () = assert!(…).
    • FALLBACK_RATE = 50_000_000 (7-decimal fixed point), MAX_STALENESS_TOLERANCE.
  • rate_application.rs
    • is_stale(now, last_updated) — stale ⇔ age > MAX_ORACLE_AGE (boundary age is
      fresh; saturating sub tolerates clock skew).
    • apply_rate_to_volume(volume, rate) — overflow-checked volume * rate / 1e7.
    • resolve_rate(env, oracle) — fresh oracle price, else conservative
      FALLBACK_RATE with a StaleFbk(now, last_updated, fallback) event.
    • get_fresh_rate(env, oracle) -> Result<i128, SettlementError> — strict,
      halt-on-stale variant (step 7: propagate a Result instead of panicking).
  • lib.rs
    • Extend the PriceOracle cross-contract interface with
      get_price() -> OraclePrice, an XDR-compatible mirror of
      price_oracle::PriceData (incl. last_updated).
    • Add SettlementError::OracleStale.
    • finalize_settlement now uses resolve_rate, so rate_used reflects the rate
      actually applied (fresh price or fallback).
  • conversion.rsconvert_to_settlement_currency takes the already-resolved
    rate as a parameter (single source of truth; no double oracle fetch).

Files

  • contracts/settlement/src/constants.rs
  • contracts/settlement/src/rate_application.rs
  • contracts/settlement/src/conversion.rs
  • contracts/settlement/src/lib.rs
  • contracts/settlement/src/test.rs

Utility-Protocol#7)

finalize_settlement priced volume from the oracle without checking the
feed's freshness. A stale feed lets an attacker accumulate under-priced
settlements during the stale window.

- constants: MAX_ORACLE_AGE=600 (compile-time bounded to [300,3600]),
  FALLBACK_RATE=50_000_000, MAX_STALENESS_TOLERANCE
- rate_application: is_stale(), apply_rate_to_volume(), resolve_rate()
  (fresh price or conservative FALLBACK_RATE + StaleFbk event), and a
  strict get_fresh_rate() -> Result<i128, OracleStale>
- lib.rs: extend PriceOracle interface with get_price() -> OraclePrice
  (mirrors price_oracle::PriceData incl. last_updated); add
  SettlementError::OracleStale; finalize_settlement uses resolve_rate so
  rate_used reflects the rate actually applied
- conversion: take the resolved rate as a parameter
- tests: fresh (under + boundary), stale->fallback, strict Result
  rejection, plus pure unit tests for is_stale/apply_rate_to_volume and
  the MAX_ORACLE_AGE bound

Note: the real tariff oracle lives in utility_contracts, which does not
compile (129+ pre-existing SDK-23 errors per COMPILATION_STATUS.md). The
staleness control is implemented in the settlement crate where the rate is
consumed (issue step 7) and the code builds/tests cleanly.
…s-settlement

# Conflicts:
#	contracts/settlement/src/lib.rs
#	contracts/settlement/src/test.rs
@elizabetheonoja-art elizabetheonoja-art merged commit d390826 into Utility-Protocol:main Jun 25, 2026
2 checks 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.

Oracle Price Feed Staleness Attack Vector in Tariff Rate Computation

2 participants