1use core::fmt;
6use core::str::FromStr;
7use core::convert::TryFrom;
8
9#[cfg(feature = "alloc")]
10use alloc::{boxed::Box, string::String};
11
12const SATS_IN_BTC: u64 = 100_000_000;
13const MAX_MONEY_SAT: u64 = 21_000_000 * SATS_IN_BTC;
14const MAX_MONEY_MSAT: u64 = MAX_MONEY_SAT * 1000;
15
16#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
33pub struct Amount(u64);
34
35impl Amount {
36 pub const ZERO: Amount = Amount(0);
38
39 pub const ONE_MSAT: Amount = Amount(1);
41
42 pub const MAX: Amount = Amount(MAX_MONEY_MSAT);
44
45 pub const ONE_SAT: Amount = Amount(1000);
47
48 pub const ONE_BTC: Amount = Amount(1000 * SATS_IN_BTC);
50
51 #[inline]
66 pub fn from_msat(msat: u64) -> Result<Self, OverflowError> {
67 if msat > MAX_MONEY_MSAT {
68 Err(OverflowError { amount: msat, denomination: "millisatoshis", })
69 } else {
70 Ok(Amount(msat))
71 }
72 }
73
74 #[inline]
89 pub fn from_sat(sat: u64) -> Result<Self, OverflowError> {
90 if sat > MAX_MONEY_SAT {
91 Err(OverflowError { amount: sat, denomination: "satoshis", })
92 } else {
93 Ok(Amount(sat * 1000))
94 }
95 }
96
97 #[inline]
106 pub fn to_msat(self) -> u64 {
107 self.0
108 }
109
110 #[inline]
123 pub fn to_sat(self) -> Result<u64, FractionError> {
124 if self.0 % 1000 == 0 {
125 Ok(self.0 / 1000)
126 } else {
127 Err(FractionError { amount: self.0, })
128 }
129 }
130
131 #[inline]
140 pub fn to_sat_floor(self) -> u64 {
141 self.0 / 1000
142 }
143
144 #[inline]
153 pub fn to_sat_ceiling(self) -> u64 {
154 (self.0 + 999) / 1000
155 }
156
157 #[inline]
166 pub fn to_sat_round(self) -> u64 {
167 (self.0 + 500) / 1000
168 }
169
170 fn parse_raw(mut s: &str) -> Result<Self, ParseErrorInner> {
174 if s.ends_with(" msat") {
175 s = &s[..(s.len() - 5)];
176 }
177
178 let amount = s.parse::<u64>()?;
179
180 Self::from_msat(amount).map_err(Into::into)
181 }
182
183 #[cfg(feature = "alloc")]
185 #[inline]
186 fn internal_parse<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, ParseError> {
187 Self::parse_raw(s.as_ref()).map_err(|error| ParseError {
188 input: s.into(),
189 reason: error,
190 })
191 }
192
193 #[cfg(not(feature = "alloc"))]
195 #[inline]
196 fn internal_parse<S: AsRef<str>>(s: S) -> Result<Self, ParseError> {
197 Self::parse_raw(s.as_ref()).map_err(|error| ParseError {
198 reason: error,
199 })
200 }
201}
202
203impl fmt::Display for Amount {
205 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206 write!(f, "{} msat", self.0)
207 }
208}
209
210impl fmt::Debug for Amount {
212 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213 fmt::Display::fmt(self, f)
214 }
215}
216
217
218impl core::ops::Add for Amount {
220 type Output = Self;
221
222 #[inline]
223 fn add(self, rhs: Amount) -> Self::Output {
224 let sum = self.0 + rhs.0;
225 assert!(
226 sum <= MAX_MONEY_MSAT,
227 "adding amounts {} + {} overflowed the maximum number of 21 million bitcoins",
228 self,
229 rhs,
230 );
231
232 Amount(sum)
233 }
234}
235
236impl core::ops::AddAssign for Amount {
238 #[inline]
239 fn add_assign(&mut self, rhs: Amount) {
240 *self = *self + rhs;
241 }
242}
243
244impl core::ops::Sub for Amount {
246 type Output = Self;
247
248 #[inline]
249 fn sub(self, rhs: Amount) -> Self::Output {
250 Amount(self.0.checked_sub(rhs.0).expect("underflow when subtracting amounts"))
251 }
252}
253
254impl core::ops::SubAssign for Amount {
256 #[inline]
257 fn sub_assign(&mut self, rhs: Amount) {
258 *self = *self - rhs
259 }
260}
261
262impl core::ops::Mul<u64> for Amount {
264 type Output = Self;
265
266 fn mul(self, rhs: u64) -> Self::Output {
267 match self.0.checked_mul(rhs) {
268 Some(amount) if amount <= MAX_MONEY_MSAT => Amount(amount),
269 _ => panic!("multiplying {} by {} overflowed the maximum number of 21 million bitcoins", self, rhs),
270 }
271 }
272}
273
274impl core::ops::Mul<Amount> for u64 {
276 type Output = Amount;
277
278 fn mul(self, rhs: Amount) -> Self::Output {
279 rhs * self
280 }
281}
282
283impl core::ops::MulAssign<u64> for Amount {
285 fn mul_assign(&mut self, rhs: u64) {
286 *self = *self * rhs;
287 }
288}
289
290impl core::ops::Div<u64> for Amount {
291 type Output = Self;
292
293 fn div(self, rhs: u64) -> Self::Output {
294 Amount(self.0 / rhs)
295 }
296}
297
298impl core::ops::DivAssign<u64> for Amount {
299 fn div_assign(&mut self, rhs: u64) {
300 *self = *self / rhs;
301 }
302}
303
304impl core::ops::Rem<u64> for Amount {
305 type Output = Self;
306
307 fn rem(self, rhs: u64) -> Self::Output {
308 Amount(self.0 % rhs)
309 }
310}
311
312impl core::ops::RemAssign<u64> for Amount {
313 fn rem_assign(&mut self, rhs: u64) {
314 *self = *self % rhs;
315 }
316}
317
318impl FromStr for Amount {
321 type Err = ParseError;
322
323 #[inline]
324 fn from_str(s: &str) -> Result<Self, Self::Err> {
325 Self::internal_parse(s)
326 }
327}
328
329impl<'a> TryFrom<&'a str> for Amount {
332 type Error = ParseError;
333
334 #[inline]
335 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
336 Self::internal_parse(s)
337 }
338}
339
340#[cfg(feature = "alloc")]
343impl TryFrom<String> for Amount {
344 type Error = ParseError;
345
346 #[inline]
347 fn try_from(s: String) -> Result<Self, Self::Error> {
348 Self::internal_parse(s)
349 }
350}
351
352#[cfg(feature = "alloc")]
355impl TryFrom<Box<str>> for Amount {
356 type Error = ParseError;
357
358 #[inline]
359 fn try_from(s: Box<str>) -> Result<Self, Self::Error> {
360 Self::internal_parse(s)
361 }
362}
363
364#[derive(Debug, Clone)]
368pub struct ParseError {
369 #[cfg(feature = "alloc")]
371 input: String,
372 reason: ParseErrorInner,
374}
375
376impl fmt::Display for ParseError {
377 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
378 write_err!(f, "failed to parse{} millisatoshis", opt_fmt!("alloc", format_args!(" '{}' as", &self.input)); &self.reason)
379 }
380}
381
382#[cfg(feature = "std")]
383impl std::error::Error for ParseError {
384 #[inline]
385 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
386 Some(&self.reason)
387 }
388}
389
390#[derive(Debug, Clone)]
394enum ParseErrorInner {
395 ParseInt(core::num::ParseIntError),
396 Overflow(OverflowError),
397}
398
399impl From<core::num::ParseIntError> for ParseErrorInner {
400 fn from(value: core::num::ParseIntError) -> Self {
401 ParseErrorInner::ParseInt(value)
402 }
403}
404
405impl From<OverflowError> for ParseErrorInner {
406 fn from(value: OverflowError) -> Self {
407 ParseErrorInner::Overflow(value)
408 }
409}
410
411impl fmt::Display for ParseErrorInner {
412 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
413 match self {
414 ParseErrorInner::ParseInt(error) => write_err!(f, "invalid integer"; error),
415 ParseErrorInner::Overflow(error) => write_err!(f, "value above supply cap"; error),
416 }
417 }
418}
419
420#[cfg(feature = "std")]
421impl std::error::Error for ParseErrorInner {
422 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
423 match self {
424 ParseErrorInner::ParseInt(error) => Some(error),
425 ParseErrorInner::Overflow(error) => Some(error),
426 }
427 }
428}
429
430#[derive(Debug, Clone)]
434pub struct OverflowError {
435 amount: u64,
436 denomination: &'static str,
437}
438
439impl fmt::Display for OverflowError {
440 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
441 write!(f, "{} {} exceeds the maximum number of 21 million bitcoins", self.amount, self.denomination)
442 }
443}
444
445#[cfg(feature = "std")]
446impl std::error::Error for OverflowError {}
447
448#[derive(Debug, Clone)]
452pub struct FractionError {
453 amount: u64,
454}
455
456impl fmt::Display for FractionError {
457 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458 write!(f, "{} millisatoshis can not be converted to satoshis because it's not rounded to thousands", self.amount)
459 }
460}
461
462#[cfg(feature = "std")]
463impl std::error::Error for FractionError {}
464
465#[cfg(feature = "bitcoin-units")]
466mod impl_bitcoin {
467 use super::{Amount, OverflowError, FractionError};
468 use core::convert::TryFrom;
469
470 impl TryFrom<bitcoin_units::Amount> for Amount {
471 type Error = OverflowError;
472
473 fn try_from(value: bitcoin_units::Amount) -> Result<Self, Self::Error> {
474 Self::from_sat(value.to_sat())
475 }
476 }
477
478 impl TryFrom<Amount> for bitcoin_units::Amount {
479 type Error = FractionError;
480
481 fn try_from(value: Amount) -> Result<Self, Self::Error> {
482 Ok(Self::from_sat(value.to_sat()?))
483 }
484 }
485}
486
487#[cfg(feature = "parse_arg")]
488mod parse_arg_impl {
489 use core::fmt;
490 use super::Amount;
491
492 impl parse_arg::ParseArgFromStr for Amount {
493 fn describe_type<W: fmt::Write>(mut writer: W) -> fmt::Result {
494 writer.write_str("millisatoshis - a non-negative integer up to 2 100 000 000 000 000 000")
495 }
496 }
497}
498
499#[cfg(feature = "serde")]
500mod serde_impl {
501 use core::fmt;
502 use super::Amount;
503 use serde::{Serialize, Deserialize, Serializer, Deserializer, de::{Visitor, Error}};
504
505 struct HRVisitor;
506
507 impl<'de> Visitor<'de> for HRVisitor {
508 type Value = Amount;
509
510 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
511 formatter.write_str("a non-negative integer up to 2 100 000 000 000 000 000")
512 }
513
514 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: Error {
515 Amount::from_msat(v).map_err(|_| {
516 E::invalid_value(serde::de::Unexpected::Unsigned(v), &"a non-negative integer up to 2 100 000 000 000 000 000")
517 })
518 }
519 }
520
521 impl Serialize for Amount {
523 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
524 serializer.serialize_u64(self.0)
525 }
526 }
527
528 impl<'de> Deserialize<'de> for Amount {
530 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
531 deserializer.deserialize_u64(HRVisitor)
532 }
533 }
534}
535
536#[cfg(feature = "postgres-types")]
537mod postgres_impl {
538 use alloc::boxed::Box;
539 use super::Amount;
540 use postgres_types::{ToSql, FromSql, IsNull, Type};
541 use bytes::BytesMut;
542 use std::error::Error;
543 use core::convert::TryInto;
544
545 impl ToSql for Amount {
547 fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Send + Sync + 'static>> {
548 (self.to_msat() as i64).to_sql(ty, out)
550 }
551
552 fn accepts(ty: &Type) -> bool {
553 <i64 as ToSql>::accepts(ty)
554 }
555
556 postgres_types::to_sql_checked!();
557 }
558
559 impl<'a> FromSql<'a> for Amount {
561 fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Send + Sync + 'static>> {
562 let msats = <i64>::from_sql(ty, raw)?
563 .try_into()
564 .map_err(Box::new)?;
565 Amount::from_msat(msats).map_err(|error| Box::new(error) as _)
566 }
567
568 fn accepts(ty: &Type) -> bool {
569 <i64 as FromSql>::accepts(ty)
570 }
571 }
572}
573
574#[cfg(feature = "slog")]
576mod slog_impl {
577 use super::Amount;
578 use slog::{Key, Value, Record, Serializer};
579
580 impl Value for Amount {
582 fn serialize(&self, _rec: &Record, key: Key, serializer: &mut dyn Serializer) -> slog::Result {
583 serializer.emit_u64(key, self.0)
584 }
585 }
586
587 impl_error_value!(super::ParseError, super::OverflowError, super::FractionError);
588}
589
590#[cfg(test)]
591mod tests {
592 use super::Amount;
593
594 #[test]
595 fn amount_max() {
596 assert_eq!(Amount::from_msat(super::MAX_MONEY_MSAT).unwrap(), Amount::MAX);
597 }
598
599 chk_err_impl! {
600 parse_amount_error_empty, "", Amount, ["failed to parse '' as millisatoshis", "invalid integer", "cannot parse integer from empty string"], ["failed to parse millisatoshis", "invalid integer", "cannot parse integer from empty string"];
601 parse_amount_error_overflow, "2100000000000000001", Amount, [
602 "failed to parse '2100000000000000001' as millisatoshis",
603 "value above supply cap",
604 "2100000000000000001 millisatoshis exceeds the maximum number of 21 million bitcoins"
605 ], [
606 "failed to parse millisatoshis",
607 "value above supply cap",
608 "2100000000000000001 millisatoshis exceeds the maximum number of 21 million bitcoins"
609 ];
610 }
611}