Skip to main content

tycho_executor/
lib.rs

1use anyhow::{Context, Result};
2use tycho_types::cell::Lazy;
3use tycho_types::dict;
4use tycho_types::error::Error;
5use tycho_types::models::{
6    Account, AccountState, AccountStatus, BlockId, CurrencyCollection, HashUpdate, IntAddr,
7    LibDescr, Message, OwnedMessage, ShardAccount, SimpleLib, StdAddr, StorageInfo, StorageUsed,
8    TickTock, Transaction, TxInfo,
9};
10use tycho_types::num::{Tokens, Uint15};
11use tycho_types::prelude::*;
12
13pub use self::config::ParsedConfig;
14pub use self::error::{TxError, TxResult};
15use self::util::new_varuint56_truncate;
16pub use self::util::{ExtStorageStat, OwnedExtStorageStat, StorageStatLimits};
17
18mod config;
19mod error;
20mod util;
21
22pub mod phase {
23    pub use self::action::{ActionPhaseContext, ActionPhaseFull};
24    pub use self::bounce::BouncePhaseContext;
25    pub use self::compute::{
26        ComputePhaseContext, ComputePhaseFull, ComputePhaseSmcInfo, TransactionInput,
27    };
28    pub use self::receive::{MsgStateInit, ReceivedMessage};
29    pub use self::storage::StoragePhaseContext;
30
31    mod action;
32    mod bounce;
33    mod compute;
34    mod credit;
35    mod receive;
36    mod storage;
37}
38
39mod tx {
40    mod ordinary;
41    mod ticktock;
42}
43
44/// Transaction executor.
45pub struct Executor<'a> {
46    params: &'a ExecutorParams,
47    config: &'a ParsedConfig,
48    min_lt: u64,
49    override_special: Option<bool>,
50}
51
52impl<'a> Executor<'a> {
53    pub fn new(params: &'a ExecutorParams, config: &'a ParsedConfig) -> Self {
54        Self {
55            params,
56            config,
57            min_lt: 0,
58            override_special: None,
59        }
60    }
61
62    pub fn with_min_lt(mut self, min_lt: u64) -> Self {
63        self.set_min_lt(min_lt);
64        self
65    }
66
67    pub fn set_min_lt(&mut self, min_lt: u64) {
68        self.min_lt = min_lt;
69    }
70
71    pub fn override_special(mut self, is_special: bool) -> Self {
72        self.override_special = Some(is_special);
73        self
74    }
75
76    #[inline]
77    pub fn begin_ordinary<'s, M>(
78        &self,
79        address: &StdAddr,
80        is_external: bool,
81        msg: M,
82        state: &'s ShardAccount,
83    ) -> TxResult<UncommittedTransaction<'a, 's>>
84    where
85        M: LoadMessage,
86    {
87        self.begin_ordinary_ext(address, is_external, msg, state, None)
88    }
89
90    pub fn begin_ordinary_ext<'s, M>(
91        &self,
92        address: &StdAddr,
93        is_external: bool,
94        msg: M,
95        state: &'s ShardAccount,
96        inspector: Option<&mut ExecutorInspector<'_>>,
97    ) -> TxResult<UncommittedTransaction<'a, 's>>
98    where
99        M: LoadMessage,
100    {
101        let msg_root = msg.load_message_root()?;
102
103        let account = state.load_account()?;
104        let mut exec = self.begin(address, account)?;
105        let info = exec.run_ordinary_transaction(is_external, msg_root.clone(), inspector)?;
106
107        UncommittedTransaction::with_info(exec, state, Some(msg_root), info).map_err(TxError::Fatal)
108    }
109
110    #[inline]
111    pub fn begin_tick_tock<'s>(
112        &self,
113        address: &StdAddr,
114        kind: TickTock,
115        state: &'s ShardAccount,
116    ) -> TxResult<UncommittedTransaction<'a, 's>> {
117        self.begin_tick_tock_ext(address, kind, state, None)
118    }
119
120    pub fn begin_tick_tock_ext<'s>(
121        &self,
122        address: &StdAddr,
123        kind: TickTock,
124        state: &'s ShardAccount,
125        inspector: Option<&mut ExecutorInspector<'_>>,
126    ) -> TxResult<UncommittedTransaction<'a, 's>> {
127        let account = state.load_account()?;
128        let mut exec = self.begin(address, account)?;
129        let info = exec.run_tick_tock_transaction(kind, inspector)?;
130
131        UncommittedTransaction::with_info(exec, state, None, info).map_err(TxError::Fatal)
132    }
133
134    pub fn begin(&self, address: &StdAddr, account: Option<Account>) -> Result<ExecutorState<'a>> {
135        let is_special = self
136            .override_special
137            .unwrap_or_else(|| self.config.is_special(address));
138
139        let acc_address;
140        let acc_storage_stat;
141        let acc_balance;
142        let acc_state;
143        let orig_status;
144        let end_status;
145        let start_lt;
146        match account {
147            Some(acc) => {
148                acc_address = 'addr: {
149                    if let IntAddr::Std(acc_addr) = acc.address
150                        && acc_addr == *address
151                    {
152                        break 'addr acc_addr;
153                    }
154                    anyhow::bail!("account address mismatch");
155                };
156                acc_storage_stat = acc.storage_stat;
157                acc_balance = acc.balance;
158                acc_state = acc.state;
159                orig_status = acc_state.status();
160                end_status = orig_status;
161                start_lt = std::cmp::max(self.min_lt, acc.last_trans_lt);
162            }
163            None => {
164                acc_address = address.clone();
165                acc_storage_stat = StorageInfo {
166                    used: StorageUsed::ZERO,
167                    storage_extra: Default::default(),
168                    last_paid: 0,
169                    due_payment: None,
170                };
171                acc_balance = CurrencyCollection::ZERO;
172                acc_state = AccountState::Uninit;
173                orig_status = AccountStatus::NotExists;
174                end_status = AccountStatus::Uninit;
175                start_lt = self.min_lt;
176            }
177        };
178
179        let mut is_marks_authority = false;
180        let mut is_suspended_by_marks = false;
181        if self.params.authority_marks_enabled
182            && let Some(marks) = &self.config.authority_marks
183        {
184            is_marks_authority = marks.is_authority(address);
185            is_suspended_by_marks =
186                !is_special && !is_marks_authority && marks.is_suspended(&acc_balance)?;
187        }
188
189        Ok(ExecutorState {
190            params: self.params,
191            config: self.config,
192            is_special,
193            is_marks_authority,
194            is_suspended_by_marks,
195            address: acc_address,
196            storage_stat: acc_storage_stat,
197            balance: acc_balance,
198            state: acc_state,
199            orig_status,
200            end_status,
201            start_lt,
202            end_lt: start_lt + 1,
203            out_msgs: Vec::new(),
204            total_fees: Tokens::ZERO,
205            burned: Tokens::ZERO,
206            cached_storage_stat: None,
207        })
208    }
209}
210
211/// Executor internals inspector.
212#[derive(Default)]
213pub struct ExecutorInspector<'e> {
214    /// Actions list from compute phase.
215    pub actions: Option<Cell>,
216    /// A set of changes of the public libraries dict.
217    ///
218    /// NOTE: The order is the same as the actions order so
219    /// it can contain duplicates and must be folded before use.
220    pub public_libs_diff: Vec<PublicLibraryChange>,
221    /// Compute phase exit code.
222    pub exit_code: Option<i32>,
223    /// Hash of the library in case it was missing during execution.
224    pub missing_library: Option<HashBytes>,
225    /// Total gas consumed (including the remaining "free" gas
226    /// and everything that exceeds the limit).
227    pub total_gas_used: u64,
228    /// Debug output target.
229    pub debug: Option<&'e mut dyn std::fmt::Write>,
230    /// Hook for a compute phase to modify C7 register data.
231    pub modify_smc_info: Option<&'e mut ModifySmcInfoFn>,
232}
233
234/// Public library diff operation.
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub enum PublicLibraryChange {
237    Add(Cell),
238    Remove(HashBytes),
239}
240
241impl PublicLibraryChange {
242    /// Returns a hash of the changed library.
243    pub fn lib_hash(&self) -> &HashBytes {
244        match self {
245            Self::Add(cell) => cell.repr_hash(),
246            Self::Remove(hash) => hash,
247        }
248    }
249}
250
251/// Hook for a compute phase to modify C7 register data.
252pub type ModifySmcInfoFn = dyn FnMut(&mut phase::ComputePhaseSmcInfo) -> Result<()>;
253
254/// Shared state for executor phases.
255pub struct ExecutorState<'a> {
256    pub params: &'a ExecutorParams,
257    pub config: &'a ParsedConfig,
258
259    pub is_special: bool,
260    pub is_marks_authority: bool,
261    pub is_suspended_by_marks: bool,
262
263    pub address: StdAddr,
264    pub storage_stat: StorageInfo,
265    pub balance: CurrencyCollection,
266    pub state: AccountState,
267
268    pub orig_status: AccountStatus,
269    pub end_status: AccountStatus,
270    pub start_lt: u64,
271    pub end_lt: u64,
272
273    pub out_msgs: Vec<Lazy<OwnedMessage>>,
274    pub total_fees: Tokens,
275
276    pub burned: Tokens,
277
278    pub cached_storage_stat: Option<OwnedExtStorageStat>,
279}
280
281#[cfg(test)]
282impl<'a> ExecutorState<'a> {
283    pub(crate) fn new_non_existent(
284        params: &'a ExecutorParams,
285        config: &'a impl AsRef<ParsedConfig>,
286        address: &StdAddr,
287    ) -> Self {
288        Self {
289            params,
290            config: config.as_ref(),
291            is_special: false,
292            is_marks_authority: false,
293            is_suspended_by_marks: false,
294            address: address.clone(),
295            storage_stat: Default::default(),
296            balance: CurrencyCollection::ZERO,
297            state: AccountState::Uninit,
298            orig_status: AccountStatus::NotExists,
299            end_status: AccountStatus::Uninit,
300            start_lt: 0,
301            end_lt: 1,
302            out_msgs: Vec::new(),
303            total_fees: Tokens::ZERO,
304            burned: Tokens::ZERO,
305            cached_storage_stat: None,
306        }
307    }
308
309    pub(crate) fn new_uninit(
310        params: &'a ExecutorParams,
311        config: &'a impl AsRef<ParsedConfig>,
312        address: &StdAddr,
313        balance: impl Into<CurrencyCollection>,
314    ) -> Self {
315        let mut res = Self::new_non_existent(params, config, address);
316        res.balance = balance.into();
317        res.orig_status = AccountStatus::Uninit;
318
319        if params.authority_marks_enabled
320            && let Some(marks) = &config.as_ref().authority_marks
321        {
322            res.is_marks_authority = marks.is_authority(address);
323            res.is_suspended_by_marks = !res.is_special
324                && !res.is_marks_authority
325                && marks.is_suspended(&res.balance).unwrap();
326        }
327
328        res
329    }
330
331    pub(crate) fn new_frozen(
332        params: &'a ExecutorParams,
333        config: &'a impl AsRef<ParsedConfig>,
334        address: &StdAddr,
335        balance: impl Into<CurrencyCollection>,
336        state_hash: HashBytes,
337    ) -> Self {
338        let mut res = Self::new_uninit(params, config, address, balance);
339        res.state = AccountState::Frozen(state_hash);
340        res.orig_status = AccountStatus::Frozen;
341        res.end_status = AccountStatus::Frozen;
342        res
343    }
344
345    pub(crate) fn new_active(
346        params: &'a ExecutorParams,
347        config: &'a impl AsRef<ParsedConfig>,
348        address: &StdAddr,
349        balance: impl Into<CurrencyCollection>,
350        data: Cell,
351        code_boc: impl AsRef<[u8]>,
352    ) -> Self {
353        use tycho_types::models::StateInit;
354
355        let mut res = Self::new_uninit(params, config, address, balance);
356        res.state = AccountState::Active(StateInit {
357            split_depth: None,
358            special: None,
359            code: Some(Boc::decode(code_boc).unwrap()),
360            data: Some(data),
361            libraries: Dict::new(),
362        });
363        res.orig_status = AccountStatus::Active;
364        res.end_status = AccountStatus::Active;
365        res
366    }
367}
368
369/// Executor configuration parameters.
370#[derive(Default, Clone)]
371pub struct ExecutorParams {
372    /// Public libraries from the referenced masterchain state.
373    pub libraries: Dict<HashBytes, LibDescr>,
374    /// Rand seed of the block.
375    pub rand_seed: HashBytes,
376    /// Unix timestamp in seconds of the block.
377    pub block_unixtime: u32,
378    /// Logical time of the block.
379    pub block_lt: u64,
380    /// Last known previous masterchain block before current.
381    pub prev_mc_block_id: Option<BlockId>,
382    /// VM behaviour modifiers.
383    pub vm_modifiers: tycho_vm::BehaviourModifiers,
384    /// Prevent [`Frozen`] accounts from being deleted
385    /// when their storage due is too high.
386    ///
387    /// [`Frozen`]: tycho_types::models::AccountState::Frozen
388    pub disable_delete_frozen_accounts: bool,
389    /// Charge account balance for additional `total_action_fees`
390    /// when action phase fails.
391    pub charge_action_fees_on_fail: bool,
392    /// Attaches an original message body as an additional cell
393    /// to a bounced message body.
394    pub full_body_in_bounced: bool,
395    /// More gas-predictable extra currency behaviour.
396    pub strict_extra_currency: bool,
397    /// Whether accounts can be suspended by authority marks.
398    pub authority_marks_enabled: bool,
399}
400
401/// Executed transaction.
402pub struct UncommittedTransaction<'a, 's> {
403    original: &'s ShardAccount,
404    exec: ExecutorState<'a>,
405    in_msg: Option<Cell>,
406    info: Lazy<TxInfo>,
407}
408
409impl<'a, 's> UncommittedTransaction<'a, 's> {
410    #[inline]
411    pub fn with_info(
412        exec: ExecutorState<'a>,
413        original: &'s ShardAccount,
414        in_msg: Option<Cell>,
415        info: impl Into<TxInfo>,
416    ) -> Result<Self> {
417        let info = info.into();
418        let info = Lazy::new(&info)?;
419        Ok(Self {
420            original,
421            exec,
422            in_msg,
423            info,
424        })
425    }
426
427    /// Creates a partially finalized transaction.
428    ///
429    /// It differs from the normal transaction by having a stub `state_update`
430    /// and possibly denormalized `end_status`.
431    pub fn build_uncommitted(&self) -> Result<Transaction, Error> {
432        thread_local! {
433            static EMPTY_STATE_UPDATE: Lazy<HashUpdate> = Lazy::new(&HashUpdate {
434                old: HashBytes::ZERO,
435                new: HashBytes::ZERO,
436            })
437            .unwrap();
438        }
439
440        self.build_transaction(self.exec.end_status, EMPTY_STATE_UPDATE.with(Clone::clone))
441    }
442
443    /// Creates a final transaction and a new contract state.
444    pub fn commit(mut self) -> Result<ExecutorOutput> {
445        // Collect brief account state info and build new account state.
446        let account_state;
447        let new_state_meta;
448        let end_status = match self.build_account_state()? {
449            None => {
450                // TODO: Replace with a constant?
451                account_state = CellBuilder::build_from(false)?;
452
453                // Brief meta.
454                new_state_meta = AccountMeta {
455                    balance: CurrencyCollection::ZERO,
456                    libraries: Dict::new(),
457                    exists: false,
458                };
459
460                // Done
461                AccountStatus::NotExists
462            }
463            Some(state) => {
464                // Load previous account storage info.
465                let prev_account_storage = 'prev: {
466                    let mut cs = self.original.account.as_slice_allow_exotic();
467                    if !cs.load_bit()? {
468                        // account_none$0
469                        break 'prev None;
470                    }
471                    // account$1
472                    // addr:MsgAddressInt
473                    IntAddr::load_from(&mut cs)?;
474                    // storage_stat:StorageInfo
475                    let storage_info = StorageInfo::load_from(&mut cs)?;
476                    // storage:AccountStorage
477                    Some((storage_info.used, cs))
478                };
479
480                // Serialize part of the new account state to compute new storage stats.
481                let mut account_storage = CellBuilder::new();
482                // last_trans_lt:uint64
483                account_storage.store_u64(self.exec.end_lt)?;
484                // balance:CurrencyCollection
485                self.exec
486                    .balance
487                    .store_into(&mut account_storage, Cell::empty_context())?;
488                // state:AccountState
489                state.store_into(&mut account_storage, Cell::empty_context())?;
490
491                // Update storage info.
492                self.exec.storage_stat.used = compute_storage_used(
493                    prev_account_storage,
494                    account_storage.as_full_slice(),
495                    &mut self.exec.cached_storage_stat,
496                    self.exec.params.strict_extra_currency,
497                )?;
498
499                // Build new account state.
500                account_state = CellBuilder::build_from((
501                    true,                            // account$1
502                    &self.exec.address,              // addr:MsgAddressInt
503                    &self.exec.storage_stat,         // storage_stat:StorageInfo
504                    account_storage.as_full_slice(), // storage:AccountStorage
505                ))?;
506
507                // Brief meta.
508                let libraries = match &state {
509                    AccountState::Active(state) => state.libraries.clone(),
510                    AccountState::Frozen(..) | AccountState::Uninit => Dict::new(),
511                };
512                new_state_meta = AccountMeta {
513                    balance: self.exec.balance.clone(),
514                    libraries,
515                    exists: true,
516                };
517
518                // Done
519                state.status()
520            }
521        };
522
523        // Serialize transaction.
524        let state_update = Lazy::new(&HashUpdate {
525            old: *self.original.account.repr_hash(),
526            new: *account_state.repr_hash(),
527        })?;
528        let transaction = self
529            .build_transaction(end_status, state_update)
530            .and_then(|tx| Lazy::new(&tx))?;
531
532        // Collect brief transaction info.
533        let transaction_meta = TransactionMeta {
534            total_fees: self.exec.total_fees,
535            next_lt: self.exec.end_lt,
536            out_msgs: self.exec.out_msgs,
537        };
538
539        // New shard account state.
540        let new_state = ShardAccount {
541            // SAFETY: `account_state` is an ordinary cell.
542            account: unsafe { Lazy::from_raw_unchecked(account_state) },
543            last_trans_hash: *transaction.repr_hash(),
544            last_trans_lt: self.exec.start_lt,
545        };
546
547        // Done
548        Ok(ExecutorOutput {
549            new_state,
550            new_state_meta,
551            transaction,
552            transaction_meta,
553            burned: self.exec.burned,
554        })
555    }
556
557    fn build_account_state(&self) -> Result<Option<AccountState>> {
558        Ok(match self.exec.end_status {
559            // Account was deleted.
560            AccountStatus::NotExists => None,
561            // Uninit account with zero balance is also treated as deleted.
562            AccountStatus::Uninit if self.exec.balance.is_zero() => None,
563            // Uninit account stays the same.
564            AccountStatus::Uninit => Some(AccountState::Uninit),
565            // Active account must have a known active state.
566            AccountStatus::Active => {
567                debug_assert!(matches!(self.exec.state, AccountState::Active(_)));
568                Some(self.exec.state.clone())
569            }
570            // Normalize frozen state.
571            AccountStatus::Frozen => {
572                let cell;
573                let frozen_hash = match &self.exec.state {
574                    // Uninit accounts can't be frozen, but if they accidentialy can
575                    // just use the account address as frozen state hash to produce the
576                    // same uninit state.
577                    AccountState::Uninit => &self.exec.address.address,
578                    // To freeze an active account we must compute a hash of its state.
579                    AccountState::Active(state_init) => {
580                        cell = CellBuilder::build_from(state_init)?;
581                        cell.repr_hash()
582                    }
583                    // Account is already frozen.
584                    AccountState::Frozen(hash_bytes) => hash_bytes,
585                };
586
587                // Normalize account state.
588                Some(if frozen_hash == &self.exec.address.address {
589                    AccountState::Uninit
590                } else {
591                    AccountState::Frozen(*frozen_hash)
592                })
593            }
594        })
595    }
596
597    fn build_transaction(
598        &self,
599        end_status: AccountStatus,
600        state_update: Lazy<HashUpdate>,
601    ) -> Result<Transaction, Error> {
602        Ok(Transaction {
603            account: self.exec.address.address,
604            lt: self.exec.start_lt,
605            prev_trans_hash: self.original.last_trans_hash,
606            prev_trans_lt: self.original.last_trans_lt,
607            now: self.exec.params.block_unixtime,
608            out_msg_count: Uint15::new(self.exec.out_msgs.len() as _),
609            orig_status: self.exec.orig_status,
610            end_status,
611            in_msg: self.in_msg.clone(),
612            out_msgs: build_out_msgs(&self.exec.out_msgs)?,
613            total_fees: self.exec.total_fees.into(),
614            state_update,
615            info: self.info.clone(),
616        })
617    }
618}
619
620fn compute_storage_used(
621    mut prev: Option<(StorageUsed, CellSlice<'_>)>,
622    mut new_storage: CellSlice<'_>,
623    cache: &mut Option<OwnedExtStorageStat>,
624    without_extra_currencies: bool,
625) -> Result<StorageUsed> {
626    fn skip_extra(slice: &mut CellSlice<'_>) -> Result<bool, Error> {
627        let mut cs = *slice;
628        cs.skip_first(64, 0)?; // skip lt
629        let balance = CurrencyCollection::load_from(&mut cs)?;
630        Ok(if balance.other.is_empty() {
631            false
632        } else {
633            slice.skip_first(0, 1)?;
634            true
635        })
636    }
637
638    if without_extra_currencies {
639        if let Some((_, prev)) = &mut prev {
640            skip_extra(prev)?;
641        }
642        skip_extra(&mut new_storage)?;
643    }
644
645    // Try to reuse previous storage stats if no cells were changed.
646    if let Some((prev_used, prev_storage)) = prev {
647        'reuse: {
648            // Always recompute when previous stats are invalid.
649            if prev_used.cells.is_zero()
650                || prev_used.bits.into_inner() < prev_storage.size_bits() as u64
651            {
652                break 'reuse;
653            }
654
655            // Simple check that some cells were changed.
656            if prev_storage.size_refs() != new_storage.size_refs() {
657                break 'reuse;
658            }
659
660            // Compare each cell.
661            for (prev, new) in prev_storage.references().zip(new_storage.references()) {
662                if prev != new {
663                    break 'reuse;
664                }
665            }
666
667            // Adjust bit size of the root slice.
668            return Ok(StorageUsed {
669                // Compute the truncated difference of the previous storage root and a new one.
670                bits: new_varuint56_truncate(
671                    (prev_used.bits.into_inner() - prev_storage.size_bits() as u64)
672                        .saturating_add(new_storage.size_bits() as u64),
673                ),
674                // All other stats are unchanged.
675                cells: prev_used.cells,
676            });
677        }
678    }
679
680    // Init cache.
681    let cache = cache.get_or_insert_with(OwnedExtStorageStat::unlimited);
682    cache.set_unlimited();
683
684    // Compute stats for childern.
685    for cell in new_storage.references().cloned() {
686        cache.add_cell(cell);
687    }
688    let stats = cache.stats();
689
690    // Done.
691    Ok(StorageUsed {
692        cells: new_varuint56_truncate(stats.cell_count.saturating_add(1)),
693        bits: new_varuint56_truncate(stats.bit_count.saturating_add(new_storage.size_bits() as _)),
694    })
695}
696
697/// Committed transaction output.
698#[derive(Clone, Debug)]
699pub struct ExecutorOutput {
700    pub new_state: ShardAccount,
701    pub new_state_meta: AccountMeta,
702    pub transaction: Lazy<Transaction>,
703    pub transaction_meta: TransactionMeta,
704    pub burned: Tokens,
705}
706
707/// Short account description.
708#[derive(Clone, Debug)]
709pub struct AccountMeta {
710    pub balance: CurrencyCollection,
711    pub libraries: Dict<HashBytes, SimpleLib>,
712    pub exists: bool,
713}
714
715/// Short transaction description.
716#[derive(Clone, Debug)]
717pub struct TransactionMeta {
718    /// A sum of all collected fees from all phases.
719    pub total_fees: Tokens,
720    /// List of outbound messages.
721    pub out_msgs: Vec<Lazy<OwnedMessage>>,
722    /// Minimal logical time for the next transaction on this account.
723    pub next_lt: u64,
724}
725
726/// Message cell source.
727pub trait LoadMessage {
728    fn load_message_root(self) -> Result<Cell>;
729}
730
731impl<T: LoadMessage + Clone> LoadMessage for &T {
732    #[inline]
733    fn load_message_root(self) -> Result<Cell> {
734        T::load_message_root(T::clone(self))
735    }
736}
737
738impl LoadMessage for Cell {
739    #[inline]
740    fn load_message_root(self) -> Result<Cell> {
741        Ok(self)
742    }
743}
744
745impl<T: EquivalentRepr<OwnedMessage>> LoadMessage for Lazy<T> {
746    #[inline]
747    fn load_message_root(self) -> Result<Cell> {
748        Ok(self.into_inner())
749    }
750}
751
752impl LoadMessage for OwnedMessage {
753    #[inline]
754    fn load_message_root(self) -> Result<Cell> {
755        CellBuilder::build_from(self).context("failed to serialize inbound message")
756    }
757}
758
759impl LoadMessage for Message<'_> {
760    #[inline]
761    fn load_message_root(self) -> Result<Cell> {
762        CellBuilder::build_from(self).context("failed to serialize inbound message")
763    }
764}
765
766fn build_out_msgs(out_msgs: &[Lazy<OwnedMessage>]) -> Result<Dict<Uint15, Cell>, Error> {
767    dict::build_dict_from_sorted_iter(
768        out_msgs
769            .iter()
770            .enumerate()
771            .map(|(i, msg)| (Uint15::new(i as _), msg.inner().clone())),
772        Cell::empty_context(),
773    )
774    .map(Dict::from_raw)
775}
776
777#[cfg(test)]
778mod tests {
779    use std::rc::Rc;
780
781    use tycho_types::boc::BocRepr;
782    use tycho_types::models::{BlockchainConfig, MsgInfo, StateInit};
783
784    use super::*;
785
786    pub fn make_default_config() -> Rc<ParsedConfig> {
787        thread_local! {
788            pub static PARSED_CONFIG: Rc<ParsedConfig> = make_custom_config(|_| Ok(()));
789        }
790
791        PARSED_CONFIG.with(Clone::clone)
792    }
793
794    pub fn make_custom_config<F>(f: F) -> Rc<ParsedConfig>
795    where
796        F: FnOnce(&mut BlockchainConfig) -> anyhow::Result<()>,
797    {
798        let mut config: BlockchainConfig =
799            BocRepr::decode(include_bytes!("../res/config.boc")).unwrap();
800
801        config.params.set_global_id(100).unwrap();
802
803        // TODO: Update config BOC
804        config
805            .params
806            .set_size_limits(&ParsedConfig::DEFAULT_SIZE_LIMITS_CONFIG)
807            .unwrap();
808
809        f(&mut config).unwrap();
810
811        Rc::new(ParsedConfig::parse(config, u32::MAX).unwrap())
812    }
813
814    pub fn make_default_params() -> ExecutorParams {
815        ExecutorParams {
816            block_unixtime: 1738799198,
817            full_body_in_bounced: false,
818            strict_extra_currency: true,
819            vm_modifiers: tycho_vm::BehaviourModifiers {
820                chksig_always_succeed: true,
821                ..Default::default()
822            },
823            ..Default::default()
824        }
825    }
826
827    pub fn make_message(
828        info: impl Into<MsgInfo>,
829        init: Option<StateInit>,
830        body: Option<CellBuilder>,
831    ) -> Cell {
832        let body = match &body {
833            None => Cell::empty_cell_ref().as_slice_allow_exotic(),
834            Some(cell) => cell.as_full_slice(),
835        };
836        CellBuilder::build_from(Message {
837            info: info.into(),
838            init,
839            body,
840            layout: None,
841        })
842        .unwrap()
843    }
844
845    pub fn make_big_tree(depth: u8, count: &mut u16, target: u16) -> Cell {
846        *count += 1;
847
848        if depth == 0 {
849            CellBuilder::build_from(*count).unwrap()
850        } else {
851            let mut b = CellBuilder::new();
852            for _ in 0..4 {
853                if *count < target {
854                    b.store_reference(make_big_tree(depth - 1, count, target))
855                        .unwrap();
856                }
857            }
858            b.build().unwrap()
859        }
860    }
861}