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