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) if balance > other_balance => {
316 ok!(result.0.set(currency_id, other_balance));
317 }
318 None if !balance.is_zero() => {
321 ok!(result.0.remove_raw(currency_id));
323 }
324 _ => {}
327 }
328 }
329 Ok(result)
330 }
331}
332
333impl<S> TryFrom<&'_ HashMap<u32, VarUint248, S>> for ExtraCurrencyCollection
334where
335 S: std::hash::BuildHasher,
336{
337 type Error = Error;
338
339 fn try_from(value: &'_ HashMap<u32, VarUint248, S>) -> Result<Self, Self::Error> {
340 let mut values = value.iter().collect::<Box<[_]>>();
341 values.sort_unstable_by_key(|(id, _)| *id);
342 Dict::try_from_sorted_slice(&values).map(Self)
343 }
344}
345
346impl<S> TryFrom<HashMap<u32, VarUint248, S>> for ExtraCurrencyCollection
347where
348 S: std::hash::BuildHasher,
349{
350 type Error = Error;
351
352 fn try_from(value: HashMap<u32, VarUint248, S>) -> Result<Self, Self::Error> {
353 let mut values = value.into_iter().collect::<Box<[_]>>();
354 values.sort_unstable_by_key(|(id, _)| *id);
355 Dict::try_from_sorted_slice(&values).map(Self)
356 }
357}
358
359impl TryFrom<&'_ BTreeMap<u32, VarUint248>> for ExtraCurrencyCollection {
360 type Error = Error;
361
362 #[inline]
363 fn try_from(value: &'_ BTreeMap<u32, VarUint248>) -> Result<Self, Self::Error> {
364 Dict::try_from_btree(value).map(Self)
365 }
366}
367
368impl TryFrom<BTreeMap<u32, VarUint248>> for ExtraCurrencyCollection {
369 type Error = Error;
370
371 #[inline]
372 fn try_from(value: BTreeMap<u32, VarUint248>) -> Result<Self, Self::Error> {
373 Self::try_from(&value)
374 }
375}
376
377impl From<Dict<u32, VarUint248>> for ExtraCurrencyCollection {
378 #[inline]
379 fn from(value: Dict<u32, VarUint248>) -> Self {
380 Self(value)
381 }
382}
383
384impl From<ExtraCurrencyCollection> for Dict<u32, VarUint248> {
385 #[inline]
386 fn from(value: ExtraCurrencyCollection) -> Self {
387 value.0
388 }
389}
390
391impl ExactSize for ExtraCurrencyCollection {
392 #[inline]
393 fn exact_size(&self) -> Size {
394 self.0.exact_size()
395 }
396}
397
398#[cfg(feature = "arbitrary")]
399impl<'a> arbitrary::Arbitrary<'a> for ExtraCurrencyCollection {
400 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
401 let size = u.arbitrary::<u8>()?;
402 if size <= 128 {
403 Ok(Self(Dict::new()))
404 } else {
405 let mut dict = Dict::<u32, VarUint248>::new();
406 for _ in 128..size {
407 dict.set(u.arbitrary::<u32>()?, u.arbitrary::<VarUint248>()?)
408 .unwrap();
409 }
410 Ok(Self(dict))
411 }
412 }
413
414 fn size_hint(_: usize) -> (usize, Option<usize>) {
415 (1, None)
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422 use crate::cell::Lazy;
423 use crate::models::{DepthBalanceInfo, ShardAccount, ShardAccounts};
424
425 fn _cc_must_use() -> anyhow::Result<()> {
426 #[expect(unused_must_use)]
427 {
428 CurrencyCollection::new(10).checked_add(&CurrencyCollection::ZERO)?;
429 }
430
431 #[expect(unused_must_use)]
432 {
433 ExtraCurrencyCollection::new().checked_add(&ExtraCurrencyCollection::new())?;
434 }
435
436 Ok(())
437 }
438
439 #[test]
440 fn cc_math() -> anyhow::Result<()> {
441 let value = CurrencyCollection {
442 tokens: Tokens::new(1),
443 other: ExtraCurrencyCollection::try_from_iter([(1, VarUint248::new(1000))])?,
444 };
445
446 let mut new_value = CurrencyCollection::ZERO;
447 new_value.try_add_assign(&value)?;
448 assert_eq!(new_value, value);
449
450 new_value.try_add_assign(&value)?;
451 assert_ne!(new_value, value);
452
453 let extra = new_value.other.as_dict().get(1)?;
454 assert_eq!(extra, Some(VarUint248::new(2000)));
455
456 Ok(())
457 }
458
459 #[test]
460 fn aug_dict() -> anyhow::Result<()> {
461 let mut accounts = ShardAccounts::new();
462 accounts.set(
463 HashBytes([0; 32]),
464 DepthBalanceInfo {
465 split_depth: 0,
466 balance: CurrencyCollection {
467 tokens: Tokens::new(500_000_000_000),
468 other: ExtraCurrencyCollection::new(),
469 },
470 },
471 ShardAccount {
472 account: Lazy::from_raw(Cell::empty_cell())?,
473 last_trans_lt: 0,
474 last_trans_hash: Default::default(),
475 },
476 )?;
477
478 accounts.set(
479 HashBytes([1; 32]),
480 DepthBalanceInfo {
481 split_depth: 0,
482 balance: CurrencyCollection {
483 tokens: Tokens::new(500_000_000_000),
484 other: ExtraCurrencyCollection::try_from_iter([(2, VarUint248::new(1000))])?,
485 },
486 },
487 ShardAccount {
488 account: Lazy::from_raw(Cell::empty_cell())?,
489 last_trans_lt: 0,
490 last_trans_hash: Default::default(),
491 },
492 )?;
493
494 Ok(())
495 }
496}