moksha_core/
amount.rs

1//! This module defines the `Amount` and `SplitAmount` structs, which are used for representing and splitting amounts in Cashu.
2//!
3//! The `Amount` struct represents an amount in satoshis, with a single `u64` field for the amount. The struct provides a `split` method that splits the amount into a `SplitAmount` struct.
4//!
5//! The `SplitAmount` struct represents a split amount, with a `Vec<u64>` field for the split amounts. The struct provides a `create_secrets` method that generates a vector of random strings for use as secrets in the split transaction. The struct also implements the `IntoIterator` trait, which allows it to be iterated over as a vector of `u64` values.
6//!
7//! Both the `Amount` and `SplitAmount` structs are serializable and deserializable using serde.
8use rand::distributions::Alphanumeric;
9use rand::Rng;
10
11#[derive(Debug, Clone)]
12pub struct Amount(pub u64);
13
14impl Amount {
15    pub fn split(&self) -> SplitAmount {
16        split_amount(self.0).into()
17    }
18}
19
20#[derive(Debug, Clone)]
21pub struct SplitAmount(Vec<u64>);
22
23impl From<Vec<u64>> for SplitAmount {
24    fn from(from: Vec<u64>) -> Self {
25        Self(from)
26    }
27}
28
29impl SplitAmount {
30    pub fn create_secrets(&self) -> Vec<String> {
31        (0..self.0.len())
32            .map(|_| generate_random_string())
33            .collect::<Vec<String>>()
34    }
35
36    pub fn len(&self) -> usize {
37        self.0.len()
38    }
39
40    pub fn is_empty(&self) -> bool {
41        self.0.is_empty()
42    }
43}
44
45impl From<u64> for Amount {
46    fn from(amount: u64) -> Self {
47        Self(amount)
48    }
49}
50
51impl IntoIterator for SplitAmount {
52    type Item = u64;
53    type IntoIter = std::vec::IntoIter<Self::Item>;
54
55    fn into_iter(self) -> Self::IntoIter {
56        self.0.into_iter()
57    }
58}
59
60/// split a decimal amount into a vector of powers of 2
61fn split_amount(amount: u64) -> Vec<u64> {
62    format!("{amount:b}")
63        .chars()
64        .rev()
65        .enumerate()
66        .filter_map(|(i, c)| {
67            if c == '1' {
68                return Some(2_u64.pow(i as u32));
69            }
70            None
71        })
72        .collect::<Vec<u64>>()
73}
74
75pub fn generate_random_string() -> String {
76    rand::thread_rng()
77        .sample_iter(&Alphanumeric)
78        .take(24)
79        .map(char::from)
80        .collect()
81}
82
83#[cfg(test)]
84mod tests {
85    use crate::amount::SplitAmount;
86    use pretty_assertions::assert_eq;
87
88    #[test]
89    fn test_split_amount() -> anyhow::Result<()> {
90        let bits = super::split_amount(13);
91        assert_eq!(bits, vec![1, 4, 8]);
92
93        let bits = super::split_amount(63);
94        assert_eq!(bits, vec![1, 2, 4, 8, 16, 32]);
95
96        let bits = super::split_amount(64);
97        assert_eq!(bits, vec![64]);
98        Ok(())
99    }
100
101    #[test]
102    fn test_create_secrets() {
103        let amounts = vec![1, 2, 3, 4, 5, 6, 7];
104        let secrets = SplitAmount::from(amounts.clone()).create_secrets();
105        assert!(secrets.len() == amounts.len());
106        assert_eq!(secrets.first().unwrap().len(), 24);
107    }
108}