1pub mod price_map;
3
4mod feed;
6
7pub mod switchboard;
9
10pub mod chainlink;
12
13pub mod pyth;
15
16pub mod validator;
18
19pub mod time;
21
22use std::ops::Deref;
23
24use crate::{
25 states::{TokenMapAccess, TokenMapLoader},
26 CoreError, CoreResult,
27};
28use anchor_lang::prelude::*;
29use num_enum::{IntoPrimitive, TryFromPrimitive};
30
31use self::price_map::PriceMap;
32use super::{HasMarketMeta, Seed, Store, TokenConfig, TokenMapHeader, TokenMapRef};
33
34pub use self::{
35 chainlink::Chainlink,
36 feed::{PriceFeed, PriceFeedPrice},
37 pyth::Pyth,
38 switchboard::Switchboard,
39 time::{ValidateOracleTime, ValidateOracleTimeExt},
40 validator::PriceValidator,
41};
42
43const MAX_FLAGS: usize = 8;
44
45#[repr(u8)]
46#[non_exhaustive]
47#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
48enum OracleFlag {
49 Cleared,
51 }
53
54gmsol_utils::flags!(OracleFlag, MAX_FLAGS, u8);
55
56#[account(zero_copy)]
58#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
59pub struct Oracle {
60 version: u8,
61 #[cfg_attr(feature = "debug", debug(skip))]
62 padding_0: [u8; 7],
63 pub store: Pubkey,
65 pub(crate) authority: Pubkey,
68 min_oracle_ts: i64,
69 max_oracle_ts: i64,
70 min_oracle_slot: u64,
71 primary: PriceMap,
72 flags: OracleFlagContainer,
73 #[cfg_attr(feature = "debug", debug(skip))]
74 padding_1: [u8; 3],
75 #[cfg_attr(feature = "debug", debug(skip))]
76 reserved: [u8; 256],
77}
78
79impl gmsol_utils::InitSpace for Oracle {
80 const INIT_SPACE: usize = std::mem::size_of::<Self>();
81}
82
83impl Seed for Oracle {
84 const SEED: &'static [u8] = b"oracle";
85}
86
87impl Oracle {
88 pub(crate) fn init(&mut self, store: Pubkey, authority: Pubkey) {
90 self.clear_all_prices();
91 self.store = store;
92 self.authority = authority;
93 }
94
95 pub fn is_cleared(&self) -> bool {
97 self.flags.get_flag(OracleFlag::Cleared)
98 }
99
100 pub(crate) fn set_prices_from_remaining_accounts<'info>(
102 &mut self,
103 mut validator: PriceValidator,
104 map: &TokenMapRef,
105 tokens: &[Pubkey],
106 remaining_accounts: &'info [AccountInfo<'info>],
107 chainlink: Option<&Program<'info, Chainlink>>,
108 ) -> Result<()> {
109 require!(self.is_cleared(), CoreError::PricesAreAlreadySet);
110 require!(self.primary.is_empty(), CoreError::PricesAreAlreadySet);
111 require!(
112 tokens.len() <= PriceMap::MAX_TOKENS,
113 CoreError::ExceedMaxLengthLimit
114 );
115 require!(
116 tokens.len() <= remaining_accounts.len(),
117 ErrorCode::AccountNotEnoughKeys
118 );
119 for (idx, token) in tokens.iter().enumerate() {
122 let feed = &remaining_accounts[idx];
123 let token_config = map.get(token).ok_or_else(|| error!(CoreError::NotFound))?;
124
125 require!(token_config.is_enabled(), CoreError::TokenConfigDisabled);
126
127 let oracle_price = OraclePrice::parse_from_feed_account(
128 validator.clock(),
129 token_config,
130 chainlink,
131 feed,
132 )?;
133
134 validator.validate_one(
135 token_config,
136 &oracle_price.provider,
137 oracle_price.oracle_ts,
138 oracle_price.oracle_slot,
139 &oracle_price.price,
140 )?;
141 self.primary
142 .set(token, oracle_price.price, token_config.is_synthetic())?;
143 }
144 self.update_oracle_ts_and_slot(validator)?;
145 Ok(())
146 }
147
148 pub fn min_oracle_slot(&self) -> Option<u64> {
150 if self.is_cleared() {
151 None
152 } else {
153 Some(self.min_oracle_slot)
154 }
155 }
156
157 pub fn min_oracle_ts(&self) -> i64 {
159 self.min_oracle_ts
160 }
161
162 pub fn max_oracle_ts(&self) -> i64 {
164 self.max_oracle_ts
165 }
166
167 fn update_oracle_ts_and_slot(&mut self, mut validator: PriceValidator) -> Result<()> {
168 validator.merge_range(
169 self.min_oracle_slot(),
170 self.min_oracle_ts,
171 self.max_oracle_ts,
172 );
173 if let Some((min_slot, min_ts, max_ts)) = validator.finish()? {
174 self.min_oracle_slot = min_slot;
175 self.min_oracle_ts = min_ts;
176 self.max_oracle_ts = max_ts;
177 self.flags.set_flag(OracleFlag::Cleared, false);
178 }
179 Ok(())
180 }
181
182 pub(crate) fn clear_all_prices(&mut self) {
184 self.primary.clear();
185 self.min_oracle_ts = i64::MAX;
186 self.max_oracle_ts = i64::MIN;
187 self.min_oracle_slot = u64::MAX;
188 self.flags.set_flag(OracleFlag::Cleared, true);
189 }
190
191 #[inline(never)]
192 pub(crate) fn with_prices<'info, T>(
193 &mut self,
194 store: &AccountLoader<'info, Store>,
195 token_map: &AccountLoader<'info, TokenMapHeader>,
196 tokens: &[Pubkey],
197 remaining_accounts: &'info [AccountInfo<'info>],
198 chainlink: Option<&Program<'info, Chainlink>>,
199 f: impl FnOnce(&mut Self, &'info [AccountInfo<'info>]) -> Result<T>,
200 ) -> Result<T> {
201 let validator = PriceValidator::try_from(store.load()?.deref())?;
202 require_gte!(
203 remaining_accounts.len(),
204 tokens.len(),
205 CoreError::NotEnoughTokenFeeds,
206 );
207 let feeds = &remaining_accounts[..tokens.len()];
208 let remaining_accounts = &remaining_accounts[tokens.len()..];
209 let res = {
210 let token_map = token_map.load_token_map()?;
211 self.set_prices_from_remaining_accounts(validator, &token_map, tokens, feeds, chainlink)
212 };
213 match res {
214 Ok(()) => {
215 let output = f(self, remaining_accounts);
216 self.clear_all_prices();
217 output
218 }
219 Err(err) => {
220 self.clear_all_prices();
221 Err(err)
222 }
223 }
224 }
225
226 pub(crate) fn validate_time(&self, target: &impl ValidateOracleTime) -> CoreResult<()> {
228 if self.max_oracle_ts < self.min_oracle_ts {
229 msg!("min = {}, max = {}", self.min_oracle_ts, self.max_oracle_ts);
230 return Err(CoreError::InvalidOracleTimestampsRange);
231 }
232 target.validate_min_oracle_slot(self)?;
233 target.validate_min_oracle_ts(self)?;
234 target.validate_max_oracle_ts(self)?;
235 Ok(())
236 }
237
238 pub fn get_primary_price(
240 &self,
241 token: &Pubkey,
242 allow_synthetic: bool,
243 ) -> Result<gmsol_model::price::Price<u128>> {
244 let price = self
245 .primary
246 .get(token)
247 .ok_or_else(|| error!(CoreError::MissingOraclePrice))?;
248
249 if !allow_synthetic {
259 require!(
260 !price.is_synthetic(),
261 CoreError::SyntheticTokenPriceIsNotAllowed
262 );
263 }
264 Ok(gmsol_model::price::Price {
265 min: price.min().to_unit_price(),
266 max: price.max().to_unit_price(),
267 })
268 }
269
270 pub(crate) fn market_prices(
272 &self,
273 market: &impl HasMarketMeta,
274 ) -> Result<gmsol_model::price::Prices<u128>> {
275 let meta = market.market_meta();
276 let prices = gmsol_model::price::Prices {
277 index_token_price: self.get_primary_price(&meta.index_token_mint, true)?,
278 long_token_price: self.get_primary_price(&meta.long_token_mint, false)?,
279 short_token_price: self.get_primary_price(&meta.short_token_mint, false)?,
280 };
281 Ok(prices)
282 }
283}
284
285#[repr(u8)]
287#[derive(
288 Clone,
289 Copy,
290 Default,
291 TryFromPrimitive,
292 IntoPrimitive,
293 PartialEq,
294 Eq,
295 Hash,
296 strum::EnumString,
297 strum::Display,
298)]
299#[strum(serialize_all = "snake_case")]
300#[cfg_attr(feature = "enum-iter", derive(strum::EnumIter))]
301#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
302#[cfg_attr(feature = "clap", clap(rename_all = "snake_case"))]
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
305#[cfg_attr(feature = "debug", derive(Debug))]
306#[non_exhaustive]
307pub enum PriceProviderKind {
308 #[default]
310 ChainlinkDataStreams = 0,
311 Pyth = 1,
313 Chainlink = 2,
315 Switchboard = 3,
317}
318
319impl PriceProviderKind {
320 pub fn from_program_id(program_id: &Pubkey) -> Option<Self> {
322 if *program_id == Chainlink::id() {
323 Some(Self::Chainlink)
324 } else if *program_id == Pyth::id() {
325 Some(Self::Pyth)
326 } else if *program_id == Switchboard::id() {
327 Some(Self::Switchboard)
328 } else {
329 None
330 }
331 }
332}
333
334struct OraclePrice {
335 provider: PriceProviderKind,
336 oracle_slot: u64,
337 oracle_ts: i64,
338 price: gmsol_utils::Price,
339}
340
341impl OraclePrice {
342 fn parse_from_feed_account<'info>(
343 clock: &Clock,
344 token_config: &TokenConfig,
345 chainlink: Option<&Program<'info, Chainlink>>,
346 account: &'info AccountInfo<'info>,
347 ) -> Result<Self> {
348 let (provider, parsed) = match PriceProviderKind::from_program_id(account.owner) {
349 Some(provider) => (provider, None),
350 None if *account.owner == crate::ID => {
351 let loader = AccountLoader::<'info, PriceFeed>::try_from(account)?;
352 let feed = loader.load()?;
353 let kind = feed.provider()?;
354 (kind, Some(feed.check_and_get_price(clock, token_config)?))
355 }
356 None => return Err(error!(CoreError::InvalidPriceFeedAccount)),
357 };
358
359 require_eq!(token_config.expected_provider()?, provider);
360
361 let feed_id = token_config.get_feed(&provider)?;
362
363 let (oracle_slot, oracle_ts, price) = match provider {
364 PriceProviderKind::ChainlinkDataStreams => {
365 parsed.ok_or_else(|| error!(CoreError::Internal))?
366 }
367 PriceProviderKind::Pyth => {
368 let (oracle_slot, oracle_ts, price) =
369 Pyth::check_and_get_price(clock, token_config, account, &feed_id)?;
370 (oracle_slot, oracle_ts, price)
371 }
372 PriceProviderKind::Chainlink => {
373 require_keys_eq!(feed_id, account.key(), CoreError::InvalidPriceFeedAccount);
374 let program =
375 chainlink.ok_or_else(|| error!(CoreError::ChainlinkProgramIsRequired))?;
376 let (oracle_slot, oracle_ts, price) = Chainlink::check_and_get_chainlink_price(
377 clock,
378 program,
379 token_config,
380 account,
381 )?;
382 (oracle_slot, oracle_ts, price)
383 }
384 PriceProviderKind::Switchboard => {
385 require_keys_eq!(feed_id, account.key(), CoreError::InvalidPriceFeedAccount);
386 Switchboard::check_and_get_price(clock, token_config, account)?
387 }
388 };
389
390 Ok(Self {
391 provider,
392 oracle_slot,
393 oracle_ts,
394 price,
395 })
396 }
397}