miden_protocol/asset/
asset_amount.rs1use alloc::string::ToString;
2use core::fmt;
3use core::ops::{Add, Sub};
4
5use super::super::errors::AssetError;
6use super::super::utils::serde::{
7 ByteReader,
8 ByteWriter,
9 Deserializable,
10 DeserializationError,
11 Serializable,
12};
13use crate::Felt;
14
15#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct AssetAmount(u64);
24
25impl AssetAmount {
26 pub const MAX: Self = Self(2u64.pow(63) - 2u64.pow(31));
31
32 pub const ZERO: Self = Self(0);
34
35 pub fn new(amount: u64) -> Result<Self, AssetError> {
41 if amount > Self::MAX.0 {
42 return Err(AssetError::FungibleAssetAmountTooBig(amount));
43 }
44 Ok(Self(amount))
45 }
46
47 pub const fn as_u64(&self) -> u64 {
49 self.0
50 }
51
52 pub const fn as_i64(&self) -> i64 {
58 self.0 as i64
59 }
60}
61
62impl Add for AssetAmount {
63 type Output = Result<Self, AssetError>;
64
65 fn add(self, other: Self) -> Self::Output {
66 let raw = self.0.checked_add(other.0).expect("even MAX + MAX should not overflow u64");
67 Self::new(raw)
68 }
69}
70
71impl Sub for AssetAmount {
72 type Output = Result<Self, AssetError>;
73
74 fn sub(self, other: Self) -> Self::Output {
75 let raw =
76 self.0
77 .checked_sub(other.0)
78 .ok_or(AssetError::FungibleAssetAmountNotSufficient {
79 minuend: self.0,
80 subtrahend: other.0,
81 })?;
82 Ok(Self(raw))
83 }
84}
85
86impl From<u8> for AssetAmount {
90 fn from(value: u8) -> Self {
91 Self(value as u64)
92 }
93}
94
95impl From<u16> for AssetAmount {
96 fn from(value: u16) -> Self {
97 Self(value as u64)
98 }
99}
100
101impl From<u32> for AssetAmount {
102 fn from(value: u32) -> Self {
103 Self(value as u64)
104 }
105}
106
107impl TryFrom<u64> for AssetAmount {
108 type Error = AssetError;
109
110 fn try_from(value: u64) -> Result<Self, Self::Error> {
111 Self::new(value)
112 }
113}
114
115impl TryFrom<Felt> for AssetAmount {
116 type Error = AssetError;
117
118 fn try_from(value: Felt) -> Result<Self, Self::Error> {
119 Self::new(value.as_canonical_u64())
120 }
121}
122
123impl From<AssetAmount> for u64 {
124 fn from(amount: AssetAmount) -> Self {
125 amount.0
126 }
127}
128
129impl From<AssetAmount> for Felt {
130 fn from(amount: AssetAmount) -> Self {
131 Felt::try_from(amount.0).expect("asset amount should guarantee felt validity")
132 }
133}
134
135impl fmt::Display for AssetAmount {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 write!(f, "{}", self.0)
141 }
142}
143
144impl Serializable for AssetAmount {
148 fn write_into<W: ByteWriter>(&self, target: &mut W) {
149 target.write(self.0);
150 }
151
152 fn get_size_hint(&self) -> usize {
153 self.0.get_size_hint()
154 }
155}
156
157impl Deserializable for AssetAmount {
158 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
159 let amount: u64 = source.read()?;
160 Self::new(amount).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
161 }
162}
163
164#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn valid_amounts() {
173 let val: u64 = AssetAmount::new(0).unwrap().into();
174 assert_eq!(val, 0);
175 let val: u64 = AssetAmount::new(1000).unwrap().into();
176 assert_eq!(val, 1000);
177 let val: u64 = AssetAmount::new(AssetAmount::MAX.0).unwrap().into();
178 assert_eq!(val, AssetAmount::MAX.0);
179 }
180
181 #[test]
182 fn exceeds_max() {
183 assert!(AssetAmount::new(AssetAmount::MAX.0 + 1).is_err());
184 assert!(AssetAmount::new(u64::MAX).is_err());
185 }
186
187 #[test]
188 fn from_small_types() {
189 let a: AssetAmount = 42u8.into();
190 let val: u64 = a.into();
191 assert_eq!(val, 42);
192
193 let b: AssetAmount = 1000u16.into();
194 let val: u64 = b.into();
195 assert_eq!(val, 1000);
196
197 let c: AssetAmount = 100_000u32.into();
198 let val: u64 = c.into();
199 assert_eq!(val, 100_000);
200 }
201
202 #[test]
203 fn try_from_u64() {
204 assert!(AssetAmount::try_from(0u64).is_ok());
205 assert!(AssetAmount::try_from(AssetAmount::MAX.0).is_ok());
206 assert!(AssetAmount::try_from(AssetAmount::MAX.0 + 1).is_err());
207 }
208
209 #[test]
210 fn display() {
211 assert_eq!(AssetAmount::new(12345).unwrap().to_string(), "12345");
212 }
213
214 #[test]
215 fn into_u64() {
216 let amount = AssetAmount::new(500).unwrap();
217 let raw: u64 = amount.into();
218 assert_eq!(raw, 500);
219 }
220
221 #[test]
222 fn add_amounts() {
223 let a = AssetAmount::new(100).unwrap();
224 let b = AssetAmount::new(200).unwrap();
225 let val: u64 = (a + b).unwrap().into();
226 assert_eq!(val, 300);
227 }
228
229 #[test]
230 fn add_overflow() {
231 let max = AssetAmount::new(AssetAmount::MAX.0).unwrap();
232 let one = AssetAmount::new(1).unwrap();
233 assert!((max + one).is_err());
234 }
235
236 #[test]
237 fn sub_amounts() {
238 let a = AssetAmount::new(300).unwrap();
239 let b = AssetAmount::new(100).unwrap();
240 let val: u64 = (a - b).unwrap().into();
241 assert_eq!(val, 200);
242 }
243
244 #[test]
245 fn sub_underflow() {
246 let a = AssetAmount::new(50).unwrap();
247 let b = AssetAmount::new(100).unwrap();
248 assert!((a - b).is_err());
249 }
250}