1use serde::{Deserialize, Serialize};
4use solana_program::pubkey::Pubkey;
5use crate::errors::{ProgramError, Result};
6use crate::constants::*;
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
9pub struct PriceData {
10 pub price: u64,
11 pub confidence: u64,
12 pub last_update: i64,
13 pub slot: u64,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct OracleConfig {
18 pub oracle_id: Pubkey,
19 pub feed_id: Pubkey,
20 pub circuit_breaker_active: bool,
21 pub max_price_change_bps: u16,
22 pub staleness_threshold: i64,
23}
24
25pub struct OracleManager;
26
27impl OracleManager {
28 pub fn validate_price(price_data: &PriceData, current_slot: u64) -> Result<()> {
30 if price_data.price == 0 {
31 return Err(ProgramError::InvalidOraclePrice);
32 }
33
34 let slot_diff = current_slot.saturating_sub(price_data.slot) as i64;
35 if slot_diff > MAX_ORACLE_STALENESS_SLOTS {
36 return Err(ProgramError::StalePriceData);
37 }
38
39 Ok(())
40 }
41
42 pub fn aggregate_prices(prices: &[PriceData], config: &OracleConfig) -> Result<PriceData> {
44 if prices.is_empty() {
45 return Err(ProgramError::InvalidOraclePrice);
46 }
47
48 let mut total_price: u128 = 0;
49 let mut total_confidence: u128 = 0;
50 let mut latest_update: i64 = 0;
51 let mut latest_slot: u64 = 0;
52
53 for price in prices {
54 total_price += price.price as u128;
55 total_confidence += price.confidence as u128;
56 if price.last_update > latest_update {
57 latest_update = price.last_update;
58 }
59 if price.slot > latest_slot {
60 latest_slot = price.slot;
61 }
62 }
63
64 let count = prices.len() as u128;
65 let avg_price = (total_price / count) as u64;
66 let avg_confidence = (total_confidence / count) as u64;
67
68 Ok(PriceData {
69 price: avg_price,
70 confidence: avg_confidence,
71 last_update: latest_update,
72 slot: latest_slot,
73 })
74 }
75
76 pub fn check_circuit_breaker(
78 previous_price: u64,
79 new_price: u64,
80 max_change_bps: u16,
81 ) -> Result<bool> {
82 if previous_price == 0 {
83 return Ok(true);
84 }
85
86 let change_bps = if new_price >= previous_price {
87 ((new_price - previous_price) as u128 * 10_000) / previous_price as u128
88 } else {
89 ((previous_price - new_price) as u128 * 10_000) / previous_price as u128
90 } as u16;
91
92 Ok(change_bps <= max_change_bps)
93 }
94
95 pub fn normalize_price(price: u64, from_decimals: u8, to_decimals: u8) -> Result<u64> {
97 if from_decimals >= to_decimals {
98 Ok(price / 10u64.pow((from_decimals - to_decimals) as u32))
99 } else {
100 let multiplier = 10u64.pow((to_decimals - from_decimals) as u32);
101 price.checked_mul(multiplier).ok_or(ProgramError::Overflow)
102 }
103 }
104
105 pub fn calculate_price_impact(
107 order_size: u64,
108 available_liquidity: u64,
109 base_price: u64,
110 ) -> Result<u64> {
111 if available_liquidity == 0 {
112 return Err(ProgramError::InsufficientLiquidity);
113 }
114
115 let utilization_pct = (order_size as u128 * 100) / available_liquidity as u128;
116 let impact_bps = (utilization_pct as u16).min(500); let impact = (base_price as u128 * impact_bps as u128) / 10_000;
119 Ok((base_price as u128 + impact) as u64)
120 }
121
122 pub fn detect_manipulation(
124 prices: &[PriceData],
125 volatility_threshold: u16,
126 ) -> Result<bool> {
127 if prices.len() < 2 {
128 return Ok(false);
129 }
130
131 let mut min_price: u64 = u64::MAX;
132 let mut max_price: u64 = 0;
133
134 for price in prices {
135 min_price = min_price.min(price.price);
136 max_price = max_price.max(price.price);
137 }
138
139 let avg_price = prices.iter().map(|p| p.price as u128).sum::<u128>() / prices.len() as u128;
140 let volatility = if avg_price > 0 {
141 ((max_price as u128 - min_price as u128) * 10_000) / avg_price
142 } else {
143 0
144 } as u16;
145
146 Ok(volatility > volatility_threshold)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_validate_price() {
156 let price_data = PriceData {
157 price: 1000,
158 confidence: 100,
159 last_update: 0,
160 slot: 100,
161 };
162
163 assert!(OracleManager::validate_price(&price_data, 200).is_ok());
164 assert!(OracleManager::validate_price(&price_data, 1000).is_err());
165 }
166
167 #[test]
168 fn test_aggregate_prices() {
169 let config = OracleConfig {
170 oracle_id: Pubkey::new_unique(),
171 feed_id: Pubkey::new_unique(),
172 circuit_breaker_active: false,
173 max_price_change_bps: 500,
174 staleness_threshold: 300,
175 };
176
177 let prices = vec![
178 PriceData {
179 price: 100,
180 confidence: 10,
181 last_update: 0,
182 slot: 100,
183 },
184 PriceData {
185 price: 110,
186 confidence: 10,
187 last_update: 1,
188 slot: 101,
189 },
190 ];
191
192 let result = OracleManager::aggregate_prices(&prices, &config);
193 assert!(result.is_ok());
194 }
195
196 #[test]
197 fn test_circuit_breaker() {
198 assert!(OracleManager::check_circuit_breaker(100, 105, 1000).is_ok());
199 assert!(OracleManager::check_circuit_breaker(100, 150, 100).is_ok());
200 }
201}