amount.rs 42 KB

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