gmsol_store/states/oracle/
switchboard.rs

1use std::ops::Deref;
2
3use crate::{states::TokenConfig, CoreError};
4use anchor_lang::prelude::*;
5use anchor_lang::Discriminator;
6use anchor_lang::ZeroCopy;
7use gmsol_utils::price::Decimal;
8use gmsol_utils::price::Price;
9use switchboard_on_demand::Discriminator as _;
10
11/// The Switchboard receiver program.
12pub struct Switchboard;
13
14#[cfg(feature = "devnet")]
15impl Id for Switchboard {
16    fn id() -> Pubkey {
17        switchboard_on_demand::ON_DEMAND_DEVNET_PID
18    }
19}
20
21#[cfg(not(feature = "devnet"))]
22impl Id for Switchboard {
23    fn id() -> Pubkey {
24        switchboard_on_demand::ON_DEMAND_MAINNET_PID
25    }
26}
27
28#[repr(C)]
29#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
30struct SbFeed {
31    feed: switchboard_on_demand::SbFeed,
32}
33
34impl Deref for SbFeed {
35    type Target = switchboard_on_demand::SbFeed;
36
37    fn deref(&self) -> &Self::Target {
38        &self.feed
39    }
40}
41
42impl ZeroCopy for SbFeed {}
43
44impl Owner for SbFeed {
45    fn owner() -> Pubkey {
46        Switchboard::id()
47    }
48}
49
50impl Discriminator for SbFeed {
51    const DISCRIMINATOR: [u8; 8] = switchboard_on_demand::SbFeed::DISCRIMINATOR;
52}
53
54impl Switchboard {
55    #[allow(clippy::manual_inspect)]
56    pub(super) fn check_and_get_price<'info>(
57        clock: &Clock,
58        token_config: &TokenConfig,
59        feed: &'info AccountInfo<'info>,
60    ) -> Result<(u64, i64, Price)> {
61        let feed = AccountLoader::<SbFeed>::try_from(feed)?;
62        let feed = feed.load()?;
63        let result_ts = feed.result_ts();
64        require_gte!(
65            result_ts.saturating_add(token_config.heartbeat_duration().into()),
66            clock.unix_timestamp,
67            CoreError::PriceFeedNotUpdated
68        );
69        Ok((
70            feed.result_land_slot(),
71            result_ts,
72            Self::price_from(&feed, token_config)?,
73        ))
74    }
75
76    fn price_from(feed: &SbFeed, token_config: &TokenConfig) -> Result<Price> {
77        let min_price = feed
78            .min_value()
79            .ok_or_else(|| error!(CoreError::PriceIsStale))?;
80        let min_price = from_rust_decimal_price(&min_price, token_config)?;
81        let max_price = feed
82            .max_value()
83            .ok_or_else(|| error!(CoreError::PriceIsStale))?;
84        let max_price = from_rust_decimal_price(&max_price, token_config)?;
85        Ok(Price {
86            min: min_price,
87            max: max_price,
88        })
89    }
90}
91
92fn from_rust_decimal_price(
93    price: &rust_decimal::Decimal,
94    token_config: &TokenConfig,
95) -> Result<Decimal> {
96    Decimal::try_from_price(
97        price
98            .mantissa()
99            .try_into()
100            .map_err(|_| error!(CoreError::InvalidPriceFeedPrice))?,
101        price
102            .scale()
103            .try_into()
104            .map_err(|_| error!(CoreError::InvalidPriceFeedPrice))?,
105        token_config.token_decimals(),
106        token_config.precision(),
107    )
108    .map_err(|_| error!(CoreError::InvalidPriceFeedPrice))
109}