1use std::collections::{BTreeMap, HashMap};
4
5use crate::cell::*;
6use crate::dict::{AugDictExtra, Dict};
7use crate::error::Error;
8use crate::num::{Tokens, VarUint248};
9
10#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[must_use]
14pub struct CurrencyCollection {
15 pub tokens: Tokens,
17 pub other: ExtraCurrencyCollection,
19}
20
21impl Default for CurrencyCollection {
22 #[inline]
23 fn default() -> Self {
24 Self::ZERO
25 }
26}
27
28impl CurrencyCollection {
29 pub const ZERO: Self = Self {
32 tokens: Tokens::ZERO,
33 other: ExtraCurrencyCollection::new(),
34 };
35
36 pub const fn new(tokens: u128) -> Self {
39 Self {
40 tokens: Tokens::new(tokens),
41 other: ExtraCurrencyCollection::new(),
42 }
43 }
44
45 pub fn is_zero(&self) -> bool {
47 self.tokens.is_zero() && self.other.is_empty()
48 }
49
50 pub const fn bit_len(&self) -> u16 {
52 self.tokens.unwrap_bit_len() + 1
53 }
54
55 pub fn checked_add(&self, other: &Self) -> Result<Self, Error> {
59 Ok(Self {
60 tokens: match self.tokens.checked_add(other.tokens) {
61 Some(value) => value,
62 None => return Err(Error::IntOverflow),
63 },
64 other: ok!(self.other.checked_add(&other.other)),
65 })
66 }
67
68 pub fn checked_sub(&self, other: &Self) -> Result<Self, Error> {
72 Ok(Self {
73 tokens: match self.tokens.checked_sub(other.tokens) {
74 Some(value) => value,
75 None => return Err(Error::IntOverflow),
76 },
77 other: ok!(self.other.checked_sub(&other.other)),
78 })
79 }
80
81 pub fn try_add_assign_tokens(&mut self, other: Tokens) -> Result<(), Error> {
83 match self.tokens.checked_add(other) {
84 Some(value) => {
85 self.tokens = value;
86 Ok(())
87 }
88 None => Err(Error::IntOverflow),
89 }
90 }
91
92 pub fn try_sub_assign_tokens(&mut self, other: Tokens) -> Result<(), Error> {
94 match self.tokens.checked_sub(other) {
95 Some(value) => {
96 self.tokens = value;
97 Ok(())
98 }
99 None => Err(Error::IntOverflow),
100 }
101 }
102
103 pub fn try_add_assign(&mut self, other: &Self) -> Result<(), Error> {
105 *self = ok!(self.checked_add(other));
106 Ok(())
107 }
108
109 pub fn try_sub_assign(&mut self, other: &Self) -> Result<(), Error> {
111 *self = ok!(self.checked_sub(other));
112 Ok(())
113 }
114
115 pub fn checked_clamp(&self, other: &Self) -> Result<Self, Error> {
117 Ok(Self {
118 other: ok!(self.other.checked_clamp(&other.other)),
119 tokens: std::cmp::min(self.tokens, other.tokens),
120 })
121 }
122}
123
124impl From<Tokens> for CurrencyCollection {
125 #[inline]
126 fn from(tokens: Tokens) -> Self {
127 Self {
128 tokens,
129 other: ExtraCurrencyCollection::new(),
130 }
131 }
132}
133
134impl ExactSize for CurrencyCollection {
135 #[inline]
136 fn exact_size(&self) -> Size {
137 self.tokens.exact_size() + self.other.exact_size()
138 }
139}
140
141impl AugDictExtra for CurrencyCollection {
142 fn comp_add(
143 left: &mut CellSlice,
144 right: &mut CellSlice,
145 b: &mut CellBuilder,
146 cx: &dyn CellContext,
147 ) -> Result<(), Error> {
148 let left = ok!(Self::load_from(left));
149 let right = ok!(Self::load_from(right));
150 ok!(left.checked_add(&right)).store_into(b, cx)
151 }
152}
153
154#[cfg(feature = "arbitrary")]
155impl<'a> arbitrary::Arbitrary<'a> for CurrencyCollection {
156 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
157 Ok(Self {
158 tokens: u.arbitrary()?,
159 other: u.arbitrary()?,
160 })
161 }
162
163 #[inline]
164 fn size_hint(depth: usize) -> (usize, Option<usize>) {
165 Self::try_size_hint(depth).unwrap_or_default()
166 }
167
168 #[inline]
169 fn try_size_hint(
170 depth: usize,
171 ) -> Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
172 Ok(arbitrary::size_hint::and(
173 <Tokens as arbitrary::Arbitrary>::try_size_hint(depth)?,
174 <ExtraCurrencyCollection as arbitrary::Arbitrary>::try_size_hint(depth)?,
175 ))
176 }
177}
178
179#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
182#[must_use]
183#[repr(transparent)]
184pub struct ExtraCurrencyCollection(Dict<u32, VarUint248>);
185
186impl Default for ExtraCurrencyCollection {
187 #[inline]
188 fn default() -> Self {
189 Self(Dict::new())
190 }
191}
192
193impl ExtraCurrencyCollection {
194 pub const fn new() -> Self {
196 Self(Dict::new())
197 }
198
199 pub const fn from_raw(dict: Option<Cell>) -> Self {
201 Self(Dict::from_raw(dict))
202 }
203
204 pub fn try_from_iter<I>(iter: I) -> Result<Self, Error>
206 where
207 I: IntoIterator<Item = (u32, VarUint248)>,
208 {
209 let mut values = iter.into_iter().collect::<Box<[_]>>();
210 values.sort_unstable_by_key(|(id, _)| *id);
211 Dict::try_from_sorted_slice(&values).map(Self)
212 }
213
214 #[inline]
216 pub const fn is_empty(&self) -> bool {
217 self.0.is_empty()
218 }
219
220 #[inline]
222 pub const fn as_dict(&self) -> &Dict<u32, VarUint248> {
223 &self.0
224 }
225
226 #[inline]
228 pub fn into_dict(self) -> Dict<u32, VarUint248> {
229 self.0
230 }
231
232 #[inline]
234 pub fn as_dict_mut(&mut self) -> &mut Dict<u32, VarUint248> {
235 &mut self.0
236 }
237
238 pub fn normalized(&self) -> Result<Self, Error> {
240 let mut result = self.clone();
241 for entry in self.0.iter() {
242 let (currency_id, other) = ok!(entry);
243 if other.is_zero() {
244 ok!(result.0.remove(currency_id));
245 }
246 }
247 Ok(result)
248 }
249
250 pub fn normalize(&mut self) -> Result<(), Error> {
252 let mut result = self.clone();
253 for entry in self.0.iter() {
254 let (currency_id, other) = ok!(entry);
255 if other.is_zero() {
256 ok!(result.0.remove(currency_id));
257 }
258 }
259 *self = result;
260 Ok(())
261 }
262
263 pub fn checked_add(&self, other: &Self) -> Result<Self, Error> {
267 let mut result = self.clone();
268 for entry in other.0.iter() {
269 let (currency_id, other) = ok!(entry);
270
271 let existing = ok!(result.as_dict().get(currency_id)).unwrap_or_default();
272 match existing.checked_add(&other) {
273 Some(value) if value.is_zero() => {
274 ok!(result.0.remove(currency_id));
275 }
276 Some(ref value) => {
277 ok!(result.0.set(currency_id, value));
278 }
279 None => return Err(Error::IntOverflow),
280 };
281 }
282 Ok(result)
283 }
284
285 pub fn checked_sub(&self, other: &Self) -> Result<Self, Error> {
289 let mut result = self.clone();
290 for entry in other.0.iter() {
291 let (currency_id, other) = ok!(entry);
292
293 let existing = ok!(result.as_dict().get(currency_id)).unwrap_or_default();
294 match existing.checked_sub(&other) {
295 Some(value) if value.is_zero() => {
296 ok!(result.0.remove(currency_id));
297 }
298 Some(ref value) => {
299 ok!(result.0.set(currency_id, value));
300 }
301 None => return Err(Error::IntOverflow),
302 };
303 }
304 Ok(result)
305 }
306
307 pub fn checked_clamp(&self, other: &Self) -> Result<Self, Error> {
309 let mut result = self.clone();
310 for entry in self.0.iter() {
311 let (currency_id, balance) = ok!(entry);
312 match ok!(other.0.get(currency_id)) {
313 Some(other_balance) => {
316 if balance > other_balance {
317 ok!(result.0.set(currency_id, other_balance));
318 }
319 }
320 None if !balance.is_zero() => {
323 ok!(result.0.remove_raw(currency_id));
325 }
326 None => {}
329 }
330 }
331 Ok(result)
332 }
333}
334
335impl<S> TryFrom<&'_ HashMap<u32, VarUint248, S>> for ExtraCurrencyCollection
336where
337 S: std::hash::BuildHasher,
338{
339 type Error = Error;
340
341 fn try_from(value: &'_ HashMap<u32, VarUint248, S>) -> Result<Self, Self::Error> {
342 let mut values = value.iter().collect::<Box<[_]>>();
343 values.sort_unstable_by_key(|(id, _)| *id);
344 Dict::try_from_sorted_slice(&values).map(Self)
345 }
346}
347
348impl<S> TryFrom<HashMap<u32, VarUint248, S>> for ExtraCurrencyCollection
349where
350 S: std::hash::BuildHasher,
351{
352 type Error = Error;
353
354 fn try_from(value: HashMap<u32, VarUint248, S>) -> Result<Self, Self::Error> {
355 let mut values = value.into_iter().collect::<Box<[_]>>();
356 values.sort_unstable_by_key(|(id, _)| *id);
357 Dict::try_from_sorted_slice(&values).map(Self)
358 }
359}
360
361impl TryFrom<&'_ BTreeMap<u32, VarUint248>> for ExtraCurrencyCollection {
362 type Error = Error;
363
364 #[inline]
365 fn try_from(value: &'_ BTreeMap<u32, VarUint248>) -> Result<Self, Self::Error> {
366 Dict::try_from_btree(value).map(Self)
367 }
368}
369
370impl TryFrom<BTreeMap<u32, VarUint248>> for ExtraCurrencyCollection {
371 type Error = Error;
372
373 #[inline]
374 fn try_from(value: BTreeMap<u32, VarUint248>) -> Result<Self, Self::Error> {
375 Self::try_from(&value)
376 }
377}
378
379impl From<Dict<u32, VarUint248>> for ExtraCurrencyCollection {
380 #[inline]
381 fn from(value: Dict<u32, VarUint248>) -> Self {
382 Self(value)
383 }
384}
385
386impl From<ExtraCurrencyCollection> for Dict<u32, VarUint248> {
387 #[inline]
388 fn from(value: ExtraCurrencyCollection) -> Self {
389 value.0
390 }
391}
392
393impl ExactSize for ExtraCurrencyCollection {
394 #[inline]
395 fn exact_size(&self) -> Size {
396 self.0.exact_size()
397 }
398}
399
400#[cfg(feature = "arbitrary")]
401impl<'a> arbitrary::Arbitrary<'a> for ExtraCurrencyCollection {
402 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
403 let size = u.arbitrary::<u8>()?;
404 if size <= 128 {
405 Ok(Self(Dict::new()))
406 } else {
407 let mut dict = Dict::<u32, VarUint248>::new();
408 for _ in 128..size {
409 dict.set(u.arbitrary::<u32>()?, u.arbitrary::<VarUint248>()?)
410 .unwrap();
411 }
412 Ok(Self(dict))
413 }
414 }
415
416 fn size_hint(_: usize) -> (usize, Option<usize>) {
417 (1, None)
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use crate::cell::Lazy;
425 use crate::models::{DepthBalanceInfo, ShardAccount, ShardAccounts};
426
427 fn _cc_must_use() -> anyhow::Result<()> {
428 #[expect(unused_must_use)]
429 {
430 CurrencyCollection::new(10).checked_add(&CurrencyCollection::ZERO)?;
431 }
432
433 #[expect(unused_must_use)]
434 {
435 ExtraCurrencyCollection::new().checked_add(&ExtraCurrencyCollection::new())?;
436 }
437
438 Ok(())
439 }
440
441 #[test]
442 fn cc_math() -> anyhow::Result<()> {
443 let value = CurrencyCollection {
444 tokens: Tokens::new(1),
445 other: ExtraCurrencyCollection::try_from_iter([(1, VarUint248::new(1000))])?,
446 };
447
448 let mut new_value = CurrencyCollection::ZERO;
449 new_value.try_add_assign(&value)?;
450 assert_eq!(new_value, value);
451
452 new_value.try_add_assign(&value)?;
453 assert_ne!(new_value, value);
454
455 let extra = new_value.other.as_dict().get(1)?;
456 assert_eq!(extra, Some(VarUint248::new(2000)));
457
458 Ok(())
459 }
460
461 #[test]
462 fn aug_dict() -> anyhow::Result<()> {
463 let mut accounts = ShardAccounts::new();
464 accounts.set(
465 HashBytes([0; 32]),
466 DepthBalanceInfo {
467 split_depth: 0,
468 balance: CurrencyCollection {
469 tokens: Tokens::new(500_000_000_000),
470 other: ExtraCurrencyCollection::new(),
471 },
472 },
473 ShardAccount {
474 account: Lazy::from_raw(Cell::empty_cell())?,
475 last_trans_lt: 0,
476 last_trans_hash: Default::default(),
477 },
478 )?;
479
480 accounts.set(
481 HashBytes([1; 32]),
482 DepthBalanceInfo {
483 split_depth: 0,
484 balance: CurrencyCollection {
485 tokens: Tokens::new(500_000_000_000),
486 other: ExtraCurrencyCollection::try_from_iter([(2, VarUint248::new(1000))])?,
487 },
488 },
489 ShardAccount {
490 account: Lazy::from_raw(Cell::empty_cell())?,
491 last_trans_lt: 0,
492 last_trans_hash: Default::default(),
493 },
494 )?;
495
496 Ok(())
497 }
498}