gmsol_utils/price/
decimal.rs

1use std::{cmp::Ordering, ops::Div};
2
3use anchor_lang::{
4    prelude::{borsh, AnchorDeserialize, AnchorSerialize},
5    InitSpace,
6};
7
8/// Decimal type for storing prices.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, AnchorSerialize, AnchorDeserialize, InitSpace)]
10pub struct Decimal {
11    /// Value.
12    pub value: u32,
13    /// Decimal multiplier.
14    pub decimal_multiplier: u8,
15}
16
17impl Decimal {
18    /// The Maximum Decimals.
19    /// Should satisfy `MAX_DECIMALS <= 30`.
20    pub const MAX_DECIMALS: u8 = 20;
21
22    /// The Maximum Decimal Multiplier,
23    /// which should satisfy `u32::MAX * 10^{MAX_DECIMAL_MULTIPLIER} <= u128::MAX`.
24    pub const MAX_DECIMAL_MULTIPLIER: u8 = 20;
25
26    fn multiplier(&self) -> u128 {
27        10u128.pow(self.decimal_multiplier as u32)
28    }
29
30    /// Returns the price of one unit (with decimals to be [`MAX_DECIMALS`](Self::MAX_DECIMALS)).
31    pub fn to_unit_price(&self) -> u128 {
32        self.value as u128 * self.multiplier()
33    }
34
35    /// Creates a new [`Decimal`] with the given unit price, keeping `decimal_multiplier` unchanged.
36    /// Returns `None` if the price is too big.
37    pub fn with_unit_price(&self, price: u128, round_up: bool) -> Option<Self> {
38        let multiplier = self.multiplier();
39        debug_assert!(multiplier != 0);
40        let value = if round_up {
41            price.div_ceil(multiplier)
42        } else {
43            price.div(multiplier)
44        };
45        Some(Self {
46            value: value.try_into().ok()?,
47            decimal_multiplier: self.decimal_multiplier,
48        })
49    }
50
51    /// Create price decimal from the given `price` with `decimals`,
52    /// where `token_decimals` is the expected unit and with expected `precision`.
53    pub fn try_from_price(
54        mut price: u128,
55        decimals: u8,
56        token_decimals: u8,
57        precision: u8,
58    ) -> Result<Self, DecimalError> {
59        if token_decimals > Self::MAX_DECIMALS
60            || precision > Self::MAX_DECIMALS
61            || decimals > Self::MAX_DECIMALS
62        {
63            return Err(DecimalError::ExceedMaxDecimals);
64        }
65        if token_decimals + precision > Self::MAX_DECIMALS {
66            return Err(DecimalError::ExceedMaxDecimals);
67        }
68        // Convert the `price` to be with decimals of `token_decimals`.
69        let divisor_exp = match decimals.cmp(&token_decimals) {
70            Ordering::Equal => None,
71            Ordering::Less => {
72                // CHECK: Since `token_decimals` and `decimals` are both less than `MAX_DECIMALS`,
73                // the pow will never overflow.
74                let multiplier = 10u128.pow((token_decimals - decimals) as u32);
75                price = price
76                    .checked_mul(multiplier)
77                    .ok_or(DecimalError::Overflow)?;
78                None
79            }
80            Ordering::Greater => Some(decimals - token_decimals),
81        };
82
83        let decimal_multiplier = Self::decimal_multiplier_from_precision(token_decimals, precision);
84        debug_assert!(
85            decimal_multiplier <= Self::MAX_DECIMAL_MULTIPLIER,
86            "must not exceed `MAX_DECIMAL_MULTIPLIER`"
87        );
88        // CHECK: 2 * MAX_DECIMALS + MAX_DECIMAL_MULTIPLIER <= u8::MAX
89        let multiplier = (token_decimals << 1) + decimal_multiplier;
90        let value = if Self::MAX_DECIMALS >= multiplier {
91            let mut exp = Self::MAX_DECIMALS - multiplier;
92            if let Some(divisor_exp) = divisor_exp {
93                if exp >= divisor_exp {
94                    exp -= divisor_exp;
95                    // CHECK: Since `exp <= MAX_DECIMALS <= 30`, the pow will never overflow.
96                    price
97                        .checked_mul(10u128.pow((exp) as u32))
98                        .ok_or(DecimalError::Overflow)?
99                } else {
100                    exp = divisor_exp - exp;
101                    // CHECK: Since `divisor_exp <= decimals <= MAX_DECIMALS <= 30`, the pow will never overflow.
102                    price / 10u128.pow(exp as u32)
103                }
104            } else {
105                // CHECK: Since `exp <= MAX_DECIMALS <= 30`, the pow will never overflow.
106                price
107                    .checked_mul(10u128.pow((exp) as u32))
108                    .ok_or(DecimalError::Overflow)?
109            }
110        } else {
111            // CHECK: Since `multiplier == 2 * token_decimals + decimal_multiplier <= token_decimals + MAX_DECIMALS <= 2 * MAX_DECIMALS`,
112            // `multiplier - MAX_DECIMALS <= MAX_DECIMALS <= 30` will never make the pow overflow.
113            let mut ans = price / 10u128.pow((multiplier - Self::MAX_DECIMALS) as u32);
114            if let Some(exp) = divisor_exp {
115                ans /= 10u128.pow(exp as u32)
116            }
117            ans
118        };
119        Ok(Self {
120            value: value as u32,
121            decimal_multiplier,
122        })
123    }
124
125    /// Calculate the decimal multiplier with the desired precision.
126    /// # Warning
127    /// One should check that `decimals + precision` is not greater than [`MAX_DECIMALS`](Self::MAX_DECIMALS),
128    /// otherwise the result might be incorrect due to underflow.
129    pub const fn decimal_multiplier_from_precision(decimals: u8, precision: u8) -> u8 {
130        Self::MAX_DECIMALS - decimals - precision
131    }
132
133    /// Returns the max representable decimal with the same decimal multiplier.
134    pub fn maximum(&self) -> Self {
135        Self {
136            value: u32::MAX,
137            decimal_multiplier: self.decimal_multiplier,
138        }
139    }
140}
141
142/// Errors of decimals.
143#[derive(Debug, thiserror::Error)]
144pub enum DecimalError {
145    /// Exceed the maximum decimals.
146    #[error("exceeds the maximum decimals")]
147    ExceedMaxDecimals,
148    /// Invalid decimals.
149    #[error("exceeds the maximum decimal multiplier")]
150    ExceedMaxDecimalMultiplier,
151    /// Overflow.
152    #[error("overflow")]
153    Overflow,
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_price_1() {
162        // The price of ETH is 5,000 with 18 decimals and the decimal multiplier is set to 8 (so that we have decimals of precision 4).
163        let price = Decimal::try_from_price(5_000_000_000_000_000_000_000, 18, 8, 4).unwrap();
164        assert_eq!(price.to_unit_price(), 5_000_000_000_000_000);
165        assert_eq!(price.decimal_multiplier, 8);
166    }
167
168    #[test]
169    fn test_price_2() {
170        // The price of BTC is 60,000 with 8 decimals and the decimal multiplier is set to 10 (so that we have decimals of precision 2).
171        let price = Decimal::try_from_price(6_000_000_000_000, 8, 8, 2).unwrap();
172        assert_eq!(price.to_unit_price(), 60_000_000_000_000_000);
173        assert_eq!(price.decimal_multiplier, 10);
174    }
175
176    #[test]
177    fn test_price_3() {
178        // The price of USDC is 1 with 6 decimals and the decimal multiplier is set to 8 (so that we have decimals of precision 6).
179        let price = Decimal::try_from_price(1_000_000, 6, 6, 6).unwrap();
180        assert_eq!(price.to_unit_price(), 100_000_000_000_000);
181        assert_eq!(price.decimal_multiplier, 8);
182    }
183
184    #[test]
185    fn test_price_4() {
186        // The price of DG is 0.00000001 with 18 decimals and the decimal multiplier is set to 1 (so that we have decimals of precision 11).
187        let price = Decimal::try_from_price(10_000_000_000, 18, 8, 11).unwrap();
188        assert_eq!(price.to_unit_price(), 10_000);
189        assert_eq!(price.decimal_multiplier, 1);
190    }
191
192    #[test]
193    fn test_price_5() {
194        // The price of one WNT is 5,000
195        // price decimals: 5
196        // token decimals: 8
197        // expected precision: 4
198        let price = Decimal::try_from_price(500_000_000, 5, 8, 4).unwrap();
199        // 5,000 / 10^18 * 10^20
200        assert_eq!(price.to_unit_price(), 5_000_000_000_000_000);
201        assert_eq!(price.decimal_multiplier, 20 - 8 - 4);
202    }
203
204    #[test]
205    fn test_price_6() {
206        // The price of one WBTC is 50,000
207        // price decimals: 8
208        // token decimals: 8
209        // expected precision: 2
210        let price = Decimal::try_from_price(5_000_000_000_000, 8, 8, 2).unwrap();
211        // 50,000 / 10^8 * 10^20
212        assert_eq!(price.to_unit_price(), 50_000_000_000_000_000);
213        assert_eq!(price.decimal_multiplier, 20 - 8 - 2);
214    }
215
216    #[test]
217    fn test_price_7() {
218        // The price of one token is 5.0
219        // price decimals: 12
220        // token decimals: 8
221        // expected precision: 2
222        let price = Decimal::try_from_price(5_000_000_000_000, 12, 8, 2).unwrap();
223        // 5 / 10^8 * 10^20
224        assert_eq!(price.to_unit_price(), 5_000_000_000_000);
225        assert_eq!(price.decimal_multiplier, 20 - 8 - 2);
226    }
227
228    #[test]
229    fn test_price_8() {
230        // The price of one SHIB is 1.77347 * 10^-5
231        // price decimals: 10
232        // token decimals: 5
233        // expected precision: 9
234        let price = Decimal::try_from_price(177_347, 10, 5, 9).unwrap();
235        assert_eq!(price.to_unit_price(), 17_734_000_000);
236        assert_eq!(price.decimal_multiplier, 20 - 5 - 9);
237    }
238
239    #[test]
240    fn test_price_max_price() {
241        /*
242            https://arbitrum-api.gmxinfra.io/tokens :
243            {"symbol":"ETH","address":"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1","decimals":18},
244            {"symbol":"BTC","address":"0x47904963fc8b2340414262125aF798B9655E58Cd","decimals":8,"synthetic":true},
245            {"symbol":"USDC","address":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831","decimals":6}
246        https://arbitrum-api.gmxinfra.io/prices/tickers :
247        {"tokenAddress":"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1","tokenSymbol":"ETH","minPrice":"2960193941697386","maxPrice":"2960410000000000","updatedAt":1731093960710,"timestamp":1731093960},
248        {"tokenAddress":"0x47904963fc8b2340414262125aF798B9655E58Cd","tokenSymbol":"BTC","minPrice":"769507202712389700000000000","maxPrice":"769509257839876500000000000","updatedAt":1731093959803,"timestamp":1731093959},
249        {"tokenAddress":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831","tokenSymbol":"USDC","minPrice":"999883816264707600000000","maxPrice":"1000000000000000000000000","updatedAt":1731093960613,"timestamp":1731093960}
250        https://api.gmx.io/prices :
251        "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1":"2959011950971600000000000000000000"
252        eth price approx $2960.41 per 1 ETH
253        */
254        // https://solscan.io/account/716hFAECqotxcXcj8Hs8nr7AG6q9dBw2oX3k3M8V7uGq#data solana chainlink eth oracle has 8 decimals. token_decimal config would have to be << 18 to work
255        let price_eth = Decimal::try_from_price(296041000000, 8, 8, 4).unwrap();
256        assert_eq!(price_eth.value, 29604100); // precision 4
257        assert_eq!(price_eth.decimal_multiplier, 8); // 8 (20-4-8)
258        assert_eq!(price_eth.to_unit_price(), 2960410000000000); // 12 decimals (4+8)
259                                                                 // chopped off 2 decimals from maxPrice
260        let price_btc = Decimal::try_from_price(7695092578398765000000000, 20, 8, 2).unwrap();
261        assert_eq!(price_btc.value, 7695092); // precision 2
262        assert_eq!(price_btc.decimal_multiplier, 10); // 10 (20-2-8)
263        assert_eq!(price_btc.to_unit_price(), 76950920000000000); // 12 decimals (2+10)
264                                                                  // chopped off 4 decimals
265        let price_usdc = Decimal::try_from_price(100000000000000000000, 20, 6, 6).unwrap();
266        assert_eq!(price_usdc.value, 1000000); // precision 6
267        assert_eq!(price_usdc.decimal_multiplier, 8); // 8 (20-6-6)
268        assert_eq!(price_usdc.to_unit_price(), 100000000000000); // 14 decimals (6+8)
269
270        let fiat_value_eth = price_eth.to_unit_price() * 10u128.pow(8);
271        assert_eq!(fiat_value_eth, 296041000000000000000000u128); // 20 decimals (unit price decimals+token_decimals = 20 for one full unit)
272        let fiat_eth_to_btc = fiat_value_eth / price_btc.to_unit_price();
273        assert_eq!(fiat_eth_to_btc, 3847140);
274        let fiat_eth_to_usdc = fiat_value_eth / price_usdc.to_unit_price();
275        assert_eq!(fiat_eth_to_usdc, 2960410000);
276    }
277}