use pyth_sdk_solana::state::SolanaPriceAccount;
use serde::{Deserialize, Serialize};
use solana_program::account_info::AccountInfo;
use solana_program::clock::Clock;
use solana_program::sysvar::Sysvar;
use crate::accounts::oracles::oracle_params::{DataLen, Endian, OracleParams};
use crate::accounts::oracles::oracle_provider::OracleProvider;
use crate::dvl_error::DvlError;
use crate::errors::*;
pub const ORACLE_PARAMS_QUANTITY: usize = 3;
pub const ORACLES_DATA_SIZE: usize = 216;
pub const ORACLES_DATA_COUNT: usize = 8;
#[repr(C)]
#[derive(Copy, Clone, Serialize, Deserialize)]
pub struct OracleData {
pub base_ticker: [u8; 8], pub relative_ticker: [u8; 8], pub configured: bool, pub use_relative_oracle: bool, pub relative_oracle_num: u8, pub reserved: u8, pub max_price_deviation: u32, pub params: [OracleParams; ORACLE_PARAMS_QUANTITY], }
impl OracleData {
pub fn get_asset_price(&self, ext_oracles_accounts: &[AccountInfo]) -> Result<f64, DvlError> {
let current_time = Clock::get().
map_err(|_| DvlError::new(ContractError::TimeReadError))?
.unix_timestamp;
let mut prices = Vec::<f64>::new();
for account in ext_oracles_accounts {
for oracle_param in self.params.iter() {
if account.key == &oracle_param.account {
if !oracle_param.enabled {
continue;
}
match oracle_param.provider {
OracleProvider::Custom => {
let timestamp_offset = oracle_param.timestamp.offset as usize;
let ext_oracle_data = account.try_borrow_data().
map_err(|_| DvlError::new_with_account(AccountTag::ExternalOracle, ContractError::AccountSize))?;
let timestamp = Self::read_value(&ext_oracle_data[timestamp_offset..], oracle_param.timestamp.data_len, oracle_param.timestamp.endian)
.map_err(|_| DvlError::new_with_account(AccountTag::Oracle, ContractError::TimeReadError))? as i64;
if (current_time - timestamp).abs() > oracle_param.max_timestamp_diff_sec as i64 {
continue; }
let mantissa_offset = oracle_param.mantissa.offset as usize;
let mantissa = Self::read_value(&ext_oracle_data[mantissa_offset..], oracle_param.mantissa.data_len, oracle_param.mantissa.endian)?;
let exponent_offset = oracle_param.exponent.offset as usize;
let exponent = Self::read_value(&ext_oracle_data[exponent_offset..], oracle_param.exponent.data_len, oracle_param.exponent.endian)? as i32;
let price = mantissa as f64 * 10f64.powi(-exponent);
prices.push(price);
}
OracleProvider::Switchboard => {
continue;}
OracleProvider::Pyth => {
let price_feed = SolanaPriceAccount::account_info_to_feed( &account ).
map_err(|_| {
DvlError::new_with_account(AccountTag::Oracle, ContractError::AssetPriceUnavailable)
})?;
let current_price = price_feed.get_price_no_older_than(current_time, oracle_param.max_timestamp_diff_sec as u64).unwrap();
prices.push(current_price.price as f64 * 10f64.powi(current_price.expo));
}
}
}
}
}
return match prices.len() {
0 => Err(DvlError::new(ContractError::AssetPriceUnavailable)),
2 => {
if (prices[1] - prices[0]).abs() > self.max_price_deviation as f64 {
return Err(DvlError::new(ContractError::PriceDiscrepancyError))
}
Ok(prices[0])
}
3 => {
let max_deviation = self.max_price_deviation as f64;
if (prices[1] - prices[0]).abs() > max_deviation || (prices[2] - prices[1]).abs() > max_deviation {
return Err(DvlError::new(ContractError::PriceDiscrepancyError))
}
Ok(prices[0])
}
_ => Ok(prices[0])
};
}
fn read_value(data: &[u8], data_len: DataLen, endian: Endian) -> Result<u128, DvlError> {
let val = match data_len {
DataLen::U8 => data[0] as u128,
DataLen::U32 => {
if endian == Endian::LE {
u32::from_le_bytes(data[..4].try_into().unwrap()) as u128
} else {
u32::from_be_bytes(data[..4].try_into().unwrap()) as u128
}
},
DataLen::U64 => {
if endian == Endian::LE {
u64::from_le_bytes(data[..8].try_into().unwrap()) as u128
} else {
u64::from_be_bytes(data[..8].try_into().unwrap()) as u128
}
},
DataLen::U128 => {
if endian == Endian::LE {
u128::from_le_bytes(data[..16].try_into().unwrap())
} else {
u128::from_be_bytes(data[..16].try_into().unwrap())
}
},
_ => return Err(DvlError::new(ContractError::ComputationError)), };
Ok(val)
}
}
#[cfg(test)]
impl Default for OracleData {
fn default() -> Self {
Self {
base_ticker: [0; 8],
relative_ticker: [0; 8],
configured: false,
use_relative_oracle: false,
relative_oracle_num: 0,
max_price_deviation: 0,
reserved: 0,
params: [OracleParams::default(); ORACLE_PARAMS_QUANTITY],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
fn test_instruments_account_offsets() {
assert_eq!(mem::size_of::<OracleData>(), ORACLES_DATA_SIZE);
}
}