precolator-program 1.0.0

Core Rust library for the Precolator perpetual futures trading protocol on Solana — oracle management, position handling, risk engine, and liquidation system.
Documentation
// Oracle module - Price feed management and aggregation

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 {
    /// Validate oracle price and confidence
    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(())
    }

    /// Aggregate prices from multiple oracle sources
    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,
        })
    }

    /// Check if price movement exceeds circuit breaker threshold
    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)
    }

    /// Calculate normalized price with specified decimals
    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)
        }
    }

    /// Calculate price impact for large orders
    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); // Max 5% impact

        let impact = (base_price as u128 * impact_bps as u128) / 10_000;
        Ok((base_price as u128 + impact) as u64)
    }

    /// Detect potential oracle manipulation
    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());
    }
}