Skip to main content

mina_tx_type/
currency.rs

1//! Currency types for Mina Protocol transactions.
2//!
3//! This module provides numeric types for representing amounts and fees
4//! in Mina Protocol transactions. All types are `no_std` compatible.
5
6use core::{fmt, ops::Neg};
7
8/// Sign of a value, either positive or negative.
9///
10/// Used in conjunction with [`Signed`] to represent signed quantities
11/// where the sign is tracked separately from the magnitude.
12#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
13pub enum Sign {
14    /// Positive value
15    #[default]
16    Pos,
17    /// Negative value
18    Neg,
19}
20
21impl Sign {
22    /// Returns `true` if the sign is positive.
23    #[inline]
24    #[must_use]
25    pub const fn is_pos(&self) -> bool {
26        matches!(self, Self::Pos)
27    }
28
29    /// Returns `true` if the sign is negative.
30    #[inline]
31    #[must_use]
32    pub const fn is_neg(&self) -> bool {
33        matches!(self, Self::Neg)
34    }
35}
36
37impl Neg for Sign {
38    type Output = Self;
39
40    fn neg(self) -> Self::Output {
41        match self {
42            Self::Pos => Self::Neg,
43            Self::Neg => Self::Pos,
44        }
45    }
46}
47
48impl fmt::Display for Sign {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::Pos => write!(f, "+"),
52            Self::Neg => write!(f, "-"),
53        }
54    }
55}
56
57/// Trait for unsigned numeric types supporting arithmetic operations.
58///
59/// This trait defines the core operations needed for currency magnitude types
60/// like [`Amount`] and [`Fee`].
61pub trait Magnitude: Copy + PartialEq + PartialOrd {
62    /// The zero value for this type.
63    const ZERO: Self;
64
65    /// Returns `true` if this value is zero.
66    fn is_zero(self) -> bool {
67        self == Self::ZERO
68    }
69
70    /// Returns the absolute difference between `self` and `other`.
71    #[must_use]
72    fn abs_diff(self, other: Self) -> Self;
73
74    /// Checked addition. Returns `None` if overflow occurred.
75    fn checked_add(self, other: Self) -> Option<Self>;
76
77    /// Checked subtraction. Returns `None` if underflow occurred.
78    fn checked_sub(self, other: Self) -> Option<Self>;
79}
80
81/// A signed value composed of a magnitude and a sign.
82///
83/// This type represents signed quantities where the sign is tracked
84/// separately from the magnitude. This is useful for representing
85/// values that can be positive or negative without using two's complement.
86///
87/// Zero is always normalized to have a positive sign.
88#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
89pub struct Signed<T: Magnitude> {
90    magnitude: T,
91    sign: Sign,
92}
93
94impl<T: Magnitude> Default for Signed<T> {
95    fn default() -> Self {
96        Self::zero()
97    }
98}
99
100impl<T: Magnitude> Signed<T> {
101    /// Normalizes the sign: zero magnitude always has positive sign.
102    fn normalize(magnitude: T, sign: Sign) -> Self {
103        let sign = if magnitude.is_zero() { Sign::Pos } else { sign };
104        Self { magnitude, sign }
105    }
106
107    /// Creates a new signed value with the given magnitude and sign.
108    ///
109    /// Zero magnitude is always normalized to positive sign.
110    #[must_use]
111    pub fn new(magnitude: T, sign: Sign) -> Self {
112        Self::normalize(magnitude, sign)
113    }
114
115    /// Creates a new positive signed value.
116    ///
117    /// Zero magnitude is normalized to positive sign.
118    #[must_use]
119    pub fn pos(magnitude: T) -> Self {
120        Self::normalize(magnitude, Sign::Pos)
121    }
122
123    /// Creates a new negative signed value.
124    ///
125    /// Zero magnitude is normalized to positive sign.
126    #[must_use]
127    pub fn neg(magnitude: T) -> Self {
128        Self::normalize(magnitude, Sign::Neg)
129    }
130
131    /// Creates the zero value.
132    #[must_use]
133    pub const fn zero() -> Self {
134        Self {
135            magnitude: T::ZERO,
136            sign: Sign::Pos,
137        }
138    }
139
140    /// Returns the magnitude (absolute value).
141    #[must_use]
142    pub const fn magnitude(&self) -> T {
143        self.magnitude
144    }
145
146    /// Returns the sign.
147    #[must_use]
148    pub const fn sign(&self) -> Sign {
149        self.sign
150    }
151
152    /// Returns `true` if this value is zero.
153    #[must_use]
154    pub fn is_zero(&self) -> bool {
155        self.magnitude.is_zero()
156    }
157
158    /// Returns `true` if this value is positive (including zero).
159    #[must_use]
160    pub const fn is_pos(&self) -> bool {
161        self.sign.is_pos()
162    }
163
164    /// Returns `true` if this value is negative.
165    #[must_use]
166    pub const fn is_neg(&self) -> bool {
167        self.sign.is_neg()
168    }
169
170    /// Checked addition of two signed values.
171    ///
172    /// Returns `None` if overflow occurs.
173    #[must_use]
174    pub fn checked_add(self, other: Self) -> Option<Self> {
175        if self.sign == other.sign {
176            // Same sign: add magnitudes, keep sign
177            let magnitude = self.magnitude.checked_add(other.magnitude)?;
178            Some(Self::normalize(magnitude, self.sign))
179        } else {
180            // Opposite signs: subtract smaller from larger
181            let (magnitude, sign) = if self.magnitude >= other.magnitude {
182                (self.magnitude.abs_diff(other.magnitude), self.sign)
183            } else {
184                (other.magnitude.abs_diff(self.magnitude), other.sign)
185            };
186            Some(Self::normalize(magnitude, sign))
187        }
188    }
189
190    /// Checked subtraction of two signed values.
191    ///
192    /// Returns `None` if overflow occurs.
193    #[must_use]
194    pub fn checked_sub(self, other: Self) -> Option<Self> {
195        self.checked_add(-other)
196    }
197}
198
199impl<T: Magnitude + fmt::Display> fmt::Display for Signed<T> {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        if self.sign.is_neg() {
202            write!(f, "-{}", self.magnitude)
203        } else {
204            write!(f, "{}", self.magnitude)
205        }
206    }
207}
208
209impl<T: Magnitude> Neg for Signed<T> {
210    type Output = Self;
211
212    fn neg(self) -> Self::Output {
213        Self::normalize(self.magnitude, -self.sign)
214    }
215}
216
217/// Macro to implement common numeric type functionality.
218///
219/// This generates a newtype wrapper around `u64` with the [`Magnitude`]
220/// trait implemented.
221macro_rules! impl_number {
222    ($name:ident, $doc:expr) => {
223        #[doc = $doc]
224        #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
225        pub struct $name(u64);
226
227        impl $name {
228            /// The minimum value (zero).
229            pub const MIN: Self = Self(0);
230
231            /// The maximum value.
232            pub const MAX: Self = Self(u64::MAX);
233
234            /// Creates a new value from a `u64`.
235            #[must_use]
236            pub const fn new(value: u64) -> Self {
237                Self(value)
238            }
239
240            /// Returns the inner `u64` value.
241            #[must_use]
242            pub const fn inner(&self) -> u64 {
243                self.0
244            }
245        }
246
247        impl Magnitude for $name {
248            const ZERO: Self = Self(0);
249
250            fn abs_diff(self, other: Self) -> Self {
251                Self(self.0.abs_diff(other.0))
252            }
253
254            fn checked_add(self, other: Self) -> Option<Self> {
255                self.0.checked_add(other.0).map(Self)
256            }
257
258            fn checked_sub(self, other: Self) -> Option<Self> {
259                self.0.checked_sub(other.0).map(Self)
260            }
261        }
262
263        impl From<u64> for $name {
264            fn from(value: u64) -> Self {
265                Self(value)
266            }
267        }
268
269        impl From<$name> for u64 {
270            fn from(value: $name) -> u64 {
271                value.0
272            }
273        }
274
275        impl fmt::Display for $name {
276            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277                write!(f, "{}", self.0)
278            }
279        }
280    };
281}
282
283impl_number!(
284    Amount,
285    "An amount of currency in nanomina (1 MINA = 1e9 nanomina).\n\n\
286     This type represents positive currency amounts used in transactions,\n\
287     account balances, and coinbase rewards."
288);
289
290impl_number!(
291    Fee,
292    "A transaction fee in nanomina (1 MINA = 1e9 nanomina).\n\n\
293     This type represents fees paid for transaction processing."
294);
295
296impl From<Fee> for Amount {
297    fn from(fee: Fee) -> Self {
298        Self::new(fee.inner())
299    }
300}