# Oracle Integration -- Secure Price Feed Consumption on Solana
**Applies to:** All Solana protocols that consume external price data (lending, perpetuals, DEX aggregators, options, structured products, stablecoins)
**Audience:** Solana program developers, protocol architects, security engineers, auditors
**Related:** [[circuit-breakers]], [[monitoring]], [[authority-design]], [[rpc-security]]
---
## 1. Why Oracle Integration Is a Security Surface
An oracle is a source of external data that your program trusts to make on-chain decisions. For most DeFi protocols, this means price feeds -- the price of SOL/USD, BTC/USD, or any other asset pair. Your program uses these prices to determine collateral values, liquidation thresholds, swap rates, and settlement amounts.
The security implications are direct: **if the oracle data is wrong, your program makes wrong decisions with real money.** A stale price can prevent necessary liquidations. A manipulated price can trigger unjust liquidations. A compromised oracle can drain the entire protocol.
### 1.1 The Trust Boundary
When your program reads a price from an oracle account, it is trusting:
1. **The oracle provider** -- that they are publishing accurate data
2. **The oracle update mechanism** -- that data is being updated frequently enough
3. **The data sources** -- that the underlying price data comes from reliable, manipulation-resistant sources
4. **The on-chain delivery** -- that the oracle account has not been tampered with and is the genuine oracle account
5. **Freshness** -- that the data was published recently enough to be relevant
Each of these is a point of failure. Your program must validate every one of them, because the oracle provider's guarantees alone are not sufficient for a secure system.
### 1.2 Historical Oracle Exploits
Oracle-related exploits are among the most common and damaging in DeFi:
- **Mango Markets (October 2022):** An attacker manipulated the MNGO/USDC price on a low-liquidity market, causing the Mango protocol's oracle to report an inflated MNGO price. The attacker used this inflated collateral value to borrow (and drain) over $100M from the protocol.
- **Bonk Rewards Incident:** A stale oracle price allowed users to claim rewards at an outdated, favorable rate. The protocol did not check oracle freshness, and the delay between oracle updates was exploitable.
- **Various lending protocol liquidation failures:** Protocols that did not properly handle oracle staleness during network congestion failed to liquidate undercollateralized positions. When the oracle finally updated with a large price movement, the positions were deeply underwater, causing bad debt.
These are not exotic attacks. They are the predictable consequence of trusting oracle data without validation.
---
## 2. Oracle Fundamentals on Solana
### 2.1 Major Oracle Providers
| Provider | Model | Solana Support | Key Feature |
|----------|-------|----------------|-------------|
| **Pyth Network** | Pull-based (on-demand updates) and push-based | Native | Confidence intervals, sub-second updates, first-party data from exchanges |
| **Switchboard** | Push-based (periodic updates) and on-demand | Native | Customizable oracle queues, permissionless feeds |
| **Chainlink** | Push-based | Via integration partners | Widely used on other chains, limited Solana presence |
| **DIA** | On-demand and push-based | Native | Customizable data sources, transparent methodology |
### 2.2 Pull vs. Push Oracle Models
**Push-based oracles** publish price updates on a fixed schedule or when price deviates beyond a threshold. The oracle account on-chain always contains the most recently pushed data. Your program reads the account directly.
**Pull-based oracles** (like Pyth's on-demand model) require the consumer to request a price update as part of their transaction. The price data is fetched and verified within the same transaction that uses it. This ensures freshness but requires the transaction to include the oracle update instruction.
**Security tradeoffs:**
| Model | Freshness Guarantee | Cost | Complexity | Manipulation Window |
|-------|---------------------|------|------------|---------------------|
| **Push** | Depends on update frequency (seconds to minutes) | Oracle provider pays for updates | Simple to consume | Between updates, price may be stale |
| **Pull** | Near-zero staleness (updated in same transaction) | Consumer pays for update | Must include update instruction | Minimal -- but depends on data source quality |
### 2.3 Oracle Account Verification
Your program MUST verify that it is reading from the correct oracle account. An attacker can create a fake oracle account with manipulated prices and pass it to your instruction.
**How to verify:**
```rust
// Verify the oracle account is the expected account
// NEVER accept an arbitrary oracle account from the instruction input
// without verifying it against a known, stored pubkey
#[account(
constraint = oracle_account.key() == config.oracle_pubkey
@ ErrorCode::InvalidOracle
)]
pub oracle_account: AccountInfo<'info>,
```
**The oracle pubkey should be stored in your protocol's configuration account**, set by an admin authority, and only changeable through a governed process. Do not accept oracle addresses as arbitrary instruction arguments without on-chain verification.
---
## 3. Staleness Checks
### 3.1 What Is Staleness?
A stale price is a price that was published too long ago to be reliable for current decision-making. On Solana, oracle prices become stale when:
- The oracle provider's update transactions are delayed (network congestion, provider issues)
- The Solana network itself experiences a slowdown or halt
- The oracle provider stops publishing for a specific feed (decommissioning, bug)
- Market hours end for traditional assets (see Section 5)
### 3.2 Implementing Staleness Checks
Every oracle read MUST include a staleness check. The check compares the oracle's last update timestamp to the current on-chain clock:
```rust
use anchor_lang::prelude::*;
/// Maximum allowed age of oracle data in seconds.
/// This value should be tuned per asset and per use case.
const MAX_ORACLE_AGE_SECONDS: i64 = 30;
pub fn check_oracle_freshness(
oracle_timestamp: i64,
current_timestamp: i64,
max_age_seconds: i64,
) -> Result<()> {
let oracle_age = current_timestamp
.checked_sub(oracle_timestamp)
.ok_or(ErrorCode::OracleTimestampOverflow)?;
require!(
oracle_age <= max_age_seconds,
ErrorCode::OracleStale
);
// Also check for future timestamps (indicates clock issues or manipulation)
require!(
oracle_timestamp <= current_timestamp,
ErrorCode::OracleTimestampInFuture
);
Ok(())
}
```
### 3.3 Choosing Staleness Thresholds
The appropriate staleness threshold depends on the asset, the operation, and the protocol:
| Context | Recommended Max Age | Rationale |
|---------|---------------------|-----------|
| **Liquidation decisions** | 30-60 seconds | Liquidations must use current prices to be fair. Stale prices can cause unjust liquidations or missed liquidations. |
| **New position opening** | 30-60 seconds | Users opening positions deserve current pricing. |
| **Position closing / withdrawals** | 60-120 seconds | Slightly more lenient to avoid blocking user exits during minor oracle delays. |
| **Risk parameter calculations** | 60-300 seconds | Less time-sensitive. Used for maintenance margin calculations, interest rate adjustments. |
| **Traditional asset prices** | Depends on market hours | During market hours: 60-120 seconds. Outside market hours: use last closing price with appropriate handling (see Section 5). |
| **Highly volatile assets** | 10-30 seconds | Fast-moving prices require tighter staleness. A 60-second-old price for a volatile altcoin can be dangerously wrong. |
### 3.4 What to Do When the Oracle Is Stale
When the staleness check fails, the protocol must decide what to do. The correct default is to restrict operations:
| Operation | Action When Oracle Stale |
|-----------|-------------------------|
| **New deposits / position opens** | Block. Do not allow new risk to be taken on stale data. |
| **Withdrawals / position closes** | Allow at conservative valuation (see Section 6) or block if staleness exceeds a secondary, longer threshold. |
| **Liquidations** | Block. Liquidating at a stale price is potentially unjust and can be exploited. |
| **Settlements** | Block. Settlement at wrong prices compounds errors. |
| **Oracle-independent operations** | Allow. Operations that do not use price data (e.g., cancelling an order, updating metadata) should not be affected. |
This maps directly to the circuit breaker pattern (see [[circuit-breakers]]). When the oracle is stale beyond threshold, the protocol should automatically enter a ReduceOnly or restricted state.
---
## 4. Confidence Intervals and Deviation Checks
### 4.1 What Are Confidence Intervals?
Pyth Network provides not just a price but a confidence interval -- a range that represents the oracle's uncertainty about the true price. The confidence interval widens when:
- Different data sources report significantly different prices
- Market liquidity is low
- Market volatility is high
- Fewer data sources are contributing
A confidence interval of $100 +/- $0.50 means the oracle is confident the true price is between $99.50 and $100.50. A confidence interval of $100 +/- $5.00 means much less certainty.
### 4.2 Using Confidence Intervals
Your protocol should use confidence intervals to make conservative decisions:
```rust
/// Check that the oracle confidence interval is within acceptable bounds.
/// A wide confidence interval indicates the oracle is uncertain about the price.
pub fn check_oracle_confidence(
price: i64,
confidence: u64,
max_confidence_ratio_bps: u64, // e.g., 200 = 2%
) -> Result<()> {
// confidence / price should be below the threshold
let price_abs = (price as u64);
let ratio_bps = confidence
.checked_mul(10000)
.ok_or(ErrorCode::MathOverflow)?
.checked_div(price_abs)
.ok_or(ErrorCode::MathOverflow)?;
require!(
ratio_bps <= max_confidence_ratio_bps,
ErrorCode::OracleConfidenceTooWide
);
Ok(())
}
```
### 4.3 Conservative Pricing with Confidence Intervals
When the protocol must choose between favoring users or favoring protocol safety, use the conservative bound of the confidence interval:
| Operation | Conservative Direction | Price to Use |
|-----------|----------------------|--------------|
| **Valuing user collateral** | Lower is safer for the protocol | Price - Confidence |
| **Valuing user debt** | Higher is safer for the protocol | Price + Confidence |
| **Liquidation threshold** | Use the pessimistic collateral value | Price - Confidence for collateral |
| **New borrows** | Use pessimistic collateral and optimistic debt | Collateral: Price - Confidence; Debt: Price + Confidence |
This is the **conservative valuation principle**: when uncertain, favor the protocol's safety. Users may get slightly worse prices, but the protocol avoids insolvency.
### 4.4 Deviation Checks Against Secondary Sources
A single oracle can be wrong. Cross-referencing against a secondary source detects outliers:
```rust
/// Check that two oracle prices are within acceptable deviation of each other.
pub fn check_price_deviation(
primary_price: i64,
secondary_price: i64,
max_deviation_bps: u64, // e.g., 200 = 2%
) -> Result<()> {
let diff = (primary_price - secondary_price).unsigned_abs();
let avg = ((primary_price + secondary_price) / 2).unsigned_abs();
let deviation_bps = diff
.checked_mul(10000)
.ok_or(ErrorCode::MathOverflow)?
.checked_div(avg)
.ok_or(ErrorCode::MathOverflow)?;
require!(
deviation_bps <= max_deviation_bps,
ErrorCode::OracleDeviationTooHigh
);
Ok(())
}
```
**When deviation is too high:**
- Block risk-increasing operations (new positions, borrows)
- Allow risk-decreasing operations (withdrawals, position closes) at the conservative price
- Alert the team for investigation
- Consider entering ReduceOnly state (see [[circuit-breakers]])
---
## 5. Market-Hours Gaps
### 5.1 The Problem
Traditional assets (stocks, commodities, forex) trade during specific market hours. Outside these hours, no new price data is generated. The last published price may be hours or even days old (weekends).
For protocols that use oracle prices for traditional assets (synthetic stocks, commodity-linked tokens, real-world asset protocols), this creates a dilemma: the oracle price is "stale" by the staleness threshold, but this staleness is expected and legitimate.
### 5.2 Handling Market-Hours Gaps
| Approach | Description | Tradeoff |
|----------|-------------|----------|
| **Use last close price** | Accept the last pre-close price during market hours gaps. Override the staleness check for expected gaps. | Simple. Risk: overnight news events make the last close price irrelevant when markets reopen. |
| **Widen staleness threshold during gaps** | Increase the max oracle age during known market-hours gaps (e.g., allow 16-hour staleness overnight, 64-hour over weekends). | Flexible. Risk: a truly stale oracle (provider failure) during a gap goes undetected. |
| **Block operations during gaps** | Do not allow any price-dependent operations during market-hours gaps. | Safest. Risk: users cannot exit positions during gaps, which can cause losses if markets gap against them. |
| **Use conservative pricing** | Allow operations during gaps but apply a haircut to the last known price (e.g., reduce collateral value by 5% during a gap). | Balanced. Users can exit at a penalty. Protocol is protected against adverse gaps. |
**Recommended approach for L3+:** Block risk-increasing operations during market-hours gaps. Allow risk-decreasing operations at a conservative valuation (last close price minus a gap haircut). Alert the team when gap periods begin and end.
### 5.3 Market-Hours Configuration
Define market hours in your protocol configuration, not hardcoded in the program:
```rust
#[account]
pub struct OracleConfig {
/// Oracle account pubkey
pub oracle_pubkey: Pubkey,
/// Maximum oracle age in seconds during normal market hours
pub max_age_normal: i64,
/// Maximum oracle age in seconds during expected market-hours gaps
pub max_age_gap: i64,
/// Market hours: start time (seconds from midnight UTC)
pub market_open_utc: u32,
/// Market hours: end time (seconds from midnight UTC)
pub market_close_utc: u32,
/// Which days the market is open (bitmask: bit 0 = Sunday, bit 6 = Saturday)
pub trading_days: u8,
/// Haircut applied to collateral valuation during market-hours gaps (basis points)
pub gap_haircut_bps: u16,
/// Authority that can update these parameters
pub oracle_admin: Pubkey,
}
```
---
## 6. Conservative Valuation Principles
### 6.1 The Core Principle
When there is uncertainty about a price -- whether from wide confidence intervals, moderate staleness, cross-source deviation, or market-hours gaps -- the protocol should value assets conservatively:
- **Collateral is worth less** than the oracle says (protects against liquidation threshold violations)
- **Debt is worth more** than the oracle says (ensures sufficient collateralization)
- **Fees are calculated on the higher value** (prevents fee underpayment)
### 6.2 Applying Haircuts
A haircut is a percentage reduction applied to an asset's oracle value to account for uncertainty:
```rust
/// Apply a haircut to a price for conservative valuation.
/// haircut_bps: 500 = 5% haircut (value reduced to 95%)
pub fn apply_haircut(
price: u64,
haircut_bps: u64,
) -> Result<u64> {
let reduced = price
.checked_mul(10000 - haircut_bps)
.ok_or(ErrorCode::MathOverflow)?
.checked_div(10000)
.ok_or(ErrorCode::MathOverflow)?;
Ok(reduced)
}
```
### 6.3 When to Apply Haircuts
| Condition | Suggested Haircut | Rationale |
|-----------|-------------------|-----------|
| Normal operation, high-confidence oracle | 0% | Oracle data is reliable. |
| Confidence interval > 1% of price | 1-3% additional | Oracle is uncertain. Use pessimistic bound. |
| Oracle age > 50% of staleness threshold | 1-2% | Price is aging. Hedge against staleness. |
| Market-hours gap (expected) | 3-10% | Overnight or weekend gaps can produce significant moves. |
| Cross-source deviation > 1% | 2-5% | Sources disagree. Use conservative price. |
| Network congestion (oracle updates delayed) | 2-5% | Delays increase price uncertainty. |
---
## 7. Latency-Aware Execution
### 7.1 The Latency Problem
On Solana, there is always a delay between when a price is observed off-chain and when a transaction using that price is executed on-chain. This delay creates opportunities for front-running and arbitrage.
Consider a liquidation:
1. Off-chain monitoring detects that a position is liquidatable at the current oracle price
2. A liquidation transaction is submitted
3. Between submission and execution, the oracle price may change
4. The liquidation executes at a price that is different from when the decision was made
### 7.2 Mitigations for Latency
| Mitigation | Description |
|------------|-------------|
| **Use on-chain oracle price, not off-chain price** | Your program should always read the oracle price on-chain at execution time, not accept a price as an instruction argument. The on-chain price at execution time is the source of truth. |
| **Apply slippage protection** | For operations like swaps, implement slippage bounds that reject the transaction if the price has moved too far from expectations. |
| **Use pull-based oracles for time-sensitive operations** | Pull-based oracles (like Pyth on-demand) update the price in the same transaction, minimizing the window between price observation and price use. |
| **Rate-limit oracle-dependent operations** | Prevent rapid-fire operations that exploit small oracle update windows. |
### 7.3 Oracle Update Ordering
In a transaction that both updates an oracle and uses the updated price, ensure the update instruction comes before the usage instruction. If the order is reversed, the program reads the old price before the update:
```
Correct:
Instruction 1: Update Pyth price feed
Instruction 2: Your program reads the updated price and acts on it
Incorrect:
Instruction 1: Your program reads the OLD price
Instruction 2: Update Pyth price feed (too late, your program already read the old price)
```
Your program should verify that the oracle timestamp in the data it reads is recent, regardless of instruction ordering. The staleness check (Section 3) catches this.
---
## 8. Pause and Guardrails When Oracle Data Is Unreliable
### 8.1 Automated Guardrails
Your protocol should automatically restrict operations when oracle data quality degrades:
| Condition | Automated Response |
|-----------|-------------------|
| Oracle stale beyond threshold | Enter ReduceOnly state. Block new positions. Allow exits. |
| Confidence interval too wide | Block liquidations and new positions. Allow exits at conservative price. |
| Cross-source deviation too high | Block all price-dependent operations. Alert team. |
| Oracle returns zero or negative price | Emergency halt. This should never happen with a functioning oracle. |
| Oracle account data length unexpected | Reject. The account may have been reallocated or replaced. |
These guardrails should be implemented in the program logic, not relying on off-chain monitoring alone. Off-chain monitoring should alert the team, but on-chain guardrails must enforce safety even if monitoring fails.
### 8.2 Integration with Circuit Breakers
Oracle guardrails complement the circuit breaker (see [[circuit-breakers]]):
```
Oracle-specific guardrail (per-feed)
|
v (triggers if oracle data is bad)
ReduceOnly or per-market restriction
|
v (if multiple oracles fail or situation escalates)
Protocol-wide circuit breaker (EmergencyStop)
```
The oracle guardrail is a more granular control. If one oracle feed is stale, only the markets using that feed are restricted. The circuit breaker is a protocol-wide halt for systemic issues.
### 8.3 Automation Guardian Integration
The automation guardian (see [[circuit-breakers]], [[authority-design]]) should monitor oracle health and trigger restrictions automatically:
| Trigger | Guardian Action |
|---------|----------------|
| Oracle stale > 2x threshold | Escalate to ReduceOnly for affected markets |
| Oracle stale > 5x threshold | Consider protocol-wide ReduceOnly |
| Multiple oracles stale simultaneously | Escalate to EmergencyStop |
| Confidence > 5% of price | Escalate affected markets to ReduceOnly |
| Cross-source deviation > 10% | Alert + ReduceOnly for affected markets |
---
## 9. Multi-Oracle Strategies
### 9.1 Why Use Multiple Oracles?
A single oracle is a single point of failure. Using multiple oracles provides:
- **Redundancy:** If one oracle fails, the protocol can fall back to another
- **Cross-validation:** Comparing two oracles detects manipulation or errors in either
- **Availability:** Different oracle providers may have different uptime characteristics
### 9.2 Multi-Oracle Patterns
| Pattern | How It Works | Tradeoff |
|---------|-------------|----------|
| **Primary with fallback** | Use Oracle A. If Oracle A is stale or unavailable, use Oracle B. | Simple. Risk: if Oracle A is manipulated but not stale, the fallback is never triggered. |
| **Median of three** | Use three oracles. Take the median price. | Robust against one bad oracle. Cost: three oracle accounts per feed. |
| **Best-of-two with deviation check** | Use two oracles. If they agree (within threshold), use the average. If they disagree, restrict operations. | Good balance. Cost: two oracle accounts per feed. |
| **Weighted average** | Assign weights to different oracles based on trust, freshness, or confidence. | Flexible. Complex to implement and tune. |
### 9.3 Implementing Primary with Fallback
```rust
pub fn get_validated_price(
primary_oracle: &AccountInfo,
fallback_oracle: &AccountInfo,
current_timestamp: i64,
max_age_seconds: i64,
max_deviation_bps: u64,
) -> Result<ValidatedPrice> {
// Try primary oracle first
let primary_result = read_and_validate_oracle(
primary_oracle,
current_timestamp,
max_age_seconds,
);
match primary_result {
Ok(primary_price) => {
// Primary is valid. Optionally cross-check with fallback.
let fallback_result = read_and_validate_oracle(
fallback_oracle,
current_timestamp,
max_age_seconds,
);
if let Ok(fallback_price) = fallback_result {
// Both available -- check deviation
check_price_deviation(
primary_price.price,
fallback_price.price,
max_deviation_bps,
)?;
}
// Fallback unavailable is acceptable if primary is valid
Ok(primary_price)
}
Err(_) => {
// Primary failed -- use fallback
let fallback_price = read_and_validate_oracle(
fallback_oracle,
current_timestamp,
max_age_seconds,
)?;
// Emit event: primary oracle failed, using fallback
emit!(OracleFallbackUsed {
primary: primary_oracle.key(),
fallback: fallback_oracle.key(),
timestamp: current_timestamp,
});
Ok(fallback_price)
}
}
}
```
### 9.4 Oracle Switching Risks
When switching from one oracle to another (whether as a fallback or a planned migration), be aware of:
- **Price discontinuities:** Different oracles may report slightly different prices for the same asset. Switching oracles can cause sudden apparent price changes that trigger liquidations or other actions.
- **Methodology differences:** Different oracles aggregate from different sources. One oracle's "BTC/USD" may include different exchanges than another's.
- **Authority change:** The new oracle has a different operator with different trust assumptions.
---
## 10. Oracle Manipulation Risks
### 10.1 On-Chain Manipulation
Some "oracles" use on-chain data (e.g., AMM pool ratios) as their price source. These are vulnerable to flash loan manipulation:
1. Attacker takes a flash loan of a large amount of Token A
2. Dumps Token A into an AMM pool, crashing the pool's price ratio
3. Uses the manipulated pool ratio (read as a "price") to borrow or liquidate at a favorable rate in the victim protocol
4. Repays the flash loan
**Mitigation:** Do not use AMM pool ratios as a price oracle for lending or collateral valuation. Use off-chain aggregated oracles (Pyth, Switchboard) that aggregate from multiple sources and are resistant to single-market manipulation.
### 10.2 Low-Liquidity Market Manipulation
Even off-chain oracle providers can be manipulated if the underlying market has low liquidity:
- The Mango Markets exploit worked because the MNGO/USDC market had low liquidity
- The attacker placed large orders on both sides, artificially moving the market price
- The oracle faithfully reported this manipulated price
- The protocol trusted the oracle price without additional validation
**Mitigation:**
- For low-liquidity assets, apply additional haircuts and tighter borrowing limits
- Use oracle confidence intervals -- low-liquidity markets produce wider confidence intervals
- Set lower collateral factors for assets with thin markets
- Consider not supporting assets with insufficient liquidity for reliable price discovery
### 10.3 Oracle Provider Compromise
If the oracle provider itself is compromised (keys stolen, infrastructure hacked), all feeds from that provider are untrustworthy.
**Mitigation:**
- Use multiple oracle providers for critical feeds
- Monitor for anomalous price movements that do not correspond to market events
- Implement circuit breakers that trigger on extreme price movements regardless of oracle freshness
---
## 11. Common Mistakes
| Mistake | Risk | Prevention |
|---------|------|------------|
| **No staleness check on oracle data** | Program uses hours-old prices. Liquidations execute at wrong prices. Positions open at stale rates. | Every oracle read must include a staleness check. No exceptions. |
| **Staleness threshold too generous** | Threshold of 600 seconds (10 minutes) allows significant price movement, especially for volatile assets. | Tune staleness thresholds per asset and per operation. Start conservative (30-60 seconds for volatile assets). |
| **No confidence interval check** | Program uses a price with very wide confidence. The "price" could be off by 5% in either direction. | Check confidence intervals. Reject or restrict operations when confidence is too wide. |
| **Using AMM pool ratios as price oracle** | Flash loan manipulation can move pool ratios instantaneously, enabling exploits. | Use off-chain aggregated oracles (Pyth, Switchboard) that are resistant to single-market manipulation. |
| **Accepting arbitrary oracle accounts** | Attacker passes a fake oracle account with a manipulated price to the instruction. | Verify oracle account pubkeys against stored configuration. Never accept unverified oracle accounts. |
| **Not handling market-hours gaps** | Traditional asset prices become stale outside market hours. Protocol either blocks all operations or uses dangerously old prices. | Implement market-hours-aware staleness logic with conservative pricing during gaps. |
| **Single oracle dependency** | If the one oracle provider goes down, the entire protocol halts. | Use at least two oracle sources for critical price feeds. Implement primary/fallback logic. |
| **No automated oracle guardrails** | When the oracle degrades, the protocol continues operating at full capacity with bad data. | Implement on-chain guardrails that automatically restrict operations when oracle data quality degrades. |
| **Not monitoring oracle health** | Oracle staleness or degradation goes undetected until a user reports a problem. | Monitor oracle freshness, confidence, and cross-source deviation. Alert on anomalies. |
| **Trusting oracle data during extreme market events** | During market crashes, oracle data may be delayed, incomplete, or conflicting. The protocol continues to operate normally. | During extreme market conditions, the automation guardian should consider ReduceOnly mode even if oracles are technically within thresholds. |
| **Using on-chain TWAP from a manipulable pool** | TWAP (Time-Weighted Average Price) from a low-liquidity pool can be manipulated over time by an attacker willing to sustain the cost. | Use TWAPs only from highly liquid pools, and only as a secondary validation -- not as the primary price source. |
| **Not validating oracle data length and format** | A reallocated or corrupted oracle account may contain unexpected data. Parsing garbage data leads to incorrect prices. | Validate the oracle account's owner program, data length, and discriminator before parsing. |
---
## Relevant Controls
The following controls from the [[general/control-registry|Control Registry]] apply to this topic. Refer to the Control Registry for level-specific requirements (L1/L2/L3/L4).
Relevant control families: **SOS-ORA**.
---
## References
- [Pyth Network Documentation](https://docs.pyth.network/)
- [Switchboard Documentation](https://docs.switchboard.xyz/)
- [Pyth Best Practices for Consumers](https://docs.pyth.network/price-feeds/best-practices)
- [[circuit-breakers]] -- Emergency stop and program state management
- [[monitoring]] -- On-chain and off-chain monitoring and alerting
- [[authority-design]] -- In-protocol authority architecture and separation
- [[rpc-security]] -- RPC provider trust, privacy, and multi-provider strategy
---
*This article is part of the [[README|SOS Standard Security Knowledge Base]]. For the complete standard, see [[general/sos-standard|SOS Standard]].*