cashu/
amount.rs

1//! CDK Amount
2//!
3//! Is any unit and will be treated as the unit of the wallet
4
5use std::cmp::Ordering;
6use std::collections::HashMap;
7use std::fmt;
8use std::str::FromStr;
9
10use lightning::offers::offer::Offer;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14use crate::nuts::CurrencyUnit;
15use crate::Id;
16
17/// Amount Error
18#[derive(Debug, Error)]
19pub enum Error {
20    /// Split Values must be less then or equal to amount
21    #[error("Split Values must be less then or equal to amount")]
22    SplitValuesGreater,
23    /// Amount overflow
24    #[error("Amount Overflow")]
25    AmountOverflow,
26    /// Cannot convert units
27    #[error("Cannot convert units")]
28    CannotConvertUnits,
29    /// Invalid amount
30    #[error("Invalid Amount: {0}")]
31    InvalidAmount(String),
32    /// Amount undefined
33    #[error("Amount undefined")]
34    AmountUndefined,
35    /// Utf8 parse error
36    #[error(transparent)]
37    Utf8ParseError(#[from] std::string::FromUtf8Error),
38}
39
40/// Amount can be any unit
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
42#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
43#[serde(transparent)]
44pub struct Amount(u64);
45
46/// Fees and and amount type, it can be casted just as a reference to the inner amounts, or a single
47/// u64 which is the fee
48#[derive(Debug, Clone)]
49pub struct FeeAndAmounts {
50    fee: u64,
51    amounts: Vec<u64>,
52}
53
54impl From<(u64, Vec<u64>)> for FeeAndAmounts {
55    fn from(value: (u64, Vec<u64>)) -> Self {
56        Self {
57            fee: value.0,
58            amounts: value.1,
59        }
60    }
61}
62
63impl FeeAndAmounts {
64    /// Fees
65    #[inline(always)]
66    pub fn fee(&self) -> u64 {
67        self.fee
68    }
69
70    /// Amounts
71    #[inline(always)]
72    pub fn amounts(&self) -> &[u64] {
73        &self.amounts
74    }
75}
76
77/// Fees and Amounts for each Keyset
78pub type KeysetFeeAndAmounts = HashMap<Id, FeeAndAmounts>;
79
80impl FromStr for Amount {
81    type Err = Error;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        let value = s
85            .parse::<u64>()
86            .map_err(|_| Error::InvalidAmount(s.to_owned()))?;
87        Ok(Amount(value))
88    }
89}
90
91impl Amount {
92    /// Amount zero
93    pub const ZERO: Amount = Amount(0);
94
95    /// Amount one
96    pub const ONE: Amount = Amount(1);
97
98    /// Split into parts that are powers of two
99    pub fn split(&self, fee_and_amounts: &FeeAndAmounts) -> Vec<Self> {
100        fee_and_amounts
101            .amounts
102            .iter()
103            .rev()
104            .fold((Vec::new(), self.0), |(mut acc, total), &amount| {
105                if total >= amount {
106                    acc.push(Self::from(amount));
107                }
108                (acc, total % amount)
109            })
110            .0
111    }
112
113    /// Split into parts that are powers of two by target
114    pub fn split_targeted(
115        &self,
116        target: &SplitTarget,
117        fee_and_amounts: &FeeAndAmounts,
118    ) -> Result<Vec<Self>, Error> {
119        let mut parts = match target {
120            SplitTarget::None => self.split(fee_and_amounts),
121            SplitTarget::Value(amount) => {
122                if self.le(amount) {
123                    return Ok(self.split(fee_and_amounts));
124                }
125
126                let mut parts_total = Amount::ZERO;
127                let mut parts = Vec::new();
128
129                // The powers of two that are need to create target value
130                let parts_of_value = amount.split(fee_and_amounts);
131
132                while parts_total.lt(self) {
133                    for part in parts_of_value.iter().copied() {
134                        if (part + parts_total).le(self) {
135                            parts.push(part);
136                        } else {
137                            let amount_left = *self - parts_total;
138                            parts.extend(amount_left.split(fee_and_amounts));
139                        }
140
141                        parts_total = Amount::try_sum(parts.clone().iter().copied())?;
142
143                        if parts_total.eq(self) {
144                            break;
145                        }
146                    }
147                }
148
149                parts
150            }
151            SplitTarget::Values(values) => {
152                let values_total: Amount = Amount::try_sum(values.clone().into_iter())?;
153
154                match self.cmp(&values_total) {
155                    Ordering::Equal => values.clone(),
156                    Ordering::Less => {
157                        return Err(Error::SplitValuesGreater);
158                    }
159                    Ordering::Greater => {
160                        let extra = *self - values_total;
161                        let mut extra_amount = extra.split(fee_and_amounts);
162                        let mut values = values.clone();
163
164                        values.append(&mut extra_amount);
165                        values
166                    }
167                }
168            }
169        };
170
171        parts.sort();
172        Ok(parts)
173    }
174
175    /// Splits amount into powers of two while accounting for the swap fee
176    pub fn split_with_fee(&self, fee_and_amounts: &FeeAndAmounts) -> Result<Vec<Self>, Error> {
177        let without_fee_amounts = self.split(fee_and_amounts);
178        let total_fee_ppk = fee_and_amounts
179            .fee
180            .checked_mul(without_fee_amounts.len() as u64)
181            .ok_or(Error::AmountOverflow)?;
182        let fee = Amount::from(total_fee_ppk.div_ceil(1000));
183        let new_amount = self.checked_add(fee).ok_or(Error::AmountOverflow)?;
184
185        let split = new_amount.split(fee_and_amounts);
186        let split_fee_ppk = (split.len() as u64)
187            .checked_mul(fee_and_amounts.fee)
188            .ok_or(Error::AmountOverflow)?;
189        let split_fee = Amount::from(split_fee_ppk.div_ceil(1000));
190
191        if let Some(net_amount) = new_amount.checked_sub(split_fee) {
192            if net_amount >= *self {
193                return Ok(split);
194            }
195        }
196        self.checked_add(Amount::ONE)
197            .ok_or(Error::AmountOverflow)?
198            .split_with_fee(fee_and_amounts)
199    }
200
201    /// Checked addition for Amount. Returns None if overflow occurs.
202    pub fn checked_add(self, other: Amount) -> Option<Amount> {
203        self.0.checked_add(other.0).map(Amount)
204    }
205
206    /// Checked subtraction for Amount. Returns None if overflow occurs.
207    pub fn checked_sub(self, other: Amount) -> Option<Amount> {
208        self.0.checked_sub(other.0).map(Amount)
209    }
210
211    /// Checked multiplication for Amount. Returns None if overflow occurs.
212    pub fn checked_mul(self, other: Amount) -> Option<Amount> {
213        self.0.checked_mul(other.0).map(Amount)
214    }
215
216    /// Checked division for Amount. Returns None if overflow occurs.
217    pub fn checked_div(self, other: Amount) -> Option<Amount> {
218        self.0.checked_div(other.0).map(Amount)
219    }
220
221    /// Try sum to check for overflow
222    pub fn try_sum<I>(iter: I) -> Result<Self, Error>
223    where
224        I: IntoIterator<Item = Self>,
225    {
226        iter.into_iter().try_fold(Amount::ZERO, |acc, x| {
227            acc.checked_add(x).ok_or(Error::AmountOverflow)
228        })
229    }
230
231    /// Convert unit
232    pub fn convert_unit(
233        &self,
234        current_unit: &CurrencyUnit,
235        target_unit: &CurrencyUnit,
236    ) -> Result<Amount, Error> {
237        to_unit(self.0, current_unit, target_unit)
238    }
239    ///
240    /// Convert to u64
241    pub fn to_u64(self) -> u64 {
242        self.0
243    }
244
245    /// Convert to i64
246    pub fn to_i64(self) -> Option<i64> {
247        if self.0 <= i64::MAX as u64 {
248            Some(self.0 as i64)
249        } else {
250            None
251        }
252    }
253
254    /// Create from i64, returning None if negative
255    pub fn from_i64(value: i64) -> Option<Self> {
256        if value >= 0 {
257            Some(Amount(value as u64))
258        } else {
259            None
260        }
261    }
262}
263
264impl Default for Amount {
265    fn default() -> Self {
266        Amount::ZERO
267    }
268}
269
270impl Default for &Amount {
271    fn default() -> Self {
272        &Amount::ZERO
273    }
274}
275
276impl fmt::Display for Amount {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        if let Some(width) = f.width() {
279            write!(f, "{:width$}", self.0, width = width)
280        } else {
281            write!(f, "{}", self.0)
282        }
283    }
284}
285
286impl From<u64> for Amount {
287    fn from(value: u64) -> Self {
288        Self(value)
289    }
290}
291
292impl From<&u64> for Amount {
293    fn from(value: &u64) -> Self {
294        Self(*value)
295    }
296}
297
298impl From<Amount> for u64 {
299    fn from(value: Amount) -> Self {
300        value.0
301    }
302}
303
304impl AsRef<u64> for Amount {
305    fn as_ref(&self) -> &u64 {
306        &self.0
307    }
308}
309
310impl std::ops::Add for Amount {
311    type Output = Amount;
312
313    fn add(self, rhs: Amount) -> Self::Output {
314        self.checked_add(rhs)
315            .expect("Addition overflow: the sum of the amounts exceeds the maximum value")
316    }
317}
318
319impl std::ops::AddAssign for Amount {
320    fn add_assign(&mut self, rhs: Self) {
321        *self = self
322            .checked_add(rhs)
323            .expect("AddAssign overflow: the sum of the amounts exceeds the maximum value");
324    }
325}
326
327impl std::ops::Sub for Amount {
328    type Output = Amount;
329
330    fn sub(self, rhs: Amount) -> Self::Output {
331        self.checked_sub(rhs)
332            .expect("Subtraction underflow: cannot subtract a larger amount from a smaller amount")
333    }
334}
335
336impl std::ops::SubAssign for Amount {
337    fn sub_assign(&mut self, other: Self) {
338        *self = self
339            .checked_sub(other)
340            .expect("SubAssign underflow: cannot subtract a larger amount from a smaller amount");
341    }
342}
343
344impl std::ops::Mul for Amount {
345    type Output = Self;
346
347    fn mul(self, other: Self) -> Self::Output {
348        self.checked_mul(other)
349            .expect("Multiplication overflow: the product of the amounts exceeds the maximum value")
350    }
351}
352
353impl std::ops::Div for Amount {
354    type Output = Self;
355
356    fn div(self, other: Self) -> Self::Output {
357        self.checked_div(other)
358            .expect("Division error: cannot divide by zero or overflow occurred")
359    }
360}
361
362/// Convert offer to amount in unit
363pub fn amount_for_offer(offer: &Offer, unit: &CurrencyUnit) -> Result<Amount, Error> {
364    let offer_amount = offer.amount().ok_or(Error::AmountUndefined)?;
365
366    let (amount, currency) = match offer_amount {
367        lightning::offers::offer::Amount::Bitcoin { amount_msats } => {
368            (amount_msats, CurrencyUnit::Msat)
369        }
370        lightning::offers::offer::Amount::Currency {
371            iso4217_code,
372            amount,
373        } => (
374            amount,
375            CurrencyUnit::from_str(&String::from_utf8(iso4217_code.to_vec())?)
376                .map_err(|_| Error::CannotConvertUnits)?,
377        ),
378    };
379
380    to_unit(amount, &currency, unit).map_err(|_err| Error::CannotConvertUnits)
381}
382
383/// Kinds of targeting that are supported
384#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
385pub enum SplitTarget {
386    /// Default target; least amount of proofs
387    #[default]
388    None,
389    /// Target amount for wallet to have most proofs that add up to value
390    Value(Amount),
391    /// Specific amounts to split into **MUST** equal amount being split
392    Values(Vec<Amount>),
393}
394
395/// Msats in sat
396pub const MSAT_IN_SAT: u64 = 1000;
397
398/// Helper function to convert units
399pub fn to_unit<T>(
400    amount: T,
401    current_unit: &CurrencyUnit,
402    target_unit: &CurrencyUnit,
403) -> Result<Amount, Error>
404where
405    T: Into<u64>,
406{
407    let amount = amount.into();
408    match (current_unit, target_unit) {
409        (CurrencyUnit::Sat, CurrencyUnit::Sat) => Ok(amount.into()),
410        (CurrencyUnit::Msat, CurrencyUnit::Msat) => Ok(amount.into()),
411        (CurrencyUnit::Sat, CurrencyUnit::Msat) => amount
412            .checked_mul(MSAT_IN_SAT)
413            .map(Amount::from)
414            .ok_or(Error::AmountOverflow),
415        (CurrencyUnit::Msat, CurrencyUnit::Sat) => Ok((amount / MSAT_IN_SAT).into()),
416        (CurrencyUnit::Usd, CurrencyUnit::Usd) => Ok(amount.into()),
417        (CurrencyUnit::Eur, CurrencyUnit::Eur) => Ok(amount.into()),
418        _ => Err(Error::CannotConvertUnits),
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    #[test]
427    fn test_split_amount() {
428        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
429
430        assert_eq!(
431            Amount::from(1).split(&fee_and_amounts),
432            vec![Amount::from(1)]
433        );
434        assert_eq!(
435            Amount::from(2).split(&fee_and_amounts),
436            vec![Amount::from(2)]
437        );
438        assert_eq!(
439            Amount::from(3).split(&fee_and_amounts),
440            vec![Amount::from(2), Amount::from(1)]
441        );
442        let amounts: Vec<Amount> = [8, 2, 1].iter().map(|a| Amount::from(*a)).collect();
443        assert_eq!(Amount::from(11).split(&fee_and_amounts), amounts);
444        let amounts: Vec<Amount> = [128, 64, 32, 16, 8, 4, 2, 1]
445            .iter()
446            .map(|a| Amount::from(*a))
447            .collect();
448        assert_eq!(Amount::from(255).split(&fee_and_amounts), amounts);
449    }
450
451    #[test]
452    fn test_split_target_amount() {
453        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
454        let amount = Amount(65);
455
456        let split = amount
457            .split_targeted(&SplitTarget::Value(Amount(32)), &fee_and_amounts)
458            .unwrap();
459        assert_eq!(vec![Amount(1), Amount(32), Amount(32)], split);
460
461        let amount = Amount(150);
462
463        let split = amount
464            .split_targeted(&SplitTarget::Value(Amount::from(50)), &fee_and_amounts)
465            .unwrap();
466        assert_eq!(
467            vec![
468                Amount(2),
469                Amount(2),
470                Amount(2),
471                Amount(16),
472                Amount(16),
473                Amount(16),
474                Amount(32),
475                Amount(32),
476                Amount(32)
477            ],
478            split
479        );
480
481        let amount = Amount::from(63);
482
483        let split = amount
484            .split_targeted(&SplitTarget::Value(Amount::from(32)), &fee_and_amounts)
485            .unwrap();
486        assert_eq!(
487            vec![
488                Amount(1),
489                Amount(2),
490                Amount(4),
491                Amount(8),
492                Amount(16),
493                Amount(32)
494            ],
495            split
496        );
497    }
498
499    #[test]
500    fn test_split_with_fee() {
501        let fee_and_amounts = (1, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
502        let amount = Amount(2);
503
504        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
505        assert_eq!(split, vec![Amount(2), Amount(1)]);
506
507        let amount = Amount(3);
508
509        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
510        assert_eq!(split, vec![Amount(4)]);
511
512        let amount = Amount(3);
513        let fee_and_amounts = (1000, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
514
515        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
516        // With fee_ppk=1000 (100%), amount 3 requires proofs totaling at least 5
517        // to cover both the amount (3) and fees (~2 for 2 proofs)
518        assert_eq!(split, vec![Amount(4), Amount(1)]);
519    }
520
521    #[test]
522    fn test_split_with_fee_reported_issue() {
523        let fee_and_amounts = (100, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
524        // Test the reported issue: mint 600, send 300 with fee_ppk=100
525        let amount = Amount(300);
526
527        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
528
529        // Calculate the total fee for the split
530        let total_fee_ppk = (split.len() as u64) * fee_and_amounts.fee;
531        let total_fee = Amount::from(total_fee_ppk.div_ceil(1000));
532
533        // The split should cover the amount plus fees
534        let split_total = Amount::try_sum(split.iter().copied()).unwrap();
535        assert!(
536            split_total >= amount + total_fee,
537            "Split total {} should be >= amount {} + fee {}",
538            split_total,
539            amount,
540            total_fee
541        );
542    }
543
544    #[test]
545    fn test_split_with_fee_edge_cases() {
546        // Test various amounts with fee_ppk=100
547        let test_cases = vec![
548            (Amount(1), 100),
549            (Amount(10), 100),
550            (Amount(50), 100),
551            (Amount(100), 100),
552            (Amount(200), 100),
553            (Amount(300), 100),
554            (Amount(500), 100),
555            (Amount(600), 100),
556            (Amount(1000), 100),
557            (Amount(1337), 100),
558            (Amount(5000), 100),
559        ];
560
561        for (amount, fee_ppk) in test_cases {
562            let fee_and_amounts =
563                (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
564            let result = amount.split_with_fee(&fee_and_amounts);
565            assert!(
566                result.is_ok(),
567                "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
568                amount,
569                fee_ppk,
570                result.err()
571            );
572
573            let split = result.unwrap();
574
575            // Verify the split covers the required amount
576            let split_total = Amount::try_sum(split.iter().copied()).unwrap();
577            let fee_for_split = (split.len() as u64) * fee_ppk;
578            let total_fee = Amount::from(fee_for_split.div_ceil(1000));
579
580            // The net amount after fees should be at least the original amount
581            let net_amount = split_total.checked_sub(total_fee);
582            assert!(
583                net_amount.is_some(),
584                "Net amount calculation failed for amount {} with fee_ppk {}",
585                amount,
586                fee_ppk
587            );
588            assert!(
589                net_amount.unwrap() >= amount,
590                "Net amount {} is less than required {} for amount {} with fee_ppk {}",
591                net_amount.unwrap(),
592                amount,
593                amount,
594                fee_ppk
595            );
596        }
597    }
598
599    #[test]
600    fn test_split_with_fee_high_fees() {
601        // Test with very high fees
602        let test_cases = vec![
603            (Amount(10), 500),  // 50% fee
604            (Amount(10), 1000), // 100% fee
605            (Amount(10), 2000), // 200% fee
606            (Amount(100), 500),
607            (Amount(100), 1000),
608            (Amount(100), 2000),
609        ];
610
611        for (amount, fee_ppk) in test_cases {
612            let fee_and_amounts =
613                (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
614            let result = amount.split_with_fee(&fee_and_amounts);
615            assert!(
616                result.is_ok(),
617                "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
618                amount,
619                fee_ppk,
620                result.err()
621            );
622
623            let split = result.unwrap();
624            let split_total = Amount::try_sum(split.iter().copied()).unwrap();
625
626            // With high fees, we just need to ensure we can cover the amount
627            assert!(
628                split_total > amount,
629                "Split total {} should be greater than amount {} for fee_ppk {}",
630                split_total,
631                amount,
632                fee_ppk
633            );
634        }
635    }
636
637    #[test]
638    fn test_split_with_fee_recursion_limit() {
639        // Test that the recursion doesn't go infinite
640        // This tests the edge case where the method keeps adding Amount::ONE
641        let amount = Amount(1);
642        let fee_ppk = 10000;
643        let fee_and_amounts = (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
644
645        let result = amount.split_with_fee(&fee_and_amounts);
646        assert!(
647            result.is_ok(),
648            "split_with_fee should handle extreme fees without infinite recursion"
649        );
650    }
651
652    #[test]
653    fn test_split_values() {
654        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
655        let amount = Amount(10);
656
657        let target = vec![Amount(2), Amount(4), Amount(4)];
658
659        let split_target = SplitTarget::Values(target.clone());
660
661        let values = amount
662            .split_targeted(&split_target, &fee_and_amounts)
663            .unwrap();
664
665        assert_eq!(target, values);
666
667        let target = vec![Amount(2), Amount(4), Amount(4)];
668
669        let split_target = SplitTarget::Values(vec![Amount(2), Amount(4)]);
670
671        let values = amount
672            .split_targeted(&split_target, &fee_and_amounts)
673            .unwrap();
674
675        assert_eq!(target, values);
676
677        let split_target = SplitTarget::Values(vec![Amount(2), Amount(10)]);
678
679        let values = amount.split_targeted(&split_target, &fee_and_amounts);
680
681        assert!(values.is_err())
682    }
683
684    #[test]
685    #[should_panic]
686    fn test_amount_addition() {
687        let amount_one: Amount = u64::MAX.into();
688        let amount_two: Amount = 1.into();
689
690        let amounts = vec![amount_one, amount_two];
691
692        let _total: Amount = Amount::try_sum(amounts).unwrap();
693    }
694
695    #[test]
696    fn test_try_amount_addition() {
697        let amount_one: Amount = u64::MAX.into();
698        let amount_two: Amount = 1.into();
699
700        let amounts = vec![amount_one, amount_two];
701
702        let total = Amount::try_sum(amounts);
703
704        assert!(total.is_err());
705        let amount_one: Amount = 10000.into();
706        let amount_two: Amount = 1.into();
707
708        let amounts = vec![amount_one, amount_two];
709        let total = Amount::try_sum(amounts).unwrap();
710
711        assert_eq!(total, 10001.into());
712    }
713
714    #[test]
715    fn test_amount_to_unit() {
716        let amount = Amount::from(1000);
717        let current_unit = CurrencyUnit::Sat;
718        let target_unit = CurrencyUnit::Msat;
719
720        let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
721
722        assert_eq!(converted, 1000000.into());
723
724        let amount = Amount::from(1000);
725        let current_unit = CurrencyUnit::Msat;
726        let target_unit = CurrencyUnit::Sat;
727
728        let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
729
730        assert_eq!(converted, 1.into());
731
732        let amount = Amount::from(1);
733        let current_unit = CurrencyUnit::Usd;
734        let target_unit = CurrencyUnit::Usd;
735
736        let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
737
738        assert_eq!(converted, 1.into());
739
740        let amount = Amount::from(1);
741        let current_unit = CurrencyUnit::Eur;
742        let target_unit = CurrencyUnit::Eur;
743
744        let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
745
746        assert_eq!(converted, 1.into());
747
748        let amount = Amount::from(1);
749        let current_unit = CurrencyUnit::Sat;
750        let target_unit = CurrencyUnit::Eur;
751
752        let converted = to_unit(amount, &current_unit, &target_unit);
753
754        assert!(converted.is_err());
755    }
756
757    /// Tests that the subtraction operator correctly computes the difference between amounts.
758    ///
759    /// This test verifies that the `-` operator for Amount produces the expected result.
760    /// It's particularly important because the subtraction operation is used in critical
761    /// code paths like `split_targeted`, where incorrect subtraction could lead to
762    /// infinite loops or wrong calculations.
763    ///
764    /// Mutant testing: Catches mutations that replace the subtraction implementation
765    /// with `Default::default()` (returning Amount::ZERO), which would cause infinite
766    /// loops in `split_targeted` at line 138 where `*self - parts_total` is computed.
767    #[test]
768    fn test_amount_sub_operator() {
769        let amount1 = Amount::from(100);
770        let amount2 = Amount::from(30);
771
772        let result = amount1 - amount2;
773        assert_eq!(result, Amount::from(70));
774
775        let amount1 = Amount::from(1000);
776        let amount2 = Amount::from(1);
777
778        let result = amount1 - amount2;
779        assert_eq!(result, Amount::from(999));
780
781        let amount1 = Amount::from(255);
782        let amount2 = Amount::from(128);
783
784        let result = amount1 - amount2;
785        assert_eq!(result, Amount::from(127));
786    }
787
788    /// Tests that the subtraction operator panics when attempting to subtract
789    /// a larger amount from a smaller amount (underflow).
790    ///
791    /// This test verifies the safety property that Amount subtraction will panic
792    /// rather than wrap around on underflow. This is critical for preventing
793    /// bugs where negative amounts could be interpreted as very large positive amounts.
794    ///
795    /// Mutant testing: Catches mutations that remove the panic behavior or return
796    /// default values instead of properly handling underflow.
797    #[test]
798    #[should_panic(expected = "Subtraction underflow")]
799    fn test_amount_sub_underflow() {
800        let amount1 = Amount::from(30);
801        let amount2 = Amount::from(100);
802
803        let _result = amount1 - amount2;
804    }
805
806    /// Tests that checked_add correctly computes the sum and returns the actual value.
807    ///
808    /// This is critical because checked_add is used in recursive functions like
809    /// split_with_fee. If it returns Some(Amount::ZERO) instead of the actual sum,
810    /// the recursion would never terminate.
811    ///
812    /// Mutant testing: Kills mutations that replace the implementation with
813    /// `Some(Default::default())`, which would cause infinite loops in split_with_fee
814    /// at line 198 where it recursively calls itself with incremented amounts.
815    #[test]
816    fn test_checked_add_returns_correct_value() {
817        let amount1 = Amount::from(100);
818        let amount2 = Amount::from(50);
819
820        let result = amount1.checked_add(amount2);
821        assert_eq!(result, Some(Amount::from(150)));
822
823        let amount1 = Amount::from(1);
824        let amount2 = Amount::from(1);
825
826        let result = amount1.checked_add(amount2);
827        assert_eq!(result, Some(Amount::from(2)));
828        assert_ne!(result, Some(Amount::ZERO));
829
830        let amount1 = Amount::from(1000);
831        let amount2 = Amount::from(337);
832
833        let result = amount1.checked_add(amount2);
834        assert_eq!(result, Some(Amount::from(1337)));
835    }
836
837    /// Tests that checked_add returns None on overflow.
838    #[test]
839    fn test_checked_add_overflow() {
840        let amount1 = Amount::from(u64::MAX);
841        let amount2 = Amount::from(1);
842
843        let result = amount1.checked_add(amount2);
844        assert!(result.is_none());
845    }
846
847    /// Tests that try_sum correctly computes the total sum of amounts.
848    ///
849    /// This is critical because try_sum is used in loops like split_targeted at line 130
850    /// to track progress. If it returns Ok(Amount::ZERO) instead of the actual sum,
851    /// the loop condition `parts_total.eq(self)` would never be true, causing an infinite loop.
852    ///
853    /// Mutant testing: Kills mutations that replace the implementation with
854    /// `Ok(Default::default())`, which would cause infinite loops.
855    #[test]
856    fn test_try_sum_returns_correct_value() {
857        let amounts = vec![Amount::from(10), Amount::from(20), Amount::from(30)];
858        let result = Amount::try_sum(amounts).unwrap();
859        assert_eq!(result, Amount::from(60));
860        assert_ne!(result, Amount::ZERO);
861
862        let amounts = vec![Amount::from(1), Amount::from(1), Amount::from(1)];
863        let result = Amount::try_sum(amounts).unwrap();
864        assert_eq!(result, Amount::from(3));
865
866        let amounts = vec![Amount::from(100)];
867        let result = Amount::try_sum(amounts).unwrap();
868        assert_eq!(result, Amount::from(100));
869
870        let empty: Vec<Amount> = vec![];
871        let result = Amount::try_sum(empty).unwrap();
872        assert_eq!(result, Amount::ZERO);
873    }
874
875    /// Tests that try_sum returns error on overflow.
876    #[test]
877    fn test_try_sum_overflow() {
878        let amounts = vec![Amount::from(u64::MAX), Amount::from(1)];
879        let result = Amount::try_sum(amounts);
880        assert!(result.is_err());
881    }
882
883    /// Tests that split returns a non-empty vec with actual values, not defaults.
884    ///
885    /// The split function is used in split_targeted's while loop (line 122).
886    /// If split returns an empty vec or vec with Amount::ZERO when it shouldn't,
887    /// the loop that extends parts with split results would never make progress,
888    /// causing an infinite loop.
889    ///
890    /// Mutant testing: Kills mutations that replace split with `vec![]` or
891    /// `vec![Default::default()]` which would cause infinite loops.
892    #[test]
893    fn test_split_returns_correct_values() {
894        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
895
896        let amount = Amount::from(11);
897        let result = amount.split(&fee_and_amounts);
898        assert!(!result.is_empty());
899        assert_eq!(Amount::try_sum(result.iter().copied()).unwrap(), amount);
900
901        let amount = Amount::from(255);
902        let result = amount.split(&fee_and_amounts);
903        assert!(!result.is_empty());
904        assert_eq!(Amount::try_sum(result.iter().copied()).unwrap(), amount);
905
906        let amount = Amount::from(7);
907        let result = amount.split(&fee_and_amounts);
908        assert_eq!(
909            result,
910            vec![Amount::from(4), Amount::from(2), Amount::from(1)]
911        );
912        for r in &result {
913            assert_ne!(*r, Amount::ZERO);
914        }
915    }
916
917    /// Tests that the modulo operation in split works correctly.
918    ///
919    /// At line 108, split uses modulo (%) to compute the remainder.
920    /// If this is mutated to division (/), it would produce wrong results
921    /// that could cause infinite loops in code that depends on split.
922    ///
923    /// Mutant testing: Kills mutations that replace `%` with `/`.
924    #[test]
925    fn test_split_modulo_operation() {
926        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
927
928        let amount = Amount::from(15);
929        let result = amount.split(&fee_and_amounts);
930
931        assert_eq!(
932            result,
933            vec![
934                Amount::from(8),
935                Amount::from(4),
936                Amount::from(2),
937                Amount::from(1)
938            ]
939        );
940
941        let total = Amount::try_sum(result.iter().copied()).unwrap();
942        assert_eq!(total, amount);
943    }
944
945    /// Tests that From<u64> correctly converts values to Amount.
946    ///
947    /// This conversion is used throughout the codebase including in loops and split operations.
948    /// If it returns Default::default() (Amount::ZERO) instead of the actual value,
949    /// it can cause infinite loops where amounts are being accumulated or compared.
950    ///
951    /// Mutant testing: Kills mutations that replace From<u64> with `Default::default()`.
952    #[test]
953    fn test_from_u64_returns_correct_value() {
954        let amount = Amount::from(100u64);
955        assert_eq!(amount, Amount(100));
956        assert_ne!(amount, Amount::ZERO);
957
958        let amount = Amount::from(1u64);
959        assert_eq!(amount, Amount(1));
960        assert_eq!(amount, Amount::ONE);
961
962        let amount = Amount::from(1337u64);
963        assert_eq!(amount.to_u64(), 1337);
964    }
965
966    /// Tests that checked_mul returns the correct product value.
967    ///
968    /// This is critical for any multiplication operations. If it returns None
969    /// or Some(Amount::ZERO) instead of the actual product, calculations will be wrong.
970    ///
971    /// Mutant testing: Kills mutations that replace checked_mul with None or Some(Default::default()).
972    #[test]
973    fn test_checked_mul_returns_correct_value() {
974        let amount1 = Amount::from(10);
975        let amount2 = Amount::from(5);
976        let result = amount1.checked_mul(amount2);
977        assert_eq!(result, Some(Amount::from(50)));
978        assert_ne!(result, None);
979        assert_ne!(result, Some(Amount::ZERO));
980
981        let amount1 = Amount::from(100);
982        let amount2 = Amount::from(20);
983        let result = amount1.checked_mul(amount2);
984        assert_eq!(result, Some(Amount::from(2000)));
985        assert_ne!(result, Some(Amount::ZERO));
986
987        let amount1 = Amount::from(7);
988        let amount2 = Amount::from(13);
989        let result = amount1.checked_mul(amount2);
990        assert_eq!(result, Some(Amount::from(91)));
991
992        // Test multiplication by zero
993        let amount1 = Amount::from(100);
994        let amount2 = Amount::ZERO;
995        let result = amount1.checked_mul(amount2);
996        assert_eq!(result, Some(Amount::ZERO));
997
998        // Test multiplication by one
999        let amount1 = Amount::from(42);
1000        let amount2 = Amount::ONE;
1001        let result = amount1.checked_mul(amount2);
1002        assert_eq!(result, Some(Amount::from(42)));
1003
1004        // Test overflow
1005        let amount1 = Amount::from(u64::MAX);
1006        let amount2 = Amount::from(2);
1007        let result = amount1.checked_mul(amount2);
1008        assert!(result.is_none());
1009    }
1010
1011    /// Tests that checked_div returns the correct quotient value.
1012    ///
1013    /// This is critical for division operations. If it returns None or
1014    /// Some(Amount::ZERO) instead of the actual quotient, calculations will be wrong.
1015    ///
1016    /// Mutant testing: Kills mutations that replace checked_div with None or Some(Default::default()).
1017    #[test]
1018    fn test_checked_div_returns_correct_value() {
1019        let amount1 = Amount::from(100);
1020        let amount2 = Amount::from(5);
1021        let result = amount1.checked_div(amount2);
1022        assert_eq!(result, Some(Amount::from(20)));
1023        assert_ne!(result, None);
1024        assert_ne!(result, Some(Amount::ZERO));
1025
1026        let amount1 = Amount::from(1000);
1027        let amount2 = Amount::from(10);
1028        let result = amount1.checked_div(amount2);
1029        assert_eq!(result, Some(Amount::from(100)));
1030        assert_ne!(result, Some(Amount::ZERO));
1031
1032        let amount1 = Amount::from(91);
1033        let amount2 = Amount::from(7);
1034        let result = amount1.checked_div(amount2);
1035        assert_eq!(result, Some(Amount::from(13)));
1036
1037        // Test division by one
1038        let amount1 = Amount::from(42);
1039        let amount2 = Amount::ONE;
1040        let result = amount1.checked_div(amount2);
1041        assert_eq!(result, Some(Amount::from(42)));
1042
1043        // Test integer division (truncation)
1044        let amount1 = Amount::from(10);
1045        let amount2 = Amount::from(3);
1046        let result = amount1.checked_div(amount2);
1047        assert_eq!(result, Some(Amount::from(3)));
1048
1049        // Test division by zero
1050        let amount1 = Amount::from(100);
1051        let amount2 = Amount::ZERO;
1052        let result = amount1.checked_div(amount2);
1053        assert!(result.is_none());
1054    }
1055
1056    /// Tests that Amount::convert_unit returns the correct converted value.
1057    ///
1058    /// This is critical for unit conversions. If it returns Ok(Amount::ZERO)
1059    /// instead of the actual converted value, all conversions will be wrong.
1060    ///
1061    /// Mutant testing: Kills mutations that replace convert_unit with Ok(Default::default()).
1062    #[test]
1063    fn test_convert_unit_returns_correct_value() {
1064        let amount = Amount::from(1000);
1065        let result = amount
1066            .convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Msat)
1067            .unwrap();
1068        assert_eq!(result, Amount::from(1_000_000));
1069        assert_ne!(result, Amount::ZERO);
1070
1071        let amount = Amount::from(5000);
1072        let result = amount
1073            .convert_unit(&CurrencyUnit::Msat, &CurrencyUnit::Sat)
1074            .unwrap();
1075        assert_eq!(result, Amount::from(5));
1076        assert_ne!(result, Amount::ZERO);
1077
1078        let amount = Amount::from(123);
1079        let result = amount
1080            .convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Sat)
1081            .unwrap();
1082        assert_eq!(result, Amount::from(123));
1083
1084        let amount = Amount::from(456);
1085        let result = amount
1086            .convert_unit(&CurrencyUnit::Usd, &CurrencyUnit::Usd)
1087            .unwrap();
1088        assert_eq!(result, Amount::from(456));
1089
1090        let amount = Amount::from(789);
1091        let result = amount
1092            .convert_unit(&CurrencyUnit::Eur, &CurrencyUnit::Eur)
1093            .unwrap();
1094        assert_eq!(result, Amount::from(789));
1095
1096        // Test invalid conversion
1097        let amount = Amount::from(100);
1098        let result = amount.convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Eur);
1099        assert!(result.is_err());
1100    }
1101}