Procedures for heartbeat failures, deviation spikes, network partitions, and timeouts. Implementation lives in:
- Validator:
contracts/vault/src/oracle.rs - Mock feed:
contracts/mock-strategy/src/mock_oracle.rs - Tests:
contracts/vault/src/oracle_failure_tests.rs
The vault is fail-closed. Invalid oracle data never falls back to a cached
or stale price. total_assets() (and anything that calls it, including
invest) aborts.
| Mode | Mock API | Validator error | Operator meaning |
|---|---|---|---|
| Heartbeat / stale | StaleHeartbeat or ledger time > heartbeat |
HeartbeatExceeded |
Feed stopped updating |
| Deviation spike | DeviationSpike (3× last price) |
PriceDeviationExceeded |
Flash crash / manipulation |
| Zero / negative | ZeroPrice / NegativePrice |
PriceZero |
Corrupt payload |
| Bad decimals | InvalidDecimals |
InvalidDecimals |
Precision attack |
| Future timestamp | FutureTimestamp |
TimestampInFuture |
Clock / injection |
| Network partition | NetworkPartition |
host panic oracle network partition |
Oracle contract unreachable |
| RPC / call timeout | Timeout |
host panic oracle timeout |
Call does not return |
Heartbeat default is 3600s. Deviation circuit breaker default is 5000 bps
(50%) vs the last validated price (LastValidatedPrice).
- Confirm —
try_total_assets/ RPC error. Pause reason should bePauseReason::OracleFailureif the vault is halted. - Halt price-sensitive flow — do not invest, divest-for-rebalance, or quote TVL from the failed feed. Deposits that do not consult the oracle still mint against accounting state; prefer pausing if the feed is required for user-facing NAV.
- Classify
- Heartbeat / timeout: wait for a fresh push, or rotate the oracle via the
timelocked
queue_price_oracle_change→execute_price_oracle_change. - Deviation spike: do not widen
MAX_PRICE_DEVIATION_BPSin an incident. Investigate the upstream feed; resume only after a new price is within 50% of the last validated value or after a deliberate admin oracle rotation (which clears the last-price cache on a new address only after the next successful read). - Partition: restore the RPC / contract; the mock
NetworkPartitionflag is the test stand-in.
- Heartbeat / timeout: wait for a fresh push, or rotate the oracle via the
timelocked
- Resume —
set_oracle_enabled(true)only if it was turned off. Calltotal_assetsonce in a dry-run / testnet check. Unpause if paused. - Disable (last resort) —
set_oracle_enabled(false)skips the feed. Phase 1 defaults to disabled. Production should keep it enabled once the feed is live.
cargo test -p vault oracle
cargo test -p mock-strategyCoverage target for oracle modules is ≥ 90% of validator branches (heartbeat, deviation, bounds, slippage, conversion) plus every mock failure mode wired through the vault.