tycho_types/models/account/
mod.rs

1//! Account state models.
2
3use crate::cell::*;
4use crate::dict::*;
5use crate::error::*;
6use crate::models::currency::CurrencyCollection;
7use crate::models::message::IntAddr;
8use crate::num::*;
9
10/// Amount of unique cells and bits for shard states.
11#[derive(Debug, Default, Clone, Eq, PartialEq, Store, Load)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
14pub struct StorageUsed {
15    /// Amount of unique cells.
16    pub cells: VarUint56,
17    /// The total number of bits in unique cells.
18    pub bits: VarUint56,
19}
20
21impl StorageUsed {
22    /// The additive identity for this type, i.e. `0`.
23    pub const ZERO: Self = Self {
24        cells: VarUint56::ZERO,
25        bits: VarUint56::ZERO,
26    };
27
28    /// Computes a total storage usage stats.
29    ///
30    /// `cell_limit` is the maximum number of unique cells to visit.
31    /// If the limit is reached, the function will return [`Error::Cancelled`].
32    pub fn compute(account: &Account, cell_limit: usize) -> Result<Self, Error> {
33        let cell = {
34            let cx = Cell::empty_context();
35            let mut storage = CellBuilder::new();
36            storage.store_u64(account.last_trans_lt)?;
37            account.balance.store_into(&mut storage, cx)?;
38            account.state.store_into(&mut storage, cx)?;
39            storage.build_ext(cx)?
40        };
41
42        let Some(res) = cell.compute_unique_stats(cell_limit) else {
43            return Err(Error::Cancelled);
44        };
45
46        let res = Self {
47            cells: VarUint56::new(res.cell_count),
48            bits: VarUint56::new(res.bit_count),
49        };
50
51        if res.cells.is_valid() || !res.bits.is_valid() {
52            return Err(Error::IntOverflow);
53        }
54
55        Ok(res)
56    }
57}
58
59/// Amount of unique cells and bits.
60#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Store, Load)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
63pub struct StorageUsedShort {
64    /// Amount of unique cells.
65    pub cells: VarUint56,
66    /// The total number of bits in unique cells.
67    pub bits: VarUint56,
68}
69
70impl StorageUsedShort {
71    /// The additive identity for this type, i.e. `0`.
72    pub const ZERO: Self = Self {
73        cells: VarUint56::ZERO,
74        bits: VarUint56::ZERO,
75    };
76}
77
78/// Storage state extra
79#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
82pub enum StorageExtra {
83    /// Storage extra is not present.
84    #[default]
85    None,
86    /// Storage extra is present.
87    DictHash(HashBytes),
88}
89
90impl Store for StorageExtra {
91    fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> {
92        match self {
93            StorageExtra::None => builder.store_zeros(3)?,
94            StorageExtra::DictHash(dict_hash) => {
95                builder.store_small_uint(1, 3)?;
96                builder.store_u256(dict_hash)?;
97            }
98        }
99        Ok(())
100    }
101}
102
103impl<'a> Load<'a> for StorageExtra {
104    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
105        match slice.load_small_uint(3)? {
106            0b000 => Ok(Self::None),
107            0b001 => Ok(Self::DictHash(slice.load_u256()?)),
108            _ => Err(Error::InvalidTag),
109        }
110    }
111}
112
113/// Storage profile of an account.
114#[derive(Debug, Default, Clone, Eq, PartialEq, Store, Load)]
115#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
116#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
117pub struct StorageInfo {
118    /// Amount of unique cells and bits which account state occupies.
119    pub used: StorageUsed,
120    /// Storage extra
121    pub storage_extra: StorageExtra,
122    /// Unix timestamp of the last storage phase.
123    pub last_paid: u32,
124    /// Account debt for storing its state.
125    pub due_payment: Option<Tokens>,
126}
127
128/// Brief account status.
129#[derive(Debug, Clone, Copy, Eq, PartialEq)]
130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
132pub enum AccountStatus {
133    /// Account exists but has not yet been deployed.
134    Uninit = 0b00,
135    /// Account exists but has been frozen.
136    Frozen = 0b01,
137    /// Account exists and has been deployed.
138    Active = 0b10,
139    /// Account does not exist.
140    NotExists = 0b11,
141}
142
143impl AccountStatus {
144    /// The number of data bits that this struct occupies.
145    pub const BITS: u16 = 2;
146}
147
148impl Store for AccountStatus {
149    #[inline]
150    fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> {
151        builder.store_small_uint(*self as u8, 2)
152    }
153}
154
155impl<'a> Load<'a> for AccountStatus {
156    #[inline]
157    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
158        match slice.load_small_uint(2) {
159            Ok(ty) => Ok(match ty {
160                0b00 => Self::Uninit,
161                0b01 => Self::Frozen,
162                0b10 => Self::Active,
163                0b11 => Self::NotExists,
164                _ => {
165                    debug_assert!(false, "unexpected small uint");
166                    // SAFETY: `load_small_uint` must return 2 bits
167                    unsafe { std::hint::unreachable_unchecked() }
168                }
169            }),
170            Err(e) => Err(e),
171        }
172    }
173}
174
175/// Shard accounts entry.
176#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178pub struct ShardAccount {
179    /// Optional reference to account state.
180    pub account: Lazy<OptionalAccount>,
181    /// The exact hash of the last transaction.
182    pub last_trans_hash: HashBytes,
183    /// The exact logical time of the last transaction.
184    pub last_trans_lt: u64,
185}
186
187impl ShardAccount {
188    /// Tries to load account data.
189    pub fn load_account(&self) -> Result<Option<Account>, Error> {
190        let OptionalAccount(account) = ok!(self.account.load());
191        Ok(account)
192    }
193}
194
195#[cfg(feature = "arbitrary")]
196impl<'a> arbitrary::Arbitrary<'a> for ShardAccount {
197    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
198        let account = u.arbitrary::<OptionalAccount>()?;
199        Ok(Self {
200            account: Lazy::new(&account).unwrap(),
201            last_trans_hash: u.arbitrary()?,
202            last_trans_lt: u.arbitrary()?,
203        })
204    }
205
206    #[inline]
207    fn size_hint(depth: usize) -> (usize, Option<usize>) {
208        Self::try_size_hint(depth).unwrap_or_default()
209    }
210
211    fn try_size_hint(
212        depth: usize,
213    ) -> arbitrary::Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
214        Ok(arbitrary::size_hint::and_all(&[
215            <OptionalAccount as arbitrary::Arbitrary>::try_size_hint(depth)?,
216            <HashBytes as arbitrary::Arbitrary>::try_size_hint(depth)?,
217            <u64 as arbitrary::Arbitrary>::try_size_hint(depth)?,
218        ]))
219    }
220}
221
222/// A wrapper for `Option<Account>` with customized representation.
223#[derive(Default, Debug, Clone, Eq, PartialEq, Store, Load)]
224#[repr(transparent)]
225#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
226pub struct OptionalAccount(pub Option<Account>);
227
228impl OptionalAccount {
229    /// Non-existing account.
230    pub const EMPTY: Self = Self(None);
231
232    /// Returns an optional account status.
233    pub fn status(&self) -> AccountStatus {
234        match &self.0 {
235            None => AccountStatus::NotExists,
236            Some(account) => account.state.status(),
237        }
238    }
239
240    /// Logical time after the last transaction execution if account exists
241    /// or zero otherwise.
242    pub fn last_trans_lt(&self) -> u64 {
243        match &self.0 {
244            None => 0,
245            Some(account) => account.last_trans_lt,
246        }
247    }
248
249    /// Account balance for all currencies.
250    #[cfg(feature = "sync")]
251    pub fn balance(&self) -> &CurrencyCollection {
252        static DEFAULT_VALANCE: CurrencyCollection = CurrencyCollection::ZERO;
253
254        match &self.0 {
255            None => &DEFAULT_VALANCE,
256            Some(account) => &account.balance,
257        }
258    }
259
260    /// Returns an account state if it exists.
261    pub fn state(&self) -> Option<&AccountState> {
262        Some(&self.0.as_ref()?.state)
263    }
264}
265
266impl AsRef<Option<Account>> for OptionalAccount {
267    #[inline]
268    fn as_ref(&self) -> &Option<Account> {
269        &self.0
270    }
271}
272
273impl AsMut<Option<Account>> for OptionalAccount {
274    #[inline]
275    fn as_mut(&mut self) -> &mut Option<Account> {
276        &mut self.0
277    }
278}
279
280impl From<Account> for OptionalAccount {
281    #[inline]
282    fn from(value: Account) -> Self {
283        Self(Some(value))
284    }
285}
286
287#[cfg(feature = "arbitrary")]
288impl<'a> arbitrary::Arbitrary<'a> for OptionalAccount {
289    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
290        u.ratio(9u8, 10u8)?
291            .then(|| u.arbitrary())
292            .transpose()
293            .map(Self)
294    }
295
296    #[inline]
297    fn size_hint(depth: usize) -> (usize, Option<usize>) {
298        Self::try_size_hint(depth).unwrap_or_default()
299    }
300
301    #[inline]
302    fn try_size_hint(
303        depth: usize,
304    ) -> arbitrary::Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
305        <Option<Account>>::try_size_hint(depth)
306    }
307}
308
309/// Existing account data.
310#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
311#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
313pub struct Account {
314    /// Account address.
315    pub address: IntAddr,
316    /// Storage statistics.
317    pub storage_stat: StorageInfo,
318    /// Logical time after the last transaction execution.
319    pub last_trans_lt: u64,
320    /// Account balance for all currencies.
321    pub balance: CurrencyCollection,
322    /// Account state.
323    pub state: AccountState,
324}
325
326/// State of an existing account.
327#[derive(Debug, Clone, Eq, PartialEq)]
328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329#[cfg_attr(feature = "serde", serde(tag = "status"))]
330#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
331pub enum AccountState {
332    /// Account exists but has not yet been deployed.
333    Uninit,
334    /// Account exists and has been deployed.
335    Active(StateInit),
336    /// Account exists but has been frozen. Contains a hash of the last known [`StateInit`].
337    Frozen(HashBytes),
338}
339
340impl AccountState {
341    /// Returns an account status.
342    pub fn status(&self) -> AccountStatus {
343        match self {
344            Self::Uninit => AccountStatus::Uninit,
345            Self::Active(_) => AccountStatus::Active,
346            Self::Frozen(_) => AccountStatus::Frozen,
347        }
348    }
349}
350
351impl Store for AccountState {
352    fn store_into(
353        &self,
354        builder: &mut CellBuilder,
355        context: &dyn CellContext,
356    ) -> Result<(), Error> {
357        match self {
358            Self::Uninit => builder.store_small_uint(0b00, 2),
359            Self::Active(state) => {
360                ok!(builder.store_bit_one());
361                state.store_into(builder, context)
362            }
363            Self::Frozen(hash) => {
364                ok!(builder.store_small_uint(0b01, 2));
365                builder.store_u256(hash)
366            }
367        }
368    }
369}
370
371impl<'a> Load<'a> for AccountState {
372    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
373        Ok(if ok!(slice.load_bit()) {
374            match StateInit::load_from(slice) {
375                Ok(state) => Self::Active(state),
376                Err(e) => return Err(e),
377            }
378        } else if ok!(slice.load_bit()) {
379            match slice.load_u256() {
380                Ok(state) => Self::Frozen(state),
381                Err(e) => return Err(e),
382            }
383        } else {
384            Self::Uninit
385        })
386    }
387}
388
389/// Deployed account state.
390#[derive(Debug, Clone, Default, Eq, PartialEq, Store, Load)]
391#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
392pub struct StateInit {
393    /// Optional split depth for large smart contracts.
394    pub split_depth: Option<SplitDepth>,
395    /// Optional special contract flags.
396    pub special: Option<SpecialFlags>,
397    /// Optional contract code.
398    #[cfg_attr(feature = "serde", serde(with = "crate::boc::Boc"))]
399    pub code: Option<Cell>,
400    /// Optional contract data.
401    #[cfg_attr(feature = "serde", serde(with = "crate::boc::Boc"))]
402    pub data: Option<Cell>,
403    /// Libraries used in smart-contract.
404    pub libraries: Dict<HashBytes, SimpleLib>,
405}
406
407impl StateInit {
408    /// Exact size of this value when it is stored in slice.
409    pub const fn exact_size_const(&self) -> Size {
410        Size {
411            bits: self.bit_len(),
412            refs: self.reference_count(),
413        }
414    }
415
416    /// Returns the number of data bits that this struct occupies.
417    pub const fn bit_len(&self) -> u16 {
418        (1 + self.split_depth.is_some() as u16 * SplitDepth::BITS)
419            + (1 + self.special.is_some() as u16 * SpecialFlags::BITS)
420            + 3
421    }
422
423    /// Returns the number of references that this struct occupies.
424    pub const fn reference_count(&self) -> u8 {
425        self.code.is_some() as u8 + self.data.is_some() as u8 + !self.libraries.is_empty() as u8
426    }
427}
428
429impl ExactSize for StateInit {
430    #[inline]
431    fn exact_size(&self) -> Size {
432        self.exact_size_const()
433    }
434}
435
436#[cfg(feature = "arbitrary")]
437impl<'a> arbitrary::Arbitrary<'a> for StateInit {
438    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
439        let split_depth = u.ratio(1u8, 50u8)?.then(|| u.arbitrary()).transpose()?;
440        let special = u.ratio(1u8, 50u8)?.then(|| u.arbitrary()).transpose()?;
441        let code = u.ratio(9u8, 10u8)?.then(|| u.arbitrary()).transpose()?;
442        let data = u.ratio(9u8, 10u8)?.then(|| u.arbitrary()).transpose()?;
443
444        let mut libraries = Dict::new();
445        match u.arbitrary::<u8>()? {
446            0..=128 => {}
447            n => {
448                for _ in 128..n {
449                    let lib = u.arbitrary::<SimpleLib>()?;
450                    if lib.root.level() != 0 || lib.root.has_max_depth() {
451                        return Err(arbitrary::Error::IncorrectFormat);
452                    }
453                    libraries.set(u.arbitrary::<HashBytes>()?, lib).unwrap();
454                }
455            }
456        }
457
458        Ok(Self {
459            split_depth,
460            special,
461            code,
462            data,
463            libraries,
464        })
465    }
466
467    #[inline]
468    fn size_hint(depth: usize) -> (usize, Option<usize>) {
469        Self::try_size_hint(depth).unwrap_or_default()
470    }
471
472    fn try_size_hint(
473        depth: usize,
474    ) -> arbitrary::Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
475        Ok(arbitrary::size_hint::and_all(&[
476            <Option<SplitDepth>>::try_size_hint(depth)?,
477            <Option<SpecialFlags>>::try_size_hint(depth)?,
478            <Option<Cell>>::try_size_hint(depth)?,
479            <Option<Cell>>::try_size_hint(depth)?,
480            (1, None),
481        ]))
482    }
483}
484
485/// Special transactions execution flags.
486#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
487#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
488#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
489pub struct SpecialFlags {
490    /// Account will be called at the beginning of each block.
491    pub tick: bool,
492    /// Account will be called at the end of each block.
493    pub tock: bool,
494}
495
496impl SpecialFlags {
497    /// The number of data bits that this struct occupies.
498    pub const BITS: u16 = 2;
499}
500
501impl Store for SpecialFlags {
502    fn store_into(&self, builder: &mut CellBuilder, _: &dyn CellContext) -> Result<(), Error> {
503        builder.store_small_uint(((self.tick as u8) << 1) | self.tock as u8, 2)
504    }
505}
506
507impl<'a> Load<'a> for SpecialFlags {
508    fn load_from(slice: &mut CellSlice<'a>) -> Result<Self, Error> {
509        match slice.load_small_uint(2) {
510            Ok(data) => Ok(Self {
511                tick: data & 0b10 != 0,
512                tock: data & 0b01 != 0,
513            }),
514            Err(e) => Err(e),
515        }
516    }
517}
518
519/// Simple TVM library.
520#[derive(Debug, Clone, Eq, PartialEq, Store, Load)]
521#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
522#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
523pub struct SimpleLib {
524    /// Whether this library is accessible from other accounts.
525    pub public: bool,
526    /// Library code.
527    #[cfg_attr(feature = "serde", serde(with = "crate::boc::Boc"))]
528    pub root: Cell,
529}