gmsol_utils/
oracle.rs

1#![allow(deprecated)]
2
3use num_enum::{IntoPrimitive, TryFromPrimitive};
4
5use crate::{price::Decimal, token_config::TokenConfig, Price};
6
7/// Max number of oracle flags.
8pub const MAX_ORACLE_FLAGS: usize = 8;
9
10/// Oracle error.
11#[derive(Debug, thiserror::Error)]
12pub enum OracleError {
13    /// Invalid price feed price.
14    #[error("invalid price feed price: {0}")]
15    InvalidPriceFeedPrice(&'static str),
16}
17
18type OracleResult<T> = std::result::Result<T, OracleError>;
19
20/// Supported Price Provider Kind.
21#[repr(u8)]
22#[derive(
23    Clone,
24    Copy,
25    Default,
26    TryFromPrimitive,
27    IntoPrimitive,
28    PartialEq,
29    Eq,
30    Hash,
31    strum::EnumString,
32    strum::Display,
33)]
34#[strum(serialize_all = "snake_case")]
35#[cfg_attr(feature = "enum-iter", derive(strum::EnumIter))]
36#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
37#[cfg_attr(feature = "clap", clap(rename_all = "snake_case"))]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
40#[cfg_attr(feature = "debug", derive(Debug))]
41#[non_exhaustive]
42pub enum PriceProviderKind {
43    /// Chainlink Data Streams.
44    #[default]
45    ChainlinkDataStreams = 0,
46    /// Pyth Oracle V2.
47    Pyth = 1,
48    /// Chainlink Data Feed.
49    #[deprecated(since = "0.6.0", note = "use `ChainlinkDataStreams` instead")]
50    Chainlink = 2,
51    /// Switchboard On-Demand (V3) Data Feed.
52    Switchboard = 3,
53}
54
55/// Convert pyth price value with confidence to [`Price`].
56pub fn pyth_price_with_confidence_to_price(
57    price: i64,
58    confidence: u64,
59    exponent: i32,
60    token_config: &TokenConfig,
61) -> OracleResult<Price> {
62    let mid_price: u64 = price
63        .try_into()
64        .map_err(|_| OracleError::InvalidPriceFeedPrice("mid_price"))?;
65    // Note: No validation of Pyth’s price volatility has been conducted yet.
66    // Exercise caution when choosing Pyth as the primary oracle.
67    let min_price = mid_price
68        .checked_sub(confidence)
69        .ok_or(OracleError::InvalidPriceFeedPrice("min_price"))?;
70    let max_price = mid_price
71        .checked_add(confidence)
72        .ok_or(OracleError::InvalidPriceFeedPrice("max_price"))?;
73    Ok(Price {
74        min: pyth_price_value_to_decimal(min_price, exponent, token_config)?,
75        max: pyth_price_value_to_decimal(max_price, exponent, token_config)?,
76    })
77}
78
79/// Pyth price value to decimal.
80pub fn pyth_price_value_to_decimal(
81    mut value: u64,
82    exponent: i32,
83    token_config: &TokenConfig,
84) -> OracleResult<Decimal> {
85    // actual price == value * 10^exponent
86    // - If `exponent` is not positive, then the `decimals` is set to `-exponent`.
87    // - Otherwise, we should use `value * 10^exponent` as `price` argument, and let `decimals` be `0`.
88    let decimals: u8 = if exponent <= 0 {
89        (-exponent)
90            .try_into()
91            .map_err(|_| OracleError::InvalidPriceFeedPrice("exponent too small"))?
92    } else {
93        let factor = 10u64
94            .checked_pow(exponent as u32)
95            .ok_or(OracleError::InvalidPriceFeedPrice("exponent too big"))?;
96        value = value
97            .checked_mul(factor)
98            .ok_or(OracleError::InvalidPriceFeedPrice("price overflow"))?;
99        0
100    };
101    let price = Decimal::try_from_price(
102        value as u128,
103        decimals,
104        token_config.token_decimals(),
105        token_config.precision(),
106    )
107    .map_err(|_| OracleError::InvalidPriceFeedPrice("converting to Decimal"))?;
108    Ok(price)
109}
110
111/// Oracle flag.
112#[repr(u8)]
113#[non_exhaustive]
114#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
115pub enum OracleFlag {
116    /// Cleared.
117    Cleared,
118    // CHECK: should have no more than `MAX_ORACLE_FLAGS` of flags.
119}