gmsol_utils/price/
decimal.rs

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