1use core::convert::{Infallible, TryFrom};
2use core::fmt;
3use core::iter::Sum;
4use core::num::NonZeroU64;
5use core::ops::{Add, Div, Mul, Neg, Sub};
6
7#[cfg(feature = "std")]
8use std::error;
9
10#[cfg(feature = "std")]
11use memuse::DynamicUsage;
12
13pub const COIN: u64 = 1_0000_0000;
14pub const MAX_MONEY: u64 = 21_000_000 * COIN;
15pub const MAX_BALANCE: i64 = MAX_MONEY as i64;
16
17#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
26pub struct ZatBalance(i64);
27
28#[cfg(feature = "std")]
29memuse::impl_no_dynamic_usage!(ZatBalance);
30
31impl ZatBalance {
32 pub const fn zero() -> Self {
34 ZatBalance(0)
35 }
36
37 pub const fn const_from_i64(amount: i64) -> Self {
41 assert!(-MAX_BALANCE <= amount && amount <= MAX_BALANCE); ZatBalance(amount)
43 }
44
45 pub const fn const_from_u64(amount: u64) -> Self {
49 assert!(amount <= MAX_MONEY); ZatBalance(amount as i64)
51 }
52
53 pub fn from_i64(amount: i64) -> Result<Self, BalanceError> {
57 if (-MAX_BALANCE..=MAX_BALANCE).contains(&amount) {
58 Ok(ZatBalance(amount))
59 } else if amount < -MAX_BALANCE {
60 Err(BalanceError::Underflow)
61 } else {
62 Err(BalanceError::Overflow)
63 }
64 }
65
66 pub fn from_nonnegative_i64(amount: i64) -> Result<Self, BalanceError> {
70 if (0..=MAX_BALANCE).contains(&amount) {
71 Ok(ZatBalance(amount))
72 } else if amount < 0 {
73 Err(BalanceError::Underflow)
74 } else {
75 Err(BalanceError::Overflow)
76 }
77 }
78
79 pub fn from_u64(amount: u64) -> Result<Self, BalanceError> {
83 if amount <= MAX_MONEY {
84 Ok(ZatBalance(amount as i64))
85 } else {
86 Err(BalanceError::Overflow)
87 }
88 }
89
90 pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
94 let amount = i64::from_le_bytes(bytes);
95 ZatBalance::from_i64(amount)
96 }
97
98 pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
102 let amount = i64::from_le_bytes(bytes);
103 ZatBalance::from_nonnegative_i64(amount)
104 }
105
106 pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
110 let amount = u64::from_le_bytes(bytes);
111 ZatBalance::from_u64(amount)
112 }
113
114 pub fn to_i64_le_bytes(self) -> [u8; 8] {
116 self.0.to_le_bytes()
117 }
118
119 pub const fn is_positive(self) -> bool {
122 self.0.is_positive()
123 }
124
125 pub const fn is_negative(self) -> bool {
128 self.0.is_negative()
129 }
130
131 pub fn sum<I: IntoIterator<Item = ZatBalance>>(values: I) -> Option<ZatBalance> {
132 let mut result = ZatBalance::zero();
133 for value in values {
134 result = (result + value)?;
135 }
136 Some(result)
137 }
138}
139
140impl TryFrom<i64> for ZatBalance {
141 type Error = BalanceError;
142
143 fn try_from(value: i64) -> Result<Self, BalanceError> {
144 ZatBalance::from_i64(value)
145 }
146}
147
148impl From<ZatBalance> for i64 {
149 fn from(amount: ZatBalance) -> i64 {
150 amount.0
151 }
152}
153
154impl From<&ZatBalance> for i64 {
155 fn from(amount: &ZatBalance) -> i64 {
156 amount.0
157 }
158}
159
160impl TryFrom<ZatBalance> for u64 {
161 type Error = BalanceError;
162
163 fn try_from(value: ZatBalance) -> Result<Self, Self::Error> {
164 value.0.try_into().map_err(|_| BalanceError::Underflow)
165 }
166}
167
168impl Add<ZatBalance> for ZatBalance {
169 type Output = Option<ZatBalance>;
170
171 fn add(self, rhs: ZatBalance) -> Option<ZatBalance> {
172 ZatBalance::from_i64(self.0 + rhs.0).ok()
173 }
174}
175
176impl Add<ZatBalance> for Option<ZatBalance> {
177 type Output = Self;
178
179 fn add(self, rhs: ZatBalance) -> Option<ZatBalance> {
180 self.and_then(|lhs| lhs + rhs)
181 }
182}
183
184impl Sub<ZatBalance> for ZatBalance {
185 type Output = Option<ZatBalance>;
186
187 fn sub(self, rhs: ZatBalance) -> Option<ZatBalance> {
188 ZatBalance::from_i64(self.0 - rhs.0).ok()
189 }
190}
191
192impl Sub<ZatBalance> for Option<ZatBalance> {
193 type Output = Self;
194
195 fn sub(self, rhs: ZatBalance) -> Option<ZatBalance> {
196 self.and_then(|lhs| lhs - rhs)
197 }
198}
199
200impl Sum<ZatBalance> for Option<ZatBalance> {
201 fn sum<I: Iterator<Item = ZatBalance>>(mut iter: I) -> Self {
202 iter.try_fold(ZatBalance::zero(), |acc, a| acc + a)
203 }
204}
205
206impl<'a> Sum<&'a ZatBalance> for Option<ZatBalance> {
207 fn sum<I: Iterator<Item = &'a ZatBalance>>(mut iter: I) -> Self {
208 iter.try_fold(ZatBalance::zero(), |acc, a| acc + *a)
209 }
210}
211
212impl Neg for ZatBalance {
213 type Output = Self;
214
215 fn neg(self) -> Self {
216 ZatBalance(-self.0)
217 }
218}
219
220impl Mul<usize> for ZatBalance {
221 type Output = Option<ZatBalance>;
222
223 fn mul(self, rhs: usize) -> Option<ZatBalance> {
224 let rhs: i64 = rhs.try_into().ok()?;
225 self.0
226 .checked_mul(rhs)
227 .and_then(|i| ZatBalance::try_from(i).ok())
228 }
229}
230
231#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
236pub struct Zatoshis(u64);
237
238pub struct QuotRem<A> {
240 quotient: A,
241 remainder: A,
242}
243
244impl<A> QuotRem<A> {
245 pub fn quotient(&self) -> &A {
247 &self.quotient
248 }
249
250 pub fn remainder(&self) -> &A {
252 &self.remainder
253 }
254}
255
256impl Zatoshis {
257 pub const ZERO: Self = Zatoshis(0);
259
260 pub fn into_u64(self) -> u64 {
262 self.0
263 }
264
265 pub fn from_u64(amount: u64) -> Result<Self, BalanceError> {
269 if (0..=MAX_MONEY).contains(&amount) {
270 Ok(Zatoshis(amount))
271 } else {
272 Err(BalanceError::Overflow)
273 }
274 }
275
276 pub const fn const_from_u64(amount: u64) -> Self {
280 assert!(amount <= MAX_MONEY); Zatoshis(amount)
282 }
283
284 pub fn from_nonnegative_i64(amount: i64) -> Result<Self, BalanceError> {
288 u64::try_from(amount)
289 .map_err(|_| BalanceError::Underflow)
290 .and_then(Self::from_u64)
291 }
292
293 pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
297 let amount = u64::from_le_bytes(bytes);
298 Self::from_u64(amount)
299 }
300
301 pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
306 let amount = i64::from_le_bytes(bytes);
307 Self::from_nonnegative_i64(amount)
308 }
309
310 pub fn to_i64_le_bytes(self) -> [u8; 8] {
313 (self.0 as i64).to_le_bytes()
314 }
315
316 pub fn is_zero(&self) -> bool {
318 self == &Zatoshis::ZERO
319 }
320
321 pub fn is_positive(&self) -> bool {
323 self > &Zatoshis::ZERO
324 }
325
326 pub fn div_with_remainder(&self, divisor: NonZeroU64) -> QuotRem<Zatoshis> {
328 let divisor = u64::from(divisor);
329 QuotRem {
332 quotient: Zatoshis(self.0 / divisor),
333 remainder: Zatoshis(self.0 % divisor),
334 }
335 }
336}
337
338impl From<Zatoshis> for ZatBalance {
339 fn from(n: Zatoshis) -> Self {
340 ZatBalance(n.0 as i64)
341 }
342}
343
344impl From<&Zatoshis> for ZatBalance {
345 fn from(n: &Zatoshis) -> Self {
346 ZatBalance(n.0 as i64)
347 }
348}
349
350impl From<Zatoshis> for u64 {
351 fn from(n: Zatoshis) -> Self {
352 n.into_u64()
353 }
354}
355
356impl TryFrom<u64> for Zatoshis {
357 type Error = BalanceError;
358
359 fn try_from(value: u64) -> Result<Self, Self::Error> {
360 Zatoshis::from_u64(value)
361 }
362}
363
364impl TryFrom<ZatBalance> for Zatoshis {
365 type Error = BalanceError;
366
367 fn try_from(value: ZatBalance) -> Result<Self, Self::Error> {
368 Zatoshis::from_nonnegative_i64(value.0)
369 }
370}
371
372impl Add<Zatoshis> for Zatoshis {
373 type Output = Option<Zatoshis>;
374
375 fn add(self, rhs: Zatoshis) -> Option<Zatoshis> {
376 Self::from_u64(self.0.checked_add(rhs.0)?).ok()
377 }
378}
379
380impl Add<Zatoshis> for Option<Zatoshis> {
381 type Output = Self;
382
383 fn add(self, rhs: Zatoshis) -> Option<Zatoshis> {
384 self.and_then(|lhs| lhs + rhs)
385 }
386}
387
388impl Sub<Zatoshis> for Zatoshis {
389 type Output = Option<Zatoshis>;
390
391 fn sub(self, rhs: Zatoshis) -> Option<Zatoshis> {
392 Zatoshis::from_u64(self.0.checked_sub(rhs.0)?).ok()
393 }
394}
395
396impl Sub<Zatoshis> for Option<Zatoshis> {
397 type Output = Self;
398
399 fn sub(self, rhs: Zatoshis) -> Option<Zatoshis> {
400 self.and_then(|lhs| lhs - rhs)
401 }
402}
403
404impl Mul<u64> for Zatoshis {
405 type Output = Option<Self>;
406
407 fn mul(self, rhs: u64) -> Option<Zatoshis> {
408 Zatoshis::from_u64(self.0.checked_mul(rhs)?).ok()
409 }
410}
411
412impl Mul<usize> for Zatoshis {
413 type Output = Option<Self>;
414
415 fn mul(self, rhs: usize) -> Option<Zatoshis> {
416 self * u64::try_from(rhs).ok()?
417 }
418}
419
420impl Sum<Zatoshis> for Option<Zatoshis> {
421 fn sum<I: Iterator<Item = Zatoshis>>(mut iter: I) -> Self {
422 iter.try_fold(Zatoshis::ZERO, |acc, a| acc + a)
423 }
424}
425
426impl<'a> Sum<&'a Zatoshis> for Option<Zatoshis> {
427 fn sum<I: Iterator<Item = &'a Zatoshis>>(mut iter: I) -> Self {
428 iter.try_fold(Zatoshis::ZERO, |acc, a| acc + *a)
429 }
430}
431
432impl Div<NonZeroU64> for Zatoshis {
433 type Output = Zatoshis;
434
435 fn div(self, rhs: NonZeroU64) -> Zatoshis {
436 Zatoshis(self.0 / u64::from(rhs))
439 }
440}
441
442#[derive(Copy, Clone, Debug, PartialEq, Eq)]
445pub enum BalanceError {
446 Overflow,
447 Underflow,
448}
449
450#[cfg(feature = "std")]
451impl error::Error for BalanceError {}
452
453impl fmt::Display for BalanceError {
454 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
455 match &self {
456 BalanceError::Overflow => {
457 write!(
458 f,
459 "ZatBalance addition resulted in a value outside the valid range."
460 )
461 }
462 BalanceError::Underflow => write!(
463 f,
464 "ZatBalance subtraction resulted in a value outside the valid range."
465 ),
466 }
467 }
468}
469
470impl From<Infallible> for BalanceError {
471 fn from(_value: Infallible) -> Self {
472 unreachable!()
473 }
474}
475
476#[cfg(any(test, feature = "test-dependencies"))]
477pub mod testing {
478 use proptest::prelude::prop_compose;
479
480 use super::{ZatBalance, Zatoshis, MAX_BALANCE, MAX_MONEY};
481
482 prop_compose! {
483 pub fn arb_zat_balance()(amt in -MAX_BALANCE..MAX_BALANCE) -> ZatBalance {
484 ZatBalance::from_i64(amt).unwrap()
485 }
486 }
487
488 prop_compose! {
489 pub fn arb_positive_zat_balance()(amt in 1i64..MAX_BALANCE) -> ZatBalance {
490 ZatBalance::from_i64(amt).unwrap()
491 }
492 }
493
494 prop_compose! {
495 pub fn arb_nonnegative_zat_balance()(amt in 0i64..MAX_BALANCE) -> ZatBalance {
496 ZatBalance::from_i64(amt).unwrap()
497 }
498 }
499
500 prop_compose! {
501 pub fn arb_zatoshis()(amt in 0u64..MAX_MONEY) -> Zatoshis {
502 Zatoshis::from_u64(amt).unwrap()
503 }
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use crate::value::MAX_BALANCE;
510
511 use super::ZatBalance;
512
513 #[test]
514 fn amount_in_range() {
515 let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00";
516 assert_eq!(ZatBalance::from_u64_le_bytes(*zero).unwrap(), ZatBalance(0));
517 assert_eq!(
518 ZatBalance::from_nonnegative_i64_le_bytes(*zero).unwrap(),
519 ZatBalance(0)
520 );
521 assert_eq!(ZatBalance::from_i64_le_bytes(*zero).unwrap(), ZatBalance(0));
522
523 let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff";
524 assert!(ZatBalance::from_u64_le_bytes(*neg_one).is_err());
525 assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_one).is_err());
526 assert_eq!(
527 ZatBalance::from_i64_le_bytes(*neg_one).unwrap(),
528 ZatBalance(-1)
529 );
530
531 let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00";
532 assert_eq!(
533 ZatBalance::from_u64_le_bytes(*max_money).unwrap(),
534 ZatBalance(MAX_BALANCE)
535 );
536 assert_eq!(
537 ZatBalance::from_nonnegative_i64_le_bytes(*max_money).unwrap(),
538 ZatBalance(MAX_BALANCE)
539 );
540 assert_eq!(
541 ZatBalance::from_i64_le_bytes(*max_money).unwrap(),
542 ZatBalance(MAX_BALANCE)
543 );
544
545 let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
546 assert!(ZatBalance::from_u64_le_bytes(*max_money_p1).is_err());
547 assert!(ZatBalance::from_nonnegative_i64_le_bytes(*max_money_p1).is_err());
548 assert!(ZatBalance::from_i64_le_bytes(*max_money_p1).is_err());
549
550 let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff";
551 assert!(ZatBalance::from_u64_le_bytes(*neg_max_money).is_err());
552 assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money).is_err());
553 assert_eq!(
554 ZatBalance::from_i64_le_bytes(*neg_max_money).unwrap(),
555 ZatBalance(-MAX_BALANCE)
556 );
557
558 let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff";
559 assert!(ZatBalance::from_u64_le_bytes(*neg_max_money_m1).is_err());
560 assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money_m1).is_err());
561 assert!(ZatBalance::from_i64_le_bytes(*neg_max_money_m1).is_err());
562 }
563
564 #[test]
565 fn add_overflow() {
566 let v = ZatBalance(MAX_BALANCE);
567 assert_eq!(v + ZatBalance(1), None)
568 }
569
570 #[test]
571 fn sub_underflow() {
572 let v = ZatBalance(-MAX_BALANCE);
573 assert_eq!(v - ZatBalance(1), None)
574 }
575}