kujira_std/
price.rs

1use std::{
2    cmp::Ordering,
3    ops::{Deref, Div, DivAssign, Mul, MulAssign},
4};
5
6use cosmwasm_schema::cw_serde;
7use cosmwasm_std::{Decimal, Fraction, StdResult, Uint128};
8
9use crate::querier::KujiraQuerier;
10
11pub const REFERENCE_DECIMAL_PLACES: u8 = 6;
12
13/// `HumanPrice` is returned from the oracle querier and is a decimal value
14/// representing the exchange rate of the given denom, *without any normalization*.
15///
16/// # NOTE
17/// Denominations with different decimals will have `value = amount * price.normalize(decimals)`
18/// So do NOT use this value directly for calculations, but rather use the `NormalizedPrice`
19#[cw_serde]
20#[derive(Copy, Eq, PartialOrd, Ord)]
21pub struct HumanPrice(Decimal);
22
23impl HumanPrice {
24    pub fn normalize(&self, decimals: u8) -> NormalizedPrice {
25        NormalizedPrice::from_raw(self.0, decimals)
26    }
27}
28
29impl From<Decimal> for HumanPrice {
30    fn from(value: Decimal) -> Self {
31        HumanPrice(value)
32    }
33}
34
35impl From<HumanPrice> for Decimal {
36    fn from(value: HumanPrice) -> Self {
37        value.0
38    }
39}
40
41/// `NormalizedPrice` should be used in all operations involving
42/// calculating the value of coins given the oracle price.
43/// **When comparing values of non-standard denominations, failing
44/// to normalize the price can cause unexpected and incorrect results.**
45///
46/// Standard denominations have 6 decimal places, so we use that as
47/// the reference point.
48#[cw_serde]
49#[derive(Copy, Eq, PartialOrd, Ord)]
50pub struct NormalizedPrice(Decimal);
51
52impl NormalizedPrice {
53    /// This is unsafe because it does not check that the price is
54    /// normalized to the reference decimal places.
55    /// Most likely during testing.
56    pub fn unsafe_unchecked(price: Decimal) -> Self {
57        Self(price)
58    }
59
60    pub fn from_raw(price: Decimal, decimals: u8) -> Self {
61        // delta is i16 because we subtract two u8s
62        let delta: i16 = i16::from(REFERENCE_DECIMAL_PLACES) - i16::from(decimals);
63        Self::from_delta(price, delta)
64    }
65
66    pub fn from_delta(price: Decimal, delta: i16) -> Self {
67        match delta.cmp(&0) {
68            Ordering::Equal => Self(price),
69            Ordering::Greater => Self(Decimal::from_ratio(
70                price.numerator() * Uint128::from(10u128.pow(u32::from(delta.unsigned_abs()))),
71                price.denominator(),
72            )),
73            Ordering::Less => Self(Decimal::from_ratio(
74                price.numerator(),
75                price.denominator() * Uint128::from(10u128.pow(u32::from(delta.unsigned_abs()))),
76            )),
77        }
78    }
79
80    pub fn from_oracle<T: Into<String>>(
81        querier: &KujiraQuerier,
82        denom: T,
83        decimals: u8,
84    ) -> StdResult<Self> {
85        querier
86            .query_exchange_rate(denom)
87            .map(|price| price.normalize(decimals))
88    }
89
90    pub fn inner(&self) -> Decimal {
91        self.0
92    }
93}
94
95impl Deref for NormalizedPrice {
96    type Target = Decimal;
97
98    fn deref(&self) -> &Self::Target {
99        &self.0
100    }
101}
102
103impl From<NormalizedPrice> for Decimal {
104    fn from(price: NormalizedPrice) -> Self {
105        price.0
106    }
107}
108
109impl Mul<NormalizedPrice> for NormalizedPrice {
110    type Output = NormalizedPrice;
111
112    fn mul(self, rhs: NormalizedPrice) -> Self::Output {
113        NormalizedPrice(self.0 * rhs.0)
114    }
115}
116
117impl MulAssign<NormalizedPrice> for NormalizedPrice {
118    fn mul_assign(&mut self, rhs: NormalizedPrice) {
119        self.0 *= rhs.0
120    }
121}
122
123impl Div<NormalizedPrice> for NormalizedPrice {
124    type Output = NormalizedPrice;
125
126    fn div(self, rhs: NormalizedPrice) -> Self::Output {
127        NormalizedPrice(self.0 / rhs.0)
128    }
129}
130
131impl DivAssign<NormalizedPrice> for NormalizedPrice {
132    fn div_assign(&mut self, rhs: NormalizedPrice) {
133        self.0 /= rhs.0
134    }
135}
136
137impl Mul<Uint128> for NormalizedPrice {
138    type Output = Uint128;
139
140    fn mul(self, rhs: Uint128) -> Self::Output {
141        rhs.mul_floor(self.0)
142    }
143}
144
145impl Mul<NormalizedPrice> for Uint128 {
146    type Output = Uint128;
147
148    fn mul(self, rhs: NormalizedPrice) -> Self::Output {
149        self.mul_floor(rhs.0)
150    }
151}
152
153impl MulAssign<NormalizedPrice> for Uint128 {
154    fn mul_assign(&mut self, rhs: NormalizedPrice) {
155        *self = self.mul_floor(rhs.0)
156    }
157}
158
159impl Div<Uint128> for NormalizedPrice {
160    type Output = Option<Uint128>;
161
162    fn div(self, rhs: Uint128) -> Self::Output {
163        self.0.inv().map(|inv| rhs.mul_floor(inv))
164    }
165}
166
167impl Div<NormalizedPrice> for Uint128 {
168    type Output = Option<Uint128>;
169
170    fn div(self, rhs: NormalizedPrice) -> Self::Output {
171        rhs.0.inv().map(|inv| self.mul_floor(inv))
172    }
173}
174#[cfg(test)]
175mod tests {
176    use cosmwasm_std::Decimal;
177
178    use super::{HumanPrice, NormalizedPrice};
179
180    #[test]
181    fn serialize_human_price() {
182        let price = HumanPrice(Decimal::percent(459));
183        let serialized = serde_json::to_string(&price).unwrap();
184        assert_eq!(serialized, r#""4.59""#);
185    }
186
187    #[test]
188    fn deserialize_human_price() {
189        let price = HumanPrice(Decimal::percent(459));
190        let deserialized: HumanPrice = serde_json::from_str(r#""4.59""#).unwrap();
191        assert_eq!(price, deserialized);
192    }
193
194    #[test]
195    fn serialize_normalized_price() {
196        let price = NormalizedPrice(Decimal::percent(459));
197        let serialized = serde_json::to_string(&price).unwrap();
198        assert_eq!(serialized, r#""4.59""#);
199    }
200
201    #[test]
202    fn deserialize_normalized_price() {
203        let price = NormalizedPrice(Decimal::percent(459));
204        let deserialized: NormalizedPrice = serde_json::from_str(r#""4.59""#).unwrap();
205        assert_eq!(price, deserialized);
206    }
207}