ln_types/
amount.rs

1//! Bitcoin amount with millisatoshi precision.
2//!
3//! This module provides the [`Amount`] type and the related error types.
4
5use 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/// Number of millisatoshis.
17///
18/// This type represents a number of millisatoshis (thousands of satoshi) which is the base unit of
19/// the lightning network.
20/// It provides ordinary arithmetic and conversion methods.
21///
22/// ## Invariants
23///
24/// This type guarantees that the amount stays less than or equal to 21 million bitcoins.
25/// However `unsafe` code **must not** rely on this, at least for now.
26/// This implies that arithmetic operations always panic on overflow.
27///
28/// ## `Display` implementation
29///
30/// To avoid confusion, the amount is displayed with ` msat` suffix - e.g. `42 msat`.
31/// No other representations are supported yet, feel free to contribute!
32#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
33pub struct Amount(u64);
34
35impl Amount {
36    /// Zero bitcoins.
37    pub const ZERO: Amount = Amount(0);
38
39    /// One millisatoshi
40    pub const ONE_MSAT: Amount = Amount(1);
41
42    /// 21 million bitcoins.
43    pub const MAX: Amount = Amount(MAX_MONEY_MSAT);
44
45    /// One satoshi
46    pub const ONE_SAT: Amount = Amount(1000);
47
48    /// One bitcoin
49    pub const ONE_BTC: Amount = Amount(1000 * SATS_IN_BTC);
50
51    /// Constructs the amount from raw millisatosis.
52    ///
53    /// The value is directly converted with an overflow check.
54    ///
55    /// ## Errors
56    ///
57    /// This method returns an error if the amount exceeds Bitcoin supply cap
58    ///
59    /// ## Example
60    ///
61    /// ```
62    /// let msat = ln_types::Amount::from_msat(1000).unwrap();
63    /// assert_eq!(msat, ln_types::Amount::ONE_SAT);
64    /// ```
65    #[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    /// Constructs the amount from raw satosis.
75    ///
76    /// The value is converted with an overflow check.
77    ///
78    /// ## Errors
79    ///
80    /// This method returns an error if the amount exceeds Bitcoin supply cap
81    ///
82    /// ## Example
83    ///
84    /// ```
85    /// let msat = ln_types::Amount::from_sat(100_000_000).unwrap();
86    /// assert_eq!(msat, ln_types::Amount::ONE_BTC);
87    /// ```
88    #[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    /// Converts the value to raw millisatoshis.
98    ///
99    /// ## Example
100    ///
101    /// ```
102    /// let msat = ln_types::Amount::ONE_SAT.to_msat();
103    /// assert_eq!(msat, 1000);
104    /// ```
105    #[inline]
106    pub fn to_msat(self) -> u64 {
107        self.0
108    }
109
110    /// Attempts to convert the value to raw satoshis.
111    ///
112    /// ## Errors
113    ///
114    /// This method returns an error if the number of millisatoshis isn't rounded to thousands.
115    ///
116    /// ## Example
117    ///
118    /// ```
119    /// let msat = ln_types::Amount::ONE_SAT.to_sat().unwrap();
120    /// assert_eq!(msat, 1);
121    /// ```
122    #[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    /// Converts to satoshis rounding down.
132    ///
133    /// ## Example
134    ///
135    /// ```
136    /// let msat = ln_types::Amount::ONE_MSAT.to_sat_floor();
137    /// assert_eq!(msat, 0);
138    /// ```
139    #[inline]
140    pub fn to_sat_floor(self) -> u64 {
141        self.0 / 1000
142    }
143
144    /// Converts to satoshis rounding up.
145    ///
146    /// ## Example
147    ///
148    /// ```
149    /// let msat = ln_types::Amount::ONE_MSAT.to_sat_ceiling();
150    /// assert_eq!(msat, 1);
151    /// ```
152    #[inline]
153    pub fn to_sat_ceiling(self) -> u64 {
154        (self.0 + 999) / 1000
155    }
156
157    /// Converts to satoshis rounding.
158    ///
159    /// ## Example
160    ///
161    /// ```
162    /// let msat = ln_types::Amount::ONE_MSAT.to_sat_round();
163    /// assert_eq!(msat, 0);
164    /// ```
165    #[inline]
166    pub fn to_sat_round(self) -> u64 {
167        (self.0 + 500) / 1000
168    }
169
170    /// Internal monomorphic parsing method.
171    ///
172    /// This should improve codegen without requiring allocations.
173    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    /// Generic wrapper for parsing that is used to implement parsing from multiple types.
184    #[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    /// Generic wrapper for parsing that is used to implement parsing from multiple types.
194    #[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
203/// Displays the amount followed by denomination ` msat`.
204impl fmt::Display for Amount {
205    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206        write!(f, "{} msat", self.0)
207    }
208}
209
210/// Displays the amount followed by denomination ` msat`.
211impl fmt::Debug for Amount {
212    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213        fmt::Display::fmt(self, f)
214    }
215}
216
217
218/// Panics on overflow
219impl 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
236/// Panics on overflow
237impl core::ops::AddAssign for Amount {
238    #[inline]
239    fn add_assign(&mut self, rhs: Amount) {
240        *self = *self + rhs;
241    }
242}
243
244/// Panics on overflow
245impl 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
254/// Panics on overflow
255impl core::ops::SubAssign for Amount {
256    #[inline]
257    fn sub_assign(&mut self, rhs: Amount) {
258        *self = *self - rhs
259    }
260}
261
262/// Panics on overflow
263impl 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
274/// Panics on overflow
275impl 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
283/// Panics on overflow
284impl 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
318/// Accepts an unsigned integer up to 21 000 000 BTC
319/// The amount may optionally be followed by denomination ` msat`.
320impl 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
329/// Accepts an unsigned integer up to 21 000 000 BTC
330/// The amount may optionally be followed by denomination ` msat`.
331impl<'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/// Accepts an unsigned integer up to 21 000 000 BTC
341/// The amount may optionally be followed by denomination ` msat`.
342#[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/// Accepts an unsigned integer up to 21 000 000 BTC
353/// The amount may optionally be followed by denomination ` msat`.
354#[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/// Error returned when parsing text representation fails.
365///
366/// **Important: consumer code MUST NOT match on this using `ParseError { .. }` syntax.
367#[derive(Debug, Clone)]
368pub struct ParseError {
369    /// The string that was attempted to be parsed
370    #[cfg(feature = "alloc")]
371    input: String,
372    /// Information about what exactly went wrong
373    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/// Details about the error.
391///
392/// This is private to avoid committing to a representation.
393#[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/// Error returned when a conversion exceeds Bitcoin supply cap.
431///
432/// **Important: consumer code MUST NOT match on this using `OverflowError { .. }` syntax.
433#[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/// Error returned when a conversion to satoshis fails due to the value not being round.
449///
450/// **Important: consumer code MUST NOT match on this using `FractionError { .. }` syntax.
451#[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    /// The value is serialized as `u64` msats.
522    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    /// The value is deserialized as `u64` msats.
529    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    /// Stored as `i64` msats
546    impl ToSql for Amount {
547        fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Send + Sync + 'static>> {
548            // Amount guarantees to always be in bounds
549            (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    /// Retrieved as `i64` msats with range check
560    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/// Implementations of `slog` traits
575#[cfg(feature = "slog")]
576mod slog_impl {
577    use super::Amount;
578    use slog::{Key, Value, Record, Serializer};
579
580    /// Logs msats using `emit_u64`
581    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}