levana_perpswap_cosmos/number/
convert.rs

1use anyhow::{Context, Result};
2use cosmwasm_std::{Decimal256, Uint128, Uint256};
3
4use super::types::{Signed, UnsignedDecimal};
5use super::Number;
6use std::fmt::Write;
7use std::str::FromStr;
8
9impl Number {
10    /// Returns the ratio (nominator / denominator) as a positive Number
11    pub fn from_ratio_u256<A: Into<Uint256>, B: Into<Uint256>>(
12        numerator: A,
13        denominator: B,
14    ) -> Self {
15        Number::new_positive(Decimal256::from_ratio(numerator, denominator))
16    }
17
18    /// Represent as a u128 encoded with given decimal places
19    ///
20    /// NOTE decimals may be dropped if precision isn't sufficient to represent
21    /// all digits completely
22    // (also seems to maybe not be sufficient for yocto near?)
23    pub fn to_u128_with_precision(&self, precision: u32) -> Option<u128> {
24        if self.is_negative() {
25            return None;
26        }
27
28        // Adjust precision based on given value and chuck in array
29        let factor = Decimal256::one().atomics() / Uint256::from_u128(10).pow(precision);
30        let raw = self.value().atomics() / factor;
31
32        Uint128::try_from(raw).ok().map(|x| x.into())
33    }
34
35    /// helper to get from native currency to Number
36    /// e.g. from uusd to UST, as a Decimal
37    pub fn from_fixed_u128(amount: u128, places: u32) -> Self {
38        (Self::from(amount) / Self::from(10u128.pow(places))).unwrap()
39    }
40
41    /// Useful for when Number is used as a PrimaryKey
42    /// and is guaranteed to always be positive
43    pub fn to_unsigned_key_bytes(&self) -> Option<[u8; 32]> {
44        if self.is_positive_or_zero() {
45            Some(self.value().atomics().to_be_bytes())
46        } else {
47            None
48        }
49    }
50
51    /// Round-tripping with [Self::to_unsigned_key_bytes]
52    pub fn from_unsigned_key_bytes(bytes: [u8; 32]) -> Self {
53        Number::new_positive(Decimal256::new(Uint256::from_be_bytes(bytes)))
54    }
55}
56
57//not allowed due to From<Decimal>- impl <T: AsRef<str>> From<T> for Number {
58impl TryFrom<&str> for Number {
59    type Error = anyhow::Error;
60
61    fn try_from(val: &str) -> Result<Self> {
62        Number::from_str(val)
63    }
64}
65impl TryFrom<String> for Number {
66    type Error = anyhow::Error;
67
68    fn try_from(val: String) -> Result<Self> {
69        Number::from_str(&val)
70    }
71}
72
73impl<T: UnsignedDecimal> FromStr for Signed<T> {
74    type Err = anyhow::Error;
75
76    /// Converts the decimal string to a Number
77    /// Possible inputs: "1.23", "1", "000012", "1.123000000", "-1.23"
78    /// Disallowed: "", ".23"
79    ///
80    /// This never performs any kind of rounding.
81    /// More than 18 fractional digits, even zeros, result in an error.
82    fn from_str(input: &str) -> Result<Self> {
83        match input.strip_prefix('-') {
84            Some(input) => Decimal256::from_str(input)
85                .map(T::from_decimal256)
86                .map(Signed::new_negative),
87            None => Decimal256::from_str(input)
88                .map(T::from_decimal256)
89                .map(Signed::new_positive),
90        }
91        .with_context(|| format!("Unable to parse Number from {input:?}"))
92    }
93}
94
95impl<T: UnsignedDecimal> From<u128> for Signed<T> {
96    fn from(val: u128) -> Self {
97        Signed::new_positive(T::from_decimal256(Decimal256::from_ratio(val, 1u32)))
98    }
99}
100
101impl<T: UnsignedDecimal> From<u64> for Signed<T> {
102    fn from(val: u64) -> Self {
103        u128::from(val).into()
104    }
105}
106
107#[cfg(test)]
108impl From<f64> for Number {
109    fn from(val: f64) -> Self {
110        // there is probably a faster way using direct math, e.g. converting to a fraction
111        Self::from_str(&format!("{}", val)).unwrap()
112    }
113}
114
115impl<T: UnsignedDecimal> std::fmt::Display for Signed<T> {
116    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
117        if self.is_zero() {
118            write!(f, "0")
119        } else {
120            if self.is_negative() {
121                f.write_char('-')?;
122            }
123            write!(f, "{}", self.value())
124        }
125    }
126}
127impl<T: UnsignedDecimal> std::fmt::Debug for Signed<T> {
128    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
129        write!(f, "{}", self)
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn non_ascii_does_not_panic() {
139        Number::try_from("αβ").unwrap_err();
140    }
141
142    #[test]
143    fn roundtrip_unsigned_bytes() {
144        let n = Number::from_str("1.42522").unwrap();
145        let bytes = n.to_unsigned_key_bytes().unwrap();
146        let n2 = Number::from_unsigned_key_bytes(bytes);
147        assert_eq!(n, n2);
148    }
149}