ant_evm/
amount.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use crate::{EvmError, Result};
10
11pub use evmlib::common::Amount;
12use serde::{Deserialize, Serialize};
13use std::{
14    fmt::{self, Display, Formatter},
15    str::FromStr,
16};
17
18/// The conversion from AttoTokens to raw value
19const TOKEN_TO_RAW_POWER_OF_10_CONVERSION: u64 = 18;
20/// The conversion from AttoTokens to raw value
21const TOKEN_TO_RAW_CONVERSION: u64 = 1_000_000_000_000_000_000;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
24/// An amount in SNT Atto. 10^18 Nanos = 1 SNT.
25pub struct AttoTokens(Amount);
26
27impl AttoTokens {
28    /// Type safe representation of zero AttoTokens.
29    pub const fn zero() -> Self {
30        Self(Amount::ZERO)
31    }
32
33    /// Returns whether it's a representation of zero AttoTokens.
34    pub fn is_zero(&self) -> bool {
35        self.0.is_zero()
36    }
37
38    /// New value from an amount
39    pub fn from_atto(value: Amount) -> Self {
40        Self(value)
41    }
42
43    /// New value from a number of atto tokens.
44    pub fn from_u64(value: u64) -> Self {
45        Self(Amount::from(value))
46    }
47
48    /// New value from a number of atto tokens.
49    pub fn from_u128(value: u128) -> Self {
50        Self(Amount::from(value))
51    }
52
53    /// Total AttoTokens expressed in number of nano tokens.
54    pub fn as_atto(self) -> Amount {
55        self.0
56    }
57
58    /// Computes `self + rhs`, returning `None` if overflow occurred.
59    pub fn checked_add(self, rhs: AttoTokens) -> Option<AttoTokens> {
60        self.0.checked_add(rhs.0).map(Self::from_atto)
61    }
62
63    /// Computes `self - rhs`, returning `None` if overflow occurred.
64    pub fn checked_sub(self, rhs: AttoTokens) -> Option<AttoTokens> {
65        self.0.checked_sub(rhs.0).map(Self::from_atto)
66    }
67
68    /// Converts the Nanos into bytes
69    pub fn to_bytes(&self) -> Vec<u8> {
70        self.0.as_le_bytes().to_vec()
71    }
72}
73
74impl From<u64> for AttoTokens {
75    fn from(value: u64) -> Self {
76        Self(Amount::from(value))
77    }
78}
79
80impl From<Amount> for AttoTokens {
81    fn from(value: Amount) -> Self {
82        Self(value)
83    }
84}
85
86impl FromStr for AttoTokens {
87    type Err = EvmError;
88
89    fn from_str(value_str: &str) -> Result<Self> {
90        let mut itr = value_str.splitn(2, '.');
91        let converted_units = {
92            let units = itr
93                .next()
94                .and_then(|s| s.parse::<Amount>().ok())
95                .ok_or_else(|| {
96                    EvmError::FailedToParseAttoToken("Can't parse token units".to_string())
97                })?;
98
99            // Check if the units part is too large before multiplication
100            if units > Amount::from(u64::MAX) {
101                return Err(EvmError::ExcessiveValue);
102            }
103
104            units
105                .checked_mul(Amount::from(TOKEN_TO_RAW_CONVERSION))
106                .ok_or(EvmError::ExcessiveValue)?
107        };
108
109        let remainder = {
110            let remainder_str = itr.next().unwrap_or_default().trim_end_matches('0');
111
112            if remainder_str.is_empty() {
113                Amount::ZERO
114            } else {
115                let parsed_remainder = remainder_str.parse::<Amount>().map_err(|_| {
116                    EvmError::FailedToParseAttoToken("Can't parse token remainder".to_string())
117                })?;
118
119                let remainder_conversion = TOKEN_TO_RAW_POWER_OF_10_CONVERSION
120                    .checked_sub(remainder_str.len() as u64)
121                    .ok_or(EvmError::LossOfPrecision)?;
122                if remainder_conversion > 32 {
123                    return Err(EvmError::LossOfPrecision);
124                }
125                parsed_remainder * Amount::from(10).pow(Amount::from(remainder_conversion))
126            }
127        };
128
129        Ok(Self(converted_units + remainder))
130    }
131}
132
133impl Display for AttoTokens {
134    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
135        let unit = self.0 / Amount::from(TOKEN_TO_RAW_CONVERSION);
136        let remainder = self.0 % Amount::from(TOKEN_TO_RAW_CONVERSION);
137        write!(formatter, "{unit}.{remainder:018}")
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn from_str() -> Result<()> {
147        assert_eq!(AttoTokens::from_u64(0), AttoTokens::from_str("0")?);
148        assert_eq!(AttoTokens::from_u64(0), AttoTokens::from_str("0.")?);
149        assert_eq!(AttoTokens::from_u64(0), AttoTokens::from_str("0.0")?);
150        assert_eq!(
151            AttoTokens::from_u64(1),
152            AttoTokens::from_str("0.000000000000000001")?
153        );
154        assert_eq!(
155            AttoTokens::from_u64(1_000_000_000_000_000_000),
156            AttoTokens::from_str("1")?
157        );
158        assert_eq!(
159            AttoTokens::from_u64(1_000_000_000_000_000_000),
160            AttoTokens::from_str("1.")?
161        );
162        assert_eq!(
163            AttoTokens::from_u64(1_000_000_000_000_000_000),
164            AttoTokens::from_str("1.0")?
165        );
166        assert_eq!(
167            AttoTokens::from_u64(1_000_000_000_000_000_001),
168            AttoTokens::from_str("1.000000000000000001")?
169        );
170        assert_eq!(
171            AttoTokens::from_u64(1_100_000_000_000_000_000),
172            AttoTokens::from_str("1.1")?
173        );
174        assert_eq!(
175            AttoTokens::from_u64(1_100_000_000_000_000_001),
176            AttoTokens::from_str("1.100000000000000001")?
177        );
178        assert_eq!(
179            AttoTokens::from_u128(4_294_967_295_000_000_000_000_000u128),
180            AttoTokens::from_str("4294967.295")?
181        );
182        assert_eq!(
183            AttoTokens::from_u128(4_294_967_295_999_999_999_000_000u128),
184            AttoTokens::from_str("4294967.295999999999")?,
185        );
186        assert_eq!(
187            AttoTokens::from_u128(4_294_967_295_999_999_999_000_000u128),
188            AttoTokens::from_str("4294967.2959999999990000")?,
189        );
190        assert_eq!(
191            AttoTokens::from_u128(18_446_744_074_000_000_000_000_000_000u128),
192            AttoTokens::from_str("18446744074")?
193        );
194
195        assert_eq!(
196            Err(EvmError::FailedToParseAttoToken(
197                "Can't parse token units".to_string()
198            )),
199            AttoTokens::from_str("a")
200        );
201        assert_eq!(
202            Err(EvmError::FailedToParseAttoToken(
203                "Can't parse token remainder".to_string()
204            )),
205            AttoTokens::from_str("0.a")
206        );
207        assert_eq!(
208            Err(EvmError::FailedToParseAttoToken(
209                "Can't parse token remainder".to_string()
210            )),
211            AttoTokens::from_str("0.0.0")
212        );
213        assert_eq!(
214            Err(EvmError::LossOfPrecision),
215            AttoTokens::from_str("0.0000000000000000001")
216        );
217        assert_eq!(
218            Err(EvmError::ExcessiveValue),
219            AttoTokens::from_str("340282366920938463463374607431768211455")
220        );
221        Ok(())
222    }
223
224    #[test]
225    fn display() {
226        assert_eq!(
227            "0.000000000000000000",
228            format!("{}", AttoTokens::from_u64(0))
229        );
230        assert_eq!(
231            "0.000000000000000001",
232            format!("{}", AttoTokens::from_u64(1))
233        );
234        assert_eq!(
235            "0.000000000000000010",
236            format!("{}", AttoTokens::from_u64(10))
237        );
238        assert_eq!(
239            "1.000000000000000000",
240            format!("{}", AttoTokens::from_u64(1_000_000_000_000_000_000))
241        );
242        assert_eq!(
243            "1.000000000000000001",
244            format!("{}", AttoTokens::from_u64(1_000_000_000_000_000_001))
245        );
246        assert_eq!(
247            "4.294967295000000000",
248            format!("{}", AttoTokens::from_u64(4_294_967_295_000_000_000))
249        );
250    }
251
252    #[test]
253    fn checked_add_sub() {
254        assert_eq!(
255            Some(AttoTokens::from_u64(3)),
256            AttoTokens::from_u64(1).checked_add(AttoTokens::from_u64(2))
257        );
258        assert_eq!(
259            Some(AttoTokens::from_u128(u64::MAX as u128 + 1)),
260            AttoTokens::from_u64(u64::MAX).checked_add(AttoTokens::from_u64(1))
261        );
262        assert_eq!(
263            Some(AttoTokens::from_u128(u64::MAX as u128 * 2)),
264            AttoTokens::from_u64(u64::MAX).checked_add(AttoTokens::from_u64(u64::MAX))
265        );
266
267        assert_eq!(
268            Some(AttoTokens::from_u64(0)),
269            AttoTokens::from_u64(u64::MAX).checked_sub(AttoTokens::from_u64(u64::MAX))
270        );
271        assert_eq!(
272            None,
273            AttoTokens::from_u64(0).checked_sub(AttoTokens::from_u64(1))
274        );
275    }
276}