amount.rs 68 KB

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