devol_accounts_kit/accounts/oracles/
oracles_data.rs1use pyth_sdk_solana::state::SolanaPriceAccount;
2use serde::{Deserialize, Serialize};
3use solana_program::account_info::AccountInfo;
4use solana_program::clock::Clock;
5use solana_program::sysvar::Sysvar;
6use crate::accounts::oracles::oracle_params::{DataLen, Endian, OracleParams};
7use crate::accounts::oracles::oracle_provider::OracleProvider;
8use crate::dvl_error::DvlError;
9use crate::errors::*;
10
11pub const ORACLE_PARAMS_QUANTITY: usize = 3;
12pub const ORACLES_DATA_SIZE: usize = 216;
13pub const ORACLES_DATA_COUNT: usize = 8;
14
15#[repr(C)]
16#[derive(Copy, Clone, Serialize, Deserialize)]
17pub struct OracleData {
18 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], }
27
28impl OracleData {
29 pub fn get_asset_price(&self, ext_oracles_accounts: &[AccountInfo]) -> Result<f64, DvlError> {
32 let current_time = Clock::get().
33 map_err(|_| DvlError::new(ContractError::TimeReadError))?
34 .unix_timestamp;
35 let mut prices = Vec::<f64>::new();
36
37 for account in ext_oracles_accounts {
38 for oracle_param in self.params.iter() {
39 if account.key == &oracle_param.account {
40 if !oracle_param.enabled {
41 continue;
42 }
43 match oracle_param.provider {
44 OracleProvider::Custom => {
45 let timestamp_offset = oracle_param.timestamp.offset as usize;
46 let ext_oracle_data = account.try_borrow_data().
47 map_err(|_| DvlError::new_with_account(AccountTag::ExternalOracle, ContractError::AccountSize))?;
48 let timestamp = Self::read_value(&ext_oracle_data[timestamp_offset..], oracle_param.timestamp.data_len, oracle_param.timestamp.endian)
49 .map_err(|_| DvlError::new_with_account(AccountTag::Oracle, ContractError::TimeReadError))? as i64;
50
51 if (current_time - timestamp).abs() > oracle_param.max_timestamp_diff_sec as i64 {
52 continue; }
54
55 let mantissa_offset = oracle_param.mantissa.offset as usize;
56 let mantissa = Self::read_value(&ext_oracle_data[mantissa_offset..], oracle_param.mantissa.data_len, oracle_param.mantissa.endian)?;
57
58 let exponent_offset = oracle_param.exponent.offset as usize;
59 let exponent = Self::read_value(&ext_oracle_data[exponent_offset..], oracle_param.exponent.data_len, oracle_param.exponent.endian)? as i32;
60
61 let price = mantissa as f64 * 10f64.powi(-exponent);
62 prices.push(price);
63 }
64 OracleProvider::Switchboard => {
65 continue;}
66 OracleProvider::Pyth => {
67 let price_feed = SolanaPriceAccount::account_info_to_feed( &account ).
68 map_err(|_| {
69 DvlError::new_with_account(AccountTag::Oracle, ContractError::AssetPriceUnavailable)
70 })?;
71 let current_price = price_feed.get_price_no_older_than(current_time, oracle_param.max_timestamp_diff_sec as u64).unwrap();
72 prices.push(current_price.price as f64 * 10f64.powi(current_price.expo));
73 }
74 }
75 }
76 }
77 }
78
79 return match prices.len() {
80 0 => Err(DvlError::new(ContractError::AssetPriceUnavailable)),
81 2 => {
82 if (prices[1] - prices[0]).abs() > self.max_price_deviation as f64 {
83 return Err(DvlError::new(ContractError::PriceDiscrepancyError))
84 }
85 Ok(prices[0])
86 }
87 3 => {
88 let max_deviation = self.max_price_deviation as f64;
89 if (prices[1] - prices[0]).abs() > max_deviation || (prices[2] - prices[1]).abs() > max_deviation {
90 return Err(DvlError::new(ContractError::PriceDiscrepancyError))
91 }
92 Ok(prices[0])
93 }
94 _ => Ok(prices[0])
95 };
96 }
97
98 fn read_value(data: &[u8], data_len: DataLen, endian: Endian) -> Result<u128, DvlError> {
100 let val = match data_len {
101 DataLen::U8 => data[0] as u128,
102 DataLen::U32 => {
103 if endian == Endian::LE {
104 u32::from_le_bytes(data[..4].try_into().unwrap()) as u128
105 } else {
106 u32::from_be_bytes(data[..4].try_into().unwrap()) as u128
107 }
108 },
109 DataLen::U64 => {
110 if endian == Endian::LE {
111 u64::from_le_bytes(data[..8].try_into().unwrap()) as u128
112 } else {
113 u64::from_be_bytes(data[..8].try_into().unwrap()) as u128
114 }
115 },
116 DataLen::U128 => {
117 if endian == Endian::LE {
118 u128::from_le_bytes(data[..16].try_into().unwrap())
119 } else {
120 u128::from_be_bytes(data[..16].try_into().unwrap())
121 }
122 },
123 _ => return Err(DvlError::new(ContractError::ComputationError)), };
125 Ok(val)
126 }
127
128}
129
130#[cfg(test)]
131impl Default for OracleData {
132 fn default() -> Self {
133 Self {
134 base_ticker: [0; 8],
135 relative_ticker: [0; 8],
136 configured: false,
137 use_relative_oracle: false,
138 relative_oracle_num: 0,
139 max_price_deviation: 0,
140 reserved: 0,
141 params: [OracleParams::default(); ORACLE_PARAMS_QUANTITY],
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use std::mem;
150
151 #[test]
152 fn test_instruments_account_offsets() {
153 assert_eq!(mem::size_of::<OracleData>(), ORACLES_DATA_SIZE);
155 }
156}