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