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