everscale_types/models/
currency.rs

1//! Currency collection stuff.
2
3use crate::cell::*;
4use crate::dict::{AugDictExtra, Dict};
5use crate::error::Error;
6use crate::num::{Tokens, VarUint248};
7
8/// Amounts collection.
9#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[must_use]
12pub struct CurrencyCollection {
13    /// Amount in native currency.
14    pub tokens: Tokens,
15    /// Amounts in other currencies.
16    pub other: ExtraCurrencyCollection,
17}
18
19impl Default for CurrencyCollection {
20    #[inline]
21    fn default() -> Self {
22        Self::ZERO
23    }
24}
25
26impl CurrencyCollection {
27    /// The additive identity for the currency collection
28    /// (with empty extra currencies).
29    pub const ZERO: Self = Self {
30        tokens: Tokens::ZERO,
31        other: ExtraCurrencyCollection::new(),
32    };
33
34    /// Creates a new currency collection with from the specified tokens amount
35    /// and empty extra currency collection.
36    pub const fn new(tokens: u128) -> Self {
37        Self {
38            tokens: Tokens::new(tokens),
39            other: ExtraCurrencyCollection::new(),
40        }
41    }
42
43    /// Returns the number of data bits that this struct occupies.
44    pub const fn bit_len(&self) -> u16 {
45        self.tokens.unwrap_bit_len() + 1
46    }
47
48    /// Checked currency collection addition.
49    /// Computes `self + rhs` for each currency, returning `Err`
50    /// if overflow occurred or dictionaries had invalid structure.
51    pub fn checked_add(&self, other: &Self) -> Result<Self, Error> {
52        Ok(Self {
53            tokens: match self.tokens.checked_add(other.tokens) {
54                Some(value) => value,
55                None => return Err(Error::IntOverflow),
56            },
57            other: ok!(self.other.checked_add(&other.other)),
58        })
59    }
60
61    /// Checked currency collection subtraction.
62    /// Computes `self - rhs` for each currency, returning `Err`
63    /// if overflow occurred or dictionaries had invalid structure.
64    pub fn checked_sub(&self, other: &Self) -> Result<Self, Error> {
65        Ok(Self {
66            tokens: match self.tokens.checked_sub(other.tokens) {
67                Some(value) => value,
68                None => return Err(Error::IntOverflow),
69            },
70            other: ok!(self.other.checked_sub(&other.other)),
71        })
72    }
73
74    /// Tries to add the specified amount of native tokens to the collection.
75    pub fn try_add_assign_tokens(&mut self, other: Tokens) -> Result<(), Error> {
76        match self.tokens.checked_add(other) {
77            Some(value) => {
78                self.tokens = value;
79                Ok(())
80            }
81            None => Err(Error::IntOverflow),
82        }
83    }
84
85    /// Tries to subtract the specified amount of native tokens from the collection.
86    pub fn try_sub_assign_tokens(&mut self, other: Tokens) -> Result<(), Error> {
87        match self.tokens.checked_sub(other) {
88            Some(value) => {
89                self.tokens = value;
90                Ok(())
91            }
92            None => Err(Error::IntOverflow),
93        }
94    }
95
96    /// Tries to add an other currency collection to the current one.
97    pub fn try_add_assign(&mut self, other: &Self) -> Result<(), Error> {
98        *self = ok!(self.checked_add(other));
99        Ok(())
100    }
101
102    /// Tries to subtract an other currency collection from the current one.
103    pub fn try_sub_assign(&mut self, other: &Self) -> Result<(), Error> {
104        *self = ok!(self.checked_sub(other));
105        Ok(())
106    }
107}
108
109impl From<Tokens> for CurrencyCollection {
110    #[inline]
111    fn from(tokens: Tokens) -> Self {
112        Self {
113            tokens,
114            other: ExtraCurrencyCollection::new(),
115        }
116    }
117}
118
119impl ExactSize for CurrencyCollection {
120    #[inline]
121    fn exact_size(&self) -> Size {
122        self.tokens.exact_size() + self.other.exact_size()
123    }
124}
125
126impl AugDictExtra for CurrencyCollection {
127    fn comp_add(
128        left: &mut CellSlice,
129        right: &mut CellSlice,
130        b: &mut CellBuilder,
131        cx: &mut dyn CellContext,
132    ) -> Result<(), Error> {
133        let left = ok!(Self::load_from(left));
134        let right = ok!(Self::load_from(right));
135        ok!(left.checked_add(&right)).store_into(b, cx)
136    }
137}
138
139/// Dictionary with amounts for multiple currencies.
140#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142#[must_use]
143#[repr(transparent)]
144pub struct ExtraCurrencyCollection(Dict<u32, VarUint248>);
145
146impl Default for ExtraCurrencyCollection {
147    #[inline]
148    fn default() -> Self {
149        Self(Dict::new())
150    }
151}
152
153impl ExtraCurrencyCollection {
154    /// Creates an empty extra currency collection.
155    pub const fn new() -> Self {
156        Self(Dict::new())
157    }
158
159    /// Creates a currency collection from a raw cell.
160    pub const fn from_raw(dict: Option<Cell>) -> Self {
161        Self(Dict::from_raw(dict))
162    }
163
164    /// Returns `true` if the dictionary contains no elements.
165    pub const fn is_empty(&self) -> bool {
166        self.0.is_empty()
167    }
168
169    /// Returns a reference to the underlying dictionary.
170    pub const fn as_dict(&self) -> &Dict<u32, VarUint248> {
171        &self.0
172    }
173
174    /// Returns a mutable reference to the underlying dictionary.
175    pub fn as_dict_mut(&mut self) -> &mut Dict<u32, VarUint248> {
176        &mut self.0
177    }
178
179    /// Checked extra currency collection addition.
180    /// Computes `self + rhs` for each currency, returning `Err`
181    /// if overflow occurred or dictionaries had invalid structure.
182    pub fn checked_add(&self, other: &Self) -> Result<Self, Error> {
183        let mut result = self.clone();
184        for entry in other.0.iter() {
185            let (currency_id, other) = ok!(entry);
186
187            let existing = ok!(result.as_dict().get(currency_id)).unwrap_or_default();
188            match existing.checked_add(&other) {
189                Some(ref value) => ok!(result.0.set(currency_id, value)),
190                None => return Err(Error::IntOverflow),
191            };
192        }
193        Ok(result)
194    }
195
196    /// Checked extra currency subtraction.
197    /// Computes `self - rhs` for each currency, returning `Err`
198    /// if overflow occurred or dictionaries had invalid structure.
199    pub fn checked_sub(&self, other: &Self) -> Result<Self, Error> {
200        let mut result = self.clone();
201        for entry in other.0.iter() {
202            let (currency_id, other) = ok!(entry);
203
204            let existing = ok!(result.as_dict().get(currency_id)).unwrap_or_default();
205            match existing.checked_sub(&other) {
206                Some(ref value) => ok!(result.0.set(currency_id, value)),
207                None => return Err(Error::IntOverflow),
208            };
209        }
210        Ok(result)
211    }
212}
213
214impl From<Dict<u32, VarUint248>> for ExtraCurrencyCollection {
215    #[inline]
216    fn from(value: Dict<u32, VarUint248>) -> Self {
217        Self(value)
218    }
219}
220
221impl ExactSize for ExtraCurrencyCollection {
222    #[inline]
223    fn exact_size(&self) -> Size {
224        self.0.exact_size()
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    fn _cc_must_use() -> anyhow::Result<()> {
233        #[expect(unused_must_use)]
234        {
235            CurrencyCollection::new(10).checked_add(&CurrencyCollection::ZERO)?;
236        }
237
238        #[expect(unused_must_use)]
239        {
240            ExtraCurrencyCollection::new().checked_add(&ExtraCurrencyCollection::new())?;
241        }
242
243        Ok(())
244    }
245}