1use std::cmp::Ordering;
23use std::fmt;
24use std::fmt::{Display, Formatter, Write};
25use std::iter::Sum;
26use std::num::{ParseIntError, TryFromIntError};
27use std::str::FromStr;
28
29use rgb::{FungibleState, RevealedValue};
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32use strict_encoding::{DefaultBasedStrictDumb, StrictDeserialize, StrictSerialize, VariantError};
33use strict_types::StrictVal;
34
35use crate::LIB_NAME_RGB_CONTRACT;
36
37pub const ENC_BASE32_NODIGIT: &[u8; 32] = b"abcdefghkmnABCDEFGHKMNPQRSTVWXYZ";
38fast32::make_base32_alpha!(BASE32_NODIGIT, DEC_BASE32_NODIGIT, ENC_BASE32_NODIGIT);
39
40#[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
41#[wrapper(Add, Sub, Mul, Div, Rem)]
42#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)]
43#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)]
44#[strict_type(lib = LIB_NAME_RGB_CONTRACT)]
45#[cfg_attr(
46 feature = "serde",
47 derive(Serialize, Deserialize),
48 serde(crate = "serde_crate", transparent)
49)]
50pub struct Amount(
51 #[from]
52 #[from(u32)]
53 #[from(u16)]
54 #[from(u8)]
55 u64,
56);
57
58impl StrictSerialize for Amount {}
59impl StrictDeserialize for Amount {}
60
61impl From<RevealedValue> for Amount {
62 fn from(value: RevealedValue) -> Self { Amount(value.as_u64()) }
63}
64
65impl From<FungibleState> for Amount {
66 fn from(state: FungibleState) -> Self { Amount(state.as_u64()) }
67}
68
69impl From<Amount> for FungibleState {
70 fn from(amount: Amount) -> Self { FungibleState::Bits64(amount.0) }
71}
72
73impl From<Amount> for RevealedValue {
74 fn from(amount: Amount) -> Self { RevealedValue::from(FungibleState::Bits64(amount.0)) }
75}
76
77impl Amount {
78 pub const ZERO: Self = Amount(0);
79
80 pub fn from_strict_val_unchecked(value: &StrictVal) -> Self {
81 value.unwrap_uint::<u64>().into()
82 }
83
84 pub fn with_precision(amount: u64, precision: impl Into<Precision>) -> Self {
85 precision.into().unchecked_convert(amount)
86 }
87
88 pub fn with_precision_checked(amount: u64, precision: impl Into<Precision>) -> Option<Self> {
89 precision.into().checked_convert(amount)
90 }
91
92 pub fn value(self) -> u64 { self.0 }
93
94 pub fn split(self, precision: impl Into<Precision>) -> (u64, u64) {
95 let precision = precision.into();
96 let int = self.floor(precision);
97 let fract = self.rem(precision);
98 (int, fract)
99 }
100
101 pub fn round(&self, precision: impl Into<Precision>) -> u64 {
102 let precision = precision.into();
103 let mul = precision.multiplier();
104 if self.0 == 0 {
105 return 0;
106 }
107 let inc = 2 * self.rem(precision) / mul;
108 self.0 / mul + inc
109 }
110
111 pub fn ceil(&self, precision: impl Into<Precision>) -> u64 {
112 let precision = precision.into();
113 if self.0 == 0 {
114 return 0;
115 }
116 let inc = if self.rem(precision) > 0 { 1 } else { 0 };
117 self.0 / precision.multiplier() + inc
118 }
119
120 pub fn floor(&self, precision: impl Into<Precision>) -> u64 {
121 if self.0 == 0 {
122 return 0;
123 }
124 self.0 / precision.into().multiplier()
125 }
126
127 pub fn rem(&self, precision: impl Into<Precision>) -> u64 {
128 self.0 % precision.into().multiplier()
129 }
130
131 pub fn saturating_add(&self, other: impl Into<Self>) -> Self {
132 self.0.saturating_add(other.into().0).into()
133 }
134 pub fn saturating_sub(&self, other: impl Into<Self>) -> Self {
135 self.0.saturating_sub(other.into().0).into()
136 }
137
138 pub fn saturating_add_assign(&mut self, other: impl Into<Self>) {
139 *self = self.0.saturating_add(other.into().0).into();
140 }
141 pub fn saturating_sub_assign(&mut self, other: impl Into<Self>) {
142 *self = self.0.saturating_sub(other.into().0).into();
143 }
144
145 #[must_use]
146 pub fn checked_add(&self, other: impl Into<Self>) -> Option<Self> {
147 self.0.checked_add(other.into().0).map(Self)
148 }
149 #[must_use]
150 pub fn checked_sub(&self, other: impl Into<Self>) -> Option<Self> {
151 self.0.checked_sub(other.into().0).map(Self)
152 }
153
154 #[must_use]
155 pub fn checked_add_assign(&mut self, other: impl Into<Self>) -> Option<()> {
156 *self = self.0.checked_add(other.into().0).map(Self)?;
157 Some(())
158 }
159 #[must_use]
160 pub fn checked_sub_assign(&mut self, other: impl Into<Self>) -> Option<()> {
161 *self = self.0.checked_sub(other.into().0).map(Self)?;
162 Some(())
163 }
164}
165
166impl Sum<u64> for Amount {
167 fn sum<I: Iterator<Item = u64>>(iter: I) -> Self {
168 iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value))
169 }
170}
171
172impl Sum for Amount {
173 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
174 iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value))
175 }
176}
177
178impl Display for Amount {
179 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
180 let bytes = self.0.to_le_bytes();
181 let pos = bytes.iter().rposition(|b| *b != 0).unwrap_or(0) + 1;
182 let s = BASE32_NODIGIT.encode(&bytes[..pos]);
183 f.write_str(&s)
184 }
185}
186
187impl FromStr for Amount {
188 type Err = fast32::DecodeError;
189
190 fn from_str(s: &str) -> Result<Self, Self::Err> {
191 let amount = BASE32_NODIGIT.decode_str(s)?;
192 let len = amount.len();
193 if len > 8 {
194 return Err(fast32::DecodeError::InvalidLength {
195 length: amount.len(),
196 });
197 }
198 let mut le = [0u8; 8];
199 le[..len].copy_from_slice(&amount);
200 Ok(Amount(u64::from_le_bytes(le)))
201 }
202}
203
204#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)]
205#[repr(u8)]
206#[derive(StrictType, StrictEncode, StrictDecode)]
207#[strict_type(lib = LIB_NAME_RGB_CONTRACT, tags = repr, into_u8, try_from_u8)]
208#[cfg_attr(
209 feature = "serde",
210 derive(Serialize, Deserialize),
211 serde(crate = "serde_crate", rename_all = "camelCase")
212)]
213pub enum Precision {
214 Indivisible = 0,
215 Deci = 1,
216 Centi = 2,
217 Milli = 3,
218 DeciMilli = 4,
219 CentiMilli = 5,
220 Micro = 6,
221 DeciMicro = 7,
222 #[default]
223 CentiMicro = 8,
224 Nano = 9,
225 DeciNano = 10,
226 CentiNano = 11,
227 Pico = 12,
228 DeciPico = 13,
229 CentiPico = 14,
230 Femto = 15,
231 DeciFemto = 16,
232 CentiFemto = 17,
233 Atto = 18,
234}
235impl DefaultBasedStrictDumb for Precision {}
236impl StrictSerialize for Precision {}
237impl StrictDeserialize for Precision {}
238
239impl Precision {
240 pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { value.unwrap_enum() }
241 pub const fn decimals(self) -> u8 { self as u8 }
242
243 pub const fn multiplier(self) -> u64 {
244 match self {
245 Precision::Indivisible => 1,
246 Precision::Deci => 10,
247 Precision::Centi => 100,
248 Precision::Milli => 1000,
249 Precision::DeciMilli => 10_000,
250 Precision::CentiMilli => 100_000,
251 Precision::Micro => 1_000_000,
252 Precision::DeciMicro => 10_000_000,
253 Precision::CentiMicro => 100_000_000,
254 Precision::Nano => 1_000_000_000,
255 Precision::DeciNano => 10_000_000_000,
256 Precision::CentiNano => 100_000_000_000,
257 Precision::Pico => 1_000_000_000_000,
258 Precision::DeciPico => 10_000_000_000_000,
259 Precision::CentiPico => 100_000_000_000_000,
260 Precision::Femto => 1_000_000_000_000_000,
261 Precision::DeciFemto => 10_000_000_000_000_000,
262 Precision::CentiFemto => 100_000_000_000_000_000,
263 Precision::Atto => 1_000_000_000_000_000_000,
264 }
265 }
266
267 pub fn unchecked_convert(self, amount: impl Into<u64>) -> Amount {
268 (amount.into() * self.multiplier()).into()
269 }
270
271 pub fn checked_convert(self, amount: impl Into<u64>) -> Option<Amount> {
272 amount
273 .into()
274 .checked_mul(self.multiplier())
275 .map(Amount::from)
276 }
277 pub fn saturating_convert(self, amount: impl Into<u64>) -> Amount {
278 amount.into().saturating_mul(self.multiplier()).into()
279 }
280}
281
282impl From<Precision> for u16 {
283 fn from(value: Precision) -> Self { value as u8 as u16 }
284}
285
286impl From<Precision> for u32 {
287 fn from(value: Precision) -> Self { value as u8 as u32 }
288}
289
290impl From<Precision> for u64 {
291 fn from(value: Precision) -> Self { value as u8 as u64 }
292}
293
294#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)]
295#[display("invalid precision")]
296pub struct PrecisionError;
297
298#[derive(Getters, Copy, Clone, Eq, PartialEq, Hash, Debug)]
299pub struct CoinAmount {
300 #[getter(as_copy)]
301 int: u64,
302 #[getter(as_copy)]
303 fract: u64,
304 #[getter(as_copy)]
305 precision: Precision,
306}
307
308impl CoinAmount {
309 pub fn new(amount: impl Into<Amount>, precision: impl Into<Precision>) -> Self {
310 let precision = precision.into();
311 let amount = amount.into();
312 let (int, fract) = amount.split(precision);
313 CoinAmount {
314 int,
315 fract,
316 precision,
317 }
318 }
319
320 pub fn with(
321 int: u64,
322 fract: u64,
323 precision: impl Into<Precision>,
324 ) -> Result<Self, PrecisionError> {
325 let precision = precision.into();
326 let pow = 10u64.pow(precision.decimals() as u32);
328 if fract >= pow {
330 return Err(PrecisionError);
331 }
332 Ok(CoinAmount {
333 int,
334 fract,
335 precision,
336 })
337 }
338
339 pub(crate) fn to_amount_unchecked(self) -> Amount {
340 let pow = 10u64.pow(self.precision.decimals() as u32);
342 self.int
344 .checked_mul(pow)
345 .expect("CoinAmount type garantees are broken")
346 .checked_add(self.fract)
347 .expect(
348 "integer has at least the same number of zeros in the lowest digits as much as \
349 decimals has digits at most, so overflow is not possible",
350 )
351 .into()
352 }
353}
354
355impl Display for CoinAmount {
356 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
357 if ![' ', '_'].contains(&f.fill()) {
358 panic!("disallowed fill character {} in coin amount formatting string", f.fill())
359 }
360 if f.precision().is_some() {
361 panic!("formatting precision must not be used for coin amounts")
362 }
363 let fill = &f.fill().to_string();
364 let to_chunks = |s: &str| -> String {
365 s.chars()
366 .rev()
367 .collect::<String>()
368 .as_bytes()
369 .chunks(3)
370 .map(<[u8]>::to_owned)
371 .map(|mut chunk| unsafe {
372 chunk.reverse();
373 String::from_utf8_unchecked(chunk)
374 })
375 .rev()
376 .collect::<Vec<_>>()
377 .join(fill)
378 };
379 let mut int = self.int.to_string();
380 if f.alternate() {
381 int = to_chunks(&int);
382 }
383 f.write_str(&int)?;
384 if self.fract > 0 || f.alternate() {
385 f.write_char('.')?;
386 let mut float = self.fract.to_string();
387 let len = float.len();
388 let decimals = self.precision.decimals() as usize;
389 match len.cmp(&decimals) {
390 Ordering::Less => {
391 float = format!("{:0>width$}{float}", "", width = decimals - len);
392 }
393 Ordering::Equal => {}
394 Ordering::Greater => panic!("float precision overflow for coin amount {self:?}"),
395 }
396 if f.alternate() {
397 float = to_chunks(&float);
398 } else {
399 float = float.trim_end_matches('0').to_string();
400 }
401 f.write_str(&float)?;
402 }
403 if !f.alternate() {
404 write!(f, "~{}", self.precision.decimals())?;
405 }
406 Ok(())
407 }
408}
409
410#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
411#[display(doc_comments)]
412pub enum AmountParseError {
413 InvalidInt(ParseIntError),
415 InvalidFract(ParseIntError),
417 InvalidPrecision(ParseIntError),
419
420 #[from(TryFromIntError)]
422 PrecisionOverflow,
423
424 #[from]
426 UnknownPrecision(VariantError<u8>),
427}
428
429impl FromStr for CoinAmount {
430 type Err = AmountParseError;
431
432 fn from_str(s: &str) -> Result<Self, Self::Err> {
433 let s = s.replace([' ', '_'], "");
434 let (int, remain) = s.split_once('.').unwrap_or_else(|| (&s, "0"));
435 let (fract, precision) = remain.split_once('~').unwrap_or((remain, ""));
436 let precision = if precision.is_empty() {
437 fract.len() as u64
438 } else {
439 precision
440 .parse()
441 .map_err(AmountParseError::InvalidPrecision)?
442 };
443 let int: u64 = int.parse().map_err(AmountParseError::InvalidInt)?;
444 let fract: u64 = fract.parse().map_err(AmountParseError::InvalidFract)?;
445 let precision = u8::try_from(precision)?;
446 Ok(CoinAmount {
447 int,
448 fract,
449 precision: Precision::try_from(precision)?,
450 })
451 }
452}
453
454#[cfg(test)]
455mod test {
456 use super::*;
457
458 #[test]
459 #[allow(clippy::inconsistent_digit_grouping)]
460 fn int_trailing_zeros() {
461 let amount = CoinAmount::new(10_000__43_608_195u64, Precision::default());
462 assert_eq!(amount.int(), 10_000);
463 assert_eq!(amount.fract(), 436_081_95);
464 assert_eq!(amount.precision(), Precision::default());
465 assert_eq!(format!("{amount}"), "10000.43608195~8");
466 assert_eq!(format!("{amount: >#}"), "10 000.43 608 195");
467 }
468
469 #[test]
470 #[allow(clippy::inconsistent_digit_grouping)]
471 fn sub_fraction() {
472 let amount = CoinAmount::new(10__00_008_195u64, Precision::default());
473 assert_eq!(amount.int(), 10);
474 assert_eq!(amount.fract(), 8195);
475 assert_eq!(amount.precision(), Precision::default());
476 assert_eq!(format!("{amount}"), "10.00008195~8");
477 assert_eq!(format!("{amount:#}"), "10.00 008 195");
478 }
479
480 #[test]
481 #[allow(clippy::inconsistent_digit_grouping)]
482 fn small_fraction() {
483 let amount = CoinAmount::new(10__00_000_500u64, Precision::default());
484 assert_eq!(amount.int(), 10);
485 assert_eq!(amount.fract(), 500);
486 assert_eq!(amount.precision(), Precision::default());
487 assert_eq!(format!("{amount}"), "10.000005~8");
488 assert_eq!(format!("{amount:_>#}"), "10.00_000_500");
489 }
490
491 #[test]
492 #[allow(clippy::inconsistent_digit_grouping)]
493 fn zero_fraction() {
494 let amount = CoinAmount::new(10__00_000_000u64, Precision::default());
495 assert_eq!(amount.int(), 10);
496 assert_eq!(amount.fract(), 0);
497 assert_eq!(amount.precision(), Precision::default());
498 assert_eq!(format!("{amount}"), "10~8");
499 assert_eq!(format!("{amount:_>#}"), "10.00_000_000");
500 }
501}