1use 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
18const TOKEN_TO_RAW_POWER_OF_10_CONVERSION: u64 = 18;
20const 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)]
24pub struct AttoTokens(Amount);
26
27impl AttoTokens {
28 pub const fn zero() -> Self {
30 Self(Amount::ZERO)
31 }
32
33 pub fn is_zero(&self) -> bool {
35 self.0.is_zero()
36 }
37
38 pub fn from_atto(value: Amount) -> Self {
40 Self(value)
41 }
42
43 pub fn from_u64(value: u64) -> Self {
45 Self(Amount::from(value))
46 }
47
48 pub fn from_u128(value: u128) -> Self {
50 Self(Amount::from(value))
51 }
52
53 pub fn as_atto(self) -> Amount {
55 self.0
56 }
57
58 pub fn checked_add(self, rhs: AttoTokens) -> Option<AttoTokens> {
60 self.0.checked_add(rhs.0).map(Self::from_atto)
61 }
62
63 pub fn checked_sub(self, rhs: AttoTokens) -> Option<AttoTokens> {
65 self.0.checked_sub(rhs.0).map(Self::from_atto)
66 }
67
68 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 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:032}")
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.00000000000000000000000000000000",
228 format!("{}", AttoTokens::from_u64(0))
229 );
230 assert_eq!(
231 "0.00000000000000000000000000000001",
232 format!("{}", AttoTokens::from_u64(1))
233 );
234 assert_eq!(
235 "0.00000000000000000000000000000010",
236 format!("{}", AttoTokens::from_u64(10))
237 );
238 assert_eq!(
239 "1.00000000000000000000000000000000",
240 format!("{}", AttoTokens::from_u64(1_000_000_000_000_000_000))
241 );
242 assert_eq!(
243 "1.00000000000000000000000000000001",
244 format!("{}", AttoTokens::from_u64(1_000_000_000_000_000_001))
245 );
246 assert_eq!(
247 "4.00000000000000294967295000000000",
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}