1use crate::error::TypesError;
2use cosmwasm_std::Fraction;
3use cosmwasm_std::{Decimal, Uint128};
4use nym_config::defaults::{DenomDetails, DenomDetailsOwned, NymNetworkDetails};
5use nym_validator_client::nyxd::Coin;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::cmp::Ordering;
9use std::collections::HashMap;
10use std::fmt::{Display, Formatter};
11use strum_macros::{Display, EnumString, VariantNames};
12
13#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
14#[cfg_attr(
15 feature = "generate-ts",
16 ts(
17 export,
18 export_to = "ts-packages/types/src/types/rust/CurrencyDenom.ts"
19 )
20)]
21#[cfg_attr(feature = "generate-ts", ts(rename_all = "lowercase"))]
22#[derive(
23 Display,
24 Default,
25 Serialize,
26 Deserialize,
27 Clone,
28 Debug,
29 EnumString,
30 VariantNames,
31 PartialEq,
32 Eq,
33 JsonSchema,
34)]
35#[serde(rename_all = "lowercase")]
36#[strum(serialize_all = "lowercase")]
37pub enum CurrencyDenom {
38 #[strum(ascii_case_insensitive)]
39 #[default]
40 Unknown,
41 #[strum(ascii_case_insensitive)]
42 Nym,
43 #[strum(ascii_case_insensitive)]
44 Nymt,
45 #[strum(ascii_case_insensitive)]
46 Nyx,
47 #[strum(ascii_case_insensitive)]
48 Nyxt,
49}
50
51pub type Denom = String;
52
53#[derive(Debug, Default)]
54pub struct RegisteredCoins(HashMap<Denom, CoinMetadata>);
55
56impl RegisteredCoins {
57 pub fn default_denoms(network: &NymNetworkDetails) -> Self {
58 let mut network_coins = HashMap::new();
59 network_coins.insert(
60 network.chain_details.mix_denom.base.clone(),
61 network.chain_details.mix_denom.clone().into(),
62 );
63 network_coins.insert(
64 network.chain_details.stake_denom.base.clone(),
65 network.chain_details.stake_denom.clone().into(),
66 );
67 RegisteredCoins(network_coins)
68 }
69
70 pub fn insert(&mut self, denom: Denom, metadata: CoinMetadata) -> Option<CoinMetadata> {
71 self.0.insert(denom, metadata)
72 }
73
74 pub fn remove(&mut self, denom: &Denom) -> Option<CoinMetadata> {
75 self.0.remove(denom)
76 }
77
78 pub fn attempt_create_display_coin_from_base_dec_amount(
79 &self,
80 denom: &Denom,
81 base_amount: Decimal,
82 ) -> Result<DecCoin, TypesError> {
83 for registered_coin in self.0.values() {
84 if let Some(exponent) = registered_coin.get_exponent(denom) {
85 let display_exponent = registered_coin
88 .get_exponent(®istered_coin.display)
89 .ok_or_else(|| TypesError::UnknownCoinDenom(denom.clone()))?;
90
91 return match exponent.cmp(&display_exponent) {
92 Ordering::Greater => {
93 Ok(DecCoin {
96 denom: denom.into(),
97 amount: try_scale_up_decimal(base_amount, exponent - display_exponent)?,
98 })
99 }
100 Ordering::Equal => Ok(DecCoin {
102 denom: denom.into(),
103 amount: base_amount,
104 }),
105 Ordering::Less => {
106 Ok(DecCoin {
108 denom: denom.into(),
109 amount: try_scale_down_decimal(
110 base_amount,
111 display_exponent - exponent,
112 )?,
113 })
114 }
115 };
116 }
117 }
118
119 Err(TypesError::UnknownCoinDenom(denom.clone()))
120 }
121
122 pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result<Coin, TypesError> {
123 if self.0.contains_key(&coin.denom) {
125 return coin.try_into();
128 } else {
129 for registered_coin in self.0.values() {
131 if let Some(exponent) = registered_coin.get_exponent(&coin.denom) {
132 let amount = try_convert_decimal_to_u128(coin.try_scale_up_value(exponent)?)?;
133 return Ok(Coin::new(amount, ®istered_coin.base));
134 }
135 }
136 }
137 Err(TypesError::UnknownCoinDenom(coin.denom))
138 }
139
140 pub fn attempt_convert_to_display_dec_coin(&self, coin: Coin) -> Result<DecCoin, TypesError> {
141 for registered_coin in self.0.values() {
142 if let Some(exponent) = registered_coin.get_exponent(&coin.denom) {
143 let display_exponent = registered_coin
146 .get_exponent(®istered_coin.display)
147 .ok_or_else(|| TypesError::UnknownCoinDenom(coin.denom.clone()))?;
148
149 return match exponent.cmp(&display_exponent) {
150 Ordering::Greater => {
151 Ok(DecCoin::new_scaled_up(
154 coin.amount,
155 ®istered_coin.display,
156 exponent - display_exponent,
157 )?)
158 }
159 Ordering::Equal => Ok(coin.into()),
161 Ordering::Less => {
162 Ok(DecCoin::new_scaled_down(
164 coin.amount,
165 ®istered_coin.display,
166 display_exponent - exponent,
167 )?)
168 }
169 };
170 }
171 }
172
173 Err(TypesError::UnknownCoinDenom(coin.denom))
174 }
175}
176
177#[derive(Debug)]
182pub struct DenomUnit {
183 pub denom: Denom,
184 pub exponent: u32,
185 }
187
188impl DenomUnit {
189 pub fn new(denom: Denom, exponent: u32) -> Self {
190 DenomUnit { denom, exponent }
191 }
192}
193
194#[derive(Debug)]
195pub struct CoinMetadata {
196 pub denom_units: Vec<DenomUnit>,
197 pub base: Denom,
198 pub display: Denom,
199}
200
201impl CoinMetadata {
202 pub fn new(denom_units: Vec<DenomUnit>, base: Denom, display: Denom) -> Self {
203 CoinMetadata {
204 denom_units,
205 base,
206 display,
207 }
208 }
209
210 pub fn get_exponent(&self, denom: &str) -> Option<u32> {
211 self.denom_units
212 .iter()
213 .find(|denom_unit| denom_unit.denom == denom)
214 .map(|denom_unit| denom_unit.exponent)
215 }
216}
217
218impl From<DenomDetails> for CoinMetadata {
219 fn from(denom_details: DenomDetails) -> Self {
220 CoinMetadata::new(
221 vec![
222 DenomUnit::new(denom_details.base.into(), 0),
223 DenomUnit::new(denom_details.display.into(), denom_details.display_exponent),
224 ],
225 denom_details.base.into(),
226 denom_details.display.into(),
227 )
228 }
229}
230
231impl From<DenomDetailsOwned> for CoinMetadata {
232 fn from(denom_details: DenomDetailsOwned) -> Self {
233 CoinMetadata::new(
234 vec![
235 DenomUnit::new(denom_details.base.clone(), 0),
236 DenomUnit::new(
237 denom_details.display.clone(),
238 denom_details.display_exponent,
239 ),
240 ],
241 denom_details.base,
242 denom_details.display,
243 )
244 }
245}
246
247#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)]
250#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
251#[cfg_attr(
252 feature = "generate-ts",
253 ts(export, export_to = "ts-packages/types/src/types/rust/DecCoin.ts")
254)]
255pub struct DecCoin {
256 #[cfg_attr(feature = "generate-ts", ts(as = "CurrencyDenom"))]
257 pub denom: Denom,
258 #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
261 pub amount: Decimal,
262}
263
264impl DecCoin {
265 pub fn new_base<S: Into<String>>(amount: impl Into<Uint128>, denom: S) -> Self {
266 DecCoin {
267 denom: denom.into(),
268 amount: Decimal::from_atomics(amount, 0).unwrap(),
269 }
270 }
271
272 pub fn zero<S: Into<String>>(denom: S) -> Self {
273 DecCoin {
274 denom: denom.into(),
275 amount: Decimal::zero(),
276 }
277 }
278
279 pub fn new_scaled_up<S: Into<String>>(
280 base_amount: impl Into<Uint128>,
281 denom: S,
282 exponent: u32,
283 ) -> Result<Self, TypesError> {
284 let base_amount = Decimal::from_atomics(base_amount, 0).unwrap();
285 Ok(DecCoin {
286 denom: denom.into(),
287 amount: try_scale_up_decimal(base_amount, exponent)?,
288 })
289 }
290
291 pub fn new_scaled_down<S: Into<String>>(
292 base_amount: impl Into<Uint128>,
293 denom: S,
294 exponent: u32,
295 ) -> Result<Self, TypesError> {
296 let base_amount = Decimal::from_atomics(base_amount, 0).unwrap();
297 Ok(DecCoin {
298 denom: denom.into(),
299 amount: try_scale_down_decimal(base_amount, exponent)?,
300 })
301 }
302
303 pub fn try_scale_down_value(&self, exponent: u32) -> Result<Decimal, TypesError> {
304 try_scale_down_decimal(self.amount, exponent)
305 }
306
307 pub fn try_scale_up_value(&self, exponent: u32) -> Result<Decimal, TypesError> {
308 try_scale_up_decimal(self.amount, exponent)
309 }
310}
311
312pub fn try_scale_down_decimal(dec: Decimal, exponent: u32) -> Result<Decimal, TypesError> {
314 let rhs = 10u128
315 .checked_pow(exponent)
316 .ok_or(TypesError::UnsupportedExponent(exponent))?;
317 let denominator = dec
318 .denominator()
319 .checked_mul(rhs.into())
320 .map_err(|_| TypesError::UnsupportedExponent(exponent))?;
321
322 Ok(Decimal::from_ratio(dec.numerator(), denominator))
323}
324
325pub fn try_scale_up_decimal(dec: Decimal, exponent: u32) -> Result<Decimal, TypesError> {
326 let rhs = 10u128
327 .checked_pow(exponent)
328 .ok_or(TypesError::UnsupportedExponent(exponent))?;
329 let denominator = dec
330 .denominator()
331 .checked_div(rhs.into())
332 .map_err(|_| TypesError::UnsupportedExponent(exponent))?;
333
334 Ok(Decimal::from_ratio(dec.numerator(), denominator))
335}
336
337pub fn try_convert_decimal_to_u128(dec: Decimal) -> Result<u128, TypesError> {
338 let whole = dec.numerator() / dec.denominator();
339
340 let fractional = (dec.numerator()).checked_rem(dec.denominator()).unwrap();
342
343 if fractional != Uint128::zero() {
346 return Err(TypesError::LossyCoinConversion);
347 }
348 Ok(whole.u128())
349}
350
351impl Display for DecCoin {
352 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
353 write!(f, "{} {}", self.amount, self.denom)
354 }
355}
356
357impl From<Coin> for DecCoin {
358 fn from(coin: Coin) -> Self {
359 DecCoin::new_base(coin.amount, coin.denom)
360 }
361}
362
363impl TryFrom<DecCoin> for Coin {
365 type Error = TypesError;
366
367 fn try_from(value: DecCoin) -> Result<Self, Self::Error> {
368 Ok(Coin {
369 amount: try_convert_decimal_to_u128(value.try_scale_down_value(0)?)?,
370 denom: value.denom,
371 })
372 }
373}
374
375#[cfg(test)]
376mod test {
377 use super::*;
378
379 #[test]
380 fn dec_value_scale_down() {
381 let dec = DecCoin {
382 denom: "foo".to_string(),
383 amount: "1234007000".parse().unwrap(),
384 };
385
386 assert_eq!(
387 "1234007000".parse::<Decimal>().unwrap(),
388 dec.try_scale_down_value(0).unwrap()
389 );
390 assert_eq!(
391 "123400700".parse::<Decimal>().unwrap(),
392 dec.try_scale_down_value(1).unwrap()
393 );
394 assert_eq!(
395 "12340070".parse::<Decimal>().unwrap(),
396 dec.try_scale_down_value(2).unwrap()
397 );
398 assert_eq!(
399 "123400.7".parse::<Decimal>().unwrap(),
400 dec.try_scale_down_value(4).unwrap()
401 );
402
403 let dec = DecCoin {
404 denom: "foo".to_string(),
405 amount: "10000000000".parse().unwrap(),
406 };
407
408 assert_eq!(
409 "100".parse::<Decimal>().unwrap(),
410 dec.try_scale_down_value(8).unwrap()
411 );
412 assert_eq!(
413 "1".parse::<Decimal>().unwrap(),
414 dec.try_scale_down_value(10).unwrap()
415 );
416 assert_eq!(
417 "0.01".parse::<Decimal>().unwrap(),
418 dec.try_scale_down_value(12).unwrap()
419 );
420 }
421
422 #[test]
423 fn dec_value_scale_up() {
424 let dec = DecCoin {
425 denom: "foo".to_string(),
426 amount: "1234.56".parse().unwrap(),
427 };
428
429 assert_eq!(
430 "1234.56".parse::<Decimal>().unwrap(),
431 dec.try_scale_up_value(0).unwrap()
432 );
433 assert_eq!(
434 "12345.6".parse::<Decimal>().unwrap(),
435 dec.try_scale_up_value(1).unwrap()
436 );
437 assert_eq!(
438 "123456".parse::<Decimal>().unwrap(),
439 dec.try_scale_up_value(2).unwrap()
440 );
441 assert_eq!(
442 "1234560".parse::<Decimal>().unwrap(),
443 dec.try_scale_up_value(3).unwrap()
444 );
445 assert_eq!(
446 "12345600".parse::<Decimal>().unwrap(),
447 dec.try_scale_up_value(4).unwrap()
448 );
449
450 let dec = DecCoin {
451 denom: "foo".to_string(),
452 amount: "0.00000123".parse().unwrap(),
453 };
454
455 assert_eq!(
456 "0.0000123".parse::<Decimal>().unwrap(),
457 dec.try_scale_up_value(1).unwrap()
458 );
459 assert_eq!(
460 "0.000123".parse::<Decimal>().unwrap(),
461 dec.try_scale_up_value(2).unwrap()
462 );
463 assert_eq!(
464 "123".parse::<Decimal>().unwrap(),
465 dec.try_scale_up_value(8).unwrap()
466 );
467 assert_eq!(
468 "1230".parse::<Decimal>().unwrap(),
469 dec.try_scale_up_value(9).unwrap()
470 );
471 assert_eq!(
472 "12300".parse::<Decimal>().unwrap(),
473 dec.try_scale_up_value(10).unwrap()
474 );
475 }
476
477 #[test]
478 fn coin_to_dec_coin() {
479 let coin = Coin::new(123, "foo");
480 let dec = DecCoin::from(coin.clone());
481 assert_eq!(coin.denom, dec.denom);
482 assert_eq!(dec.amount, Decimal::from_atomics(coin.amount, 0).unwrap());
483 }
484
485 #[test]
486 fn dec_coin_to_coin() {
487 let dec = DecCoin {
488 denom: "foo".to_string(),
489 amount: "123".parse().unwrap(),
490 };
491 let coin = Coin::try_from(dec.clone()).unwrap();
492 assert_eq!(dec.denom, coin.denom);
493 assert_eq!(coin.amount, 123u128);
494 }
495
496 #[test]
497 fn converting_to_display() {
498 let reg = RegisteredCoins::default_denoms(&NymNetworkDetails::new_mainnet());
499 let values = vec![
500 (1u128, "0.000001"),
501 (10u128, "0.00001"),
502 (100u128, "0.0001"),
503 (1000u128, "0.001"),
504 (10000u128, "0.01"),
505 (100000u128, "0.1"),
506 (1000000u128, "1"),
507 (1234567u128, "1.234567"),
508 (123456700u128, "123.4567"),
509 ];
510
511 for (raw, expected) in values {
512 let coin = Coin::new(
513 raw,
514 NymNetworkDetails::new_mainnet()
515 .chain_details
516 .mix_denom
517 .base
518 .clone(),
519 );
520 let display = reg.attempt_convert_to_display_dec_coin(coin).unwrap();
521 assert_eq!(
522 NymNetworkDetails::new_mainnet()
523 .chain_details
524 .mix_denom
525 .display,
526 display.denom
527 );
528 assert_eq!(expected, display.amount.to_string());
529 }
530 }
531
532 #[test]
533 fn converting_to_base() {
534 let reg = RegisteredCoins::default_denoms(&NymNetworkDetails::new_mainnet());
535 let values = vec![
536 (1u128, "0.000001"),
537 (10u128, "0.00001"),
538 (100u128, "0.0001"),
539 (1000u128, "0.001"),
540 (10000u128, "0.01"),
541 (100000u128, "0.1"),
542 (1000000u128, "1"),
543 (1234567u128, "1.234567"),
544 (123456700u128, "123.4567"),
545 ];
546
547 for (expected, raw_display) in values {
548 let coin = DecCoin {
549 denom: NymNetworkDetails::new_mainnet()
550 .chain_details
551 .mix_denom
552 .display
553 .clone(),
554 amount: raw_display.parse().unwrap(),
555 };
556 let base = reg.attempt_convert_to_base_coin(coin).unwrap();
557 assert_eq!(
558 NymNetworkDetails::new_mainnet()
559 .chain_details
560 .mix_denom
561 .base,
562 base.denom
563 );
564 assert_eq!(expected, base.amount);
565 }
566 }
567}