fvm_shared/econ/
mod.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::iter::Sum;
4use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
5
6use num_bigint::BigInt;
7use num_integer::Integer;
8use num_traits::{Signed, Zero};
9use serde::{Deserialize, Serialize, Serializer};
10
11use crate::bigint::bigint_ser;
12
13/// A quantity of native tokens.
14/// A token amount is an integer, but has a human interpretation as a value with
15/// 18 decimal places.
16/// This is a new-type in order to prevent accidental conversion from other BigInts.
17/// From/Into BigInt is missing by design.
18#[derive(Clone, PartialEq, Eq, Hash)]
19pub struct TokenAmount {
20    atto: BigInt,
21}
22
23// This type doesn't implement all the numeric traits (Num, Signed, etc),
24// opting for a minimal useful set. Others can be added if needed.
25impl TokenAmount {
26    /// The logical number of decimal places of a token unit.
27    pub const DECIMALS: usize = 18;
28
29    /// The logical precision of a token unit.
30    pub const PRECISION: u64 = 10u64.pow(Self::DECIMALS as u32);
31
32    /// Creates a token amount from a quantity of indivisible units  (10^-18 whole units).
33    pub fn from_atto(atto: impl Into<BigInt>) -> Self {
34        Self { atto: atto.into() }
35    }
36
37    /// Creates a token amount from nanoFIL.
38    pub fn from_nano(nano: impl Into<BigInt>) -> Self {
39        const NANO_PRECISION: u64 = 10u64.pow((TokenAmount::DECIMALS as u32) - 9);
40        Self {
41            atto: nano.into() * NANO_PRECISION,
42        }
43    }
44
45    /// Creates a token amount from a quantity of whole units (10^18 indivisible units).
46    pub fn from_whole(tokens: impl Into<BigInt>) -> Self {
47        Self::from_atto(tokens.into() * Self::PRECISION)
48    }
49
50    /// Returns the quantity of indivisible units.
51    pub fn atto(&self) -> &BigInt {
52        &self.atto
53    }
54
55    pub fn is_zero(&self) -> bool {
56        self.atto.is_zero()
57    }
58
59    pub fn is_positive(&self) -> bool {
60        self.atto.is_positive()
61    }
62
63    pub fn is_negative(&self) -> bool {
64        self.atto.is_negative()
65    }
66}
67
68impl Zero for TokenAmount {
69    #[inline]
70    fn zero() -> Self {
71        Self {
72            atto: BigInt::zero(),
73        }
74    }
75
76    #[inline]
77    fn is_zero(&self) -> bool {
78        self.atto.is_zero()
79    }
80}
81
82impl PartialOrd for TokenAmount {
83    #[inline]
84    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
85        Some(self.atto.cmp(&other.atto))
86    }
87}
88
89impl Ord for TokenAmount {
90    #[inline]
91    fn cmp(&self, other: &Self) -> Ordering {
92        self.atto.cmp(&other.atto)
93    }
94}
95
96impl Default for TokenAmount {
97    #[inline]
98    fn default() -> TokenAmount {
99        TokenAmount::zero()
100    }
101}
102
103impl fmt::Debug for TokenAmount {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(f, "TokenAmount({})", self)
106    }
107}
108
109/// Displays a token amount as a decimal in human units.
110/// To avoid any confusion over whether the value is in human-scale or indivisible units,
111/// the display always includes a decimal point.
112impl fmt::Display for TokenAmount {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        // Implementation based on the bigdecimal library.
115        let (q, r) = self.atto.div_rem(&BigInt::from(Self::PRECISION));
116        let before_decimal = q.abs().to_str_radix(10);
117        let after_decimal = if r.is_zero() {
118            "0".to_string()
119        } else {
120            let fraction_str = r.abs().to_str_radix(10);
121            let render = "0".repeat(Self::DECIMALS - fraction_str.len()) + fraction_str.as_str();
122            render.trim_end_matches('0').to_string()
123        };
124
125        // Alter precision after the decimal point
126        let after_decimal = if let Some(precision) = f.precision() {
127            let len = after_decimal.len();
128            if len < precision {
129                after_decimal + "0".repeat(precision - len).as_str()
130            } else {
131                after_decimal[0..precision].to_string()
132            }
133        } else {
134            after_decimal
135        };
136
137        // Always show the decimal point, even with ".0".
138        let complete_without_sign = before_decimal + "." + after_decimal.as_str();
139        // Padding works even though we have a decimal point.
140        f.pad_integral(!self.atto().is_negative(), "", &complete_without_sign)
141    }
142}
143
144impl Neg for TokenAmount {
145    type Output = TokenAmount;
146
147    #[inline]
148    fn neg(self) -> TokenAmount {
149        TokenAmount { atto: -self.atto }
150    }
151}
152
153impl Neg for &TokenAmount {
154    type Output = TokenAmount;
155
156    #[inline]
157    fn neg(self) -> TokenAmount {
158        TokenAmount {
159            atto: (&self.atto).neg(),
160        }
161    }
162}
163
164// Implements Add for all combinations of value/reference receiver and parameter.
165// (Pattern copied from BigInt multiplication).
166macro_rules! impl_add {
167    ($(impl<$($a:lifetime),*> Add<$Other:ty> for $Self:ty;)*) => {$(
168        impl<$($a),*> Add<$Other> for $Self {
169            type Output = TokenAmount;
170
171            #[inline]
172            fn add(self, other: $Other) -> TokenAmount {
173                // automatically match value/ref
174                let TokenAmount { atto: x, .. } = self;
175                let TokenAmount { atto: y, .. } = other;
176                TokenAmount {atto: x + y}
177            }
178        }
179    )*}
180}
181impl_add! {
182    impl<> Add<TokenAmount> for TokenAmount;
183    impl<'b> Add<&'b TokenAmount> for TokenAmount;
184    impl<'a> Add<TokenAmount> for &'a TokenAmount;
185    impl<'a, 'b> Add<&'b TokenAmount> for &'a TokenAmount;
186}
187
188impl AddAssign<TokenAmount> for TokenAmount {
189    #[inline]
190    fn add_assign(&mut self, other: TokenAmount) {
191        self.atto += &other.atto;
192    }
193}
194
195impl AddAssign<&TokenAmount> for TokenAmount {
196    #[inline]
197    fn add_assign(&mut self, other: &TokenAmount) {
198        self.atto += &other.atto;
199    }
200}
201
202// Implements Sub for all combinations of value/reference receiver and parameter.
203macro_rules! impl_sub {
204    ($(impl<$($a:lifetime),*> Sub<$Other:ty> for $Self:ty;)*) => {$(
205        impl<$($a),*> Sub<$Other> for $Self {
206            type Output = TokenAmount;
207
208            #[inline]
209            fn sub(self, other: $Other) -> TokenAmount {
210                // automatically match value/ref
211                let TokenAmount { atto: x, .. } = self;
212                let TokenAmount { atto: y, .. } = other;
213                TokenAmount {atto: x - y}
214            }
215        }
216    )*}
217}
218impl_sub! {
219    impl<> Sub<TokenAmount> for TokenAmount;
220    impl<'b> Sub<&'b TokenAmount> for TokenAmount;
221    impl<'a> Sub<TokenAmount> for &'a TokenAmount;
222    impl<'a, 'b> Sub<&'b TokenAmount> for &'a TokenAmount;
223}
224
225impl SubAssign<TokenAmount> for TokenAmount {
226    #[inline]
227    fn sub_assign(&mut self, other: TokenAmount) {
228        self.atto -= &other.atto;
229    }
230}
231
232impl SubAssign<&TokenAmount> for TokenAmount {
233    #[inline]
234    fn sub_assign(&mut self, other: &TokenAmount) {
235        self.atto -= &other.atto;
236    }
237}
238
239impl<T> Mul<T> for TokenAmount
240where
241    BigInt: Mul<T, Output = BigInt>,
242{
243    type Output = TokenAmount;
244
245    fn mul(self, rhs: T) -> Self::Output {
246        TokenAmount {
247            atto: self.atto * rhs,
248        }
249    }
250}
251
252impl<'a, T> Mul<T> for &'a TokenAmount
253where
254    &'a BigInt: Mul<T, Output = BigInt>,
255{
256    type Output = TokenAmount;
257
258    fn mul(self, rhs: T) -> Self::Output {
259        TokenAmount {
260            atto: &self.atto * rhs,
261        }
262    }
263}
264
265macro_rules! impl_mul {
266    ($(impl<$($a:lifetime),*> Mul<$Other:ty> for $Self:ty;)*) => {$(
267        impl<$($a),*> Mul<$Other> for $Self {
268            type Output = TokenAmount;
269
270            #[inline]
271            fn mul(self, other: $Other) -> TokenAmount {
272                other * self
273            }
274        }
275    )*}
276}
277
278macro_rules! impl_muls {
279    ($($t:ty,)*) => {$(
280        impl_mul! {
281            impl<> Mul<TokenAmount> for $t;
282            impl<'b> Mul<&'b TokenAmount> for $t;
283            impl<'a> Mul<TokenAmount> for &'a $t;
284            impl<'a, 'b> Mul<&'b TokenAmount> for &'a $t;
285        }
286    )*};
287}
288
289impl_muls! {
290    u8, u16, u32, u64, u128,
291    i8, i16, i32, i64, i128,
292    BigInt,
293}
294
295impl<T> MulAssign<T> for TokenAmount
296where
297    BigInt: MulAssign<T>,
298{
299    #[inline]
300    fn mul_assign(&mut self, other: T) {
301        self.atto *= other;
302    }
303}
304
305// Only a single div/rem method is implemented, rather than the full Div and Rem traits.
306// Division isn't a common operation with money-like units, and deserves to be treated carefully.
307impl TokenAmount {
308    #[inline]
309    pub fn div_rem(&self, other: impl Into<BigInt>) -> (TokenAmount, TokenAmount) {
310        let (q, r) = self.atto.div_rem(&other.into());
311        (TokenAmount { atto: q }, TokenAmount { atto: r })
312    }
313
314    #[inline]
315    pub fn div_ceil(&self, other: impl Into<BigInt>) -> TokenAmount {
316        TokenAmount {
317            atto: self.atto.div_ceil(&other.into()),
318        }
319    }
320
321    #[inline]
322    pub fn div_floor(&self, other: impl Into<BigInt>) -> TokenAmount {
323        TokenAmount {
324            atto: self.atto.div_floor(&other.into()),
325        }
326    }
327}
328
329impl Sum for TokenAmount {
330    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
331        Self::from_atto(iter.map(|t| t.atto).sum::<BigInt>())
332    }
333}
334
335impl<'a> Sum<&'a TokenAmount> for TokenAmount {
336    fn sum<I: Iterator<Item = &'a TokenAmount>>(iter: I) -> Self {
337        Self::from_atto(iter.map(|t| &t.atto).sum::<BigInt>())
338    }
339}
340
341// Serialisation
342
343impl Serialize for TokenAmount {
344    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
345    where
346        S: Serializer,
347    {
348        bigint_ser::serialize(&self.atto, serializer)
349    }
350}
351
352impl<'de> Deserialize<'de> for TokenAmount {
353    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
354    where
355        D: serde::Deserializer<'de>,
356    {
357        bigint_ser::deserialize(deserializer).map(|v| TokenAmount { atto: v })
358    }
359}
360
361#[cfg(test)]
362mod test {
363    use num_bigint::BigInt;
364    use num_traits::Zero;
365
366    use crate::TokenAmount;
367
368    fn whole(x: impl Into<BigInt>) -> TokenAmount {
369        TokenAmount::from_whole(x)
370    }
371
372    fn atto(x: impl Into<BigInt>) -> TokenAmount {
373        TokenAmount::from_atto(x.into())
374    }
375
376    #[test]
377    fn display_basic() {
378        fn basic(expected: &str, t: TokenAmount) {
379            assert_eq!(expected, format!("{}", t));
380        }
381
382        basic("0.0", TokenAmount::zero());
383        basic("0.000000000000000001", atto(1));
384        basic("0.000000000000001", atto(1000));
385        basic("0.1234", atto(123_400_000_000_000_000_u64));
386        basic("0.10101", atto(101_010_000_000_000_000_u64));
387        basic("1.0", whole(1));
388        basic("1.0", atto(1_000_000_000_000_000_000_u128));
389        basic("1.1", atto(1_100_000_000_000_000_000_u128));
390        basic("1.000000000000000001", atto(1_000_000_000_000_000_001_u128));
391        basic(
392            "1234.000000000123456789",
393            whole(1234) + atto(123_456_789_u64),
394        );
395    }
396
397    #[test]
398    fn display_precision() {
399        assert_eq!("0.0", format!("{:.1}", TokenAmount::zero()));
400        assert_eq!("0.000", format!("{:.3}", TokenAmount::zero()));
401        assert_eq!("0.000", format!("{:.3}", atto(1))); // Truncated.
402        assert_eq!(
403            "0.123",
404            format!("{:.3}", atto(123_456_789_000_000_000_u64)) // Truncated.
405        );
406        assert_eq!(
407            "0.123456789000",
408            format!("{:.12}", atto(123_456_789_000_000_000_u64))
409        );
410    }
411
412    #[test]
413    fn display_padding() {
414        assert_eq!("0.0", format!("{:01}", TokenAmount::zero()));
415        assert_eq!("0.0", format!("{:03}", TokenAmount::zero()));
416        assert_eq!("000.0", format!("{:05}", TokenAmount::zero()));
417        assert_eq!(
418            "0.123",
419            format!("{:01.3}", atto(123_456_789_000_000_000_u64))
420        );
421        assert_eq!(
422            "00.123",
423            format!("{:06.3}", atto(123_456_789_000_000_000_u64))
424        );
425    }
426
427    #[test]
428    fn display_negative() {
429        assert_eq!("-0.000001", format!("{:01}", -TokenAmount::from_nano(1000)));
430    }
431
432    #[test]
433    fn ops() {
434        // Test the basic operations are wired up correctly.
435        assert_eq!(atto(15), atto(10) + atto(5));
436        assert_eq!(atto(3), atto(10) - atto(7));
437        assert_eq!(atto(12), atto(3) * 4);
438        let (q, r) = atto(14).div_rem(4);
439        assert_eq!((atto(3), atto(2)), (q, r));
440
441        let mut a = atto(1);
442        a += atto(2);
443        assert_eq!(atto(3), a);
444        a *= 2;
445        assert_eq!(atto(6), a);
446        a -= atto(2);
447        assert_eq!(atto(4), a);
448    }
449
450    #[test]
451    fn nano_fil() {
452        assert_eq!(
453            TokenAmount::from_nano(1),
454            TokenAmount::from_whole(1).div_floor(10u64.pow(9))
455        )
456    }
457
458    #[test]
459    fn test_mul() {
460        let a = atto(2) * 3;
461        let b = 3 * atto(2);
462        assert_eq!(a, atto(6));
463        assert_eq!(a, b);
464    }
465
466    #[test]
467    fn test_sum() {
468        assert_eq!(
469            [1, 2, 3, 4].into_iter().map(atto).sum::<TokenAmount>(),
470            atto(10)
471        );
472    }
473}