use serde::{Deserialize, Serialize};
use solana_program::pubkey::Pubkey;
use crate::errors::{ProgramError, Result};
use crate::constants::*;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct PriceData {
pub price: u64,
pub confidence: u64,
pub last_update: i64,
pub slot: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OracleConfig {
pub oracle_id: Pubkey,
pub feed_id: Pubkey,
pub circuit_breaker_active: bool,
pub max_price_change_bps: u16,
pub staleness_threshold: i64,
}
pub struct OracleManager;
impl OracleManager {
pub fn validate_price(price_data: &PriceData, current_slot: u64) -> Result<()> {
if price_data.price == 0 {
return Err(ProgramError::InvalidOraclePrice);
}
let slot_diff = current_slot.saturating_sub(price_data.slot) as i64;
if slot_diff > MAX_ORACLE_STALENESS_SLOTS {
return Err(ProgramError::StalePriceData);
}
Ok(())
}
pub fn aggregate_prices(prices: &[PriceData], config: &OracleConfig) -> Result<PriceData> {
if prices.is_empty() {
return Err(ProgramError::InvalidOraclePrice);
}
let mut total_price: u128 = 0;
let mut total_confidence: u128 = 0;
let mut latest_update: i64 = 0;
let mut latest_slot: u64 = 0;
for price in prices {
total_price += price.price as u128;
total_confidence += price.confidence as u128;
if price.last_update > latest_update {
latest_update = price.last_update;
}
if price.slot > latest_slot {
latest_slot = price.slot;
}
}
let count = prices.len() as u128;
let avg_price = (total_price / count) as u64;
let avg_confidence = (total_confidence / count) as u64;
Ok(PriceData {
price: avg_price,
confidence: avg_confidence,
last_update: latest_update,
slot: latest_slot,
})
}
pub fn check_circuit_breaker(
previous_price: u64,
new_price: u64,
max_change_bps: u16,
) -> Result<bool> {
if previous_price == 0 {
return Ok(true);
}
let change_bps = if new_price >= previous_price {
((new_price - previous_price) as u128 * 10_000) / previous_price as u128
} else {
((previous_price - new_price) as u128 * 10_000) / previous_price as u128
} as u16;
Ok(change_bps <= max_change_bps)
}
pub fn normalize_price(price: u64, from_decimals: u8, to_decimals: u8) -> Result<u64> {
if from_decimals >= to_decimals {
Ok(price / 10u64.pow((from_decimals - to_decimals) as u32))
} else {
let multiplier = 10u64.pow((to_decimals - from_decimals) as u32);
price.checked_mul(multiplier).ok_or(ProgramError::Overflow)
}
}
pub fn calculate_price_impact(
order_size: u64,
available_liquidity: u64,
base_price: u64,
) -> Result<u64> {
if available_liquidity == 0 {
return Err(ProgramError::InsufficientLiquidity);
}
let utilization_pct = (order_size as u128 * 100) / available_liquidity as u128;
let impact_bps = (utilization_pct as u16).min(500);
let impact = (base_price as u128 * impact_bps as u128) / 10_000;
Ok((base_price as u128 + impact) as u64)
}
pub fn detect_manipulation(
prices: &[PriceData],
volatility_threshold: u16,
) -> Result<bool> {
if prices.len() < 2 {
return Ok(false);
}
let mut min_price: u64 = u64::MAX;
let mut max_price: u64 = 0;
for price in prices {
min_price = min_price.min(price.price);
max_price = max_price.max(price.price);
}
let avg_price = prices.iter().map(|p| p.price as u128).sum::<u128>() / prices.len() as u128;
let volatility = if avg_price > 0 {
((max_price as u128 - min_price as u128) * 10_000) / avg_price
} else {
0
} as u16;
Ok(volatility > volatility_threshold)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_price() {
let price_data = PriceData {
price: 1000,
confidence: 100,
last_update: 0,
slot: 100,
};
assert!(OracleManager::validate_price(&price_data, 200).is_ok());
assert!(OracleManager::validate_price(&price_data, 1000).is_err());
}
#[test]
fn test_aggregate_prices() {
let config = OracleConfig {
oracle_id: Pubkey::new_unique(),
feed_id: Pubkey::new_unique(),
circuit_breaker_active: false,
max_price_change_bps: 500,
staleness_threshold: 300,
};
let prices = vec![
PriceData {
price: 100,
confidence: 10,
last_update: 0,
slot: 100,
},
PriceData {
price: 110,
confidence: 10,
last_update: 1,
slot: 101,
},
];
let result = OracleManager::aggregate_prices(&prices, &config);
assert!(result.is_ok());
}
#[test]
fn test_circuit_breaker() {
assert!(OracleManager::check_circuit_breaker(100, 105, 1000).is_ok());
assert!(OracleManager::check_circuit_breaker(100, 150, 100).is_ok());
}
}