Skip to content

feat(contracts): add oracle staleness gate to lending_pool (#1379 Phase 1) - #1571

Open
david87131 wants to merge 3 commits into
LabsCrypt:mainfrom
david87131:fix/issue-1379-oracle-staleness-check
Open

feat(contracts): add oracle staleness gate to lending_pool (#1379 Phase 1)#1571
david87131 wants to merge 3 commits into
LabsCrypt:mainfrom
david87131:fix/issue-1379-oracle-staleness-check

Conversation

@david87131

Copy link
Copy Markdown

Scope

This is a scoped-down, contract-only first slice of #1379, which is a large cross-layer epic (contract circuit-breaker, backend monitor, frontend banner, CI/infra wiring, verification — the issue itself estimates 48-72 hours across 5 phases). This PR implements only the core, security-critical piece: an on-chain staleness check that reverts when a cached oracle price is too old.

Context / honest discrepancy from the issue text

The issue assumes lending_pool and loan_manager both read a RateOracle PriceData { rate, updated_ledger } with no staleness bound. In the actual current codebase:

  • lending_pool has no oracle integration at all today.
  • loan_manager does read an oracle (RateOracleInterface::get_rate), but that interface returns a raw u32 rate with no updated_ledger/staleness metadata whatsoever — there's nothing to bound yet on that side without a larger interface change.

Given that, this PR adds the reusable staleness-check primitive to lending_pool exactly at the file path the issue calls out (contracts/lending_pool/src/oracle.rs), so it exists and is tested independently, ready to be wired into real call sites in follow-up phases.

What's implemented

  • contracts/lending_pool/src/oracle.rs:
    • PriceData { rate: i128, updated_ledger: u32 }
    • set_oracle_price(admin, asset, rate) — admin-gated, stamps the current ledger as updated_ledger (stands in for a live oracle feed until one is wired up)
    • set_oracle_max_age(admin, max_age) — admin-gated setter for the staleness threshold (defaults to DEFAULT_ORACLE_MAX_AGE_LEDGERS = 60)
    • require_fresh_price(asset) -> Result<PriceData, PoolError> — computes age = current_ledger - updated_ledger, reverts with PoolError::OracleStale if age > max_age
  • PoolError::OracleStale added to the existing error enum (matches existing style)
  • DataKey::OraclePrice(Address) and DataKey::OracleMaxAge added to storage keys
  • Public contract entrypoints: set_oracle_price, set_oracle_max_age, get_oracle_max_age, require_fresh_price, all following the contract's existing Self::admin(&env).require_auth() pattern
  • 5 new unit tests in contracts/lending_pool/src/test.rs: fresh price passes, stale price reverts with OracleStale, exact-boundary age still passes, set_oracle_price requires admin auth, missing price returns NotInitialized

What's explicitly deferred (not in this PR)

  • loan_manager integration — wiring an actual staleness bound into RateOracleInterface::get_rate / borrow / liquidate (requires extending that interface with an updated_ledger, a larger and separately-reviewable change)
  • Backend oracleHealthMonitor.ts / defaultChecker.ts drift detection
  • Frontend OracleStatusBanner.tsx / useOracleStatus.ts
  • CI/infra wiring across the 4 workflow files and docker-compose.yml
  • The pause/resume circuit-breaker state machine, debounce logic, and event emission described in the full issue
  • Governance/multisig-gated resume() flow

Test plan

  • cargo build --lib passes for lending_pool in isolation from this change
  • cargo test — all 63 tests pass (58 pre-existing + 5 new oracle tests)
  • ⚠️ Note: contracts/lending_pool/src/lib.rs and src/test.rs on main already contain pre-existing dead/broken code appended after mod test; (a duplicate deposit free function referencing an undefined Error type, and a matching broken test using undefined helpers) that fails to compile as-is. This is pre-existing on main, unrelated to this change, and out of scope here — flagging it separately rather than folding an unrelated fix into this PR. I verified this PR's own code builds and all its tests pass by temporarily excising that pre-existing dead code locally, confirming the failure is isolated to it and not to anything touched in this diff.

Addresses #1379

Add require_fresh_price to contracts/lending_pool: an admin-pushed price
cache stamped with the recording ledger, a configurable max-age (default
60 ledgers), and a PoolError::OracleStale revert when a cached price is
older than that threshold. Includes an admin-gated set_oracle_price /
set_oracle_max_age pair and unit tests covering fresh, stale, boundary,
missing-price, and auth-gated cases.

This is a scoped Phase 1 slice of LabsCrypt#1379: lending_pool has no live oracle
integration today (rate logic lives in loan_manager, which only exposes a
raw rate with no staleness metadata), so this establishes the reusable
freshness-check primitive called out in the issue's file list without
attempting the full cross-layer circuit breaker.

Addresses LabsCrypt#1379
cargo fmt --check was failing on lending_pool/src/test.rs due to a
double blank line before the new oracle staleness test section.

Addresses LabsCrypt#1379
…le-staleness-check

# Conflicts:
#	contracts/lending_pool/src/lib.rs
#	contracts/lending_pool/src/test.rs
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