Skip to main content

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    /// Cannot perform operation on amounts with different units
30    #[error("Unit mismatch: cannot operate on {0} and {1}")]
31    UnitMismatch(CurrencyUnit, CurrencyUnit),
32    /// Invalid amount
33    #[error("Invalid Amount: {0}")]
34    InvalidAmount(String),
35    /// Amount undefined
36    #[error("Amount undefined")]
37    AmountUndefined,
38    /// Utf8 parse error
39    #[error(transparent)]
40    Utf8ParseError(#[from] std::string::FromUtf8Error),
41    /// Cannot represent amount with available denominations
42    #[error("Cannot represent amount {0} with available denominations (got {1})")]
43    CannotSplitAmount(u64, u64),
44}
45
46/// Amount can be any unit
47///
48/// Note: `PartialOrd` is implemented manually for `Amount<CurrencyUnit>` to return `None`
49/// when comparing amounts with different units. `Ord` is only implemented for `Amount<()>`.
50#[derive(Debug, Hash, PartialEq, Eq)]
51#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
52pub struct Amount<U = ()> {
53    value: u64,
54    unit: U,
55}
56
57/// Fees and and amount type, it can be casted just as a reference to the inner amounts, or a single
58/// u64 which is the fee
59#[derive(Debug, Clone)]
60pub struct FeeAndAmounts {
61    fee: u64,
62    amounts: Vec<u64>,
63}
64
65impl From<(u64, Vec<u64>)> for FeeAndAmounts {
66    fn from(value: (u64, Vec<u64>)) -> Self {
67        Self {
68            fee: value.0,
69            amounts: value.1,
70        }
71    }
72}
73
74impl FeeAndAmounts {
75    /// Fees
76    #[inline(always)]
77    pub fn fee(&self) -> u64 {
78        self.fee
79    }
80
81    /// Amounts
82    #[inline(always)]
83    pub fn amounts(&self) -> &[u64] {
84        &self.amounts
85    }
86}
87
88/// Fees and Amounts for each Keyset
89pub type KeysetFeeAndAmounts = HashMap<Id, FeeAndAmounts>;
90
91// Copy and Clone implementations for Amount<()>
92impl Copy for Amount<()> {}
93
94impl Clone for Amount<()> {
95    fn clone(&self) -> Self {
96        *self
97    }
98}
99
100// Clone implementation for Amount<CurrencyUnit>
101impl Clone for Amount<CurrencyUnit> {
102    fn clone(&self) -> Self {
103        Self {
104            value: self.value,
105            unit: self.unit.clone(),
106        }
107    }
108}
109
110// PartialOrd implementation for Amount<()> - always comparable
111impl PartialOrd for Amount<()> {
112    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
113        Some(self.cmp(other))
114    }
115}
116
117// Ord implementation for Amount<()> - total ordering on value
118impl Ord for Amount<()> {
119    fn cmp(&self, other: &Self) -> Ordering {
120        self.value.cmp(&other.value)
121    }
122}
123
124// PartialOrd implementation for Amount<CurrencyUnit> - returns None if units differ
125impl PartialOrd for Amount<CurrencyUnit> {
126    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
127        if self.unit != other.unit {
128            // Different units are not comparable
129            None
130        } else {
131            Some(self.value.cmp(&other.value))
132        }
133    }
134}
135
136// Note: We intentionally do NOT implement Ord for Amount<CurrencyUnit>
137// because amounts with different units cannot have a total ordering.
138
139// Serialization - both variants serialize as just the u64 value
140impl<U> Serialize for Amount<U> {
141    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142    where
143        S: serde::Serializer,
144    {
145        self.value.serialize(serializer)
146    }
147}
148
149impl<'de> Deserialize<'de> for Amount<()> {
150    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
151    where
152        D: serde::Deserializer<'de>,
153    {
154        let value = u64::deserialize(deserializer)?;
155        Ok(Amount { value, unit: () })
156    }
157}
158
159impl FromStr for Amount<()> {
160    type Err = Error;
161
162    fn from_str(s: &str) -> Result<Self, Self::Err> {
163        let value = s
164            .parse::<u64>()
165            .map_err(|_| Error::InvalidAmount(s.to_owned()))?;
166        Ok(Amount { value, unit: () })
167    }
168}
169
170impl Amount<()> {
171    /// Amount zero
172    pub const ZERO: Amount<()> = Amount { value: 0, unit: () };
173
174    /// Amount one
175    pub const ONE: Amount<()> = Amount { value: 1, unit: () };
176
177    /// Convert an untyped amount to a typed one by adding a unit
178    ///
179    /// This is used at the boundary between protocol and application layers.
180    /// Protocol types use `Amount<()>` (no unit), while application types
181    /// use `Amount<CurrencyUnit>` (with unit from keyset).
182    ///
183    /// # Example
184    /// ```
185    /// # use cashu::{Amount, nuts::CurrencyUnit};
186    /// let untyped = Amount::from(100);
187    /// let typed = untyped.with_unit(CurrencyUnit::Sat);
188    /// assert_eq!(typed.value(), 100);
189    /// assert_eq!(typed.unit(), &CurrencyUnit::Sat);
190    /// ```
191    pub fn with_unit(self, unit: CurrencyUnit) -> Amount<CurrencyUnit> {
192        Amount {
193            value: self.value,
194            unit,
195        }
196    }
197
198    /// Split into parts that are powers of two
199    ///
200    /// Returns an error if the amount cannot be fully represented
201    /// with the available denominations.
202    pub fn split(&self, fee_and_amounts: &FeeAndAmounts) -> Result<Vec<Self>, Error> {
203        let parts: Vec<Self> = fee_and_amounts
204            .amounts
205            .iter()
206            .rev()
207            .fold((Vec::new(), self.value), |(mut acc, total), &amount| {
208                if total >= amount {
209                    acc.push(Self::from(amount));
210                }
211                (acc, total % amount)
212            })
213            .0;
214
215        let sum: u64 = parts.iter().map(|a| a.value).sum();
216        if sum != self.value {
217            return Err(Error::CannotSplitAmount(self.value, sum));
218        }
219
220        Ok(parts)
221    }
222
223    /// Split into parts that are powers of two by target
224    pub fn split_targeted(
225        &self,
226        target: &SplitTarget,
227        fee_and_amounts: &FeeAndAmounts,
228    ) -> Result<Vec<Self>, Error> {
229        let mut parts = match target {
230            SplitTarget::None => self.split(fee_and_amounts)?,
231            SplitTarget::Value(amount) => {
232                if self.le(amount) {
233                    return self.split(fee_and_amounts);
234                }
235
236                let mut parts_total = Amount::ZERO;
237                let mut parts = Vec::new();
238
239                // The powers of two that are need to create target value
240                let parts_of_value = amount.split(fee_and_amounts)?;
241
242                while parts_total.lt(self) {
243                    for part in parts_of_value.iter().copied() {
244                        if (part.checked_add(parts_total).ok_or(Error::AmountOverflow)?).le(self) {
245                            parts.push(part);
246                        } else {
247                            let amount_left =
248                                self.checked_sub(parts_total).ok_or(Error::AmountOverflow)?;
249                            parts.extend(amount_left.split(fee_and_amounts)?);
250                        }
251
252                        parts_total = Amount::try_sum(parts.clone().iter().copied())?;
253
254                        if parts_total.eq(self) {
255                            break;
256                        }
257                    }
258                }
259
260                parts
261            }
262            SplitTarget::Values(values) => {
263                let values_total: Amount = Amount::try_sum(values.clone().into_iter())?;
264
265                match self.cmp(&values_total) {
266                    Ordering::Equal => values.clone(),
267                    Ordering::Less => {
268                        return Err(Error::SplitValuesGreater);
269                    }
270                    Ordering::Greater => {
271                        let extra = self
272                            .checked_sub(values_total)
273                            .ok_or(Error::AmountOverflow)?;
274                        let mut extra_amount = extra.split(fee_and_amounts)?;
275                        let mut values = values.clone();
276
277                        values.append(&mut extra_amount);
278                        values
279                    }
280                }
281            }
282        };
283
284        parts.sort();
285        Ok(parts)
286    }
287
288    /// Splits amount into powers of two while accounting for the swap fee
289    pub fn split_with_fee(&self, fee_and_amounts: &FeeAndAmounts) -> Result<Vec<Self>, Error> {
290        let without_fee_amounts = self.split(fee_and_amounts)?;
291        let total_fee_ppk = fee_and_amounts
292            .fee
293            .checked_mul(without_fee_amounts.len() as u64)
294            .ok_or(Error::AmountOverflow)?;
295        let fee = Amount::from(total_fee_ppk.div_ceil(1000));
296        let new_amount = self.checked_add(fee).ok_or(Error::AmountOverflow)?;
297
298        let split = new_amount.split(fee_and_amounts)?;
299        let split_fee_ppk = (split.len() as u64)
300            .checked_mul(fee_and_amounts.fee)
301            .ok_or(Error::AmountOverflow)?;
302        let split_fee = Amount::from(split_fee_ppk.div_ceil(1000));
303
304        if let Some(net_amount) = new_amount.checked_sub(split_fee) {
305            if net_amount >= *self {
306                return Ok(split);
307            }
308        }
309        self.checked_add(Amount::ONE)
310            .ok_or(Error::AmountOverflow)?
311            .split_with_fee(fee_and_amounts)
312    }
313
314    /// Checked addition for Amount. Returns None if overflow occurs.
315    pub fn checked_add(self, other: Amount<()>) -> Option<Amount<()>> {
316        self.value
317            .checked_add(other.value)
318            .map(|v| Amount { value: v, unit: () })
319    }
320
321    /// Checked subtraction for Amount. Returns None if overflow occurs.
322    pub fn checked_sub(self, other: Amount<()>) -> Option<Amount<()>> {
323        self.value
324            .checked_sub(other.value)
325            .map(|v| Amount { value: v, unit: () })
326    }
327
328    /// Checked multiplication for Amount. Returns None if overflow occurs.
329    pub fn checked_mul(self, other: Amount<()>) -> Option<Amount<()>> {
330        self.value
331            .checked_mul(other.value)
332            .map(|v| Amount { value: v, unit: () })
333    }
334
335    /// Checked division for Amount. Returns None if overflow occurs.
336    pub fn checked_div(self, other: Amount<()>) -> Option<Amount<()>> {
337        self.value
338            .checked_div(other.value)
339            .map(|v| Amount { value: v, unit: () })
340    }
341
342    /// Subtracts `other` from `self`, returning zero if the result would be negative.
343    pub fn saturating_sub(self, other: Self) -> Self {
344        if other > self {
345            Self::ZERO
346        } else {
347            self - other
348        }
349    }
350
351    /// Try sum to check for overflow
352    pub fn try_sum<I>(iter: I) -> Result<Self, Error>
353    where
354        I: IntoIterator<Item = Self>,
355    {
356        iter.into_iter().try_fold(Amount::ZERO, |acc, x| {
357            acc.checked_add(x).ok_or(Error::AmountOverflow)
358        })
359    }
360
361    /// Convert unit
362    pub fn convert_unit(
363        &self,
364        current_unit: &CurrencyUnit,
365        target_unit: &CurrencyUnit,
366    ) -> Result<Amount<()>, Error> {
367        Amount::new(self.value, current_unit.clone())
368            .convert_to(target_unit)
369            .map(Into::into)
370    }
371
372    /// Convert to u64
373    pub fn to_u64(self) -> u64 {
374        self.value
375    }
376
377    /// Convert to i64
378    pub fn to_i64(self) -> Option<i64> {
379        if self.value <= i64::MAX as u64 {
380            Some(self.value as i64)
381        } else {
382            None
383        }
384    }
385
386    /// Create from i64, returning None if negative
387    pub fn from_i64(value: i64) -> Option<Self> {
388        if value >= 0 {
389            Some(Amount {
390                value: value as u64,
391                unit: (),
392            })
393        } else {
394            None
395        }
396    }
397}
398
399impl Default for Amount<()> {
400    fn default() -> Self {
401        Amount::ZERO
402    }
403}
404
405impl Default for &Amount<()> {
406    fn default() -> Self {
407        &Amount::ZERO
408    }
409}
410
411impl Amount<CurrencyUnit> {
412    /// Create a new Amount with an explicit unit
413    ///
414    /// This is the primary constructor for typed amounts. It works with all
415    /// CurrencyUnit variants including Custom.
416    ///
417    /// # Example
418    /// ```
419    /// # use cashu::{Amount, nuts::CurrencyUnit};
420    /// let sat_amount = Amount::new(1000, CurrencyUnit::Sat);
421    /// let custom = Amount::new(50, CurrencyUnit::Custom("BTC".into()));
422    /// ```
423    pub fn new(value: u64, unit: CurrencyUnit) -> Self {
424        Self { value, unit }
425    }
426
427    /// Get the numeric value
428    ///
429    /// # Example
430    /// ```
431    /// # use cashu::{Amount, nuts::CurrencyUnit};
432    /// let amount = Amount::new(1000, CurrencyUnit::Sat);
433    /// assert_eq!(amount.value(), 1000);
434    /// ```
435    pub fn value(&self) -> u64 {
436        self.value
437    }
438
439    /// Convert to u64
440    pub fn to_u64(self) -> u64 {
441        self.value
442    }
443
444    /// Convert to i64
445    pub fn to_i64(self) -> Option<i64> {
446        if self.value <= i64::MAX as u64 {
447            Some(self.value as i64)
448        } else {
449            None
450        }
451    }
452
453    /// Get a reference to the unit
454    ///
455    /// # Example
456    /// ```
457    /// # use cashu::{Amount, nuts::CurrencyUnit};
458    /// let amount = Amount::new(1000, CurrencyUnit::Sat);
459    /// assert_eq!(amount.unit(), &CurrencyUnit::Sat);
460    /// ```
461    pub fn unit(&self) -> &CurrencyUnit {
462        &self.unit
463    }
464
465    /// Consume self and return both value and unit
466    ///
467    /// # Example
468    /// ```
469    /// # use cashu::{Amount, nuts::CurrencyUnit};
470    /// let amount = Amount::new(1000, CurrencyUnit::Sat);
471    /// let (value, unit) = amount.into_parts();
472    /// assert_eq!(value, 1000);
473    /// assert_eq!(unit, CurrencyUnit::Sat);
474    /// ```
475    pub fn into_parts(self) -> (u64, CurrencyUnit) {
476        (self.value, self.unit)
477    }
478
479    /// Checked addition with unit verification
480    ///
481    /// Returns an error if units don't match or if overflow occurs.
482    ///
483    /// # Example
484    /// ```
485    /// # use cashu::{Amount, nuts::CurrencyUnit};
486    /// let a = Amount::new(100, CurrencyUnit::Sat);
487    /// let b = Amount::new(50, CurrencyUnit::Sat);
488    /// let sum = a.checked_add(&b).unwrap();
489    /// assert_eq!(sum.value(), 150);
490    ///
491    /// // Different units cause an error
492    /// let c = Amount::new(100, CurrencyUnit::Msat);
493    /// assert!(a.checked_add(&c).is_err());
494    /// ```
495    pub fn checked_add(&self, other: &Self) -> Result<Self, Error> {
496        if self.unit != other.unit {
497            return Err(Error::UnitMismatch(self.unit.clone(), other.unit.clone()));
498        }
499        self.value
500            .checked_add(other.value)
501            .map(|v| Amount::new(v, self.unit.clone()))
502            .ok_or(Error::AmountOverflow)
503    }
504
505    /// Checked subtraction with unit verification
506    ///
507    /// Returns an error if units don't match or if underflow occurs.
508    ///
509    /// # Example
510    /// ```
511    /// # use cashu::{Amount, nuts::CurrencyUnit};
512    /// let a = Amount::new(100, CurrencyUnit::Sat);
513    /// let b = Amount::new(30, CurrencyUnit::Sat);
514    /// let diff = a.checked_sub(&b).unwrap();
515    /// assert_eq!(diff.value(), 70);
516    /// ```
517    pub fn checked_sub(&self, other: &Self) -> Result<Self, Error> {
518        if self.unit != other.unit {
519            return Err(Error::UnitMismatch(self.unit.clone(), other.unit.clone()));
520        }
521        self.value
522            .checked_sub(other.value)
523            .map(|v| Amount::new(v, self.unit.clone()))
524            .ok_or(Error::AmountOverflow)
525    }
526
527    /// Convert to a different unit
528    ///
529    /// # Example
530    /// ```
531    /// # use cashu::{Amount, nuts::CurrencyUnit};
532    /// let sat = Amount::new(1000, CurrencyUnit::Sat);
533    /// let msat = sat.convert_to(&CurrencyUnit::Msat).unwrap();
534    /// assert_eq!(msat.value(), 1_000_000);
535    /// assert_eq!(msat.unit(), &CurrencyUnit::Msat);
536    /// ```
537    pub fn convert_to(&self, target_unit: &CurrencyUnit) -> Result<Self, Error> {
538        if &self.unit == target_unit {
539            return Ok(self.clone());
540        }
541
542        let converted_value = match (&self.unit, target_unit) {
543            (CurrencyUnit::Sat, CurrencyUnit::Msat) => self
544                .value
545                .checked_mul(MSAT_IN_SAT)
546                .ok_or(Error::AmountOverflow)?,
547            (CurrencyUnit::Msat, CurrencyUnit::Sat) => self.value / MSAT_IN_SAT,
548            _ => return Err(Error::CannotConvertUnits),
549        };
550
551        Ok(Amount::new(converted_value, target_unit.clone()))
552    }
553
554    /// Returns a string representation that includes the unit
555    pub fn display_with_unit(&self) -> String {
556        format!("{} {}", self.value, self.unit)
557    }
558}
559
560impl<U> fmt::Display for Amount<U> {
561    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562        if let Some(width) = f.width() {
563            write!(f, "{:width$}", self.value, width = width)
564        } else {
565            write!(f, "{}", self.value)
566        }
567    }
568}
569
570impl From<u64> for Amount<()> {
571    fn from(value: u64) -> Self {
572        Amount { value, unit: () }
573    }
574}
575
576impl From<&u64> for Amount<()> {
577    fn from(value: &u64) -> Self {
578        Amount {
579            value: *value,
580            unit: (),
581        }
582    }
583}
584
585impl From<Amount<()>> for u64 {
586    fn from(value: Amount<()>) -> Self {
587        value.value
588    }
589}
590
591impl From<Amount<CurrencyUnit>> for Amount<()> {
592    fn from(value: Amount<CurrencyUnit>) -> Self {
593        Amount {
594            value: value.value,
595            unit: (),
596        }
597    }
598}
599
600impl AsRef<u64> for Amount<()> {
601    fn as_ref(&self) -> &u64 {
602        &self.value
603    }
604}
605
606impl std::ops::Add for Amount<()> {
607    type Output = Amount<()>;
608
609    fn add(self, rhs: Amount<()>) -> Self::Output {
610        self.checked_add(rhs)
611            .expect("Addition overflow: the sum of the amounts exceeds the maximum value")
612    }
613}
614
615impl std::ops::AddAssign for Amount<()> {
616    fn add_assign(&mut self, rhs: Self) {
617        *self = self
618            .checked_add(rhs)
619            .expect("AddAssign overflow: the sum of the amounts exceeds the maximum value");
620    }
621}
622
623impl std::ops::Sub for Amount<()> {
624    type Output = Amount<()>;
625
626    fn sub(self, rhs: Amount<()>) -> Self::Output {
627        self.checked_sub(rhs)
628            .expect("Subtraction underflow: cannot subtract a larger amount from a smaller amount")
629    }
630}
631
632impl std::ops::SubAssign for Amount<()> {
633    fn sub_assign(&mut self, other: Self) {
634        *self = self
635            .checked_sub(other)
636            .expect("SubAssign underflow: cannot subtract a larger amount from a smaller amount");
637    }
638}
639
640impl std::ops::Mul for Amount<()> {
641    type Output = Self;
642
643    fn mul(self, other: Self) -> Self::Output {
644        self.checked_mul(other)
645            .expect("Multiplication overflow: the product of the amounts exceeds the maximum value")
646    }
647}
648
649impl std::ops::Div for Amount<()> {
650    type Output = Self;
651
652    fn div(self, other: Self) -> Self::Output {
653        self.checked_div(other)
654            .expect("Division error: cannot divide by zero or overflow occurred")
655    }
656}
657
658/// Convert offer to amount in unit
659pub fn amount_for_offer(offer: &Offer, unit: &CurrencyUnit) -> Result<Amount, Error> {
660    let offer_amount = offer.amount().ok_or(Error::AmountUndefined)?;
661
662    let (amount, currency) = match offer_amount {
663        lightning::offers::offer::Amount::Bitcoin { amount_msats } => {
664            (amount_msats, CurrencyUnit::Msat)
665        }
666        lightning::offers::offer::Amount::Currency {
667            iso4217_code,
668            amount,
669        } => (
670            amount,
671            CurrencyUnit::from_str(&String::from_utf8(iso4217_code.as_bytes().to_vec())?)
672                .map_err(|_| Error::CannotConvertUnits)?,
673        ),
674    };
675
676    Amount::new(amount, currency)
677        .convert_to(unit)
678        .map(Into::into)
679        .map_err(|_err| Error::CannotConvertUnits)
680}
681
682/// Kinds of targeting that are supported
683#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
684pub enum SplitTarget {
685    /// Default target; least amount of proofs
686    #[default]
687    None,
688    /// Target amount for wallet to have most proofs that add up to value
689    Value(Amount),
690    /// Specific amounts to split into **MUST** equal amount being split
691    Values(Vec<Amount>),
692}
693
694/// Msats in sat
695pub const MSAT_IN_SAT: u64 = 1000;
696
697#[cfg(test)]
698mod tests {
699    use super::*;
700
701    #[test]
702    fn test_split_amount() {
703        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
704
705        assert_eq!(
706            Amount::from(1).split(&fee_and_amounts).unwrap(),
707            vec![Amount::from(1)]
708        );
709        assert_eq!(
710            Amount::from(2).split(&fee_and_amounts).unwrap(),
711            vec![Amount::from(2)]
712        );
713        assert_eq!(
714            Amount::from(3).split(&fee_and_amounts).unwrap(),
715            vec![Amount::from(2), Amount::from(1)]
716        );
717        let amounts: Vec<Amount> = [8, 2, 1].iter().map(|a| Amount::from(*a)).collect();
718        assert_eq!(Amount::from(11).split(&fee_and_amounts).unwrap(), amounts);
719        let amounts: Vec<Amount> = [128, 64, 32, 16, 8, 4, 2, 1]
720            .iter()
721            .map(|a| Amount::from(*a))
722            .collect();
723        assert_eq!(Amount::from(255).split(&fee_and_amounts).unwrap(), amounts);
724    }
725
726    #[test]
727    fn test_split_target_amount() {
728        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
729        let amount = Amount::from(65);
730
731        let split = amount
732            .split_targeted(&SplitTarget::Value(Amount::from(32)), &fee_and_amounts)
733            .unwrap();
734        assert_eq!(
735            vec![Amount::from(1), Amount::from(32), Amount::from(32)],
736            split
737        );
738
739        let amount = Amount::from(150);
740
741        let split = amount
742            .split_targeted(&SplitTarget::Value(Amount::from(50)), &fee_and_amounts)
743            .unwrap();
744        assert_eq!(
745            vec![
746                Amount::from(2),
747                Amount::from(2),
748                Amount::from(2),
749                Amount::from(16),
750                Amount::from(16),
751                Amount::from(16),
752                Amount::from(32),
753                Amount::from(32),
754                Amount::from(32)
755            ],
756            split
757        );
758
759        let amount = Amount::from(63);
760
761        let split = amount
762            .split_targeted(&SplitTarget::Value(Amount::from(32)), &fee_and_amounts)
763            .unwrap();
764        assert_eq!(
765            vec![
766                Amount::from(1),
767                Amount::from(2),
768                Amount::from(4),
769                Amount::from(8),
770                Amount::from(16),
771                Amount::from(32)
772            ],
773            split
774        );
775    }
776
777    #[test]
778    fn test_split_with_fee() {
779        let fee_and_amounts = (1, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
780        let amount = Amount::from(2);
781
782        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
783        assert_eq!(split, vec![Amount::from(2), Amount::from(1)]);
784
785        let amount = Amount::from(3);
786
787        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
788        assert_eq!(split, vec![Amount::from(4)]);
789
790        let amount = Amount::from(3);
791        let fee_and_amounts = (1000, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
792
793        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
794        // With fee_ppk=1000 (100%), amount 3 requires proofs totaling at least 5
795        // to cover both the amount (3) and fees (~2 for 2 proofs)
796        assert_eq!(split, vec![Amount::from(4), Amount::from(1)]);
797    }
798
799    #[test]
800    fn test_split_with_fee_reported_issue() {
801        let fee_and_amounts = (100, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
802        // Test the reported issue: mint 600, send 300 with fee_ppk=100
803        let amount = Amount::from(300);
804
805        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
806
807        // Calculate the total fee for the split
808        let total_fee_ppk = (split.len() as u64) * fee_and_amounts.fee;
809        let total_fee = Amount::from(total_fee_ppk.div_ceil(1000));
810
811        // The split should cover the amount plus fees
812        let split_total = Amount::try_sum(split.iter().copied()).unwrap();
813        assert!(
814            split_total >= amount.checked_add(total_fee).unwrap(),
815            "Split total {} should be >= amount {} + fee {}",
816            split_total,
817            amount,
818            total_fee
819        );
820    }
821
822    #[test]
823    fn test_split_with_fee_edge_cases() {
824        // Test various amounts with fee_ppk=100
825        let test_cases = vec![
826            (Amount::from(1), 100),
827            (Amount::from(10), 100),
828            (Amount::from(50), 100),
829            (Amount::from(100), 100),
830            (Amount::from(200), 100),
831            (Amount::from(300), 100),
832            (Amount::from(500), 100),
833            (Amount::from(600), 100),
834            (Amount::from(1000), 100),
835            (Amount::from(1337), 100),
836            (Amount::from(5000), 100),
837        ];
838
839        for (amount, fee_ppk) in test_cases {
840            let fee_and_amounts =
841                (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
842            let result = amount.split_with_fee(&fee_and_amounts);
843            assert!(
844                result.is_ok(),
845                "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
846                amount,
847                fee_ppk,
848                result.err()
849            );
850
851            let split = result.unwrap();
852
853            // Verify the split covers the required amount
854            let split_total = Amount::try_sum(split.iter().copied()).unwrap();
855            let fee_for_split = (split.len() as u64) * fee_ppk;
856            let total_fee = Amount::from(fee_for_split.div_ceil(1000));
857
858            // The net amount after fees should be at least the original amount
859            let net_amount = split_total.checked_sub(total_fee);
860            assert!(
861                net_amount.is_some(),
862                "Net amount calculation failed for amount {} with fee_ppk {}",
863                amount,
864                fee_ppk
865            );
866            assert!(
867                net_amount.unwrap() >= amount,
868                "Net amount {} is less than required {} for amount {} with fee_ppk {}",
869                net_amount.unwrap(),
870                amount,
871                amount,
872                fee_ppk
873            );
874        }
875    }
876
877    #[test]
878    fn test_split_with_fee_high_fees() {
879        // Test with very high fees
880        let test_cases = vec![
881            (Amount::from(10), 500),  // 50% fee
882            (Amount::from(10), 1000), // 100% fee
883            (Amount::from(10), 2000), // 200% fee
884            (Amount::from(100), 500),
885            (Amount::from(100), 1000),
886            (Amount::from(100), 2000),
887        ];
888
889        for (amount, fee_ppk) in test_cases {
890            let fee_and_amounts =
891                (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
892            let result = amount.split_with_fee(&fee_and_amounts);
893            assert!(
894                result.is_ok(),
895                "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
896                amount,
897                fee_ppk,
898                result.err()
899            );
900
901            let split = result.unwrap();
902            let split_total = Amount::try_sum(split.iter().copied()).unwrap();
903
904            // With high fees, we just need to ensure we can cover the amount
905            assert!(
906                split_total > amount,
907                "Split total {} should be greater than amount {} for fee_ppk {}",
908                split_total,
909                amount,
910                fee_ppk
911            );
912        }
913    }
914
915    #[test]
916    fn test_split_with_fee_recursion_limit() {
917        // Test that the recursion doesn't go infinite
918        // This tests the edge case where the method keeps adding Amount::ONE
919        let amount = Amount::from(1);
920        let fee_ppk = 10000;
921        let fee_and_amounts = (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
922
923        let result = amount.split_with_fee(&fee_and_amounts);
924        assert!(
925            result.is_ok(),
926            "split_with_fee should handle extreme fees without infinite recursion"
927        );
928    }
929
930    #[test]
931    fn test_split_values() {
932        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
933        let amount = Amount::from(10);
934
935        let target = vec![Amount::from(2), Amount::from(4), Amount::from(4)];
936
937        let split_target = SplitTarget::Values(target.clone());
938
939        let values = amount
940            .split_targeted(&split_target, &fee_and_amounts)
941            .unwrap();
942
943        assert_eq!(target, values);
944
945        let target = vec![Amount::from(2), Amount::from(4), Amount::from(4)];
946
947        let split_target = SplitTarget::Values(vec![Amount::from(2), Amount::from(4)]);
948
949        let values = amount
950            .split_targeted(&split_target, &fee_and_amounts)
951            .unwrap();
952
953        assert_eq!(target, values);
954
955        let split_target = SplitTarget::Values(vec![Amount::from(2), Amount::from(10)]);
956
957        let values = amount.split_targeted(&split_target, &fee_and_amounts);
958
959        assert!(values.is_err())
960    }
961
962    #[test]
963    #[should_panic]
964    fn test_amount_addition() {
965        let amount_one: Amount = u64::MAX.into();
966        let amount_two: Amount = 1.into();
967
968        let amounts = vec![amount_one, amount_two];
969
970        let _total: Amount = Amount::try_sum(amounts).unwrap();
971    }
972
973    #[test]
974    fn test_try_amount_addition() {
975        let amount_one: Amount = u64::MAX.into();
976        let amount_two: Amount = 1.into();
977
978        let amounts = vec![amount_one, amount_two];
979
980        let total = Amount::try_sum(amounts);
981
982        assert!(total.is_err());
983        let amount_one: Amount = 10000.into();
984        let amount_two: Amount = 1.into();
985
986        let amounts = vec![amount_one, amount_two];
987        let total = Amount::try_sum(amounts).unwrap();
988
989        assert_eq!(total, 10001.into());
990    }
991
992    #[test]
993    fn test_amount_convert_to() {
994        // Sat -> Msat
995        let amount = Amount::new(1000, CurrencyUnit::Sat);
996        let converted = amount.convert_to(&CurrencyUnit::Msat).unwrap();
997        assert_eq!(converted.value(), 1000000);
998        assert_eq!(converted.unit(), &CurrencyUnit::Msat);
999
1000        // Msat -> Sat
1001        let amount = Amount::new(1000, CurrencyUnit::Msat);
1002        let converted = amount.convert_to(&CurrencyUnit::Sat).unwrap();
1003        assert_eq!(converted.value(), 1);
1004        assert_eq!(converted.unit(), &CurrencyUnit::Sat);
1005
1006        // Usd -> Usd identity conversion
1007        let amount = Amount::new(1, CurrencyUnit::Usd);
1008        let converted = amount.convert_to(&CurrencyUnit::Usd).unwrap();
1009        assert_eq!(converted.value(), 1);
1010        assert_eq!(converted.unit(), &CurrencyUnit::Usd);
1011
1012        // Eur -> Eur identity conversion
1013        let amount = Amount::new(1, CurrencyUnit::Eur);
1014        let converted = amount.convert_to(&CurrencyUnit::Eur).unwrap();
1015        assert_eq!(converted.value(), 1);
1016        assert_eq!(converted.unit(), &CurrencyUnit::Eur);
1017
1018        // Sat -> Eur should fail (no conversion path)
1019        let amount = Amount::new(1, CurrencyUnit::Sat);
1020        let converted = amount.convert_to(&CurrencyUnit::Eur);
1021        assert!(converted.is_err());
1022
1023        // Sat -> Sat identity conversion
1024        let amount = Amount::new(500, CurrencyUnit::Sat);
1025        let converted = amount.convert_to(&CurrencyUnit::Sat).unwrap();
1026        assert_eq!(converted.value(), 500);
1027        assert_eq!(converted.unit(), &CurrencyUnit::Sat);
1028
1029        // Msat -> Msat identity conversion
1030        let amount = Amount::new(5000, CurrencyUnit::Msat);
1031        let converted = amount.convert_to(&CurrencyUnit::Msat).unwrap();
1032        assert_eq!(converted.value(), 5000);
1033        assert_eq!(converted.unit(), &CurrencyUnit::Msat);
1034    }
1035
1036    #[test]
1037    fn test_amount_from_typed_to_untyped() {
1038        // Test From<Amount<CurrencyUnit>> for Amount<()>
1039        let typed = Amount::new(1000, CurrencyUnit::Sat);
1040        let untyped: Amount<()> = typed.into();
1041        assert_eq!(u64::from(untyped), 1000);
1042    }
1043
1044    /// Tests that the subtraction operator correctly computes the difference between amounts.
1045    ///
1046    /// This test verifies that the `-` operator for Amount produces the expected result.
1047    /// It's particularly important because the subtraction operation is used in critical
1048    /// code paths like `split_targeted`, where incorrect subtraction could lead to
1049    /// infinite loops or wrong calculations.
1050    ///
1051    /// Mutant testing: Catches mutations that replace the subtraction implementation
1052    /// with `Default::default()` (returning Amount::ZERO), which would cause infinite
1053    /// loops in `split_targeted` at line 138 where `*self - parts_total` is computed.
1054    #[test]
1055    fn test_amount_sub_operator() {
1056        let amount1 = Amount::from(100);
1057        let amount2 = Amount::from(30);
1058
1059        let result = amount1.checked_sub(amount2).unwrap();
1060        assert_eq!(result, Amount::from(70));
1061
1062        let amount1 = Amount::from(1000);
1063        let amount2 = Amount::from(1);
1064
1065        let result = amount1.checked_sub(amount2).unwrap();
1066        assert_eq!(result, Amount::from(999));
1067
1068        let amount1 = Amount::from(255);
1069        let amount2 = Amount::from(128);
1070
1071        let result = amount1.checked_sub(amount2).unwrap();
1072        assert_eq!(result, Amount::from(127));
1073    }
1074
1075    /// Tests that the subtraction operator panics when attempting to subtract
1076    /// a larger amount from a smaller amount (underflow).
1077    ///
1078    /// This test verifies the safety property that Amount subtraction will panic
1079    /// rather than wrap around on underflow. This is critical for preventing
1080    /// bugs where negative amounts could be interpreted as very large positive amounts.
1081    ///
1082    /// Mutant testing: Catches mutations that remove the panic behavior or return
1083    /// default values instead of properly handling underflow.
1084    #[test]
1085    #[should_panic(expected = "Subtraction underflow")]
1086    fn test_amount_sub_underflow() {
1087        let amount1 = Amount::from(30);
1088        let amount2 = Amount::from(100);
1089
1090        let _result = amount1 - amount2;
1091    }
1092
1093    /// Tests that checked_add correctly computes the sum and returns the actual value.
1094    ///
1095    /// This is critical because checked_add is used in recursive functions like
1096    /// split_with_fee. If it returns Some(Amount::ZERO) instead of the actual sum,
1097    /// the recursion would never terminate.
1098    ///
1099    /// Mutant testing: Kills mutations that replace the implementation with
1100    /// `Some(Default::default())`, which would cause infinite loops in split_with_fee
1101    /// at line 198 where it recursively calls itself with incremented amounts.
1102    #[test]
1103    fn test_checked_add_returns_correct_value() {
1104        let amount1 = Amount::from(100);
1105        let amount2 = Amount::from(50);
1106
1107        let result = amount1.checked_add(amount2);
1108        assert_eq!(result, Some(Amount::from(150)));
1109
1110        let amount1 = Amount::from(1);
1111        let amount2 = Amount::from(1);
1112
1113        let result = amount1.checked_add(amount2);
1114        assert_eq!(result, Some(Amount::from(2)));
1115        assert_ne!(result, Some(Amount::ZERO));
1116
1117        let amount1 = Amount::from(1000);
1118        let amount2 = Amount::from(337);
1119
1120        let result = amount1.checked_add(amount2);
1121        assert_eq!(result, Some(Amount::from(1337)));
1122    }
1123
1124    /// Tests that checked_add returns None on overflow.
1125    #[test]
1126    fn test_checked_add_overflow() {
1127        let amount1 = Amount::from(u64::MAX);
1128        let amount2 = Amount::from(1);
1129
1130        let result = amount1.checked_add(amount2);
1131        assert!(result.is_none());
1132    }
1133
1134    /// Tests that try_sum correctly computes the total sum of amounts.
1135    ///
1136    /// This is critical because try_sum is used in loops like split_targeted at line 130
1137    /// to track progress. If it returns Ok(Amount::ZERO) instead of the actual sum,
1138    /// the loop condition `parts_total.eq(self)` would never be true, causing an infinite loop.
1139    ///
1140    /// Mutant testing: Kills mutations that replace the implementation with
1141    /// `Ok(Default::default())`, which would cause infinite loops.
1142    #[test]
1143    fn test_try_sum_returns_correct_value() {
1144        let amounts = vec![Amount::from(10), Amount::from(20), Amount::from(30)];
1145        let result = Amount::try_sum(amounts).unwrap();
1146        assert_eq!(result, Amount::from(60));
1147        assert_ne!(result, Amount::ZERO);
1148
1149        let amounts = vec![Amount::from(1), Amount::from(1), Amount::from(1)];
1150        let result = Amount::try_sum(amounts).unwrap();
1151        assert_eq!(result, Amount::from(3));
1152
1153        let amounts = vec![Amount::from(100)];
1154        let result = Amount::try_sum(amounts).unwrap();
1155        assert_eq!(result, Amount::from(100));
1156
1157        let empty: Vec<Amount> = vec![];
1158        let result = Amount::try_sum(empty).unwrap();
1159        assert_eq!(result, Amount::ZERO);
1160    }
1161
1162    /// Tests that try_sum returns error on overflow.
1163    #[test]
1164    fn test_try_sum_overflow() {
1165        let amounts = vec![Amount::from(u64::MAX), Amount::from(1)];
1166        let result = Amount::try_sum(amounts);
1167        assert!(result.is_err());
1168    }
1169
1170    /// Tests that split returns a non-empty vec with actual values, not defaults.
1171    ///
1172    /// The split function is used in split_targeted's while loop (line 122).
1173    /// If split returns an empty vec or vec with Amount::ZERO when it shouldn't,
1174    /// the loop that extends parts with split results would never make progress,
1175    /// causing an infinite loop.
1176    ///
1177    /// Mutant testing: Kills mutations that replace split with `vec![]` or
1178    /// `vec![Default::default()]` which would cause infinite loops.
1179    #[test]
1180    fn test_split_returns_correct_values() {
1181        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
1182
1183        let amount = Amount::from(11);
1184        let result = amount.split(&fee_and_amounts).unwrap();
1185        assert!(!result.is_empty());
1186        assert_eq!(Amount::try_sum(result.iter().copied()).unwrap(), amount);
1187
1188        let amount = Amount::from(255);
1189        let result = amount.split(&fee_and_amounts).unwrap();
1190        assert!(!result.is_empty());
1191        assert_eq!(Amount::try_sum(result.iter().copied()).unwrap(), amount);
1192
1193        let amount = Amount::from(7);
1194        let result = amount.split(&fee_and_amounts).unwrap();
1195        assert_eq!(
1196            result,
1197            vec![Amount::from(4), Amount::from(2), Amount::from(1)]
1198        );
1199        for r in &result {
1200            assert_ne!(*r, Amount::ZERO);
1201        }
1202    }
1203
1204    /// Tests that the modulo operation in split works correctly.
1205    ///
1206    /// At line 108, split uses modulo (%) to compute the remainder.
1207    /// If this is mutated to division (/), it would produce wrong results
1208    /// that could cause infinite loops in code that depends on split.
1209    ///
1210    /// Mutant testing: Kills mutations that replace `%` with `/`.
1211    #[test]
1212    fn test_split_modulo_operation() {
1213        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
1214
1215        let amount = Amount::from(15);
1216        let result = amount.split(&fee_and_amounts).unwrap();
1217
1218        assert_eq!(
1219            result,
1220            vec![
1221                Amount::from(8),
1222                Amount::from(4),
1223                Amount::from(2),
1224                Amount::from(1)
1225            ]
1226        );
1227
1228        let total = Amount::try_sum(result.iter().copied()).unwrap();
1229        assert_eq!(total, amount);
1230    }
1231
1232    /// Tests that split returns an error when the amount cannot be represented
1233    /// with the available denominations.
1234    #[test]
1235    fn test_split_cannot_represent_amount() {
1236        // Only denomination 32 available - the split algorithm can only use each denomination once
1237        let fee_and_amounts: FeeAndAmounts = (0, vec![32]).into();
1238
1239        // 100 cannot be exactly represented: 100 >= 32, push(32), 100 % 32 = 4, result = [32]
1240        let amount = Amount::from(100);
1241        let result = amount.split(&fee_and_amounts);
1242        assert!(result.is_err());
1243        match result {
1244            Err(Error::CannotSplitAmount(requested, got)) => {
1245                assert_eq!(requested, 100);
1246                assert_eq!(got, 32); // Only one 32 can be taken
1247            }
1248            _ => panic!("Expected CannotSplitAmount error"),
1249        }
1250
1251        // 32 can be exactly represented
1252        let amount = Amount::from(32);
1253        let result = amount.split(&fee_and_amounts);
1254        assert!(result.is_ok());
1255        assert_eq!(result.unwrap(), vec![Amount::from(32)]);
1256
1257        // Missing denominations: only have 32 and 64, trying to split 100
1258        // 100 >= 64, push(64), 100 % 64 = 36
1259        // 36 >= 32, push(32), 36 % 32 = 4
1260        // Result: [64, 32] = 96, missing 4
1261        let fee_and_amounts: FeeAndAmounts = (0, vec![32, 64]).into();
1262        let amount = Amount::from(100);
1263        let result = amount.split(&fee_and_amounts);
1264        assert!(result.is_err());
1265        match result {
1266            Err(Error::CannotSplitAmount(requested, got)) => {
1267                assert_eq!(requested, 100);
1268                assert_eq!(got, 96);
1269            }
1270            _ => panic!("Expected CannotSplitAmount error"),
1271        }
1272    }
1273
1274    #[test]
1275    fn test_split_amount_exceeds_keyset_capacity() {
1276        // Keyset with denominations 2^0 to 2^31
1277        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
1278
1279        // Attempt to split 2^63 (way larger than sum of keyset)
1280        let amount = Amount::from(2u64.pow(63));
1281        let result = amount.split(&fee_and_amounts);
1282
1283        assert!(result.is_err());
1284        match result {
1285            Err(Error::CannotSplitAmount(requested, got)) => {
1286                assert_eq!(requested, 2u64.pow(63));
1287                // The algorithm greedily takes 2^31, and since 2^63 % 2^31 == 0, it stops there.
1288                // So "got" should be 2^31.
1289                assert_eq!(got, 2u64.pow(31));
1290            }
1291            _ => panic!("Expected CannotSplitAmount error, got {:?}", result),
1292        }
1293    }
1294
1295    /// Tests that From<u64> correctly converts values to Amount.
1296    ///
1297    /// This conversion is used throughout the codebase including in loops and split operations.
1298    /// If it returns Default::default() (Amount::ZERO) instead of the actual value,
1299    /// it can cause infinite loops where amounts are being accumulated or compared.
1300    ///
1301    /// Mutant testing: Kills mutations that replace From<u64> with `Default::default()`.
1302    #[test]
1303    fn test_from_u64_returns_correct_value() {
1304        let amount = Amount::from(100u64);
1305        assert_eq!(amount, Amount::from(100));
1306        assert_ne!(amount, Amount::ZERO);
1307
1308        let amount = Amount::from(1u64);
1309        assert_eq!(amount, Amount::from(1));
1310        assert_eq!(amount, Amount::ONE);
1311
1312        let amount = Amount::from(1337u64);
1313        assert_eq!(amount.to_u64(), 1337);
1314    }
1315
1316    /// Tests that checked_mul returns the correct product value.
1317    ///
1318    /// This is critical for any multiplication operations. If it returns None
1319    /// or Some(Amount::ZERO) instead of the actual product, calculations will be wrong.
1320    ///
1321    /// Mutant testing: Kills mutations that replace checked_mul with None or Some(Default::default()).
1322    #[test]
1323    fn test_checked_mul_returns_correct_value() {
1324        let amount1 = Amount::from(10);
1325        let amount2 = Amount::from(5);
1326        let result = amount1.checked_mul(amount2);
1327        assert_eq!(result, Some(Amount::from(50)));
1328        assert_ne!(result, None);
1329        assert_ne!(result, Some(Amount::ZERO));
1330
1331        let amount1 = Amount::from(100);
1332        let amount2 = Amount::from(20);
1333        let result = amount1.checked_mul(amount2);
1334        assert_eq!(result, Some(Amount::from(2000)));
1335        assert_ne!(result, Some(Amount::ZERO));
1336
1337        let amount1 = Amount::from(7);
1338        let amount2 = Amount::from(13);
1339        let result = amount1.checked_mul(amount2);
1340        assert_eq!(result, Some(Amount::from(91)));
1341
1342        // Test multiplication by zero
1343        let amount1 = Amount::from(100);
1344        let amount2 = Amount::ZERO;
1345        let result = amount1.checked_mul(amount2);
1346        assert_eq!(result, Some(Amount::ZERO));
1347
1348        // Test multiplication by one
1349        let amount1 = Amount::from(42);
1350        let amount2 = Amount::ONE;
1351        let result = amount1.checked_mul(amount2);
1352        assert_eq!(result, Some(Amount::from(42)));
1353
1354        // Test overflow
1355        let amount1 = Amount::from(u64::MAX);
1356        let amount2 = Amount::from(2);
1357        let result = amount1.checked_mul(amount2);
1358        assert!(result.is_none());
1359    }
1360
1361    /// Tests that checked_div returns the correct quotient value.
1362    ///
1363    /// This is critical for division operations. If it returns None or
1364    /// Some(Amount::ZERO) instead of the actual quotient, calculations will be wrong.
1365    ///
1366    /// Mutant testing: Kills mutations that replace checked_div with None or Some(Default::default()).
1367    #[test]
1368    fn test_checked_div_returns_correct_value() {
1369        let amount1 = Amount::from(100);
1370        let amount2 = Amount::from(5);
1371        let result = amount1.checked_div(amount2);
1372        assert_eq!(result, Some(Amount::from(20)));
1373        assert_ne!(result, None);
1374        assert_ne!(result, Some(Amount::ZERO));
1375
1376        let amount1 = Amount::from(1000);
1377        let amount2 = Amount::from(10);
1378        let result = amount1.checked_div(amount2);
1379        assert_eq!(result, Some(Amount::from(100)));
1380        assert_ne!(result, Some(Amount::ZERO));
1381
1382        let amount1 = Amount::from(91);
1383        let amount2 = Amount::from(7);
1384        let result = amount1.checked_div(amount2);
1385        assert_eq!(result, Some(Amount::from(13)));
1386
1387        // Test division by one
1388        let amount1 = Amount::from(42);
1389        let amount2 = Amount::ONE;
1390        let result = amount1.checked_div(amount2);
1391        assert_eq!(result, Some(Amount::from(42)));
1392
1393        // Test integer division (truncation)
1394        let amount1 = Amount::from(10);
1395        let amount2 = Amount::from(3);
1396        let result = amount1.checked_div(amount2);
1397        assert_eq!(result, Some(Amount::from(3)));
1398
1399        // Test division by zero
1400        let amount1 = Amount::from(100);
1401        let amount2 = Amount::ZERO;
1402        let result = amount1.checked_div(amount2);
1403        assert!(result.is_none());
1404    }
1405
1406    /// Tests that Amount::convert_unit returns the correct converted value.
1407    ///
1408    /// This is critical for unit conversions. If it returns Ok(Amount::ZERO)
1409    /// instead of the actual converted value, all conversions will be wrong.
1410    ///
1411    /// Mutant testing: Kills mutations that replace convert_unit with Ok(Default::default()).
1412    #[test]
1413    fn test_convert_unit_returns_correct_value() {
1414        let amount = Amount::from(1000);
1415        let result = amount
1416            .convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Msat)
1417            .unwrap();
1418        assert_eq!(result, Amount::from(1_000_000));
1419        assert_ne!(result, Amount::ZERO);
1420
1421        let amount = Amount::from(5000);
1422        let result = amount
1423            .convert_unit(&CurrencyUnit::Msat, &CurrencyUnit::Sat)
1424            .unwrap();
1425        assert_eq!(result, Amount::from(5));
1426        assert_ne!(result, Amount::ZERO);
1427
1428        let amount = Amount::from(123);
1429        let result = amount
1430            .convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Sat)
1431            .unwrap();
1432        assert_eq!(result, Amount::from(123));
1433
1434        let amount = Amount::from(456);
1435        let result = amount
1436            .convert_unit(&CurrencyUnit::Usd, &CurrencyUnit::Usd)
1437            .unwrap();
1438        assert_eq!(result, Amount::from(456));
1439
1440        let amount = Amount::from(789);
1441        let result = amount
1442            .convert_unit(&CurrencyUnit::Eur, &CurrencyUnit::Eur)
1443            .unwrap();
1444        assert_eq!(result, Amount::from(789));
1445
1446        // Test invalid conversion
1447        let amount = Amount::from(100);
1448        let result = amount.convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Eur);
1449        assert!(result.is_err());
1450    }
1451
1452    /// Tests that Amount::to_i64() returns the correct value.
1453    ///
1454    /// Mutant testing: Kills mutations that replace the return value with:
1455    /// - None
1456    /// - Some(0)
1457    /// - Some(1)
1458    /// - Some(-1)
1459    /// Also catches mutation that replaces <= with > in the comparison.
1460    #[test]
1461    fn test_amount_to_i64_returns_correct_value() {
1462        // Test with value 100 (catches None, Some(0), Some(1), Some(-1) mutations)
1463        let amount = Amount::from(100);
1464        let result = amount.to_i64();
1465        assert_eq!(result, Some(100));
1466        assert!(result.is_some());
1467        assert_ne!(result, Some(0));
1468        assert_ne!(result, Some(1));
1469        assert_ne!(result, Some(-1));
1470
1471        // Test with value 1000 (catches all constant mutations)
1472        let amount = Amount::from(1000);
1473        let result = amount.to_i64();
1474        assert_eq!(result, Some(1000));
1475        assert_ne!(result, None);
1476        assert_ne!(result, Some(0));
1477        assert_ne!(result, Some(1));
1478        assert_ne!(result, Some(-1));
1479
1480        // Test with value 2 (specifically catches Some(1) mutation)
1481        let amount = Amount::from(2);
1482        let result = amount.to_i64();
1483        assert_eq!(result, Some(2));
1484        assert_ne!(result, Some(1));
1485
1486        // Test with i64::MAX (should return Some(i64::MAX))
1487        // This catches the <= vs > mutation: if <= becomes >, this would return None
1488        let amount = Amount::from(i64::MAX as u64);
1489        let result = amount.to_i64();
1490        assert_eq!(result, Some(i64::MAX));
1491        assert!(result.is_some());
1492
1493        // Test with i64::MAX + 1 (should return None)
1494        // This is the boundary case for the <= comparison
1495        let amount = Amount::from(i64::MAX as u64 + 1);
1496        let result = amount.to_i64();
1497        assert!(result.is_none());
1498
1499        // Test with u64::MAX (should return None)
1500        let amount = Amount::from(u64::MAX);
1501        let result = amount.to_i64();
1502        assert!(result.is_none());
1503
1504        // Edge case: 0 should return Some(0)
1505        let amount = Amount::from(0);
1506        let result = amount.to_i64();
1507        assert_eq!(result, Some(0));
1508
1509        // Edge case: 1 should return Some(1)
1510        let amount = Amount::from(1);
1511        let result = amount.to_i64();
1512        assert_eq!(result, Some(1));
1513    }
1514
1515    /// Tests the boundary condition for Amount::to_i64() at i64::MAX.
1516    ///
1517    /// This specifically tests the <= vs > mutation in the condition
1518    /// `if self.0 <= i64::MAX as u64`.
1519    #[test]
1520    fn test_amount_to_i64_boundary() {
1521        // Exactly at i64::MAX - should succeed
1522        let at_max = Amount::from(i64::MAX as u64);
1523        assert!(at_max.to_i64().is_some());
1524        assert_eq!(at_max.to_i64().unwrap(), i64::MAX);
1525
1526        // One above i64::MAX - should fail
1527        let above_max = Amount::from(i64::MAX as u64 + 1);
1528        assert!(above_max.to_i64().is_none());
1529
1530        // One below i64::MAX - should succeed
1531        let below_max = Amount::from(i64::MAX as u64 - 1);
1532        assert!(below_max.to_i64().is_some());
1533        assert_eq!(below_max.to_i64().unwrap(), i64::MAX - 1);
1534    }
1535
1536    /// Tests Amount::from_i64 returns the correct value.
1537    ///
1538    /// Mutant testing: Catches mutations that:
1539    /// - Replace return with None
1540    /// - Replace return with Some(Default::default())
1541    /// - Replace >= with < in the condition
1542    #[test]
1543    fn test_amount_from_i64() {
1544        // Positive value - should return Some with correct value
1545        let result = Amount::from_i64(100);
1546        assert!(result.is_some());
1547        assert_eq!(result.unwrap(), Amount::from(100));
1548        assert_ne!(result, None);
1549        assert_ne!(result, Some(Amount::ZERO));
1550
1551        // Zero - boundary case for >= vs <
1552        // If >= becomes <, this would return None instead of Some
1553        let result = Amount::from_i64(0);
1554        assert!(result.is_some());
1555        assert_eq!(result.unwrap(), Amount::ZERO);
1556
1557        // Negative value - should return None
1558        let result = Amount::from_i64(-1);
1559        assert!(result.is_none());
1560
1561        let result = Amount::from_i64(-100);
1562        assert!(result.is_none());
1563
1564        // Large positive value
1565        let result = Amount::from_i64(i64::MAX);
1566        assert!(result.is_some());
1567        assert_eq!(result.unwrap(), Amount::from(i64::MAX as u64));
1568        assert_ne!(result, Some(Amount::ZERO));
1569
1570        // Value 1 - catches Some(Default::default()) mutation
1571        let result = Amount::from_i64(1);
1572        assert!(result.is_some());
1573        assert_eq!(result.unwrap(), Amount::ONE);
1574        assert_ne!(result, Some(Amount::ZERO));
1575    }
1576
1577    /// Tests AddAssign actually modifies the value.
1578    ///
1579    /// Mutant testing: Catches mutation that replaces add_assign with ().
1580    #[test]
1581    fn test_add_assign() {
1582        let mut amount = Amount::from(100);
1583        amount += Amount::from(50);
1584        assert_eq!(amount, Amount::from(150));
1585        assert_ne!(amount, Amount::from(100)); // Should have changed
1586
1587        let mut amount = Amount::from(1);
1588        amount += Amount::from(1);
1589        assert_eq!(amount, Amount::from(2));
1590        assert_ne!(amount, Amount::ONE); // Should have changed
1591
1592        let mut amount = Amount::ZERO;
1593        amount += Amount::from(42);
1594        assert_eq!(amount, Amount::from(42));
1595        assert_ne!(amount, Amount::ZERO); // Should have changed
1596    }
1597
1598    /// Tests SubAssign actually modifies the value.
1599    ///
1600    /// Mutant testing: Catches mutation that replaces sub_assign with ().
1601    #[test]
1602    fn test_sub_assign() {
1603        let mut amount = Amount::from(100);
1604        amount -= Amount::from(30);
1605        assert_eq!(amount, Amount::from(70));
1606        assert_ne!(amount, Amount::from(100)); // Should have changed
1607
1608        let mut amount = Amount::from(50);
1609        amount -= Amount::from(1);
1610        assert_eq!(amount, Amount::from(49));
1611        assert_ne!(amount, Amount::from(50)); // Should have changed
1612
1613        let mut amount = Amount::from(10);
1614        amount -= Amount::from(10);
1615        assert_eq!(amount, Amount::ZERO);
1616        assert_ne!(amount, Amount::from(10)); // Should have changed
1617    }
1618
1619    // Phase 2 tests: Amount<CurrencyUnit> methods
1620
1621    #[test]
1622    fn test_amount_with_currency_unit() {
1623        let amount = Amount::new(1000, CurrencyUnit::Sat);
1624        assert_eq!(amount.value(), 1000);
1625        assert_eq!(amount.unit(), &CurrencyUnit::Sat);
1626    }
1627
1628    #[test]
1629    fn test_amount_new_with_custom_unit() {
1630        let custom_unit = CurrencyUnit::Custom("BTC".to_string());
1631        let amount = Amount::new(50, custom_unit.clone());
1632
1633        assert_eq!(amount.value(), 50);
1634        assert_eq!(amount.unit(), &custom_unit);
1635    }
1636
1637    #[test]
1638    fn test_amount_into_parts() {
1639        let amount = Amount::new(1234, CurrencyUnit::Msat);
1640        let (value, unit) = amount.into_parts();
1641
1642        assert_eq!(value, 1234);
1643        assert_eq!(unit, CurrencyUnit::Msat);
1644    }
1645
1646    #[test]
1647    fn test_amount_with_unit_conversion() {
1648        let untyped: Amount<()> = Amount::from(100);
1649        let typed = untyped.with_unit(CurrencyUnit::Sat);
1650
1651        assert_eq!(typed.value(), 100);
1652        assert_eq!(typed.unit(), &CurrencyUnit::Sat);
1653    }
1654
1655    #[test]
1656    fn test_amount_with_unit_all_variants() {
1657        let untyped = Amount::from(500);
1658
1659        let sat = untyped.with_unit(CurrencyUnit::Sat);
1660        assert_eq!(sat.unit(), &CurrencyUnit::Sat);
1661
1662        let msat = untyped.with_unit(CurrencyUnit::Msat);
1663        assert_eq!(msat.unit(), &CurrencyUnit::Msat);
1664
1665        let usd = untyped.with_unit(CurrencyUnit::Usd);
1666        assert_eq!(usd.unit(), &CurrencyUnit::Usd);
1667
1668        let eur = untyped.with_unit(CurrencyUnit::Eur);
1669        assert_eq!(eur.unit(), &CurrencyUnit::Eur);
1670
1671        let custom = untyped.with_unit(CurrencyUnit::Custom("TEST".into()));
1672        assert_eq!(custom.unit(), &CurrencyUnit::Custom("TEST".into()));
1673    }
1674
1675    #[test]
1676    fn test_typed_amount_is_clone_not_copy() {
1677        let amount = Amount::new(100, CurrencyUnit::Sat);
1678        let cloned = amount.clone();
1679        // If this compiles, Clone works. Cannot test Copy directly without moving.
1680        assert_eq!(cloned.value(), 100);
1681        assert_eq!(cloned.unit(), &CurrencyUnit::Sat);
1682    }
1683
1684    // Phase 3 tests: Protocol types verification
1685
1686    #[test]
1687    fn test_untyped_amount_is_copy() {
1688        // Verify Amount<()> is Copy (required for protocol types)
1689        let amount: Amount<()> = Amount::from(100);
1690        let copy1 = amount;
1691        let copy2 = amount; // Should not move - verifies Copy
1692        assert_eq!(copy1, copy2);
1693    }
1694
1695    #[test]
1696    fn test_amount_serialization_transparent() {
1697        // Verify Amount<()> serializes as just the number (protocol compatibility)
1698        let amount = Amount::from(1234);
1699        let json = serde_json::to_string(&amount).unwrap();
1700        assert_eq!(json, "1234");
1701
1702        // Verify deserialization works
1703        let deserialized: Amount<()> = serde_json::from_str(&json).unwrap();
1704        assert_eq!(deserialized, Amount::from(1234));
1705    }
1706
1707    #[test]
1708    fn test_typed_amount_serialization() {
1709        // Verify Amount<CurrencyUnit> also serializes as just the number
1710        let amount = Amount::new(5678, CurrencyUnit::Sat);
1711        let json = serde_json::to_string(&amount).unwrap();
1712        assert_eq!(json, "5678");
1713
1714        // Note: Cannot deserialize Amount<CurrencyUnit> directly
1715        // Unit must come from context (e.g., keyset)
1716    }
1717
1718    #[test]
1719    fn test_protocol_type_pattern() {
1720        // Simulate protocol type usage pattern
1721
1722        // Protocol layer: Amount<()> is Copy and serializes transparently
1723        let protocol_amount: Amount<()> = Amount::from(1000);
1724        let _copied = protocol_amount; // Copy works
1725
1726        // Application layer: Convert to typed when needed
1727        let typed = protocol_amount.with_unit(CurrencyUnit::Sat);
1728        assert_eq!(typed.value(), 1000);
1729
1730        // Back to protocol: Extract value
1731        let back_to_protocol = Amount::from(typed.value());
1732        assert_eq!(back_to_protocol, protocol_amount);
1733    }
1734
1735    // Phase 4 tests: Unit-aware arithmetic
1736
1737    #[test]
1738    fn test_typed_amount_checked_add() {
1739        let a = Amount::new(100, CurrencyUnit::Sat);
1740        let b = Amount::new(50, CurrencyUnit::Sat);
1741
1742        let sum = a.checked_add(&b).unwrap();
1743        assert_eq!(sum.value(), 150);
1744        assert_eq!(sum.unit(), &CurrencyUnit::Sat);
1745    }
1746
1747    #[test]
1748    fn test_typed_amount_add_unit_mismatch() {
1749        let sat = Amount::new(100, CurrencyUnit::Sat);
1750        let msat = Amount::new(100, CurrencyUnit::Msat);
1751
1752        let result = sat.checked_add(&msat);
1753        assert!(result.is_err());
1754
1755        match result.unwrap_err() {
1756            Error::UnitMismatch(u1, u2) => {
1757                assert_eq!(u1, CurrencyUnit::Sat);
1758                assert_eq!(u2, CurrencyUnit::Msat);
1759            }
1760            _ => panic!("Expected UnitMismatch error"),
1761        }
1762    }
1763
1764    #[test]
1765    fn test_typed_amount_checked_sub() {
1766        let a = Amount::new(100, CurrencyUnit::Sat);
1767        let b = Amount::new(30, CurrencyUnit::Sat);
1768
1769        let diff = a.checked_sub(&b).unwrap();
1770        assert_eq!(diff.value(), 70);
1771        assert_eq!(diff.unit(), &CurrencyUnit::Sat);
1772    }
1773
1774    #[test]
1775    fn test_typed_amount_sub_unit_mismatch() {
1776        let sat = Amount::new(100, CurrencyUnit::Sat);
1777        let usd = Amount::new(30, CurrencyUnit::Usd);
1778
1779        let result = sat.checked_sub(&usd);
1780        assert!(result.is_err());
1781    }
1782
1783    #[test]
1784    fn test_typed_amount_convert_to() {
1785        // Sat to Msat
1786        let sat = Amount::new(1000, CurrencyUnit::Sat);
1787        let msat = sat.convert_to(&CurrencyUnit::Msat).unwrap();
1788        assert_eq!(msat.value(), 1_000_000);
1789        assert_eq!(msat.unit(), &CurrencyUnit::Msat);
1790
1791        // Msat to Sat
1792        let msat = Amount::new(5000, CurrencyUnit::Msat);
1793        let sat = msat.convert_to(&CurrencyUnit::Sat).unwrap();
1794        assert_eq!(sat.value(), 5);
1795        assert_eq!(sat.unit(), &CurrencyUnit::Sat);
1796
1797        // Same unit (optimization check)
1798        let sat = Amount::new(100, CurrencyUnit::Sat);
1799        let same = sat.convert_to(&CurrencyUnit::Sat).unwrap();
1800        assert_eq!(same.value(), 100);
1801        assert_eq!(same.unit(), &CurrencyUnit::Sat);
1802    }
1803
1804    #[test]
1805    fn test_typed_amount_convert_invalid() {
1806        let sat = Amount::new(100, CurrencyUnit::Sat);
1807        let result = sat.convert_to(&CurrencyUnit::Eur);
1808        assert!(result.is_err());
1809
1810        match result.unwrap_err() {
1811            Error::CannotConvertUnits => {}
1812            _ => panic!("Expected CannotConvertUnits error"),
1813        }
1814    }
1815
1816    #[test]
1817    fn test_typed_amount_add_overflow() {
1818        let a = Amount::new(u64::MAX, CurrencyUnit::Sat);
1819        let b = Amount::new(1, CurrencyUnit::Sat);
1820
1821        let result = a.checked_add(&b);
1822        assert!(result.is_err());
1823
1824        match result.unwrap_err() {
1825            Error::AmountOverflow => {}
1826            _ => panic!("Expected AmountOverflow error"),
1827        }
1828    }
1829
1830    #[test]
1831    fn test_typed_amount_sub_underflow() {
1832        let a = Amount::new(50, CurrencyUnit::Sat);
1833        let b = Amount::new(100, CurrencyUnit::Sat);
1834
1835        let result = a.checked_sub(&b);
1836        assert!(result.is_err());
1837
1838        match result.unwrap_err() {
1839            Error::AmountOverflow => {} // Underflow also returns AmountOverflow
1840            _ => panic!("Expected AmountOverflow error"),
1841        }
1842    }
1843
1844    // Phase 5 tests: PartialOrd behavior for Amount<CurrencyUnit>
1845
1846    /// Tests that equality works correctly for typed amounts with the same unit.
1847    #[test]
1848    fn test_typed_amount_equality_same_unit() {
1849        let a = Amount::new(100, CurrencyUnit::Sat);
1850        let b = Amount::new(100, CurrencyUnit::Sat);
1851
1852        assert_eq!(a, b);
1853        assert!(a == b);
1854
1855        let c = Amount::new(50, CurrencyUnit::Sat);
1856        assert_ne!(a, c);
1857        assert!(a != c);
1858    }
1859
1860    /// Tests that equality returns false for typed amounts with different units.
1861    #[test]
1862    fn test_typed_amount_equality_different_units() {
1863        let sat = Amount::new(100, CurrencyUnit::Sat);
1864        let msat = Amount::new(100, CurrencyUnit::Msat);
1865
1866        // Same value, different units - should NOT be equal
1867        assert_ne!(sat, msat);
1868        assert!(sat != msat);
1869
1870        let usd = Amount::new(100, CurrencyUnit::Usd);
1871        assert_ne!(sat, usd);
1872        assert_ne!(msat, usd);
1873    }
1874
1875    /// Tests that comparison operators work correctly for typed amounts with the same unit.
1876    #[test]
1877    fn test_typed_amount_comparison_same_unit() {
1878        let small = Amount::new(50, CurrencyUnit::Sat);
1879        let large = Amount::new(100, CurrencyUnit::Sat);
1880
1881        // Greater than
1882        assert!(large > small);
1883        assert!(!(small > large));
1884
1885        // Less than
1886        assert!(small < large);
1887        assert!(!(large < small));
1888
1889        // Greater than or equal
1890        assert!(large >= small);
1891        assert!(large >= Amount::new(100, CurrencyUnit::Sat));
1892
1893        // Less than or equal
1894        assert!(small <= large);
1895        assert!(small <= Amount::new(50, CurrencyUnit::Sat));
1896
1897        // partial_cmp returns Some
1898        assert_eq!(large.partial_cmp(&small), Some(std::cmp::Ordering::Greater));
1899        assert_eq!(small.partial_cmp(&large), Some(std::cmp::Ordering::Less));
1900        assert_eq!(
1901            small.partial_cmp(&Amount::new(50, CurrencyUnit::Sat)),
1902            Some(std::cmp::Ordering::Equal)
1903        );
1904    }
1905
1906    /// Tests that partial_cmp returns None for typed amounts with different units.
1907    /// This ensures that comparisons between different units are not accidentally valid.
1908    #[test]
1909    fn test_typed_amount_comparison_different_units_returns_none() {
1910        let sat = Amount::new(100, CurrencyUnit::Sat);
1911        let msat = Amount::new(50, CurrencyUnit::Msat);
1912
1913        // partial_cmp should return None for different units
1914        assert_eq!(sat.partial_cmp(&msat), None);
1915        assert_eq!(msat.partial_cmp(&sat), None);
1916
1917        // Different unit combinations
1918        let usd = Amount::new(100, CurrencyUnit::Usd);
1919        assert_eq!(sat.partial_cmp(&usd), None);
1920        assert_eq!(usd.partial_cmp(&sat), None);
1921
1922        let eur = Amount::new(100, CurrencyUnit::Eur);
1923        assert_eq!(usd.partial_cmp(&eur), None);
1924
1925        let custom = Amount::new(100, CurrencyUnit::Custom("BTC".into()));
1926        assert_eq!(sat.partial_cmp(&custom), None);
1927    }
1928
1929    /// Tests that comparison operators return false when comparing different units.
1930    /// Since partial_cmp returns None, all comparisons should be false.
1931    #[test]
1932    fn test_typed_amount_comparison_operators_different_units() {
1933        let sat = Amount::new(100, CurrencyUnit::Sat);
1934        let msat = Amount::new(50, CurrencyUnit::Msat);
1935
1936        // When partial_cmp returns None:
1937        // - > returns false
1938        // - < returns false
1939        // - >= returns false
1940        // - <= returns false
1941
1942        assert!(!(sat > msat));
1943        assert!(!(sat < msat));
1944        assert!(!(sat >= msat));
1945        assert!(!(sat <= msat));
1946
1947        assert!(!(msat > sat));
1948        assert!(!(msat < sat));
1949        assert!(!(msat >= sat));
1950        assert!(!(msat <= sat));
1951
1952        // Even with same value, different units should return false
1953        let sat100 = Amount::new(100, CurrencyUnit::Sat);
1954        let msat100 = Amount::new(100, CurrencyUnit::Msat);
1955
1956        assert!(!(sat100 > msat100));
1957        assert!(!(sat100 < msat100));
1958        assert!(!(sat100 >= msat100));
1959        assert!(!(sat100 <= msat100));
1960    }
1961
1962    /// Tests that Amount<()> (untyped) has total ordering and implements Ord.
1963    #[test]
1964    fn test_untyped_amount_has_total_ordering() {
1965        use std::cmp::Ordering;
1966
1967        let a: Amount<()> = Amount::from(50);
1968        let b: Amount<()> = Amount::from(100);
1969        let c: Amount<()> = Amount::from(50);
1970
1971        // Ord::cmp is available for Amount<()>
1972        assert_eq!(a.cmp(&b), Ordering::Less);
1973        assert_eq!(b.cmp(&a), Ordering::Greater);
1974        assert_eq!(a.cmp(&c), Ordering::Equal);
1975
1976        // PartialOrd returns Some (total ordering)
1977        assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
1978        assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
1979        assert_eq!(a.partial_cmp(&c), Some(Ordering::Equal));
1980    }
1981
1982    /// Tests that Amount<()> can be sorted (requires Ord).
1983    #[test]
1984    fn test_untyped_amount_sorting() {
1985        let mut amounts: Vec<Amount<()>> = vec![
1986            Amount::from(100),
1987            Amount::from(25),
1988            Amount::from(75),
1989            Amount::from(50),
1990        ];
1991
1992        amounts.sort();
1993
1994        assert_eq!(
1995            amounts,
1996            vec![
1997                Amount::from(25),
1998                Amount::from(50),
1999                Amount::from(75),
2000                Amount::from(100),
2001            ]
2002        );
2003    }
2004
2005    #[test]
2006    fn test_amount_currency_unit_to_i64() {
2007        let amount = Amount::new(100, CurrencyUnit::Sat);
2008        assert_eq!(amount.to_i64(), Some(100));
2009
2010        let amount = Amount::new(i64::MAX as u64, CurrencyUnit::Sat);
2011        assert_eq!(amount.to_i64(), Some(i64::MAX));
2012
2013        let amount = Amount::new(i64::MAX as u64 + 1, CurrencyUnit::Sat);
2014        assert_eq!(amount.to_i64(), None);
2015
2016        let amount = Amount::new(0, CurrencyUnit::Sat);
2017        assert_eq!(amount.to_i64(), Some(0));
2018
2019        let amount = Amount::new(1, CurrencyUnit::Sat);
2020        assert_eq!(amount.to_i64(), Some(1));
2021    }
2022
2023    #[test]
2024    fn test_display_with_unit() {
2025        let amount = Amount::new(100, CurrencyUnit::Sat);
2026        assert_eq!(amount.display_with_unit(), "100 sat");
2027
2028        let amount = Amount::new(50, CurrencyUnit::Msat);
2029        assert_eq!(amount.display_with_unit(), "50 msat");
2030
2031        let amount = Amount::new(100, CurrencyUnit::Usd);
2032        assert_eq!(amount.display_with_unit(), "100 usd");
2033
2034        let amount = Amount::new(123, CurrencyUnit::Custom("BTC".to_string()));
2035        assert_eq!(amount.display_with_unit(), "123 btc");
2036    }
2037
2038    #[test]
2039    fn test_amount_add_operator() {
2040        let a = Amount::from(100);
2041        let b = Amount::from(50);
2042        let sum = a + b;
2043        assert_eq!(sum, Amount::from(150));
2044        assert_ne!(sum, Amount::ZERO);
2045    }
2046
2047    /// Tests that saturating_sub correctly subtracts amounts without underflow.
2048    ///
2049    /// This is critical for any saturating subtraction operations. If it returns
2050    /// Default::default() (Amount::ZERO) always, or changes the comparison or
2051    /// subtraction operation, calculations will be wrong.
2052    ///
2053    /// Mutant testing: Kills mutations that:
2054    /// - Replace saturating_sub with Default::default()
2055    /// - Replace > with ==, <, or >= in the comparison
2056    /// - Replace - with + or / in the subtraction
2057    #[test]
2058    fn test_saturating_sub_normal_case() {
2059        // Normal subtraction: larger - smaller
2060        let amount1 = Amount::from(100);
2061        let amount2 = Amount::from(30);
2062        let result = amount1.saturating_sub(amount2);
2063        assert_eq!(result, Amount::from(70));
2064        assert_ne!(result, Amount::ZERO);
2065
2066        // Another normal case
2067        let amount1 = Amount::from(1000);
2068        let amount2 = Amount::from(1);
2069        let result = amount1.saturating_sub(amount2);
2070        assert_eq!(result, Amount::from(999));
2071
2072        // Edge case: subtraction resulting in 1
2073        let amount1 = Amount::from(2);
2074        let amount2 = Amount::from(1);
2075        let result = amount1.saturating_sub(amount2);
2076        assert_eq!(result, Amount::from(1));
2077        assert_ne!(result, Amount::ZERO);
2078    }
2079
2080    /// Tests that saturating_sub returns ZERO when subtracting a larger amount.
2081    ///
2082    /// This catches mutations that change the comparison operator (>, ==, <, >=)
2083    /// or that don't return ZERO on underflow.
2084    #[test]
2085    fn test_saturating_sub_saturates_at_zero() {
2086        // Subtracting larger from smaller should return ZERO
2087        let amount1 = Amount::from(30);
2088        let amount2 = Amount::from(100);
2089        let result = amount1.saturating_sub(amount2);
2090        assert_eq!(result, Amount::ZERO);
2091        assert_ne!(result, Amount::from(30)); // Should not be the original value
2092
2093        // Another case
2094        let amount1 = Amount::from(5);
2095        let amount2 = Amount::from(10);
2096        let result = amount1.saturating_sub(amount2);
2097        assert_eq!(result, Amount::ZERO);
2098
2099        // Edge case: subtracting from zero
2100        let amount1 = Amount::ZERO;
2101        let amount2 = Amount::from(1);
2102        let result = amount1.saturating_sub(amount2);
2103        assert_eq!(result, Amount::ZERO);
2104    }
2105
2106    /// Tests that saturating_sub returns ZERO when amounts are equal.
2107    ///
2108    /// This is a boundary case that catches comparison operator mutations.
2109    #[test]
2110    fn test_saturating_sub_equal_amounts() {
2111        // Equal amounts should return ZERO (other > self is false when equal)
2112        let amount1 = Amount::from(100);
2113        let amount2 = Amount::from(100);
2114        let result = amount1.saturating_sub(amount2);
2115        assert_eq!(result, Amount::ZERO);
2116
2117        // Another equal case
2118        let amount1 = Amount::from(1);
2119        let amount2 = Amount::from(1);
2120        let result = amount1.saturating_sub(amount2);
2121        assert_eq!(result, Amount::ZERO);
2122
2123        // Edge case: both zero
2124        let result = Amount::ZERO.saturating_sub(Amount::ZERO);
2125        assert_eq!(result, Amount::ZERO);
2126    }
2127}