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}