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 const SERIALIZED_SIZE: usize = core::mem::size_of::<u64>();
37
38 pub fn new(amount: u64) -> Result<Self, AssetError> {
44 if amount > Self::MAX.0 {
45 return Err(AssetError::FungibleAssetAmountTooBig(amount));
46 }
47 Ok(Self(amount))
48 }
49
50 pub const fn as_u64(&self) -> u64 {
52 self.0
53 }
54
55 pub const fn as_i64(&self) -> i64 {
61 self.0 as i64
62 }
63}
64
65impl Add for AssetAmount {
66 type Output = Result<Self, AssetError>;
67
68 fn add(self, other: Self) -> Self::Output {
69 let raw = self.0.checked_add(other.0).expect("even MAX + MAX should not overflow u64");
70 Self::new(raw)
71 }
72}
73
74impl Sub for AssetAmount {
75 type Output = Result<Self, AssetError>;
76
77 fn sub(self, other: Self) -> Self::Output {
78 let raw =
79 self.0
80 .checked_sub(other.0)
81 .ok_or(AssetError::FungibleAssetAmountNotSufficient {
82 minuend: self.0,
83 subtrahend: other.0,
84 })?;
85 Ok(Self(raw))
86 }
87}
88
89impl From<u8> for AssetAmount {
93 fn from(value: u8) -> Self {
94 Self(value as u64)
95 }
96}
97
98impl From<u16> for AssetAmount {
99 fn from(value: u16) -> Self {
100 Self(value as u64)
101 }
102}
103
104impl From<u32> for AssetAmount {
105 fn from(value: u32) -> Self {
106 Self(value as u64)
107 }
108}
109
110impl TryFrom<u64> for AssetAmount {
111 type Error = AssetError;
112
113 fn try_from(value: u64) -> Result<Self, Self::Error> {
114 Self::new(value)
115 }
116}
117
118impl TryFrom<Felt> for AssetAmount {
119 type Error = AssetError;
120
121 fn try_from(value: Felt) -> Result<Self, Self::Error> {
122 Self::new(value.as_canonical_u64())
123 }
124}
125
126impl From<AssetAmount> for u64 {
127 fn from(amount: AssetAmount) -> Self {
128 amount.0
129 }
130}
131
132impl From<AssetAmount> for Felt {
133 fn from(amount: AssetAmount) -> Self {
134 Felt::try_from(amount.0).expect("asset amount should guarantee felt validity")
135 }
136}
137
138impl fmt::Display for AssetAmount {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 write!(f, "{}", self.0)
144 }
145}
146
147impl Serializable for AssetAmount {
151 fn write_into<W: ByteWriter>(&self, target: &mut W) {
152 target.write(self.0);
153 }
154
155 fn get_size_hint(&self) -> usize {
156 self.0.get_size_hint()
157 }
158}
159
160impl Deserializable for AssetAmount {
161 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
162 let amount: u64 = source.read()?;
163 Self::new(amount).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
164 }
165}
166
167#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn valid_amounts() {
176 let val: u64 = AssetAmount::new(0).unwrap().into();
177 assert_eq!(val, 0);
178 let val: u64 = AssetAmount::new(1000).unwrap().into();
179 assert_eq!(val, 1000);
180 let val: u64 = AssetAmount::new(AssetAmount::MAX.0).unwrap().into();
181 assert_eq!(val, AssetAmount::MAX.0);
182 }
183
184 #[test]
185 fn exceeds_max() {
186 assert!(AssetAmount::new(AssetAmount::MAX.0 + 1).is_err());
187 assert!(AssetAmount::new(u64::MAX).is_err());
188 }
189
190 #[test]
191 fn from_small_types() {
192 let a: AssetAmount = 42u8.into();
193 let val: u64 = a.into();
194 assert_eq!(val, 42);
195
196 let b: AssetAmount = 1000u16.into();
197 let val: u64 = b.into();
198 assert_eq!(val, 1000);
199
200 let c: AssetAmount = 100_000u32.into();
201 let val: u64 = c.into();
202 assert_eq!(val, 100_000);
203 }
204
205 #[test]
206 fn try_from_u64() {
207 assert!(AssetAmount::try_from(0u64).is_ok());
208 assert!(AssetAmount::try_from(AssetAmount::MAX.0).is_ok());
209 assert!(AssetAmount::try_from(AssetAmount::MAX.0 + 1).is_err());
210 }
211
212 #[test]
213 fn display() {
214 assert_eq!(AssetAmount::new(12345).unwrap().to_string(), "12345");
215 }
216
217 #[test]
218 fn into_u64() {
219 let amount = AssetAmount::new(500).unwrap();
220 let raw: u64 = amount.into();
221 assert_eq!(raw, 500);
222 }
223
224 #[test]
225 fn add_amounts() {
226 let a = AssetAmount::new(100).unwrap();
227 let b = AssetAmount::new(200).unwrap();
228 let val: u64 = (a + b).unwrap().into();
229 assert_eq!(val, 300);
230 }
231
232 #[test]
233 fn add_overflow() {
234 let max = AssetAmount::new(AssetAmount::MAX.0).unwrap();
235 let one = AssetAmount::new(1).unwrap();
236 assert!((max + one).is_err());
237 }
238
239 #[test]
240 fn sub_amounts() {
241 let a = AssetAmount::new(300).unwrap();
242 let b = AssetAmount::new(100).unwrap();
243 let val: u64 = (a - b).unwrap().into();
244 assert_eq!(val, 200);
245 }
246
247 #[test]
248 fn sub_underflow() {
249 let a = AssetAmount::new(50).unwrap();
250 let b = AssetAmount::new(100).unwrap();
251 assert!((a - b).is_err());
252 }
253}