gmsol-store 0.6.0

GMX-Solana is an extension of GMX on the Solana blockchain.
Documentation
use std::ops::Deref;

use anchor_lang::prelude::*;
use anchor_lang::Discriminator;
use anchor_lang::ZeroCopy;
use gmsol_utils::price::Decimal;
use gmsol_utils::price::Price;
use switchboard_on_demand::Discriminator as _;

use crate::{states::TokenConfig, CoreError};

use super::OraclePriceParts;

mod id {
    use anchor_lang::prelude::Pubkey;
    use cfg_if::cfg_if;

    cfg_if! {
        if #[cfg(feature = "devnet")] {
            pub(super) const ID: Pubkey = switchboard_on_demand::ON_DEMAND_DEVNET_PID;
        } else {
            pub(super) const ID: Pubkey = switchboard_on_demand::ON_DEMAND_MAINNET_PID;
        }
    }
}

/// The Switchboard receiver program.
pub struct Switchboard;

impl Id for Switchboard {
    fn id() -> Pubkey {
        id::ID
    }
}

#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct SbFeed {
    feed: switchboard_on_demand::SbFeed,
}

impl Deref for SbFeed {
    type Target = switchboard_on_demand::SbFeed;

    fn deref(&self) -> &Self::Target {
        &self.feed
    }
}

impl ZeroCopy for SbFeed {}

impl Owner for SbFeed {
    fn owner() -> Pubkey {
        Switchboard::id()
    }
}

impl Discriminator for SbFeed {
    const DISCRIMINATOR: &'static [u8] = &switchboard_on_demand::SbFeed::DISCRIMINATOR;
}

impl Switchboard {
    #[allow(clippy::manual_inspect)]
    pub(super) fn check_and_get_price<'info>(
        clock: &Clock,
        token_config: &TokenConfig,
        feed: &'info AccountInfo<'info>,
    ) -> Result<OraclePriceParts> {
        let feed = AccountLoader::<SbFeed>::try_from(feed)?;
        let feed = feed.load()?;
        let result_ts = feed.result_ts();
        let ref_price = feed
            .value(clock)
            .map_err(|_| error!(CoreError::PriceIsStale))?;
        let ref_price = from_rust_decimal_price(&ref_price, token_config)?;
        require_gte!(
            result_ts.saturating_add(token_config.heartbeat_duration().into()),
            clock.unix_timestamp,
            CoreError::PriceFeedNotUpdated
        );
        Ok(OraclePriceParts {
            oracle_slot: feed.result_land_slot(),
            oracle_ts: result_ts,
            price: Self::price_from(&feed, token_config)?,
            ref_price: Some(ref_price),
        })
    }

    fn price_from(feed: &SbFeed, token_config: &TokenConfig) -> Result<Price> {
        let min_price = feed
            .min_value()
            .ok_or_else(|| error!(CoreError::PriceIsStale))?;
        let min_price = from_rust_decimal_price(&min_price, token_config)?;
        let max_price = feed
            .max_value()
            .ok_or_else(|| error!(CoreError::PriceIsStale))?;
        let max_price = from_rust_decimal_price(&max_price, token_config)?;
        Ok(Price {
            min: min_price,
            max: max_price,
        })
    }
}

fn from_rust_decimal_price(
    price: &rust_decimal::Decimal,
    token_config: &TokenConfig,
) -> Result<Decimal> {
    Decimal::try_from_price(
        price
            .mantissa()
            .try_into()
            .map_err(|_| error!(CoreError::InvalidPriceFeedPrice))?,
        price
            .scale()
            .try_into()
            .map_err(|_| error!(CoreError::InvalidPriceFeedPrice))?,
        token_config.token_decimals(),
        token_config.precision(),
    )
    .map_err(|_| error!(CoreError::InvalidPriceFeedPrice))
}