Skip to main content

pyth_lazer_protocol/
price.rs

1#[cfg(test)]
2mod tests;
3
4use {
5    crate::ExponentFactor,
6    rust_decimal::{prelude::FromPrimitive, Decimal},
7    serde::{Deserialize, Serialize},
8    std::num::NonZeroI64,
9    thiserror::Error,
10};
11
12#[derive(Debug, Error)]
13pub enum PriceError {
14    #[error("decimal parse error: {0}")]
15    DecimalParse(#[from] rust_decimal::Error),
16    #[error("price value is more precise than available exponent")]
17    TooPrecise,
18    #[error("zero price is unsupported")]
19    ZeroPriceUnsupported,
20    #[error("overflow")]
21    Overflow,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
25#[repr(transparent)]
26#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
27#[cfg_attr(feature = "utoipa", schema(value_type = i64))]
28pub struct Price(NonZeroI64);
29
30impl Price {
31    pub fn from_integer(value: i64, exponent: i16) -> Result<Price, PriceError> {
32        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
33            ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(PriceError::Overflow)?,
34            ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(PriceError::Overflow)?,
35        };
36        let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?;
37        Ok(Self(mantissa))
38    }
39
40    pub fn parse_str(value: &str, exponent: i16) -> Result<Price, PriceError> {
41        let value: Decimal = value.parse()?;
42        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
43            ExponentFactor::Mul(coef) => value
44                .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
45                .ok_or(PriceError::Overflow)?,
46            ExponentFactor::Div(coef) => value
47                .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
48                .ok_or(PriceError::Overflow)?,
49        };
50        if !mantissa.is_integer() {
51            return Err(PriceError::TooPrecise);
52        }
53        let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?;
54        let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::Overflow)?;
55        Ok(Self(mantissa))
56    }
57
58    pub const fn from_nonzero_mantissa(mantissa: NonZeroI64) -> Self {
59        Self(mantissa)
60    }
61
62    pub const fn from_mantissa(mantissa: i64) -> Result<Self, PriceError> {
63        if let Some(mantissa) = NonZeroI64::new(mantissa) {
64            Ok(Self(mantissa))
65        } else {
66            Err(PriceError::ZeroPriceUnsupported)
67        }
68    }
69
70    pub fn mantissa(self) -> NonZeroI64 {
71        self.0
72    }
73
74    pub fn mantissa_i64(self) -> i64 {
75        self.0.get()
76    }
77
78    pub fn to_f64(self, exponent: i16) -> Result<f64, PriceError> {
79        match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
80            // Mul/div is reversed for converting mantissa to value
81            ExponentFactor::Mul(coef) => Ok(self.0.get() as f64 / coef as f64),
82            ExponentFactor::Div(coef) => Ok(self.0.get() as f64 * coef as f64),
83        }
84    }
85
86    pub fn from_f64(value: f64, exponent: i16) -> Result<Self, PriceError> {
87        let value = Decimal::from_f64(value).ok_or(PriceError::Overflow)?;
88        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
89            ExponentFactor::Mul(coef) => value
90                .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
91                .ok_or(PriceError::Overflow)?,
92            ExponentFactor::Div(coef) => value
93                .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
94                .ok_or(PriceError::Overflow)?,
95        };
96        let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?;
97        Ok(Self(
98            NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?,
99        ))
100    }
101
102    pub fn add_with_same_exponent(self, other: Price) -> Result<Self, PriceError> {
103        let mantissa = self
104            .0
105            .get()
106            .checked_add(other.0.get())
107            .ok_or(PriceError::Overflow)?;
108        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
109    }
110
111    pub fn sub_with_same_exponent(self, other: Price) -> Result<Self, PriceError> {
112        let mantissa = self
113            .0
114            .get()
115            .checked_sub(other.0.get())
116            .ok_or(PriceError::Overflow)?;
117        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
118    }
119
120    pub fn mul_integer(self, factor: i64) -> Result<Self, PriceError> {
121        let mantissa = self
122            .0
123            .get()
124            .checked_mul(factor)
125            .ok_or(PriceError::Overflow)?;
126        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
127    }
128
129    pub fn div_integer(self, factor: i64) -> Result<Self, PriceError> {
130        let mantissa = self
131            .0
132            .get()
133            .checked_div(factor)
134            .ok_or(PriceError::Overflow)?;
135        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
136    }
137
138    pub fn mul_decimal(self, mantissa: i64, exponent: i16) -> Result<Self, PriceError> {
139        let left_mantissa = i128::from(self.0.get());
140        let right_mantissa = i128::from(mantissa);
141
142        // multiplied_mantissas = left_mantissa * right_mantissa
143        let multiplied_mantissas = left_mantissa
144            .checked_mul(right_mantissa)
145            .ok_or(PriceError::Overflow)?;
146
147        // result_mantissa = left_mantissa * right_mantissa * 10^exponent
148        // Mul/div is reversed for multiplying 10^exponent
149        let result_mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
150            ExponentFactor::Mul(coef) => multiplied_mantissas
151                .checked_div(coef.into())
152                .ok_or(PriceError::Overflow)?,
153            ExponentFactor::Div(coef) => multiplied_mantissas
154                .checked_mul(coef.into())
155                .ok_or(PriceError::Overflow)?,
156        };
157        let result_mantissa: i64 = result_mantissa
158            .try_into()
159            .map_err(|_| PriceError::Overflow)?;
160        Self::from_mantissa(result_mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
161    }
162}