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 using the available denominations
199    ///
200    /// Uses a greedy algorithm starting from the largest denomination,
201    /// taking as many of each denomination as possible before moving
202    /// to the next smaller one.
203    ///
204    /// Returns an error if the amount cannot be fully represented
205    /// with the available denominations.
206    pub fn split(&self, fee_and_amounts: &FeeAndAmounts) -> Result<Vec<Self>, Error> {
207        let parts: Vec<Self> = fee_and_amounts
208            .amounts
209            .iter()
210            .rev()
211            .fold((Vec::new(), self.value), |(mut acc, total), &amount| {
212                let count = total / amount;
213                for _ in 0..count {
214                    acc.push(Self::from(amount));
215                }
216                (acc, total % amount)
217            })
218            .0;
219
220        let sum: u64 = parts.iter().map(|a| a.value).sum();
221        if sum != self.value {
222            return Err(Error::CannotSplitAmount(self.value, sum));
223        }
224
225        Ok(parts)
226    }
227
228    /// Split into parts that are powers of two by target
229    pub fn split_targeted(
230        &self,
231        target: &SplitTarget,
232        fee_and_amounts: &FeeAndAmounts,
233    ) -> Result<Vec<Self>, Error> {
234        let mut parts = match target {
235            SplitTarget::None => self.split(fee_and_amounts)?,
236            SplitTarget::Value(amount) => {
237                if self.le(amount) {
238                    return self.split(fee_and_amounts);
239                }
240
241                let mut parts_total = Amount::ZERO;
242                let mut parts = Vec::new();
243
244                // The powers of two that are need to create target value
245                let parts_of_value = amount.split(fee_and_amounts)?;
246
247                while parts_total.lt(self) {
248                    for part in parts_of_value.iter().copied() {
249                        if (part.checked_add(parts_total).ok_or(Error::AmountOverflow)?).le(self) {
250                            parts.push(part);
251                        } else {
252                            let amount_left =
253                                self.checked_sub(parts_total).ok_or(Error::AmountOverflow)?;
254                            parts.extend(amount_left.split(fee_and_amounts)?);
255                        }
256
257                        parts_total = Amount::try_sum(parts.clone().iter().copied())?;
258
259                        if parts_total.eq(self) {
260                            break;
261                        }
262                    }
263                }
264
265                parts
266            }
267            SplitTarget::Values(values) => {
268                let values_total: Amount = Amount::try_sum(values.clone())?;
269
270                match self.cmp(&values_total) {
271                    Ordering::Equal => values.clone(),
272                    Ordering::Less => {
273                        return Err(Error::SplitValuesGreater);
274                    }
275                    Ordering::Greater => {
276                        let extra = self
277                            .checked_sub(values_total)
278                            .ok_or(Error::AmountOverflow)?;
279                        let mut extra_amount = extra.split(fee_and_amounts)?;
280                        let mut values = values.clone();
281
282                        values.append(&mut extra_amount);
283                        values
284                    }
285                }
286            }
287        };
288
289        parts.sort();
290        Ok(parts)
291    }
292
293    /// Splits amount into powers of two while accounting for the swap fee
294    pub fn split_with_fee(&self, fee_and_amounts: &FeeAndAmounts) -> Result<Vec<Self>, Error> {
295        let without_fee_amounts = self.split(fee_and_amounts)?;
296        let total_fee_ppk = fee_and_amounts
297            .fee
298            .checked_mul(without_fee_amounts.len() as u64)
299            .ok_or(Error::AmountOverflow)?;
300        let fee = Amount::from(total_fee_ppk.div_ceil(1000));
301        let new_amount = self.checked_add(fee).ok_or(Error::AmountOverflow)?;
302
303        let split = new_amount.split(fee_and_amounts)?;
304        let split_fee_ppk = (split.len() as u64)
305            .checked_mul(fee_and_amounts.fee)
306            .ok_or(Error::AmountOverflow)?;
307        let split_fee = Amount::from(split_fee_ppk.div_ceil(1000));
308
309        if let Some(net_amount) = new_amount.checked_sub(split_fee) {
310            if net_amount >= *self {
311                return Ok(split);
312            }
313        }
314        self.checked_add(Amount::ONE)
315            .ok_or(Error::AmountOverflow)?
316            .split_with_fee(fee_and_amounts)
317    }
318
319    /// Checked addition for Amount. Returns None if overflow occurs.
320    pub fn checked_add(self, other: Amount<()>) -> Option<Amount<()>> {
321        self.value
322            .checked_add(other.value)
323            .map(|v| Amount { value: v, unit: () })
324    }
325
326    /// Checked subtraction for Amount. Returns None if overflow occurs.
327    pub fn checked_sub(self, other: Amount<()>) -> Option<Amount<()>> {
328        self.value
329            .checked_sub(other.value)
330            .map(|v| Amount { value: v, unit: () })
331    }
332
333    /// Checked multiplication for Amount. Returns None if overflow occurs.
334    pub fn checked_mul(self, other: Amount<()>) -> Option<Amount<()>> {
335        self.value
336            .checked_mul(other.value)
337            .map(|v| Amount { value: v, unit: () })
338    }
339
340    /// Checked division for Amount. Returns None if overflow occurs.
341    pub fn checked_div(self, other: Amount<()>) -> Option<Amount<()>> {
342        self.value
343            .checked_div(other.value)
344            .map(|v| Amount { value: v, unit: () })
345    }
346
347    /// Subtracts `other` from `self`, returning zero if the result would be negative.
348    pub fn saturating_sub(self, other: Self) -> Self {
349        if other > self {
350            Self::ZERO
351        } else {
352            self - other
353        }
354    }
355
356    /// Try sum to check for overflow
357    pub fn try_sum<I>(iter: I) -> Result<Self, Error>
358    where
359        I: IntoIterator<Item = Self>,
360    {
361        iter.into_iter().try_fold(Amount::ZERO, |acc, x| {
362            acc.checked_add(x).ok_or(Error::AmountOverflow)
363        })
364    }
365
366    /// Convert unit
367    pub fn convert_unit(
368        &self,
369        current_unit: &CurrencyUnit,
370        target_unit: &CurrencyUnit,
371    ) -> Result<Amount<()>, Error> {
372        Amount::new(self.value, current_unit.clone())
373            .convert_to(target_unit)
374            .map(Into::into)
375    }
376
377    /// Convert to u64
378    pub fn to_u64(self) -> u64 {
379        self.value
380    }
381
382    /// Convert to i64
383    pub fn to_i64(self) -> Option<i64> {
384        if self.value <= i64::MAX as u64 {
385            Some(self.value as i64)
386        } else {
387            None
388        }
389    }
390
391    /// Create from i64, returning None if negative
392    pub fn from_i64(value: i64) -> Option<Self> {
393        if value >= 0 {
394            Some(Amount {
395                value: value as u64,
396                unit: (),
397            })
398        } else {
399            None
400        }
401    }
402}
403
404impl Default for Amount<()> {
405    fn default() -> Self {
406        Amount::ZERO
407    }
408}
409
410impl Default for &Amount<()> {
411    fn default() -> Self {
412        &Amount::ZERO
413    }
414}
415
416impl Amount<CurrencyUnit> {
417    /// Create a new Amount with an explicit unit
418    ///
419    /// This is the primary constructor for typed amounts. It works with all
420    /// CurrencyUnit variants including Custom.
421    ///
422    /// # Example
423    /// ```
424    /// # use cashu::{Amount, nuts::CurrencyUnit};
425    /// let sat_amount = Amount::new(1000, CurrencyUnit::Sat);
426    /// let custom = Amount::new(50, CurrencyUnit::Custom("BTC".into()));
427    /// ```
428    pub fn new(value: u64, unit: CurrencyUnit) -> Self {
429        Self { value, unit }
430    }
431
432    /// Get the numeric value
433    ///
434    /// # Example
435    /// ```
436    /// # use cashu::{Amount, nuts::CurrencyUnit};
437    /// let amount = Amount::new(1000, CurrencyUnit::Sat);
438    /// assert_eq!(amount.value(), 1000);
439    /// ```
440    pub fn value(&self) -> u64 {
441        self.value
442    }
443
444    /// Convert to u64
445    pub fn to_u64(self) -> u64 {
446        self.value
447    }
448
449    /// Convert to i64
450    pub fn to_i64(self) -> Option<i64> {
451        if self.value <= i64::MAX as u64 {
452            Some(self.value as i64)
453        } else {
454            None
455        }
456    }
457
458    /// Get a reference to the unit
459    ///
460    /// # Example
461    /// ```
462    /// # use cashu::{Amount, nuts::CurrencyUnit};
463    /// let amount = Amount::new(1000, CurrencyUnit::Sat);
464    /// assert_eq!(amount.unit(), &CurrencyUnit::Sat);
465    /// ```
466    pub fn unit(&self) -> &CurrencyUnit {
467        &self.unit
468    }
469
470    /// Consume self and return both value and unit
471    ///
472    /// # Example
473    /// ```
474    /// # use cashu::{Amount, nuts::CurrencyUnit};
475    /// let amount = Amount::new(1000, CurrencyUnit::Sat);
476    /// let (value, unit) = amount.into_parts();
477    /// assert_eq!(value, 1000);
478    /// assert_eq!(unit, CurrencyUnit::Sat);
479    /// ```
480    pub fn into_parts(self) -> (u64, CurrencyUnit) {
481        (self.value, self.unit)
482    }
483
484    /// Checked addition with unit verification
485    ///
486    /// Returns an error if units don't match or if overflow occurs.
487    ///
488    /// # Example
489    /// ```
490    /// # use cashu::{Amount, nuts::CurrencyUnit};
491    /// let a = Amount::new(100, CurrencyUnit::Sat);
492    /// let b = Amount::new(50, CurrencyUnit::Sat);
493    /// let sum = a.checked_add(&b).unwrap();
494    /// assert_eq!(sum.value(), 150);
495    ///
496    /// // Different units cause an error
497    /// let c = Amount::new(100, CurrencyUnit::Msat);
498    /// assert!(a.checked_add(&c).is_err());
499    /// ```
500    pub fn checked_add(&self, other: &Self) -> Result<Self, Error> {
501        if self.unit != other.unit {
502            return Err(Error::UnitMismatch(self.unit.clone(), other.unit.clone()));
503        }
504        self.value
505            .checked_add(other.value)
506            .map(|v| Amount::new(v, self.unit.clone()))
507            .ok_or(Error::AmountOverflow)
508    }
509
510    /// Checked subtraction with unit verification
511    ///
512    /// Returns an error if units don't match or if underflow occurs.
513    ///
514    /// # Example
515    /// ```
516    /// # use cashu::{Amount, nuts::CurrencyUnit};
517    /// let a = Amount::new(100, CurrencyUnit::Sat);
518    /// let b = Amount::new(30, CurrencyUnit::Sat);
519    /// let diff = a.checked_sub(&b).unwrap();
520    /// assert_eq!(diff.value(), 70);
521    /// ```
522    pub fn checked_sub(&self, other: &Self) -> Result<Self, Error> {
523        if self.unit != other.unit {
524            return Err(Error::UnitMismatch(self.unit.clone(), other.unit.clone()));
525        }
526        self.value
527            .checked_sub(other.value)
528            .map(|v| Amount::new(v, self.unit.clone()))
529            .ok_or(Error::AmountOverflow)
530    }
531
532    /// Convert to a different unit
533    ///
534    /// # Example
535    /// ```
536    /// # use cashu::{Amount, nuts::CurrencyUnit};
537    /// let sat = Amount::new(1000, CurrencyUnit::Sat);
538    /// let msat = sat.convert_to(&CurrencyUnit::Msat).unwrap();
539    /// assert_eq!(msat.value(), 1_000_000);
540    /// assert_eq!(msat.unit(), &CurrencyUnit::Msat);
541    /// ```
542    pub fn convert_to(&self, target_unit: &CurrencyUnit) -> Result<Self, Error> {
543        if &self.unit == target_unit {
544            return Ok(self.clone());
545        }
546
547        let converted_value = match (&self.unit, target_unit) {
548            (CurrencyUnit::Sat, CurrencyUnit::Msat) => self
549                .value
550                .checked_mul(MSAT_IN_SAT)
551                .ok_or(Error::AmountOverflow)?,
552            (CurrencyUnit::Msat, CurrencyUnit::Sat) => self.value / MSAT_IN_SAT,
553            _ => return Err(Error::CannotConvertUnits),
554        };
555
556        Ok(Amount::new(converted_value, target_unit.clone()))
557    }
558
559    /// Returns a string representation that includes the unit
560    pub fn display_with_unit(&self) -> String {
561        format!("{} {}", self.value, self.unit)
562    }
563
564    /// Convert to millisatoshis and return the raw u64 value
565    ///
566    /// Returns an error if the unit cannot be converted to Msat
567    /// (i.e., the unit is not Sat or Msat).
568    pub fn to_msat(&self) -> Result<u64, Error> {
569        self.convert_to(&CurrencyUnit::Msat).map(|a| a.value())
570    }
571
572    /// Convert to satoshis and return the raw u64 value
573    ///
574    /// Returns an error if the unit cannot be converted to Sat
575    /// (i.e., the unit is not Sat or Msat).
576    pub fn to_sat(&self) -> Result<u64, Error> {
577        self.convert_to(&CurrencyUnit::Sat).map(|a| a.value())
578    }
579}
580
581impl<U> fmt::Display for Amount<U> {
582    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
583        if let Some(width) = f.width() {
584            write!(f, "{:width$}", self.value, width = width)
585        } else {
586            write!(f, "{}", self.value)
587        }
588    }
589}
590
591impl From<u64> for Amount<()> {
592    fn from(value: u64) -> Self {
593        Amount { value, unit: () }
594    }
595}
596
597impl From<&u64> for Amount<()> {
598    fn from(value: &u64) -> Self {
599        Amount {
600            value: *value,
601            unit: (),
602        }
603    }
604}
605
606impl From<Amount<()>> for u64 {
607    fn from(value: Amount<()>) -> Self {
608        value.value
609    }
610}
611
612impl From<Amount<CurrencyUnit>> for Amount<()> {
613    fn from(value: Amount<CurrencyUnit>) -> Self {
614        Amount {
615            value: value.value,
616            unit: (),
617        }
618    }
619}
620
621impl AsRef<u64> for Amount<()> {
622    fn as_ref(&self) -> &u64 {
623        &self.value
624    }
625}
626
627impl std::ops::Add for Amount<()> {
628    type Output = Amount<()>;
629
630    fn add(self, rhs: Amount<()>) -> Self::Output {
631        self.checked_add(rhs)
632            .expect("Addition overflow: the sum of the amounts exceeds the maximum value")
633    }
634}
635
636impl std::ops::AddAssign for Amount<()> {
637    fn add_assign(&mut self, rhs: Self) {
638        *self = self
639            .checked_add(rhs)
640            .expect("AddAssign overflow: the sum of the amounts exceeds the maximum value");
641    }
642}
643
644impl std::ops::Sub for Amount<()> {
645    type Output = Amount<()>;
646
647    fn sub(self, rhs: Amount<()>) -> Self::Output {
648        self.checked_sub(rhs)
649            .expect("Subtraction underflow: cannot subtract a larger amount from a smaller amount")
650    }
651}
652
653impl std::ops::SubAssign for Amount<()> {
654    fn sub_assign(&mut self, other: Self) {
655        *self = self
656            .checked_sub(other)
657            .expect("SubAssign underflow: cannot subtract a larger amount from a smaller amount");
658    }
659}
660
661impl std::ops::Mul for Amount<()> {
662    type Output = Self;
663
664    fn mul(self, other: Self) -> Self::Output {
665        self.checked_mul(other)
666            .expect("Multiplication overflow: the product of the amounts exceeds the maximum value")
667    }
668}
669
670impl std::ops::Div for Amount<()> {
671    type Output = Self;
672
673    fn div(self, other: Self) -> Self::Output {
674        self.checked_div(other)
675            .expect("Division error: cannot divide by zero or overflow occurred")
676    }
677}
678
679/// Convert offer to amount in unit
680pub fn amount_for_offer(offer: &Offer, unit: &CurrencyUnit) -> Result<Amount, Error> {
681    let offer_amount = offer.amount().ok_or(Error::AmountUndefined)?;
682
683    let (amount, currency) = match offer_amount {
684        lightning::offers::offer::Amount::Bitcoin { amount_msats } => {
685            (amount_msats, CurrencyUnit::Msat)
686        }
687        lightning::offers::offer::Amount::Currency {
688            iso4217_code,
689            amount,
690        } => (
691            amount,
692            CurrencyUnit::from_str(&String::from_utf8(iso4217_code.as_bytes().to_vec())?)
693                .map_err(|_| Error::CannotConvertUnits)?,
694        ),
695    };
696
697    Amount::new(amount, currency)
698        .convert_to(unit)
699        .map(Into::into)
700        .map_err(|_err| Error::CannotConvertUnits)
701}
702
703/// Kinds of targeting that are supported
704#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
705pub enum SplitTarget {
706    /// Default target; least amount of proofs
707    #[default]
708    None,
709    /// Target amount for wallet to have most proofs that add up to value
710    Value(Amount),
711    /// Specific amounts to split into **MUST** equal amount being split
712    Values(Vec<Amount>),
713}
714
715/// Msats in sat
716pub const MSAT_IN_SAT: u64 = 1000;
717
718#[cfg(test)]
719mod tests {
720    use super::*;
721
722    #[test]
723    fn test_split_amount() {
724        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
725
726        assert_eq!(
727            Amount::from(1).split(&fee_and_amounts).unwrap(),
728            vec![Amount::from(1)]
729        );
730        assert_eq!(
731            Amount::from(2).split(&fee_and_amounts).unwrap(),
732            vec![Amount::from(2)]
733        );
734        assert_eq!(
735            Amount::from(3).split(&fee_and_amounts).unwrap(),
736            vec![Amount::from(2), Amount::from(1)]
737        );
738        let amounts: Vec<Amount> = [8, 2, 1].iter().map(|a| Amount::from(*a)).collect();
739        assert_eq!(Amount::from(11).split(&fee_and_amounts).unwrap(), amounts);
740        let amounts: Vec<Amount> = [128, 64, 32, 16, 8, 4, 2, 1]
741            .iter()
742            .map(|a| Amount::from(*a))
743            .collect();
744        assert_eq!(Amount::from(255).split(&fee_and_amounts).unwrap(), amounts);
745    }
746
747    #[test]
748    fn test_split_target_amount() {
749        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
750        let amount = Amount::from(65);
751
752        let split = amount
753            .split_targeted(&SplitTarget::Value(Amount::from(32)), &fee_and_amounts)
754            .unwrap();
755        assert_eq!(
756            vec![Amount::from(1), Amount::from(32), Amount::from(32)],
757            split
758        );
759
760        let amount = Amount::from(150);
761
762        let split = amount
763            .split_targeted(&SplitTarget::Value(Amount::from(50)), &fee_and_amounts)
764            .unwrap();
765        assert_eq!(
766            vec![
767                Amount::from(2),
768                Amount::from(2),
769                Amount::from(2),
770                Amount::from(16),
771                Amount::from(16),
772                Amount::from(16),
773                Amount::from(32),
774                Amount::from(32),
775                Amount::from(32)
776            ],
777            split
778        );
779
780        let amount = Amount::from(63);
781
782        let split = amount
783            .split_targeted(&SplitTarget::Value(Amount::from(32)), &fee_and_amounts)
784            .unwrap();
785        assert_eq!(
786            vec![
787                Amount::from(1),
788                Amount::from(2),
789                Amount::from(4),
790                Amount::from(8),
791                Amount::from(16),
792                Amount::from(32)
793            ],
794            split
795        );
796    }
797
798    #[test]
799    fn test_split_with_fee() {
800        let fee_and_amounts = (1, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
801        let amount = Amount::from(2);
802
803        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
804        assert_eq!(split, vec![Amount::from(2), Amount::from(1)]);
805
806        let amount = Amount::from(3);
807
808        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
809        assert_eq!(split, vec![Amount::from(4)]);
810
811        let amount = Amount::from(3);
812        let fee_and_amounts = (1000, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
813
814        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
815        // With fee_ppk=1000 (100%), amount 3 requires proofs totaling at least 5
816        // to cover both the amount (3) and fees (~2 for 2 proofs)
817        assert_eq!(split, vec![Amount::from(4), Amount::from(1)]);
818    }
819
820    #[test]
821    fn test_split_with_fee_reported_issue() {
822        let fee_and_amounts = (100, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
823        // Test the reported issue: mint 600, send 300 with fee_ppk=100
824        let amount = Amount::from(300);
825
826        let split = amount.split_with_fee(&fee_and_amounts).unwrap();
827
828        // Calculate the total fee for the split
829        let total_fee_ppk = (split.len() as u64) * fee_and_amounts.fee;
830        let total_fee = Amount::from(total_fee_ppk.div_ceil(1000));
831
832        // The split should cover the amount plus fees
833        let split_total = Amount::try_sum(split.iter().copied()).unwrap();
834        assert!(
835            split_total >= amount.checked_add(total_fee).unwrap(),
836            "Split total {} should be >= amount {} + fee {}",
837            split_total,
838            amount,
839            total_fee
840        );
841    }
842
843    #[test]
844    fn test_split_with_fee_edge_cases() {
845        // Test various amounts with fee_ppk=100
846        let test_cases = vec![
847            (Amount::from(1), 100),
848            (Amount::from(10), 100),
849            (Amount::from(50), 100),
850            (Amount::from(100), 100),
851            (Amount::from(200), 100),
852            (Amount::from(300), 100),
853            (Amount::from(500), 100),
854            (Amount::from(600), 100),
855            (Amount::from(1000), 100),
856            (Amount::from(1337), 100),
857            (Amount::from(5000), 100),
858        ];
859
860        for (amount, fee_ppk) in test_cases {
861            let fee_and_amounts =
862                (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
863            let result = amount.split_with_fee(&fee_and_amounts);
864            assert!(
865                result.is_ok(),
866                "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
867                amount,
868                fee_ppk,
869                result.err()
870            );
871
872            let split = result.unwrap();
873
874            // Verify the split covers the required amount
875            let split_total = Amount::try_sum(split.iter().copied()).unwrap();
876            let fee_for_split = (split.len() as u64) * fee_ppk;
877            let total_fee = Amount::from(fee_for_split.div_ceil(1000));
878
879            // The net amount after fees should be at least the original amount
880            let net_amount = split_total.checked_sub(total_fee);
881            assert!(
882                net_amount.is_some(),
883                "Net amount calculation failed for amount {} with fee_ppk {}",
884                amount,
885                fee_ppk
886            );
887            assert!(
888                net_amount.unwrap() >= amount,
889                "Net amount {} is less than required {} for amount {} with fee_ppk {}",
890                net_amount.unwrap(),
891                amount,
892                amount,
893                fee_ppk
894            );
895        }
896    }
897
898    #[test]
899    fn test_split_with_fee_high_fees() {
900        // Test with very high fees
901        let test_cases = vec![
902            (Amount::from(10), 500),  // 50% fee
903            (Amount::from(10), 1000), // 100% fee
904            (Amount::from(10), 2000), // 200% fee
905            (Amount::from(100), 500),
906            (Amount::from(100), 1000),
907            (Amount::from(100), 2000),
908        ];
909
910        for (amount, fee_ppk) in test_cases {
911            let fee_and_amounts =
912                (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
913            let result = amount.split_with_fee(&fee_and_amounts);
914            assert!(
915                result.is_ok(),
916                "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
917                amount,
918                fee_ppk,
919                result.err()
920            );
921
922            let split = result.unwrap();
923            let split_total = Amount::try_sum(split.iter().copied()).unwrap();
924
925            // With high fees, we just need to ensure we can cover the amount
926            assert!(
927                split_total > amount,
928                "Split total {} should be greater than amount {} for fee_ppk {}",
929                split_total,
930                amount,
931                fee_ppk
932            );
933        }
934    }
935
936    #[test]
937    fn test_split_with_fee_recursion_limit() {
938        // Test that the recursion doesn't go infinite
939        // This tests the edge case where the method keeps adding Amount::ONE
940        let amount = Amount::from(1);
941        let fee_ppk = 10000;
942        let fee_and_amounts = (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
943
944        let result = amount.split_with_fee(&fee_and_amounts);
945        assert!(
946            result.is_ok(),
947            "split_with_fee should handle extreme fees without infinite recursion"
948        );
949    }
950
951    #[test]
952    fn test_split_values() {
953        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
954        let amount = Amount::from(10);
955
956        let target = vec![Amount::from(2), Amount::from(4), Amount::from(4)];
957
958        let split_target = SplitTarget::Values(target.clone());
959
960        let values = amount
961            .split_targeted(&split_target, &fee_and_amounts)
962            .unwrap();
963
964        assert_eq!(target, values);
965
966        let target = vec![Amount::from(2), Amount::from(4), Amount::from(4)];
967
968        let split_target = SplitTarget::Values(vec![Amount::from(2), Amount::from(4)]);
969
970        let values = amount
971            .split_targeted(&split_target, &fee_and_amounts)
972            .unwrap();
973
974        assert_eq!(target, values);
975
976        let split_target = SplitTarget::Values(vec![Amount::from(2), Amount::from(10)]);
977
978        let values = amount.split_targeted(&split_target, &fee_and_amounts);
979
980        assert!(values.is_err())
981    }
982
983    #[test]
984    #[should_panic]
985    fn test_amount_addition() {
986        let amount_one: Amount = u64::MAX.into();
987        let amount_two: Amount = 1.into();
988
989        let amounts = vec![amount_one, amount_two];
990
991        let _total: Amount = Amount::try_sum(amounts).unwrap();
992    }
993
994    #[test]
995    fn test_try_amount_addition() {
996        let amount_one: Amount = u64::MAX.into();
997        let amount_two: Amount = 1.into();
998
999        let amounts = vec![amount_one, amount_two];
1000
1001        let total = Amount::try_sum(amounts);
1002
1003        assert!(total.is_err());
1004        let amount_one: Amount = 10000.into();
1005        let amount_two: Amount = 1.into();
1006
1007        let amounts = vec![amount_one, amount_two];
1008        let total = Amount::try_sum(amounts).unwrap();
1009
1010        assert_eq!(total, 10001.into());
1011    }
1012
1013    #[test]
1014    fn test_amount_convert_to() {
1015        // Sat -> Msat
1016        let amount = Amount::new(1000, CurrencyUnit::Sat);
1017        let converted = amount.convert_to(&CurrencyUnit::Msat).unwrap();
1018        assert_eq!(converted.value(), 1000000);
1019        assert_eq!(converted.unit(), &CurrencyUnit::Msat);
1020
1021        // Msat -> Sat
1022        let amount = Amount::new(1000, CurrencyUnit::Msat);
1023        let converted = amount.convert_to(&CurrencyUnit::Sat).unwrap();
1024        assert_eq!(converted.value(), 1);
1025        assert_eq!(converted.unit(), &CurrencyUnit::Sat);
1026
1027        // Usd -> Usd identity conversion
1028        let amount = Amount::new(1, CurrencyUnit::Usd);
1029        let converted = amount.convert_to(&CurrencyUnit::Usd).unwrap();
1030        assert_eq!(converted.value(), 1);
1031        assert_eq!(converted.unit(), &CurrencyUnit::Usd);
1032
1033        // Eur -> Eur identity conversion
1034        let amount = Amount::new(1, CurrencyUnit::Eur);
1035        let converted = amount.convert_to(&CurrencyUnit::Eur).unwrap();
1036        assert_eq!(converted.value(), 1);
1037        assert_eq!(converted.unit(), &CurrencyUnit::Eur);
1038
1039        // Sat -> Eur should fail (no conversion path)
1040        let amount = Amount::new(1, CurrencyUnit::Sat);
1041        let converted = amount.convert_to(&CurrencyUnit::Eur);
1042        assert!(converted.is_err());
1043
1044        // Sat -> Sat identity conversion
1045        let amount = Amount::new(500, CurrencyUnit::Sat);
1046        let converted = amount.convert_to(&CurrencyUnit::Sat).unwrap();
1047        assert_eq!(converted.value(), 500);
1048        assert_eq!(converted.unit(), &CurrencyUnit::Sat);
1049
1050        // Msat -> Msat identity conversion
1051        let amount = Amount::new(5000, CurrencyUnit::Msat);
1052        let converted = amount.convert_to(&CurrencyUnit::Msat).unwrap();
1053        assert_eq!(converted.value(), 5000);
1054        assert_eq!(converted.unit(), &CurrencyUnit::Msat);
1055    }
1056
1057    #[test]
1058    fn test_amount_to_msat() {
1059        let sat_amount = Amount::new(2, CurrencyUnit::Sat);
1060        assert_eq!(sat_amount.to_msat().unwrap(), 2000);
1061
1062        let msat_amount = Amount::new(2500, CurrencyUnit::Msat);
1063        assert_eq!(msat_amount.to_msat().unwrap(), 2500);
1064
1065        let usd_amount = Amount::new(1, CurrencyUnit::Usd);
1066        assert!(usd_amount.to_msat().is_err());
1067    }
1068
1069    #[test]
1070    fn test_amount_to_sat() {
1071        let msat_amount = Amount::new(2000, CurrencyUnit::Msat);
1072        assert_eq!(msat_amount.to_sat().unwrap(), 2);
1073
1074        let sat_amount = Amount::new(5, CurrencyUnit::Sat);
1075        assert_eq!(sat_amount.to_sat().unwrap(), 5);
1076
1077        let usd_amount = Amount::new(1, CurrencyUnit::Usd);
1078        assert!(usd_amount.to_sat().is_err());
1079    }
1080
1081    #[test]
1082    fn test_amount_from_typed_to_untyped() {
1083        // Test From<Amount<CurrencyUnit>> for Amount<()>
1084        let typed = Amount::new(1000, CurrencyUnit::Sat);
1085        let untyped: Amount<()> = typed.into();
1086        assert_eq!(u64::from(untyped), 1000);
1087    }
1088
1089    /// Tests that the subtraction operator correctly computes the difference between amounts.
1090    ///
1091    /// This test verifies that the `-` operator for Amount produces the expected result.
1092    /// It's particularly important because the subtraction operation is used in critical
1093    /// code paths like `split_targeted`, where incorrect subtraction could lead to
1094    /// infinite loops or wrong calculations.
1095    ///
1096    /// Mutant testing: Catches mutations that replace the subtraction implementation
1097    /// with `Default::default()` (returning Amount::ZERO), which would cause infinite
1098    /// loops in `split_targeted` at line 138 where `*self - parts_total` is computed.
1099    #[test]
1100    fn test_amount_sub_operator() {
1101        let amount1 = Amount::from(100);
1102        let amount2 = Amount::from(30);
1103
1104        let result = amount1.checked_sub(amount2).unwrap();
1105        assert_eq!(result, Amount::from(70));
1106
1107        let amount1 = Amount::from(1000);
1108        let amount2 = Amount::from(1);
1109
1110        let result = amount1.checked_sub(amount2).unwrap();
1111        assert_eq!(result, Amount::from(999));
1112
1113        let amount1 = Amount::from(255);
1114        let amount2 = Amount::from(128);
1115
1116        let result = amount1.checked_sub(amount2).unwrap();
1117        assert_eq!(result, Amount::from(127));
1118    }
1119
1120    /// Tests that the subtraction operator panics when attempting to subtract
1121    /// a larger amount from a smaller amount (underflow).
1122    ///
1123    /// This test verifies the safety property that Amount subtraction will panic
1124    /// rather than wrap around on underflow. This is critical for preventing
1125    /// bugs where negative amounts could be interpreted as very large positive amounts.
1126    ///
1127    /// Mutant testing: Catches mutations that remove the panic behavior or return
1128    /// default values instead of properly handling underflow.
1129    #[test]
1130    #[should_panic(expected = "Subtraction underflow")]
1131    fn test_amount_sub_underflow() {
1132        let amount1 = Amount::from(30);
1133        let amount2 = Amount::from(100);
1134
1135        let _result = amount1 - amount2;
1136    }
1137
1138    /// Tests that checked_add correctly computes the sum and returns the actual value.
1139    ///
1140    /// This is critical because checked_add is used in recursive functions like
1141    /// split_with_fee. If it returns Some(Amount::ZERO) instead of the actual sum,
1142    /// the recursion would never terminate.
1143    ///
1144    /// Mutant testing: Kills mutations that replace the implementation with
1145    /// `Some(Default::default())`, which would cause infinite loops in split_with_fee
1146    /// at line 198 where it recursively calls itself with incremented amounts.
1147    #[test]
1148    fn test_checked_add_returns_correct_value() {
1149        let amount1 = Amount::from(100);
1150        let amount2 = Amount::from(50);
1151
1152        let result = amount1.checked_add(amount2);
1153        assert_eq!(result, Some(Amount::from(150)));
1154
1155        let amount1 = Amount::from(1);
1156        let amount2 = Amount::from(1);
1157
1158        let result = amount1.checked_add(amount2);
1159        assert_eq!(result, Some(Amount::from(2)));
1160        assert_ne!(result, Some(Amount::ZERO));
1161
1162        let amount1 = Amount::from(1000);
1163        let amount2 = Amount::from(337);
1164
1165        let result = amount1.checked_add(amount2);
1166        assert_eq!(result, Some(Amount::from(1337)));
1167    }
1168
1169    /// Tests that checked_add returns None on overflow.
1170    #[test]
1171    fn test_checked_add_overflow() {
1172        let amount1 = Amount::from(u64::MAX);
1173        let amount2 = Amount::from(1);
1174
1175        let result = amount1.checked_add(amount2);
1176        assert!(result.is_none());
1177    }
1178
1179    /// Tests that try_sum correctly computes the total sum of amounts.
1180    ///
1181    /// This is critical because try_sum is used in loops like split_targeted at line 130
1182    /// to track progress. If it returns Ok(Amount::ZERO) instead of the actual sum,
1183    /// the loop condition `parts_total.eq(self)` would never be true, causing an infinite loop.
1184    ///
1185    /// Mutant testing: Kills mutations that replace the implementation with
1186    /// `Ok(Default::default())`, which would cause infinite loops.
1187    #[test]
1188    fn test_try_sum_returns_correct_value() {
1189        let amounts = vec![Amount::from(10), Amount::from(20), Amount::from(30)];
1190        let result = Amount::try_sum(amounts).unwrap();
1191        assert_eq!(result, Amount::from(60));
1192        assert_ne!(result, Amount::ZERO);
1193
1194        let amounts = vec![Amount::from(1), Amount::from(1), Amount::from(1)];
1195        let result = Amount::try_sum(amounts).unwrap();
1196        assert_eq!(result, Amount::from(3));
1197
1198        let amounts = vec![Amount::from(100)];
1199        let result = Amount::try_sum(amounts).unwrap();
1200        assert_eq!(result, Amount::from(100));
1201
1202        let empty: Vec<Amount> = vec![];
1203        let result = Amount::try_sum(empty).unwrap();
1204        assert_eq!(result, Amount::ZERO);
1205    }
1206
1207    /// Tests that try_sum returns error on overflow.
1208    #[test]
1209    fn test_try_sum_overflow() {
1210        let amounts = vec![Amount::from(u64::MAX), Amount::from(1)];
1211        let result = Amount::try_sum(amounts);
1212        assert!(result.is_err());
1213    }
1214
1215    /// Tests that split returns a non-empty vec with actual values, not defaults.
1216    ///
1217    /// The split function is used in split_targeted's while loop (line 122).
1218    /// If split returns an empty vec or vec with Amount::ZERO when it shouldn't,
1219    /// the loop that extends parts with split results would never make progress,
1220    /// causing an infinite loop.
1221    ///
1222    /// Mutant testing: Kills mutations that replace split with `vec![]` or
1223    /// `vec![Default::default()]` which would cause infinite loops.
1224    #[test]
1225    fn test_split_returns_correct_values() {
1226        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
1227
1228        let amount = Amount::from(11);
1229        let result = amount.split(&fee_and_amounts).unwrap();
1230        assert!(!result.is_empty());
1231        assert_eq!(Amount::try_sum(result.iter().copied()).unwrap(), amount);
1232
1233        let amount = Amount::from(255);
1234        let result = amount.split(&fee_and_amounts).unwrap();
1235        assert!(!result.is_empty());
1236        assert_eq!(Amount::try_sum(result.iter().copied()).unwrap(), amount);
1237
1238        let amount = Amount::from(7);
1239        let result = amount.split(&fee_and_amounts).unwrap();
1240        assert_eq!(
1241            result,
1242            vec![Amount::from(4), Amount::from(2), Amount::from(1)]
1243        );
1244        for r in &result {
1245            assert_ne!(*r, Amount::ZERO);
1246        }
1247    }
1248
1249    /// Tests that split produces correct decomposition for standard keysets.
1250    ///
1251    /// Verifies that the greedy algorithm correctly decomposes amounts
1252    /// using the available denominations.
1253    #[test]
1254    fn test_split_modulo_operation() {
1255        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
1256
1257        let amount = Amount::from(15);
1258        let result = amount.split(&fee_and_amounts).unwrap();
1259
1260        assert_eq!(
1261            result,
1262            vec![
1263                Amount::from(8),
1264                Amount::from(4),
1265                Amount::from(2),
1266                Amount::from(1)
1267            ]
1268        );
1269
1270        let total = Amount::try_sum(result.iter().copied()).unwrap();
1271        assert_eq!(total, amount);
1272    }
1273
1274    /// Tests that split returns an error when the amount cannot be represented
1275    /// with the available denominations.
1276    #[test]
1277    fn test_split_cannot_represent_amount() {
1278        // Only denomination 32 available
1279        let fee_and_amounts: FeeAndAmounts = (0, vec![32]).into();
1280
1281        // 100 cannot be exactly represented: 100 / 32 = 3, remainder 4
1282        // Result: [32, 32, 32] = 96, missing 4
1283        let amount = Amount::from(100);
1284        let result = amount.split(&fee_and_amounts);
1285        assert!(result.is_err());
1286        match result {
1287            Err(Error::CannotSplitAmount(requested, got)) => {
1288                assert_eq!(requested, 100);
1289                assert_eq!(got, 96); // Three 32s = 96, remainder 4 cannot be represented
1290            }
1291            _ => panic!("Expected CannotSplitAmount error"),
1292        }
1293
1294        // 32 can be exactly represented
1295        let amount = Amount::from(32);
1296        let result = amount.split(&fee_and_amounts);
1297        assert!(result.is_ok());
1298        assert_eq!(result.unwrap(), vec![Amount::from(32)]);
1299
1300        // 64 can now be represented as two 32s
1301        let amount = Amount::from(64);
1302        let result = amount.split(&fee_and_amounts);
1303        assert!(result.is_ok());
1304        assert_eq!(result.unwrap(), vec![Amount::from(32), Amount::from(32)]);
1305
1306        // Missing denominations: only have 32 and 64, trying to split 100
1307        // 100 / 64 = 1, remainder 36
1308        // 36 / 32 = 1, remainder 4
1309        // Result: [64, 32] = 96, missing 4
1310        let fee_and_amounts: FeeAndAmounts = (0, vec![32, 64]).into();
1311        let amount = Amount::from(100);
1312        let result = amount.split(&fee_and_amounts);
1313        assert!(result.is_err());
1314        match result {
1315            Err(Error::CannotSplitAmount(requested, got)) => {
1316                assert_eq!(requested, 100);
1317                assert_eq!(got, 96);
1318            }
1319            _ => panic!("Expected CannotSplitAmount error"),
1320        }
1321    }
1322
1323    #[test]
1324    fn test_split_amount_exceeds_keyset_capacity() {
1325        // Keyset with only denomination 32 — amount 100 leaves remainder 4
1326        // which cannot be represented, so split fails
1327        let fee_and_amounts: FeeAndAmounts = (0, vec![32]).into();
1328
1329        let amount = Amount::from(100);
1330        let result = amount.split(&fee_and_amounts);
1331        assert!(result.is_err());
1332        match result {
1333            Err(Error::CannotSplitAmount(requested, got)) => {
1334                assert_eq!(requested, 100);
1335                // 100 / 32 = 3 with remainder 4, so got = 96
1336                assert_eq!(got, 96);
1337            }
1338            _ => panic!("Expected CannotSplitAmount error, got {:?}", result),
1339        }
1340
1341        // Keyset {4, 16} — amount 50 leaves remainder 2
1342        // 50 / 16 = 3 remainder 2, 2 / 4 = 0 → got = 48
1343        let fee_and_amounts: FeeAndAmounts = (0, vec![4, 16]).into();
1344        let amount = Amount::from(50);
1345        let result = amount.split(&fee_and_amounts);
1346        assert!(result.is_err());
1347        match result {
1348            Err(Error::CannotSplitAmount(requested, got)) => {
1349                assert_eq!(requested, 50);
1350                assert_eq!(got, 48);
1351            }
1352            _ => panic!("Expected CannotSplitAmount error, got {:?}", result),
1353        }
1354    }
1355
1356    /// Tests that From<u64> correctly converts values to Amount.
1357    ///
1358    /// This conversion is used throughout the codebase including in loops and split operations.
1359    /// If it returns Default::default() (Amount::ZERO) instead of the actual value,
1360    /// it can cause infinite loops where amounts are being accumulated or compared.
1361    ///
1362    /// Mutant testing: Kills mutations that replace From<u64> with `Default::default()`.
1363    #[test]
1364    fn test_from_u64_returns_correct_value() {
1365        let amount = Amount::from(100u64);
1366        assert_eq!(amount, Amount::from(100));
1367        assert_ne!(amount, Amount::ZERO);
1368
1369        let amount = Amount::from(1u64);
1370        assert_eq!(amount, Amount::from(1));
1371        assert_eq!(amount, Amount::ONE);
1372
1373        let amount = Amount::from(1337u64);
1374        assert_eq!(amount.to_u64(), 1337);
1375    }
1376
1377    /// Tests that checked_mul returns the correct product value.
1378    ///
1379    /// This is critical for any multiplication operations. If it returns None
1380    /// or Some(Amount::ZERO) instead of the actual product, calculations will be wrong.
1381    ///
1382    /// Mutant testing: Kills mutations that replace checked_mul with None or Some(Default::default()).
1383    #[test]
1384    fn test_checked_mul_returns_correct_value() {
1385        let amount1 = Amount::from(10);
1386        let amount2 = Amount::from(5);
1387        let result = amount1.checked_mul(amount2);
1388        assert_eq!(result, Some(Amount::from(50)));
1389        assert_ne!(result, None);
1390        assert_ne!(result, Some(Amount::ZERO));
1391
1392        let amount1 = Amount::from(100);
1393        let amount2 = Amount::from(20);
1394        let result = amount1.checked_mul(amount2);
1395        assert_eq!(result, Some(Amount::from(2000)));
1396        assert_ne!(result, Some(Amount::ZERO));
1397
1398        let amount1 = Amount::from(7);
1399        let amount2 = Amount::from(13);
1400        let result = amount1.checked_mul(amount2);
1401        assert_eq!(result, Some(Amount::from(91)));
1402
1403        // Test multiplication by zero
1404        let amount1 = Amount::from(100);
1405        let amount2 = Amount::ZERO;
1406        let result = amount1.checked_mul(amount2);
1407        assert_eq!(result, Some(Amount::ZERO));
1408
1409        // Test multiplication by one
1410        let amount1 = Amount::from(42);
1411        let amount2 = Amount::ONE;
1412        let result = amount1.checked_mul(amount2);
1413        assert_eq!(result, Some(Amount::from(42)));
1414
1415        // Test overflow
1416        let amount1 = Amount::from(u64::MAX);
1417        let amount2 = Amount::from(2);
1418        let result = amount1.checked_mul(amount2);
1419        assert!(result.is_none());
1420    }
1421
1422    /// Tests that checked_div returns the correct quotient value.
1423    ///
1424    /// This is critical for division operations. If it returns None or
1425    /// Some(Amount::ZERO) instead of the actual quotient, calculations will be wrong.
1426    ///
1427    /// Mutant testing: Kills mutations that replace checked_div with None or Some(Default::default()).
1428    #[test]
1429    fn test_checked_div_returns_correct_value() {
1430        let amount1 = Amount::from(100);
1431        let amount2 = Amount::from(5);
1432        let result = amount1.checked_div(amount2);
1433        assert_eq!(result, Some(Amount::from(20)));
1434        assert_ne!(result, None);
1435        assert_ne!(result, Some(Amount::ZERO));
1436
1437        let amount1 = Amount::from(1000);
1438        let amount2 = Amount::from(10);
1439        let result = amount1.checked_div(amount2);
1440        assert_eq!(result, Some(Amount::from(100)));
1441        assert_ne!(result, Some(Amount::ZERO));
1442
1443        let amount1 = Amount::from(91);
1444        let amount2 = Amount::from(7);
1445        let result = amount1.checked_div(amount2);
1446        assert_eq!(result, Some(Amount::from(13)));
1447
1448        // Test division by one
1449        let amount1 = Amount::from(42);
1450        let amount2 = Amount::ONE;
1451        let result = amount1.checked_div(amount2);
1452        assert_eq!(result, Some(Amount::from(42)));
1453
1454        // Test integer division (truncation)
1455        let amount1 = Amount::from(10);
1456        let amount2 = Amount::from(3);
1457        let result = amount1.checked_div(amount2);
1458        assert_eq!(result, Some(Amount::from(3)));
1459
1460        // Test division by zero
1461        let amount1 = Amount::from(100);
1462        let amount2 = Amount::ZERO;
1463        let result = amount1.checked_div(amount2);
1464        assert!(result.is_none());
1465    }
1466
1467    /// Tests that Amount::convert_unit returns the correct converted value.
1468    ///
1469    /// This is critical for unit conversions. If it returns Ok(Amount::ZERO)
1470    /// instead of the actual converted value, all conversions will be wrong.
1471    ///
1472    /// Mutant testing: Kills mutations that replace convert_unit with Ok(Default::default()).
1473    #[test]
1474    fn test_convert_unit_returns_correct_value() {
1475        let amount = Amount::from(1000);
1476        let result = amount
1477            .convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Msat)
1478            .unwrap();
1479        assert_eq!(result, Amount::from(1_000_000));
1480        assert_ne!(result, Amount::ZERO);
1481
1482        let amount = Amount::from(5000);
1483        let result = amount
1484            .convert_unit(&CurrencyUnit::Msat, &CurrencyUnit::Sat)
1485            .unwrap();
1486        assert_eq!(result, Amount::from(5));
1487        assert_ne!(result, Amount::ZERO);
1488
1489        let amount = Amount::from(123);
1490        let result = amount
1491            .convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Sat)
1492            .unwrap();
1493        assert_eq!(result, Amount::from(123));
1494
1495        let amount = Amount::from(456);
1496        let result = amount
1497            .convert_unit(&CurrencyUnit::Usd, &CurrencyUnit::Usd)
1498            .unwrap();
1499        assert_eq!(result, Amount::from(456));
1500
1501        let amount = Amount::from(789);
1502        let result = amount
1503            .convert_unit(&CurrencyUnit::Eur, &CurrencyUnit::Eur)
1504            .unwrap();
1505        assert_eq!(result, Amount::from(789));
1506
1507        // Test invalid conversion
1508        let amount = Amount::from(100);
1509        let result = amount.convert_unit(&CurrencyUnit::Sat, &CurrencyUnit::Eur);
1510        assert!(result.is_err());
1511    }
1512
1513    /// Tests that Amount::to_i64() returns the correct value.
1514    ///
1515    /// Mutant testing: Kills mutations that replace the return value with:
1516    /// - None
1517    /// - Some(0)
1518    /// - Some(1)
1519    /// - Some(-1)
1520    ///
1521    /// Also catches mutation that replaces <= with > in the comparison.
1522    #[test]
1523    fn test_amount_to_i64_returns_correct_value() {
1524        // Test with value 100 (catches None, Some(0), Some(1), Some(-1) mutations)
1525        let amount = Amount::from(100);
1526        let result = amount.to_i64();
1527        assert_eq!(result, Some(100));
1528        assert!(result.is_some());
1529        assert_ne!(result, Some(0));
1530        assert_ne!(result, Some(1));
1531        assert_ne!(result, Some(-1));
1532
1533        // Test with value 1000 (catches all constant mutations)
1534        let amount = Amount::from(1000);
1535        let result = amount.to_i64();
1536        assert_eq!(result, Some(1000));
1537        assert_ne!(result, None);
1538        assert_ne!(result, Some(0));
1539        assert_ne!(result, Some(1));
1540        assert_ne!(result, Some(-1));
1541
1542        // Test with value 2 (specifically catches Some(1) mutation)
1543        let amount = Amount::from(2);
1544        let result = amount.to_i64();
1545        assert_eq!(result, Some(2));
1546        assert_ne!(result, Some(1));
1547
1548        // Test with i64::MAX (should return Some(i64::MAX))
1549        // This catches the <= vs > mutation: if <= becomes >, this would return None
1550        let amount = Amount::from(i64::MAX as u64);
1551        let result = amount.to_i64();
1552        assert_eq!(result, Some(i64::MAX));
1553        assert!(result.is_some());
1554
1555        // Test with i64::MAX + 1 (should return None)
1556        // This is the boundary case for the <= comparison
1557        let amount = Amount::from(i64::MAX as u64 + 1);
1558        let result = amount.to_i64();
1559        assert!(result.is_none());
1560
1561        // Test with u64::MAX (should return None)
1562        let amount = Amount::from(u64::MAX);
1563        let result = amount.to_i64();
1564        assert!(result.is_none());
1565
1566        // Edge case: 0 should return Some(0)
1567        let amount = Amount::from(0);
1568        let result = amount.to_i64();
1569        assert_eq!(result, Some(0));
1570
1571        // Edge case: 1 should return Some(1)
1572        let amount = Amount::from(1);
1573        let result = amount.to_i64();
1574        assert_eq!(result, Some(1));
1575    }
1576
1577    /// Tests the boundary condition for Amount::to_i64() at i64::MAX.
1578    ///
1579    /// This specifically tests the <= vs > mutation in the condition
1580    /// `if self.0 <= i64::MAX as u64`.
1581    #[test]
1582    fn test_amount_to_i64_boundary() {
1583        // Exactly at i64::MAX - should succeed
1584        let at_max = Amount::from(i64::MAX as u64);
1585        assert!(at_max.to_i64().is_some());
1586        assert_eq!(at_max.to_i64().unwrap(), i64::MAX);
1587
1588        // One above i64::MAX - should fail
1589        let above_max = Amount::from(i64::MAX as u64 + 1);
1590        assert!(above_max.to_i64().is_none());
1591
1592        // One below i64::MAX - should succeed
1593        let below_max = Amount::from(i64::MAX as u64 - 1);
1594        assert!(below_max.to_i64().is_some());
1595        assert_eq!(below_max.to_i64().unwrap(), i64::MAX - 1);
1596    }
1597
1598    /// Tests Amount::from_i64 returns the correct value.
1599    ///
1600    /// Mutant testing: Catches mutations that:
1601    /// - Replace return with None
1602    /// - Replace return with Some(Default::default())
1603    /// - Replace >= with < in the condition
1604    #[test]
1605    fn test_amount_from_i64() {
1606        // Positive value - should return Some with correct value
1607        let result = Amount::from_i64(100);
1608        assert!(result.is_some());
1609        assert_eq!(result.unwrap(), Amount::from(100));
1610        assert_ne!(result, None);
1611        assert_ne!(result, Some(Amount::ZERO));
1612
1613        // Zero - boundary case for >= vs <
1614        // If >= becomes <, this would return None instead of Some
1615        let result = Amount::from_i64(0);
1616        assert!(result.is_some());
1617        assert_eq!(result.unwrap(), Amount::ZERO);
1618
1619        // Negative value - should return None
1620        let result = Amount::from_i64(-1);
1621        assert!(result.is_none());
1622
1623        let result = Amount::from_i64(-100);
1624        assert!(result.is_none());
1625
1626        // Large positive value
1627        let result = Amount::from_i64(i64::MAX);
1628        assert!(result.is_some());
1629        assert_eq!(result.unwrap(), Amount::from(i64::MAX as u64));
1630        assert_ne!(result, Some(Amount::ZERO));
1631
1632        // Value 1 - catches Some(Default::default()) mutation
1633        let result = Amount::from_i64(1);
1634        assert!(result.is_some());
1635        assert_eq!(result.unwrap(), Amount::ONE);
1636        assert_ne!(result, Some(Amount::ZERO));
1637    }
1638
1639    /// Tests AddAssign actually modifies the value.
1640    ///
1641    /// Mutant testing: Catches mutation that replaces add_assign with ().
1642    #[test]
1643    fn test_add_assign() {
1644        let mut amount = Amount::from(100);
1645        amount += Amount::from(50);
1646        assert_eq!(amount, Amount::from(150));
1647        assert_ne!(amount, Amount::from(100)); // Should have changed
1648
1649        let mut amount = Amount::from(1);
1650        amount += Amount::from(1);
1651        assert_eq!(amount, Amount::from(2));
1652        assert_ne!(amount, Amount::ONE); // Should have changed
1653
1654        let mut amount = Amount::ZERO;
1655        amount += Amount::from(42);
1656        assert_eq!(amount, Amount::from(42));
1657        assert_ne!(amount, Amount::ZERO); // Should have changed
1658    }
1659
1660    /// Tests SubAssign actually modifies the value.
1661    ///
1662    /// Mutant testing: Catches mutation that replaces sub_assign with ().
1663    #[test]
1664    fn test_sub_assign() {
1665        let mut amount = Amount::from(100);
1666        amount -= Amount::from(30);
1667        assert_eq!(amount, Amount::from(70));
1668        assert_ne!(amount, Amount::from(100)); // Should have changed
1669
1670        let mut amount = Amount::from(50);
1671        amount -= Amount::from(1);
1672        assert_eq!(amount, Amount::from(49));
1673        assert_ne!(amount, Amount::from(50)); // Should have changed
1674
1675        let mut amount = Amount::from(10);
1676        amount -= Amount::from(10);
1677        assert_eq!(amount, Amount::ZERO);
1678        assert_ne!(amount, Amount::from(10)); // Should have changed
1679    }
1680
1681    // Phase 2 tests: Amount<CurrencyUnit> methods
1682
1683    #[test]
1684    fn test_amount_with_currency_unit() {
1685        let amount = Amount::new(1000, CurrencyUnit::Sat);
1686        assert_eq!(amount.value(), 1000);
1687        assert_eq!(amount.unit(), &CurrencyUnit::Sat);
1688    }
1689
1690    #[test]
1691    fn test_amount_new_with_custom_unit() {
1692        let custom_unit = CurrencyUnit::Custom("BTC".to_string());
1693        let amount = Amount::new(50, custom_unit.clone());
1694
1695        assert_eq!(amount.value(), 50);
1696        assert_eq!(amount.unit(), &custom_unit);
1697    }
1698
1699    #[test]
1700    fn test_amount_into_parts() {
1701        let amount = Amount::new(1234, CurrencyUnit::Msat);
1702        let (value, unit) = amount.into_parts();
1703
1704        assert_eq!(value, 1234);
1705        assert_eq!(unit, CurrencyUnit::Msat);
1706    }
1707
1708    #[test]
1709    fn test_amount_with_unit_conversion() {
1710        let untyped: Amount<()> = Amount::from(100);
1711        let typed = untyped.with_unit(CurrencyUnit::Sat);
1712
1713        assert_eq!(typed.value(), 100);
1714        assert_eq!(typed.unit(), &CurrencyUnit::Sat);
1715    }
1716
1717    #[test]
1718    fn test_amount_with_unit_all_variants() {
1719        let untyped = Amount::from(500);
1720
1721        let sat = untyped.with_unit(CurrencyUnit::Sat);
1722        assert_eq!(sat.unit(), &CurrencyUnit::Sat);
1723
1724        let msat = untyped.with_unit(CurrencyUnit::Msat);
1725        assert_eq!(msat.unit(), &CurrencyUnit::Msat);
1726
1727        let usd = untyped.with_unit(CurrencyUnit::Usd);
1728        assert_eq!(usd.unit(), &CurrencyUnit::Usd);
1729
1730        let eur = untyped.with_unit(CurrencyUnit::Eur);
1731        assert_eq!(eur.unit(), &CurrencyUnit::Eur);
1732
1733        let custom = untyped.with_unit(CurrencyUnit::Custom("TEST".into()));
1734        assert_eq!(custom.unit(), &CurrencyUnit::Custom("TEST".into()));
1735    }
1736
1737    #[test]
1738    fn test_typed_amount_is_clone_not_copy() {
1739        let amount = Amount::new(100, CurrencyUnit::Sat);
1740        let cloned = amount.clone();
1741        // If this compiles, Clone works. Cannot test Copy directly without moving.
1742        assert_eq!(cloned.value(), 100);
1743        assert_eq!(cloned.unit(), &CurrencyUnit::Sat);
1744    }
1745
1746    // Phase 3 tests: Protocol types verification
1747
1748    #[test]
1749    fn test_untyped_amount_is_copy() {
1750        // Verify Amount<()> is Copy (required for protocol types)
1751        let amount: Amount<()> = Amount::from(100);
1752        let copy1 = amount;
1753        let copy2 = amount; // Should not move - verifies Copy
1754        assert_eq!(copy1, copy2);
1755    }
1756
1757    #[test]
1758    fn test_amount_serialization_transparent() {
1759        // Verify Amount<()> serializes as just the number (protocol compatibility)
1760        let amount = Amount::from(1234);
1761        let json = serde_json::to_string(&amount).unwrap();
1762        assert_eq!(json, "1234");
1763
1764        // Verify deserialization works
1765        let deserialized: Amount<()> = serde_json::from_str(&json).unwrap();
1766        assert_eq!(deserialized, Amount::from(1234));
1767    }
1768
1769    #[test]
1770    fn test_typed_amount_serialization() {
1771        // Verify Amount<CurrencyUnit> also serializes as just the number
1772        let amount = Amount::new(5678, CurrencyUnit::Sat);
1773        let json = serde_json::to_string(&amount).unwrap();
1774        assert_eq!(json, "5678");
1775
1776        // Note: Cannot deserialize Amount<CurrencyUnit> directly
1777        // Unit must come from context (e.g., keyset)
1778    }
1779
1780    #[test]
1781    fn test_protocol_type_pattern() {
1782        // Simulate protocol type usage pattern
1783
1784        // Protocol layer: Amount<()> is Copy and serializes transparently
1785        let protocol_amount: Amount<()> = Amount::from(1000);
1786        let _copied = protocol_amount; // Copy works
1787
1788        // Application layer: Convert to typed when needed
1789        let typed = protocol_amount.with_unit(CurrencyUnit::Sat);
1790        assert_eq!(typed.value(), 1000);
1791
1792        // Back to protocol: Extract value
1793        let back_to_protocol = Amount::from(typed.value());
1794        assert_eq!(back_to_protocol, protocol_amount);
1795    }
1796
1797    // Phase 4 tests: Unit-aware arithmetic
1798
1799    #[test]
1800    fn test_typed_amount_checked_add() {
1801        let a = Amount::new(100, CurrencyUnit::Sat);
1802        let b = Amount::new(50, CurrencyUnit::Sat);
1803
1804        let sum = a.checked_add(&b).unwrap();
1805        assert_eq!(sum.value(), 150);
1806        assert_eq!(sum.unit(), &CurrencyUnit::Sat);
1807    }
1808
1809    #[test]
1810    fn test_typed_amount_add_unit_mismatch() {
1811        let sat = Amount::new(100, CurrencyUnit::Sat);
1812        let msat = Amount::new(100, CurrencyUnit::Msat);
1813
1814        let result = sat.checked_add(&msat);
1815        assert!(result.is_err());
1816
1817        match result.unwrap_err() {
1818            Error::UnitMismatch(u1, u2) => {
1819                assert_eq!(u1, CurrencyUnit::Sat);
1820                assert_eq!(u2, CurrencyUnit::Msat);
1821            }
1822            _ => panic!("Expected UnitMismatch error"),
1823        }
1824    }
1825
1826    #[test]
1827    fn test_typed_amount_checked_sub() {
1828        let a = Amount::new(100, CurrencyUnit::Sat);
1829        let b = Amount::new(30, CurrencyUnit::Sat);
1830
1831        let diff = a.checked_sub(&b).unwrap();
1832        assert_eq!(diff.value(), 70);
1833        assert_eq!(diff.unit(), &CurrencyUnit::Sat);
1834    }
1835
1836    #[test]
1837    fn test_typed_amount_sub_unit_mismatch() {
1838        let sat = Amount::new(100, CurrencyUnit::Sat);
1839        let usd = Amount::new(30, CurrencyUnit::Usd);
1840
1841        let result = sat.checked_sub(&usd);
1842        assert!(result.is_err());
1843    }
1844
1845    #[test]
1846    fn test_typed_amount_convert_to() {
1847        // Sat to Msat
1848        let sat = Amount::new(1000, CurrencyUnit::Sat);
1849        let msat = sat.convert_to(&CurrencyUnit::Msat).unwrap();
1850        assert_eq!(msat.value(), 1_000_000);
1851        assert_eq!(msat.unit(), &CurrencyUnit::Msat);
1852
1853        // Msat to Sat
1854        let msat = Amount::new(5000, CurrencyUnit::Msat);
1855        let sat = msat.convert_to(&CurrencyUnit::Sat).unwrap();
1856        assert_eq!(sat.value(), 5);
1857        assert_eq!(sat.unit(), &CurrencyUnit::Sat);
1858
1859        // Same unit (optimization check)
1860        let sat = Amount::new(100, CurrencyUnit::Sat);
1861        let same = sat.convert_to(&CurrencyUnit::Sat).unwrap();
1862        assert_eq!(same.value(), 100);
1863        assert_eq!(same.unit(), &CurrencyUnit::Sat);
1864    }
1865
1866    #[test]
1867    fn test_typed_amount_convert_invalid() {
1868        let sat = Amount::new(100, CurrencyUnit::Sat);
1869        let result = sat.convert_to(&CurrencyUnit::Eur);
1870        assert!(result.is_err());
1871
1872        match result.unwrap_err() {
1873            Error::CannotConvertUnits => {}
1874            _ => panic!("Expected CannotConvertUnits error"),
1875        }
1876    }
1877
1878    #[test]
1879    fn test_typed_amount_add_overflow() {
1880        let a = Amount::new(u64::MAX, CurrencyUnit::Sat);
1881        let b = Amount::new(1, CurrencyUnit::Sat);
1882
1883        let result = a.checked_add(&b);
1884        assert!(result.is_err());
1885
1886        match result.unwrap_err() {
1887            Error::AmountOverflow => {}
1888            _ => panic!("Expected AmountOverflow error"),
1889        }
1890    }
1891
1892    #[test]
1893    fn test_typed_amount_sub_underflow() {
1894        let a = Amount::new(50, CurrencyUnit::Sat);
1895        let b = Amount::new(100, CurrencyUnit::Sat);
1896
1897        let result = a.checked_sub(&b);
1898        assert!(result.is_err());
1899
1900        match result.unwrap_err() {
1901            Error::AmountOverflow => {} // Underflow also returns AmountOverflow
1902            _ => panic!("Expected AmountOverflow error"),
1903        }
1904    }
1905
1906    // Phase 5 tests: PartialOrd behavior for Amount<CurrencyUnit>
1907
1908    /// Tests that equality works correctly for typed amounts with the same unit.
1909    #[test]
1910    fn test_typed_amount_equality_same_unit() {
1911        let a = Amount::new(100, CurrencyUnit::Sat);
1912        let b = Amount::new(100, CurrencyUnit::Sat);
1913
1914        assert_eq!(a, b);
1915        assert!(a == b);
1916
1917        let c = Amount::new(50, CurrencyUnit::Sat);
1918        assert_ne!(a, c);
1919        assert!(a != c);
1920    }
1921
1922    /// Tests that equality returns false for typed amounts with different units.
1923    #[test]
1924    fn test_typed_amount_equality_different_units() {
1925        let sat = Amount::new(100, CurrencyUnit::Sat);
1926        let msat = Amount::new(100, CurrencyUnit::Msat);
1927
1928        // Same value, different units - should NOT be equal
1929        assert_ne!(sat, msat);
1930        assert!(sat != msat);
1931
1932        let usd = Amount::new(100, CurrencyUnit::Usd);
1933        assert_ne!(sat, usd);
1934        assert_ne!(msat, usd);
1935    }
1936
1937    /// Tests that comparison operators work correctly for typed amounts with the same unit.
1938    #[test]
1939    fn test_typed_amount_comparison_same_unit() {
1940        let small = Amount::new(50, CurrencyUnit::Sat);
1941        let large = Amount::new(100, CurrencyUnit::Sat);
1942
1943        // Greater than
1944        assert!(large > small);
1945        assert!(small <= large);
1946
1947        // Less than
1948        assert!(small < large);
1949        assert!(large >= small);
1950
1951        // Greater than or equal
1952        assert!(large >= small);
1953        assert!(large >= Amount::new(100, CurrencyUnit::Sat));
1954
1955        // Less than or equal
1956        assert!(small <= large);
1957        assert!(small <= Amount::new(50, CurrencyUnit::Sat));
1958
1959        // partial_cmp returns Some
1960        assert_eq!(large.partial_cmp(&small), Some(std::cmp::Ordering::Greater));
1961        assert_eq!(small.partial_cmp(&large), Some(std::cmp::Ordering::Less));
1962        assert_eq!(
1963            small.partial_cmp(&Amount::new(50, CurrencyUnit::Sat)),
1964            Some(std::cmp::Ordering::Equal)
1965        );
1966    }
1967
1968    /// Tests that partial_cmp returns None for typed amounts with different units.
1969    /// This ensures that comparisons between different units are not accidentally valid.
1970    #[test]
1971    fn test_typed_amount_comparison_different_units_returns_none() {
1972        let sat = Amount::new(100, CurrencyUnit::Sat);
1973        let msat = Amount::new(50, CurrencyUnit::Msat);
1974
1975        // partial_cmp should return None for different units
1976        assert_eq!(sat.partial_cmp(&msat), None);
1977        assert_eq!(msat.partial_cmp(&sat), None);
1978
1979        // Different unit combinations
1980        let usd = Amount::new(100, CurrencyUnit::Usd);
1981        assert_eq!(sat.partial_cmp(&usd), None);
1982        assert_eq!(usd.partial_cmp(&sat), None);
1983
1984        let eur = Amount::new(100, CurrencyUnit::Eur);
1985        assert_eq!(usd.partial_cmp(&eur), None);
1986
1987        let custom = Amount::new(100, CurrencyUnit::Custom("BTC".into()));
1988        assert_eq!(sat.partial_cmp(&custom), None);
1989    }
1990
1991    /// Tests that comparison operators return false when comparing different units.
1992    /// Since partial_cmp returns None, all comparisons should be false.
1993    #[test]
1994    fn test_typed_amount_comparison_operators_different_units() {
1995        let sat = Amount::new(100, CurrencyUnit::Sat);
1996        let msat = Amount::new(50, CurrencyUnit::Msat);
1997
1998        // When partial_cmp returns None:
1999        // - > returns false
2000        // - < returns false
2001        // - >= returns false
2002        // - <= returns false
2003
2004        assert!(sat.partial_cmp(&msat).is_none());
2005        assert!(msat.partial_cmp(&sat).is_none());
2006
2007        // Even with same value, different units should return false
2008        let sat100 = Amount::new(100, CurrencyUnit::Sat);
2009        let msat100 = Amount::new(100, CurrencyUnit::Msat);
2010
2011        assert!(sat100.partial_cmp(&msat100).is_none());
2012    }
2013
2014    /// Tests that Amount<()> (untyped) has total ordering and implements Ord.
2015    #[test]
2016    fn test_untyped_amount_has_total_ordering() {
2017        use std::cmp::Ordering;
2018
2019        let a: Amount<()> = Amount::from(50);
2020        let b: Amount<()> = Amount::from(100);
2021        let c: Amount<()> = Amount::from(50);
2022
2023        // Ord::cmp is available for Amount<()>
2024        assert_eq!(a.cmp(&b), Ordering::Less);
2025        assert_eq!(b.cmp(&a), Ordering::Greater);
2026        assert_eq!(a.cmp(&c), Ordering::Equal);
2027
2028        // PartialOrd returns Some (total ordering)
2029        assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
2030        assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
2031        assert_eq!(a.partial_cmp(&c), Some(Ordering::Equal));
2032    }
2033
2034    /// Tests that Amount<()> can be sorted (requires Ord).
2035    #[test]
2036    fn test_untyped_amount_sorting() {
2037        let mut amounts: Vec<Amount<()>> = vec![
2038            Amount::from(100),
2039            Amount::from(25),
2040            Amount::from(75),
2041            Amount::from(50),
2042        ];
2043
2044        amounts.sort();
2045
2046        assert_eq!(
2047            amounts,
2048            vec![
2049                Amount::from(25),
2050                Amount::from(50),
2051                Amount::from(75),
2052                Amount::from(100),
2053            ]
2054        );
2055    }
2056
2057    #[test]
2058    fn test_amount_currency_unit_to_i64() {
2059        let amount = Amount::new(100, CurrencyUnit::Sat);
2060        assert_eq!(amount.to_i64(), Some(100));
2061
2062        let amount = Amount::new(i64::MAX as u64, CurrencyUnit::Sat);
2063        assert_eq!(amount.to_i64(), Some(i64::MAX));
2064
2065        let amount = Amount::new(i64::MAX as u64 + 1, CurrencyUnit::Sat);
2066        assert_eq!(amount.to_i64(), None);
2067
2068        let amount = Amount::new(0, CurrencyUnit::Sat);
2069        assert_eq!(amount.to_i64(), Some(0));
2070
2071        let amount = Amount::new(1, CurrencyUnit::Sat);
2072        assert_eq!(amount.to_i64(), Some(1));
2073    }
2074
2075    #[test]
2076    fn test_display_with_unit() {
2077        let amount = Amount::new(100, CurrencyUnit::Sat);
2078        assert_eq!(amount.display_with_unit(), "100 sat");
2079
2080        let amount = Amount::new(50, CurrencyUnit::Msat);
2081        assert_eq!(amount.display_with_unit(), "50 msat");
2082
2083        let amount = Amount::new(100, CurrencyUnit::Usd);
2084        assert_eq!(amount.display_with_unit(), "100 usd");
2085
2086        let amount = Amount::new(123, CurrencyUnit::Custom("BTC".to_string()));
2087        assert_eq!(amount.display_with_unit(), "123 btc");
2088    }
2089
2090    #[test]
2091    fn test_amount_add_operator() {
2092        let a = Amount::from(100);
2093        let b = Amount::from(50);
2094        let sum = a + b;
2095        assert_eq!(sum, Amount::from(150));
2096        assert_ne!(sum, Amount::ZERO);
2097    }
2098
2099    /// Tests that saturating_sub correctly subtracts amounts without underflow.
2100    ///
2101    /// This is critical for any saturating subtraction operations. If it returns
2102    /// Default::default() (Amount::ZERO) always, or changes the comparison or
2103    /// subtraction operation, calculations will be wrong.
2104    ///
2105    /// Mutant testing: Kills mutations that:
2106    /// - Replace saturating_sub with Default::default()
2107    /// - Replace > with ==, <, or >= in the comparison
2108    /// - Replace - with + or / in the subtraction
2109    #[test]
2110    fn test_saturating_sub_normal_case() {
2111        // Normal subtraction: larger - smaller
2112        let amount1 = Amount::from(100);
2113        let amount2 = Amount::from(30);
2114        let result = amount1.saturating_sub(amount2);
2115        assert_eq!(result, Amount::from(70));
2116        assert_ne!(result, Amount::ZERO);
2117
2118        // Another normal case
2119        let amount1 = Amount::from(1000);
2120        let amount2 = Amount::from(1);
2121        let result = amount1.saturating_sub(amount2);
2122        assert_eq!(result, Amount::from(999));
2123
2124        // Edge case: subtraction resulting in 1
2125        let amount1 = Amount::from(2);
2126        let amount2 = Amount::from(1);
2127        let result = amount1.saturating_sub(amount2);
2128        assert_eq!(result, Amount::from(1));
2129        assert_ne!(result, Amount::ZERO);
2130    }
2131
2132    /// Tests that saturating_sub returns ZERO when subtracting a larger amount.
2133    ///
2134    /// This catches mutations that change the comparison operator (>, ==, <, >=)
2135    /// or that don't return ZERO on underflow.
2136    #[test]
2137    fn test_saturating_sub_saturates_at_zero() {
2138        // Subtracting larger from smaller should return ZERO
2139        let amount1 = Amount::from(30);
2140        let amount2 = Amount::from(100);
2141        let result = amount1.saturating_sub(amount2);
2142        assert_eq!(result, Amount::ZERO);
2143        assert_ne!(result, Amount::from(30)); // Should not be the original value
2144
2145        // Another case
2146        let amount1 = Amount::from(5);
2147        let amount2 = Amount::from(10);
2148        let result = amount1.saturating_sub(amount2);
2149        assert_eq!(result, Amount::ZERO);
2150
2151        // Edge case: subtracting from zero
2152        let amount1 = Amount::ZERO;
2153        let amount2 = Amount::from(1);
2154        let result = amount1.saturating_sub(amount2);
2155        assert_eq!(result, Amount::ZERO);
2156    }
2157
2158    /// Tests that saturating_sub returns ZERO when amounts are equal.
2159    ///
2160    /// This is a boundary case that catches comparison operator mutations.
2161    #[test]
2162    fn test_saturating_sub_equal_amounts() {
2163        // Equal amounts should return ZERO (other > self is false when equal)
2164        let amount1 = Amount::from(100);
2165        let amount2 = Amount::from(100);
2166        let result = amount1.saturating_sub(amount2);
2167        assert_eq!(result, Amount::ZERO);
2168
2169        // Another equal case
2170        let amount1 = Amount::from(1);
2171        let amount2 = Amount::from(1);
2172        let result = amount1.saturating_sub(amount2);
2173        assert_eq!(result, Amount::ZERO);
2174
2175        // Edge case: both zero
2176        let result = Amount::ZERO.saturating_sub(Amount::ZERO);
2177        assert_eq!(result, Amount::ZERO);
2178    }
2179
2180    /// Tests that saturating_sub handles the edge case where other == self + 1.
2181    ///
2182    /// This catches the mutation where `>` is replaced with `>=`.
2183    /// If `other == self + 1`, then `other > self` is true, so we should return ZERO.
2184    /// But if changed to `other >= self`, it would incorrectly return 1.
2185    #[test]
2186    fn test_saturating_sub_edge_case_other_greater_by_one() {
2187        // When other is exactly one greater than self
2188        let amount1 = Amount::from(10);
2189        let amount2 = Amount::from(11); // amount2 = amount1 + 1
2190        let result = amount1.saturating_sub(amount2);
2191        // Should saturate to ZERO since other > self
2192        assert_eq!(result, Amount::ZERO);
2193
2194        // Another case: subtracting 2 from 1
2195        let amount1 = Amount::from(1);
2196        let amount2 = Amount::from(2);
2197        let result = amount1.saturating_sub(amount2);
2198        assert_eq!(result, Amount::ZERO);
2199
2200        // Edge case with zero: subtracting 1 from 0
2201        let amount1 = Amount::ZERO;
2202        let amount2 = Amount::from(1);
2203        let result = amount1.saturating_sub(amount2);
2204        assert_eq!(result, Amount::ZERO);
2205    }
2206
2207    // =========================================================================
2208    // Tests for restricted/non-standard keysets (denomination reuse required)
2209    // =========================================================================
2210    //
2211    // These tests document the correct behavior for keysets where denominations
2212    // must be used more than once (e.g., a keyset with only denomination 1).
2213    // The current split() algorithm uses modulo which limits each denomination
2214    // to at most one use, causing CannotSplitAmount errors for these cases.
2215
2216    /// Tests split() with a single-denomination keyset {1}.
2217    /// This is the exact scenario from the xsr mint bug where only denomination 1
2218    /// is available. Amounts > 1 require reusing the denomination multiple times.
2219    #[test]
2220    fn test_split_single_denomination_keyset() {
2221        let fee_and_amounts: FeeAndAmounts = (0, vec![1]).into();
2222
2223        // Amount 1: trivially works (single use of denomination 1)
2224        let result = Amount::from(1).split(&fee_and_amounts).unwrap();
2225        assert_eq!(result, vec![Amount::from(1)]);
2226
2227        // Amount 2: requires denomination 1 used twice
2228        let result = Amount::from(2).split(&fee_and_amounts).unwrap();
2229        assert_eq!(result, vec![Amount::from(1), Amount::from(1)]);
2230
2231        // Amount 5: requires denomination 1 used five times
2232        let result = Amount::from(5).split(&fee_and_amounts).unwrap();
2233        assert_eq!(result, vec![Amount::from(1); 5]);
2234
2235        // Amount 10: requires denomination 1 used ten times
2236        let result = Amount::from(10).split(&fee_and_amounts).unwrap();
2237        assert_eq!(result, vec![Amount::from(1); 10]);
2238    }
2239
2240    /// Tests split() when a denomination must be reused with a two-denomination keyset {1, 5}.
2241    /// Amount 10 requires denomination 5 used twice, which the modulo approach cannot do.
2242    #[test]
2243    fn test_split_denomination_reuse_required() {
2244        let fee_and_amounts: FeeAndAmounts = (0, vec![1, 5]).into();
2245
2246        // Amount 5: single use of denomination 5
2247        let result = Amount::from(5).split(&fee_and_amounts).unwrap();
2248        assert_eq!(result, vec![Amount::from(5)]);
2249
2250        // Amount 10: requires denomination 5 used twice (10 % 5 = 0 discards remainder)
2251        let result = Amount::from(10).split(&fee_and_amounts).unwrap();
2252        let total = Amount::try_sum(result.iter().copied()).unwrap();
2253        assert_eq!(total, Amount::from(10));
2254        assert_eq!(result, vec![Amount::from(5), Amount::from(5)]);
2255
2256        // Amount 12: requires [5, 5, 1, 1]
2257        let result = Amount::from(12).split(&fee_and_amounts).unwrap();
2258        let total = Amount::try_sum(result.iter().copied()).unwrap();
2259        assert_eq!(total, Amount::from(12));
2260        assert_eq!(
2261            result,
2262            vec![
2263                Amount::from(5),
2264                Amount::from(5),
2265                Amount::from(1),
2266                Amount::from(1),
2267            ]
2268        );
2269    }
2270
2271    /// Tests split() with non-power-of-two denominations {1, 3, 5}.
2272    /// The greedy algorithm should still produce correct (if not optimal) results.
2273    #[test]
2274    fn test_split_non_power_of_two_keyset() {
2275        let fee_and_amounts: FeeAndAmounts = (0, vec![1, 3, 5]).into();
2276
2277        // Amount 6: greedy takes 5, remainder 1 -> [5, 1]
2278        let result = Amount::from(6).split(&fee_and_amounts).unwrap();
2279        let total = Amount::try_sum(result.iter().copied()).unwrap();
2280        assert_eq!(total, Amount::from(6));
2281
2282        // Amount 9: greedy takes 5, remainder 4, takes 3, remainder 1 -> [5, 3, 1]
2283        let result = Amount::from(9).split(&fee_and_amounts).unwrap();
2284        let total = Amount::try_sum(result.iter().copied()).unwrap();
2285        assert_eq!(total, Amount::from(9));
2286
2287        // Amount 10: greedy takes 5, remainder 5, takes 5 -> [5, 5]
2288        // This fails with modulo because 10 % 5 = 0 after first take
2289        let result = Amount::from(10).split(&fee_and_amounts).unwrap();
2290        let total = Amount::try_sum(result.iter().copied()).unwrap();
2291        assert_eq!(total, Amount::from(10));
2292
2293        // Amount 15: greedy takes 5 three times -> [5, 5, 5]
2294        let result = Amount::from(15).split(&fee_and_amounts).unwrap();
2295        let total = Amount::try_sum(result.iter().copied()).unwrap();
2296        assert_eq!(total, Amount::from(15));
2297    }
2298
2299    /// Tests split() with a sparse keyset {1, 8} that has a gap between denominations.
2300    /// Amounts like 3 require multiple uses of denomination 1.
2301    #[test]
2302    fn test_split_sparse_power_of_two_keyset() {
2303        let fee_and_amounts: FeeAndAmounts = (0, vec![1, 8]).into();
2304
2305        // Amount 3: no denomination 2 or 4, so needs [1, 1, 1]
2306        let result = Amount::from(3).split(&fee_and_amounts).unwrap();
2307        assert_eq!(result, vec![Amount::from(1); 3]);
2308
2309        // Amount 9: takes 8, remainder 1 -> [8, 1]
2310        let result = Amount::from(9).split(&fee_and_amounts).unwrap();
2311        let total = Amount::try_sum(result.iter().copied()).unwrap();
2312        assert_eq!(total, Amount::from(9));
2313        assert_eq!(result, vec![Amount::from(8), Amount::from(1)]);
2314
2315        // Amount 17: needs [8, 8, 1]
2316        let result = Amount::from(17).split(&fee_and_amounts).unwrap();
2317        let total = Amount::try_sum(result.iter().copied()).unwrap();
2318        assert_eq!(total, Amount::from(17));
2319        assert_eq!(
2320            result,
2321            vec![Amount::from(8), Amount::from(8), Amount::from(1)]
2322        );
2323    }
2324
2325    /// Tests split() with a partial power-of-two keyset {1, 4, 16} (missing 2 and 8).
2326    /// Some amounts require denomination reuse to fill gaps.
2327    #[test]
2328    fn test_split_partial_power_of_two_keyset() {
2329        let fee_and_amounts: FeeAndAmounts = (0, vec![1, 4, 16]).into();
2330
2331        // Amount 2: no denomination 2, needs [1, 1]
2332        let result = Amount::from(2).split(&fee_and_amounts).unwrap();
2333        assert_eq!(result, vec![Amount::from(1), Amount::from(1)]);
2334
2335        // Amount 5: takes 4, remainder 1 -> [4, 1]
2336        let result = Amount::from(5).split(&fee_and_amounts).unwrap();
2337        assert_eq!(result, vec![Amount::from(4), Amount::from(1)]);
2338
2339        // Amount 6: takes 4, remainder 2, needs two 1s -> [4, 1, 1]
2340        let result = Amount::from(6).split(&fee_and_amounts).unwrap();
2341        let total = Amount::try_sum(result.iter().copied()).unwrap();
2342        assert_eq!(total, Amount::from(6));
2343        assert_eq!(
2344            result,
2345            vec![Amount::from(4), Amount::from(1), Amount::from(1)]
2346        );
2347
2348        // Amount 20: takes 16, remainder 4 -> [16, 4]
2349        let result = Amount::from(20).split(&fee_and_amounts).unwrap();
2350        assert_eq!(result, vec![Amount::from(16), Amount::from(4)]);
2351
2352        // Amount 8: no denomination 8, needs [4, 4]
2353        let result = Amount::from(8).split(&fee_and_amounts).unwrap();
2354        assert_eq!(result, vec![Amount::from(4), Amount::from(4)]);
2355    }
2356
2357    /// Tests split() with a large amount on a single-denomination keyset {1}.
2358    /// Should produce many proofs of denomination 1.
2359    #[test]
2360    fn test_split_large_amount_single_denomination() {
2361        let fee_and_amounts: FeeAndAmounts = (0, vec![1]).into();
2362
2363        let result = Amount::from(100).split(&fee_and_amounts).unwrap();
2364        assert_eq!(result.len(), 100);
2365        assert!(result.iter().all(|a| *a == Amount::from(1)));
2366        let total = Amount::try_sum(result.iter().copied()).unwrap();
2367        assert_eq!(total, Amount::from(100));
2368    }
2369
2370    /// Tests split() with amount 0. Should return an empty vec since no proofs are needed.
2371    #[test]
2372    fn test_split_zero_amount() {
2373        let fee_and_amounts: FeeAndAmounts = (0, vec![1]).into();
2374
2375        let result = Amount::from(0).split(&fee_and_amounts).unwrap();
2376        assert!(result.is_empty());
2377
2378        // Also with standard keyset
2379        let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
2380        let result = Amount::from(0).split(&fee_and_amounts).unwrap();
2381        assert!(result.is_empty());
2382    }
2383
2384    // =========================================================================
2385    // split_targeted() tests for restricted keysets
2386    // =========================================================================
2387
2388    /// Tests split_targeted() with SplitTarget::None on a single-denomination keyset {1}.
2389    /// Should behave identically to split().
2390    #[test]
2391    fn test_split_targeted_none_single_denomination() {
2392        let fee_and_amounts: FeeAndAmounts = (0, vec![1]).into();
2393
2394        let result = Amount::from(5)
2395            .split_targeted(&SplitTarget::None, &fee_and_amounts)
2396            .unwrap();
2397        assert_eq!(result, vec![Amount::from(1); 5]);
2398
2399        let total = Amount::try_sum(result.iter().copied()).unwrap();
2400        assert_eq!(total, Amount::from(5));
2401    }
2402
2403    /// Tests split_targeted() with SplitTarget::Value(1) on a single-denomination keyset {1}.
2404    /// Requesting proofs of value 1 with only denomination 1 available.
2405    #[test]
2406    fn test_split_targeted_value_single_denomination() {
2407        let fee_and_amounts: FeeAndAmounts = (0, vec![1]).into();
2408
2409        let result = Amount::from(5)
2410            .split_targeted(&SplitTarget::Value(Amount::from(1)), &fee_and_amounts)
2411            .unwrap();
2412        assert_eq!(result, vec![Amount::from(1); 5]);
2413
2414        let total = Amount::try_sum(result.iter().copied()).unwrap();
2415        assert_eq!(total, Amount::from(5));
2416    }
2417
2418    /// Tests split_targeted() with SplitTarget::Values on a single-denomination keyset {1}.
2419    /// Specifying partial values, with the remainder split into 1s.
2420    #[test]
2421    fn test_split_targeted_values_single_denomination() {
2422        let fee_and_amounts: FeeAndAmounts = (0, vec![1]).into();
2423
2424        // Request 3 out of 5 as explicit values, remainder should be split as [1, 1]
2425        let target = SplitTarget::Values(vec![Amount::from(1), Amount::from(1), Amount::from(1)]);
2426        let result = Amount::from(5)
2427            .split_targeted(&target, &fee_and_amounts)
2428            .unwrap();
2429
2430        let total = Amount::try_sum(result.iter().copied()).unwrap();
2431        assert_eq!(total, Amount::from(5));
2432        assert_eq!(result, vec![Amount::from(1); 5]);
2433    }
2434
2435    /// Tests split_targeted() with SplitTarget::Value on a keyset {1, 5}.
2436    /// Requesting proofs of value 5 for amount 15 should produce three 5s.
2437    #[test]
2438    fn test_split_targeted_value_restricted_keyset() {
2439        let fee_and_amounts: FeeAndAmounts = (0, vec![1, 5]).into();
2440
2441        let result = Amount::from(15)
2442            .split_targeted(&SplitTarget::Value(Amount::from(5)), &fee_and_amounts)
2443            .unwrap();
2444
2445        let total = Amount::try_sum(result.iter().copied()).unwrap();
2446        assert_eq!(total, Amount::from(15));
2447        assert_eq!(
2448            result,
2449            vec![Amount::from(5), Amount::from(5), Amount::from(5)]
2450        );
2451    }
2452
2453    // =========================================================================
2454    // split_with_fee() tests for restricted keysets
2455    // =========================================================================
2456
2457    /// Tests split_with_fee() on a single-denomination keyset {1} with fees.
2458    /// Must produce enough proofs to cover the amount plus the per-proof fee.
2459    #[test]
2460    fn test_split_with_fee_single_denomination() {
2461        let fee_and_amounts: FeeAndAmounts = (100, vec![1]).into();
2462
2463        let amount = Amount::from(5);
2464        let result = amount.split_with_fee(&fee_and_amounts).unwrap();
2465
2466        let total = Amount::try_sum(result.iter().copied()).unwrap();
2467        let total_fee_ppk = (result.len() as u64) * fee_and_amounts.fee;
2468        let total_fee = Amount::from(total_fee_ppk.div_ceil(1000));
2469
2470        // The split must cover amount + fees
2471        assert!(
2472            total >= amount.checked_add(total_fee).unwrap(),
2473            "Split total {} should be >= amount {} + fee {}",
2474            total,
2475            amount,
2476            total_fee
2477        );
2478
2479        // All proofs should be denomination 1
2480        assert!(result.iter().all(|a| *a == Amount::from(1)));
2481    }
2482
2483    // =========================================================================
2484    // Regression test for the exact reported bug
2485    // =========================================================================
2486
2487    /// Regression test: xsr mint keyset with only denomination 1.
2488    /// Minting amount 2 failed with CannotSplitAmount(2, 1) because the split
2489    /// algorithm could only use each denomination once via the modulo approach.
2490    #[test]
2491    fn test_split_xsr_mint_keyset_regression() {
2492        // This exactly reproduces the xsr mint's keyset: only one key for denomination 1
2493        let fee_and_amounts: FeeAndAmounts = (0, vec![1]).into();
2494
2495        // This is the exact error case: CannotSplitAmount(2, 1)
2496        let amount = Amount::from(2);
2497        let result = amount.split(&fee_and_amounts);
2498        assert!(
2499            result.is_ok(),
2500            "split(2) with keyset {{1}} should succeed but got: {:?}",
2501            result.err()
2502        );
2503        let proofs = result.unwrap();
2504        assert_eq!(proofs, vec![Amount::from(1), Amount::from(1)]);
2505
2506        // Also verify that a typical mint amount (e.g., 5 xsr) works
2507        let amount = Amount::from(5);
2508        let result = amount.split(&fee_and_amounts);
2509        assert!(
2510            result.is_ok(),
2511            "split(5) with keyset {{1}} should succeed but got: {:?}",
2512            result.err()
2513        );
2514        let proofs = result.unwrap();
2515        assert_eq!(proofs.len(), 5);
2516        assert_eq!(
2517            Amount::try_sum(proofs.iter().copied()).unwrap(),
2518            Amount::from(5)
2519        );
2520    }
2521}