amount.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  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::fmt;
  6. use std::str::FromStr;
  7. use lightning::offers::offer::Offer;
  8. use serde::{Deserialize, Serialize};
  9. use thiserror::Error;
  10. use crate::nuts::CurrencyUnit;
  11. /// Amount Error
  12. #[derive(Debug, Error)]
  13. pub enum Error {
  14. /// Split Values must be less then or equal to amount
  15. #[error("Split Values must be less then or equal to amount")]
  16. SplitValuesGreater,
  17. /// Amount overflow
  18. #[error("Amount Overflow")]
  19. AmountOverflow,
  20. /// Cannot convert units
  21. #[error("Cannot convert units")]
  22. CannotConvertUnits,
  23. /// Invalid amount
  24. #[error("Invalid Amount: {0}")]
  25. InvalidAmount(String),
  26. /// Amount undefined
  27. #[error("Amount undefined")]
  28. AmountUndefined,
  29. /// Utf8 parse error
  30. #[error(transparent)]
  31. Utf8ParseError(#[from] std::string::FromUtf8Error),
  32. }
  33. /// Amount can be any unit
  34. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
  35. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  36. #[serde(transparent)]
  37. pub struct Amount(u64);
  38. impl FromStr for Amount {
  39. type Err = Error;
  40. fn from_str(s: &str) -> Result<Self, Self::Err> {
  41. let value = s
  42. .parse::<u64>()
  43. .map_err(|_| Error::InvalidAmount(s.to_owned()))?;
  44. Ok(Amount(value))
  45. }
  46. }
  47. impl Amount {
  48. /// Amount zero
  49. pub const ZERO: Amount = Amount(0);
  50. /// Amount one
  51. pub const ONE: Amount = Amount(1);
  52. /// Split into parts that are powers of two
  53. pub fn split(&self) -> Vec<Self> {
  54. let sats = self.0;
  55. (0_u64..64)
  56. .rev()
  57. .filter_map(|bit| {
  58. let part = 1 << bit;
  59. ((sats & part) == part).then_some(Self::from(part))
  60. })
  61. .collect()
  62. }
  63. /// Split into parts that are powers of two by target
  64. pub fn split_targeted(&self, target: &SplitTarget) -> Result<Vec<Self>, Error> {
  65. let mut parts = match target {
  66. SplitTarget::None => self.split(),
  67. SplitTarget::Value(amount) => {
  68. if self.le(amount) {
  69. return Ok(self.split());
  70. }
  71. let mut parts_total = Amount::ZERO;
  72. let mut parts = Vec::new();
  73. // The powers of two that are need to create target value
  74. let parts_of_value = amount.split();
  75. while parts_total.lt(self) {
  76. for part in parts_of_value.iter().copied() {
  77. if (part + parts_total).le(self) {
  78. parts.push(part);
  79. } else {
  80. let amount_left = *self - parts_total;
  81. parts.extend(amount_left.split());
  82. }
  83. parts_total = Amount::try_sum(parts.clone().iter().copied())?;
  84. if parts_total.eq(self) {
  85. break;
  86. }
  87. }
  88. }
  89. parts
  90. }
  91. SplitTarget::Values(values) => {
  92. let values_total: Amount = Amount::try_sum(values.clone().into_iter())?;
  93. match self.cmp(&values_total) {
  94. Ordering::Equal => values.clone(),
  95. Ordering::Less => {
  96. return Err(Error::SplitValuesGreater);
  97. }
  98. Ordering::Greater => {
  99. let extra = *self - values_total;
  100. let mut extra_amount = extra.split();
  101. let mut values = values.clone();
  102. values.append(&mut extra_amount);
  103. values
  104. }
  105. }
  106. }
  107. };
  108. parts.sort();
  109. Ok(parts)
  110. }
  111. /// Splits amount into powers of two while accounting for the swap fee
  112. pub fn split_with_fee(&self, fee_ppk: u64) -> Result<Vec<Self>, Error> {
  113. let without_fee_amounts = self.split();
  114. let total_fee_ppk = fee_ppk
  115. .checked_mul(without_fee_amounts.len() as u64)
  116. .ok_or(Error::AmountOverflow)?;
  117. let fee = Amount::from(total_fee_ppk.div_ceil(1000));
  118. let new_amount = self.checked_add(fee).ok_or(Error::AmountOverflow)?;
  119. let split = new_amount.split();
  120. let split_fee_ppk = (split.len() as u64)
  121. .checked_mul(fee_ppk)
  122. .ok_or(Error::AmountOverflow)?;
  123. let split_fee = Amount::from(split_fee_ppk.div_ceil(1000));
  124. if let Some(net_amount) = new_amount.checked_sub(split_fee) {
  125. if net_amount >= *self {
  126. return Ok(split);
  127. }
  128. }
  129. self.checked_add(Amount::ONE)
  130. .ok_or(Error::AmountOverflow)?
  131. .split_with_fee(fee_ppk)
  132. }
  133. /// Checked addition for Amount. Returns None if overflow occurs.
  134. pub fn checked_add(self, other: Amount) -> Option<Amount> {
  135. self.0.checked_add(other.0).map(Amount)
  136. }
  137. /// Checked subtraction for Amount. Returns None if overflow occurs.
  138. pub fn checked_sub(self, other: Amount) -> Option<Amount> {
  139. self.0.checked_sub(other.0).map(Amount)
  140. }
  141. /// Checked multiplication for Amount. Returns None if overflow occurs.
  142. pub fn checked_mul(self, other: Amount) -> Option<Amount> {
  143. self.0.checked_mul(other.0).map(Amount)
  144. }
  145. /// Checked division for Amount. Returns None if overflow occurs.
  146. pub fn checked_div(self, other: Amount) -> Option<Amount> {
  147. self.0.checked_div(other.0).map(Amount)
  148. }
  149. /// Try sum to check for overflow
  150. pub fn try_sum<I>(iter: I) -> Result<Self, Error>
  151. where
  152. I: IntoIterator<Item = Self>,
  153. {
  154. iter.into_iter().try_fold(Amount::ZERO, |acc, x| {
  155. acc.checked_add(x).ok_or(Error::AmountOverflow)
  156. })
  157. }
  158. /// Convert unit
  159. pub fn convert_unit(
  160. &self,
  161. current_unit: &CurrencyUnit,
  162. target_unit: &CurrencyUnit,
  163. ) -> Result<Amount, Error> {
  164. to_unit(self.0, current_unit, target_unit)
  165. }
  166. /// Convert to i64
  167. pub fn to_i64(self) -> Option<i64> {
  168. if self.0 <= i64::MAX as u64 {
  169. Some(self.0 as i64)
  170. } else {
  171. None
  172. }
  173. }
  174. /// Create from i64, returning None if negative
  175. pub fn from_i64(value: i64) -> Option<Self> {
  176. if value >= 0 {
  177. Some(Amount(value as u64))
  178. } else {
  179. None
  180. }
  181. }
  182. }
  183. impl Default for Amount {
  184. fn default() -> Self {
  185. Amount::ZERO
  186. }
  187. }
  188. impl Default for &Amount {
  189. fn default() -> Self {
  190. &Amount::ZERO
  191. }
  192. }
  193. impl fmt::Display for Amount {
  194. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  195. if let Some(width) = f.width() {
  196. write!(f, "{:width$}", self.0, width = width)
  197. } else {
  198. write!(f, "{}", self.0)
  199. }
  200. }
  201. }
  202. impl From<u64> for Amount {
  203. fn from(value: u64) -> Self {
  204. Self(value)
  205. }
  206. }
  207. impl From<&u64> for Amount {
  208. fn from(value: &u64) -> Self {
  209. Self(*value)
  210. }
  211. }
  212. impl From<Amount> for u64 {
  213. fn from(value: Amount) -> Self {
  214. value.0
  215. }
  216. }
  217. impl AsRef<u64> for Amount {
  218. fn as_ref(&self) -> &u64 {
  219. &self.0
  220. }
  221. }
  222. impl std::ops::Add for Amount {
  223. type Output = Amount;
  224. fn add(self, rhs: Amount) -> Self::Output {
  225. self.checked_add(rhs)
  226. .expect("Addition overflow: the sum of the amounts exceeds the maximum value")
  227. }
  228. }
  229. impl std::ops::AddAssign for Amount {
  230. fn add_assign(&mut self, rhs: Self) {
  231. *self = self
  232. .checked_add(rhs)
  233. .expect("AddAssign overflow: the sum of the amounts exceeds the maximum value");
  234. }
  235. }
  236. impl std::ops::Sub for Amount {
  237. type Output = Amount;
  238. fn sub(self, rhs: Amount) -> Self::Output {
  239. self.checked_sub(rhs)
  240. .expect("Subtraction underflow: cannot subtract a larger amount from a smaller amount")
  241. }
  242. }
  243. impl std::ops::SubAssign for Amount {
  244. fn sub_assign(&mut self, other: Self) {
  245. *self = self
  246. .checked_sub(other)
  247. .expect("SubAssign underflow: cannot subtract a larger amount from a smaller amount");
  248. }
  249. }
  250. impl std::ops::Mul for Amount {
  251. type Output = Self;
  252. fn mul(self, other: Self) -> Self::Output {
  253. self.checked_mul(other)
  254. .expect("Multiplication overflow: the product of the amounts exceeds the maximum value")
  255. }
  256. }
  257. impl std::ops::Div for Amount {
  258. type Output = Self;
  259. fn div(self, other: Self) -> Self::Output {
  260. self.checked_div(other)
  261. .expect("Division error: cannot divide by zero or overflow occurred")
  262. }
  263. }
  264. /// Convert offer to amount in unit
  265. pub fn amount_for_offer(offer: &Offer, unit: &CurrencyUnit) -> Result<Amount, Error> {
  266. let offer_amount = offer.amount().ok_or(Error::AmountUndefined)?;
  267. let (amount, currency) = match offer_amount {
  268. lightning::offers::offer::Amount::Bitcoin { amount_msats } => {
  269. (amount_msats, CurrencyUnit::Msat)
  270. }
  271. lightning::offers::offer::Amount::Currency {
  272. iso4217_code,
  273. amount,
  274. } => (
  275. amount,
  276. CurrencyUnit::from_str(&String::from_utf8(iso4217_code.to_vec())?)
  277. .map_err(|_| Error::CannotConvertUnits)?,
  278. ),
  279. };
  280. to_unit(amount, &currency, unit).map_err(|_err| Error::CannotConvertUnits)
  281. }
  282. /// Kinds of targeting that are supported
  283. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
  284. pub enum SplitTarget {
  285. /// Default target; least amount of proofs
  286. #[default]
  287. None,
  288. /// Target amount for wallet to have most proofs that add up to value
  289. Value(Amount),
  290. /// Specific amounts to split into **MUST** equal amount being split
  291. Values(Vec<Amount>),
  292. }
  293. /// Msats in sat
  294. pub const MSAT_IN_SAT: u64 = 1000;
  295. /// Helper function to convert units
  296. pub fn to_unit<T>(
  297. amount: T,
  298. current_unit: &CurrencyUnit,
  299. target_unit: &CurrencyUnit,
  300. ) -> Result<Amount, Error>
  301. where
  302. T: Into<u64>,
  303. {
  304. let amount = amount.into();
  305. match (current_unit, target_unit) {
  306. (CurrencyUnit::Sat, CurrencyUnit::Sat) => Ok(amount.into()),
  307. (CurrencyUnit::Msat, CurrencyUnit::Msat) => Ok(amount.into()),
  308. (CurrencyUnit::Sat, CurrencyUnit::Msat) => amount
  309. .checked_mul(MSAT_IN_SAT)
  310. .map(Amount::from)
  311. .ok_or(Error::AmountOverflow),
  312. (CurrencyUnit::Msat, CurrencyUnit::Sat) => Ok((amount / MSAT_IN_SAT).into()),
  313. (CurrencyUnit::Usd, CurrencyUnit::Usd) => Ok(amount.into()),
  314. (CurrencyUnit::Eur, CurrencyUnit::Eur) => Ok(amount.into()),
  315. _ => Err(Error::CannotConvertUnits),
  316. }
  317. }
  318. #[cfg(test)]
  319. mod tests {
  320. use super::*;
  321. #[test]
  322. fn test_split_amount() {
  323. assert_eq!(Amount::from(1).split(), vec![Amount::from(1)]);
  324. assert_eq!(Amount::from(2).split(), vec![Amount::from(2)]);
  325. assert_eq!(
  326. Amount::from(3).split(),
  327. vec![Amount::from(2), Amount::from(1)]
  328. );
  329. let amounts: Vec<Amount> = [8, 2, 1].iter().map(|a| Amount::from(*a)).collect();
  330. assert_eq!(Amount::from(11).split(), amounts);
  331. let amounts: Vec<Amount> = [128, 64, 32, 16, 8, 4, 2, 1]
  332. .iter()
  333. .map(|a| Amount::from(*a))
  334. .collect();
  335. assert_eq!(Amount::from(255).split(), amounts);
  336. }
  337. #[test]
  338. fn test_split_target_amount() {
  339. let amount = Amount(65);
  340. let split = amount
  341. .split_targeted(&SplitTarget::Value(Amount(32)))
  342. .unwrap();
  343. assert_eq!(vec![Amount(1), Amount(32), Amount(32)], split);
  344. let amount = Amount(150);
  345. let split = amount
  346. .split_targeted(&SplitTarget::Value(Amount::from(50)))
  347. .unwrap();
  348. assert_eq!(
  349. vec![
  350. Amount(2),
  351. Amount(2),
  352. Amount(2),
  353. Amount(16),
  354. Amount(16),
  355. Amount(16),
  356. Amount(32),
  357. Amount(32),
  358. Amount(32)
  359. ],
  360. split
  361. );
  362. let amount = Amount::from(63);
  363. let split = amount
  364. .split_targeted(&SplitTarget::Value(Amount::from(32)))
  365. .unwrap();
  366. assert_eq!(
  367. vec![
  368. Amount(1),
  369. Amount(2),
  370. Amount(4),
  371. Amount(8),
  372. Amount(16),
  373. Amount(32)
  374. ],
  375. split
  376. );
  377. }
  378. #[test]
  379. fn test_split_with_fee() {
  380. let amount = Amount(2);
  381. let fee_ppk = 1;
  382. let split = amount.split_with_fee(fee_ppk).unwrap();
  383. assert_eq!(split, vec![Amount(2), Amount(1)]);
  384. let amount = Amount(3);
  385. let fee_ppk = 1;
  386. let split = amount.split_with_fee(fee_ppk).unwrap();
  387. assert_eq!(split, vec![Amount(4)]);
  388. let amount = Amount(3);
  389. let fee_ppk = 1000;
  390. let split = amount.split_with_fee(fee_ppk).unwrap();
  391. // With fee_ppk=1000 (100%), amount 3 requires proofs totaling at least 5
  392. // to cover both the amount (3) and fees (~2 for 2 proofs)
  393. assert_eq!(split, vec![Amount(4), Amount(1)]);
  394. }
  395. #[test]
  396. fn test_split_with_fee_reported_issue() {
  397. // Test the reported issue: mint 600, send 300 with fee_ppk=100
  398. let amount = Amount(300);
  399. let fee_ppk = 100;
  400. let split = amount.split_with_fee(fee_ppk).unwrap();
  401. // Calculate the total fee for the split
  402. let total_fee_ppk = (split.len() as u64) * fee_ppk;
  403. let total_fee = Amount::from(total_fee_ppk.div_ceil(1000));
  404. // The split should cover the amount plus fees
  405. let split_total = Amount::try_sum(split.iter().copied()).unwrap();
  406. assert!(
  407. split_total >= amount + total_fee,
  408. "Split total {} should be >= amount {} + fee {}",
  409. split_total,
  410. amount,
  411. total_fee
  412. );
  413. }
  414. #[test]
  415. fn test_split_with_fee_edge_cases() {
  416. // Test various amounts with fee_ppk=100
  417. let test_cases = vec![
  418. (Amount(1), 100),
  419. (Amount(10), 100),
  420. (Amount(50), 100),
  421. (Amount(100), 100),
  422. (Amount(200), 100),
  423. (Amount(300), 100),
  424. (Amount(500), 100),
  425. (Amount(600), 100),
  426. (Amount(1000), 100),
  427. (Amount(1337), 100),
  428. (Amount(5000), 100),
  429. ];
  430. for (amount, fee_ppk) in test_cases {
  431. let result = amount.split_with_fee(fee_ppk);
  432. assert!(
  433. result.is_ok(),
  434. "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
  435. amount,
  436. fee_ppk,
  437. result.err()
  438. );
  439. let split = result.unwrap();
  440. // Verify the split covers the required amount
  441. let split_total = Amount::try_sum(split.iter().copied()).unwrap();
  442. let fee_for_split = (split.len() as u64) * fee_ppk;
  443. let total_fee = Amount::from(fee_for_split.div_ceil(1000));
  444. // The net amount after fees should be at least the original amount
  445. let net_amount = split_total.checked_sub(total_fee);
  446. assert!(
  447. net_amount.is_some(),
  448. "Net amount calculation failed for amount {} with fee_ppk {}",
  449. amount,
  450. fee_ppk
  451. );
  452. assert!(
  453. net_amount.unwrap() >= amount,
  454. "Net amount {} is less than required {} for amount {} with fee_ppk {}",
  455. net_amount.unwrap(),
  456. amount,
  457. amount,
  458. fee_ppk
  459. );
  460. }
  461. }
  462. #[test]
  463. fn test_split_with_fee_high_fees() {
  464. // Test with very high fees
  465. let test_cases = vec![
  466. (Amount(10), 500), // 50% fee
  467. (Amount(10), 1000), // 100% fee
  468. (Amount(10), 2000), // 200% fee
  469. (Amount(100), 500),
  470. (Amount(100), 1000),
  471. (Amount(100), 2000),
  472. ];
  473. for (amount, fee_ppk) in test_cases {
  474. let result = amount.split_with_fee(fee_ppk);
  475. assert!(
  476. result.is_ok(),
  477. "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
  478. amount,
  479. fee_ppk,
  480. result.err()
  481. );
  482. let split = result.unwrap();
  483. let split_total = Amount::try_sum(split.iter().copied()).unwrap();
  484. // With high fees, we just need to ensure we can cover the amount
  485. assert!(
  486. split_total > amount,
  487. "Split total {} should be greater than amount {} for fee_ppk {}",
  488. split_total,
  489. amount,
  490. fee_ppk
  491. );
  492. }
  493. }
  494. #[test]
  495. fn test_split_with_fee_recursion_limit() {
  496. // Test that the recursion doesn't go infinite
  497. // This tests the edge case where the method keeps adding Amount::ONE
  498. let amount = Amount(1);
  499. let fee_ppk = 10000; // Very high fee that might cause recursion
  500. let result = amount.split_with_fee(fee_ppk);
  501. assert!(
  502. result.is_ok(),
  503. "split_with_fee should handle extreme fees without infinite recursion"
  504. );
  505. }
  506. #[test]
  507. fn test_split_values() {
  508. let amount = Amount(10);
  509. let target = vec![Amount(2), Amount(4), Amount(4)];
  510. let split_target = SplitTarget::Values(target.clone());
  511. let values = amount.split_targeted(&split_target).unwrap();
  512. assert_eq!(target, values);
  513. let target = vec![Amount(2), Amount(4), Amount(4)];
  514. let split_target = SplitTarget::Values(vec![Amount(2), Amount(4)]);
  515. let values = amount.split_targeted(&split_target).unwrap();
  516. assert_eq!(target, values);
  517. let split_target = SplitTarget::Values(vec![Amount(2), Amount(10)]);
  518. let values = amount.split_targeted(&split_target);
  519. assert!(values.is_err())
  520. }
  521. #[test]
  522. #[should_panic]
  523. fn test_amount_addition() {
  524. let amount_one: Amount = u64::MAX.into();
  525. let amount_two: Amount = 1.into();
  526. let amounts = vec![amount_one, amount_two];
  527. let _total: Amount = Amount::try_sum(amounts).unwrap();
  528. }
  529. #[test]
  530. fn test_try_amount_addition() {
  531. let amount_one: Amount = u64::MAX.into();
  532. let amount_two: Amount = 1.into();
  533. let amounts = vec![amount_one, amount_two];
  534. let total = Amount::try_sum(amounts);
  535. assert!(total.is_err());
  536. let amount_one: Amount = 10000.into();
  537. let amount_two: Amount = 1.into();
  538. let amounts = vec![amount_one, amount_two];
  539. let total = Amount::try_sum(amounts).unwrap();
  540. assert_eq!(total, 10001.into());
  541. }
  542. #[test]
  543. fn test_amount_to_unit() {
  544. let amount = Amount::from(1000);
  545. let current_unit = CurrencyUnit::Sat;
  546. let target_unit = CurrencyUnit::Msat;
  547. let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
  548. assert_eq!(converted, 1000000.into());
  549. let amount = Amount::from(1000);
  550. let current_unit = CurrencyUnit::Msat;
  551. let target_unit = CurrencyUnit::Sat;
  552. let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
  553. assert_eq!(converted, 1.into());
  554. let amount = Amount::from(1);
  555. let current_unit = CurrencyUnit::Usd;
  556. let target_unit = CurrencyUnit::Usd;
  557. let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
  558. assert_eq!(converted, 1.into());
  559. let amount = Amount::from(1);
  560. let current_unit = CurrencyUnit::Eur;
  561. let target_unit = CurrencyUnit::Eur;
  562. let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
  563. assert_eq!(converted, 1.into());
  564. let amount = Amount::from(1);
  565. let current_unit = CurrencyUnit::Sat;
  566. let target_unit = CurrencyUnit::Eur;
  567. let converted = to_unit(amount, &current_unit, &target_unit);
  568. assert!(converted.is_err());
  569. }
  570. }