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.try_into().map_err(|_| DecimalError::Overflow)?,
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
255 // https://solscan.io/account/716hFAECqotxcXcj8Hs8nr7AG6q9dBw2oX3k3M8V7uGq#data solana chainlink eth oracle has 8 decimals. token_decimal config would have to be << 18 to work
256 // chopped off 4 decimals from maxPrice of 2960410000000000
257 let price_eth = Decimal::try_from_price(296041000000, 8, 8, 4).unwrap();
258 // precision 4
259 assert_eq!(price_eth.value, 29604100);
260 // 8 (20-4-8)
261 assert_eq!(price_eth.decimal_multiplier, 8);
262 // 12 decimals (4+8)
263 assert_eq!(price_eth.to_unit_price(), 2960410000000000);
264 // chopped off 2 decimals from maxPrice
265 let price_btc = Decimal::try_from_price(7695092578398765000000000, 20, 8, 2).unwrap();
266 // precision 2
267 assert_eq!(price_btc.value, 7695092);
268 // 10 (20-2-8)
269 assert_eq!(price_btc.decimal_multiplier, 10);
270 // 12 decimals (2+10)
271 assert_eq!(price_btc.to_unit_price(), 76950920000000000);
272 // chopped off 4 decimals from maxPrice of 1000000000000000000000000
273 let price_usdc = Decimal::try_from_price(100000000000000000000, 20, 6, 6).unwrap();
274 // precision 6
275 assert_eq!(price_usdc.value, 1000000);
276 // 8 (20-6-6)
277 assert_eq!(price_usdc.decimal_multiplier, 8);
278 // 14 decimals (6+8)
279 assert_eq!(price_usdc.to_unit_price(), 100000000000000);
280
281 let fiat_value_eth = price_eth.to_unit_price() * 10u128.pow(8);
282 // 20 decimals (unit price decimals+token_decimals = 20 for one full unit)
283 assert_eq!(fiat_value_eth, 296041000000000000000000u128);
284 let fiat_eth_to_btc = fiat_value_eth / price_btc.to_unit_price();
285 assert_eq!(fiat_eth_to_btc, 3847140);
286 let fiat_eth_to_usdc = fiat_value_eth / price_usdc.to_unit_price();
287 assert_eq!(fiat_eth_to_usdc, 2960410000);
288 }
289
290 #[test]
291 fn test_price_overflow() {
292 assert!(Decimal::try_from_price(u128::from(u32::MAX), 0, 0, 0).is_ok());
293 assert!(Decimal::try_from_price(u128::from(u32::MAX) + 1, 0, 0, 0).is_err());
294 }
295}