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#[derive(Debug)]
46pub struct Executor<'a> {
47 params: &'a ExecutorParams,
48 config: &'a ParsedConfig,
49 min_lt: u64,
50 override_special: Option<bool>,
51}
52
53impl<'a> Executor<'a> {
54 pub fn new(params: &'a ExecutorParams, config: &'a ParsedConfig) -> Self {
55 Self {
56 params,
57 config,
58 min_lt: 0,
59 override_special: None,
60 }
61 }
62
63 pub fn with_min_lt(mut self, min_lt: u64) -> Self {
64 self.set_min_lt(min_lt);
65 self
66 }
67
68 pub fn set_min_lt(&mut self, min_lt: u64) {
69 self.min_lt = min_lt;
70 }
71
72 pub fn override_special(mut self, is_special: bool) -> Self {
73 self.override_special = Some(is_special);
74 self
75 }
76
77 #[inline]
78 pub fn begin_ordinary<'s, M>(
79 &self,
80 address: &StdAddr,
81 is_external: bool,
82 msg: M,
83 state: &'s ShardAccount,
84 ) -> TxResult<UncommittedTransaction<'a, 's>>
85 where
86 M: LoadMessage,
87 {
88 self.begin_ordinary_ext(address, is_external, msg, state, None)
89 }
90
91 pub fn begin_ordinary_ext<'s, M>(
92 &self,
93 address: &StdAddr,
94 is_external: bool,
95 msg: M,
96 state: &'s ShardAccount,
97 inspector: Option<&mut ExecutorInspector<'_>>,
98 ) -> TxResult<UncommittedTransaction<'a, 's>>
99 where
100 M: LoadMessage,
101 {
102 let msg_root = msg.load_message_root()?;
103
104 let account = state.load_account()?;
105 let mut exec = self.begin(address, account)?;
106 let info = exec.run_ordinary_transaction(is_external, msg_root.clone(), inspector)?;
107
108 UncommittedTransaction::with_info(exec, state, Some(msg_root), info).map_err(TxError::Fatal)
109 }
110
111 #[inline]
112 pub fn check_ordinary<M>(&self, address: &StdAddr, msg: M, state: &ShardAccount) -> TxResult<()>
113 where
114 M: LoadMessage,
115 {
116 self.check_ordinary_ext(address, msg, state, None)
117 }
118
119 pub fn check_ordinary_ext<M>(
120 &self,
121 address: &StdAddr,
122 msg: M,
123 state: &ShardAccount,
124 inspector: Option<&mut ExecutorInspector<'_>>,
125 ) -> TxResult<()>
126 where
127 M: LoadMessage,
128 {
129 let msg_root = msg.load_message_root()?;
130 let account = state.load_account()?;
131 let mut exec = self.begin(address, account)?;
132 exec.check_ordinary_transaction(msg_root, inspector)
133 }
134
135 #[inline]
136 pub fn begin_tick_tock<'s>(
137 &self,
138 address: &StdAddr,
139 kind: TickTock,
140 state: &'s ShardAccount,
141 ) -> TxResult<UncommittedTransaction<'a, 's>> {
142 self.begin_tick_tock_ext(address, kind, state, None)
143 }
144
145 pub fn begin_tick_tock_ext<'s>(
146 &self,
147 address: &StdAddr,
148 kind: TickTock,
149 state: &'s ShardAccount,
150 inspector: Option<&mut ExecutorInspector<'_>>,
151 ) -> TxResult<UncommittedTransaction<'a, 's>> {
152 let account = state.load_account()?;
153 let mut exec = self.begin(address, account)?;
154 let info = exec.run_tick_tock_transaction(kind, inspector)?;
155
156 UncommittedTransaction::with_info(exec, state, None, info).map_err(TxError::Fatal)
157 }
158
159 pub fn begin(&self, address: &StdAddr, account: Option<Account>) -> Result<ExecutorState<'a>> {
160 let is_special = self
161 .override_special
162 .unwrap_or_else(|| self.config.is_special(address));
163
164 let acc_address;
165 let acc_storage_stat;
166 let acc_balance;
167 let acc_state;
168 let orig_status;
169 let end_status;
170 let start_lt;
171 match account {
172 Some(acc) => {
173 acc_address = 'addr: {
174 if let IntAddr::Std(acc_addr) = acc.address
175 && acc_addr == *address
176 {
177 break 'addr acc_addr;
178 }
179 anyhow::bail!("account address mismatch");
180 };
181 acc_storage_stat = acc.storage_stat;
182 acc_balance = acc.balance;
183 acc_state = acc.state;
184 orig_status = acc_state.status();
185 end_status = orig_status;
186 start_lt = std::cmp::max(self.min_lt, acc.last_trans_lt);
187 }
188 None => {
189 acc_address = address.clone();
190 acc_storage_stat = StorageInfo {
191 used: StorageUsed::ZERO,
192 storage_extra: Default::default(),
193 last_paid: 0,
194 due_payment: None,
195 };
196 acc_balance = CurrencyCollection::ZERO;
197 acc_state = AccountState::Uninit;
198 orig_status = AccountStatus::NotExists;
199 end_status = AccountStatus::Uninit;
200 start_lt = self.min_lt;
201 }
202 };
203
204 let mut is_marks_authority = false;
205 let mut is_suspended_by_marks = false;
206 if self.params.authority_marks_enabled
207 && let Some(marks) = &self.config.authority_marks
208 {
209 is_marks_authority = marks.is_authority(address);
210 is_suspended_by_marks =
211 !is_special && !is_marks_authority && marks.is_suspended(&acc_balance)?;
212 }
213
214 Ok(ExecutorState {
215 params: self.params,
216 config: self.config,
217 is_special,
218 is_marks_authority,
219 is_suspended_by_marks,
220 address: acc_address,
221 storage_stat: acc_storage_stat,
222 balance: acc_balance,
223 state: acc_state,
224 orig_status,
225 end_status,
226 start_lt,
227 end_lt: start_lt + 1,
228 out_msgs: Vec::new(),
229 total_fees: Tokens::ZERO,
230 burned: Tokens::ZERO,
231 cached_storage_stat: None,
232 })
233 }
234}
235
236#[derive(Default)]
238pub struct ExecutorInspector<'e> {
239 pub actions: Option<Cell>,
241 pub public_libs_diff: ahash::HashMap<HashBytes, PublicLibraryChange>,
243 pub exit_code: Option<i32>,
245 pub missing_library: Option<HashBytes>,
247 pub total_gas_used: u64,
250 pub debug: Option<&'e mut dyn std::fmt::Write>,
252 pub modify_smc_info: Option<&'e mut ModifySmcInfoFn>,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
258pub enum PublicLibraryChange {
259 Add(Cell),
260 Remove,
261}
262
263pub type ModifySmcInfoFn = dyn FnMut(&mut phase::ComputePhaseSmcInfo) -> Result<()>;
265
266#[derive(Debug)]
268pub struct ExecutorState<'a> {
269 pub params: &'a ExecutorParams,
270 pub config: &'a ParsedConfig,
271
272 pub is_special: bool,
273 pub is_marks_authority: bool,
274 pub is_suspended_by_marks: bool,
275
276 pub address: StdAddr,
277 pub storage_stat: StorageInfo,
278 pub balance: CurrencyCollection,
279 pub state: AccountState,
280
281 pub orig_status: AccountStatus,
282 pub end_status: AccountStatus,
283 pub start_lt: u64,
284 pub end_lt: u64,
285
286 pub out_msgs: Vec<Lazy<OwnedMessage>>,
287 pub total_fees: Tokens,
288
289 pub burned: Tokens,
290
291 pub cached_storage_stat: Option<OwnedExtStorageStat>,
292}
293
294#[cfg(test)]
295impl<'a> ExecutorState<'a> {
296 pub(crate) fn new_non_existent(
297 params: &'a ExecutorParams,
298 config: &'a impl AsRef<ParsedConfig>,
299 address: &StdAddr,
300 ) -> Self {
301 Self {
302 params,
303 config: config.as_ref(),
304 is_special: false,
305 is_marks_authority: false,
306 is_suspended_by_marks: false,
307 address: address.clone(),
308 storage_stat: Default::default(),
309 balance: CurrencyCollection::ZERO,
310 state: AccountState::Uninit,
311 orig_status: AccountStatus::NotExists,
312 end_status: AccountStatus::Uninit,
313 start_lt: 0,
314 end_lt: 1,
315 out_msgs: Vec::new(),
316 total_fees: Tokens::ZERO,
317 burned: Tokens::ZERO,
318 cached_storage_stat: None,
319 }
320 }
321
322 pub(crate) fn new_uninit(
323 params: &'a ExecutorParams,
324 config: &'a impl AsRef<ParsedConfig>,
325 address: &StdAddr,
326 balance: impl Into<CurrencyCollection>,
327 ) -> Self {
328 let mut res = Self::new_non_existent(params, config, address);
329 res.balance = balance.into();
330 res.orig_status = AccountStatus::Uninit;
331
332 if params.authority_marks_enabled
333 && let Some(marks) = &config.as_ref().authority_marks
334 {
335 res.is_marks_authority = marks.is_authority(address);
336 res.is_suspended_by_marks = !res.is_special
337 && !res.is_marks_authority
338 && marks.is_suspended(&res.balance).unwrap();
339 }
340
341 res
342 }
343
344 pub(crate) fn new_frozen(
345 params: &'a ExecutorParams,
346 config: &'a impl AsRef<ParsedConfig>,
347 address: &StdAddr,
348 balance: impl Into<CurrencyCollection>,
349 state_hash: HashBytes,
350 ) -> Self {
351 let mut res = Self::new_uninit(params, config, address, balance);
352 res.state = AccountState::Frozen(state_hash);
353 res.orig_status = AccountStatus::Frozen;
354 res.end_status = AccountStatus::Frozen;
355 res
356 }
357
358 pub(crate) fn new_active(
359 params: &'a ExecutorParams,
360 config: &'a impl AsRef<ParsedConfig>,
361 address: &StdAddr,
362 balance: impl Into<CurrencyCollection>,
363 data: Cell,
364 code_boc: impl AsRef<[u8]>,
365 ) -> Self {
366 use tycho_types::models::StateInit;
367
368 let mut res = Self::new_uninit(params, config, address, balance);
369 res.state = AccountState::Active(StateInit {
370 split_depth: None,
371 special: None,
372 code: Some(Boc::decode(code_boc).unwrap()),
373 data: Some(data),
374 libraries: Dict::new(),
375 });
376 res.orig_status = AccountStatus::Active;
377 res.end_status = AccountStatus::Active;
378 res
379 }
380}
381
382#[derive(Debug, Default, Clone)]
384pub struct ExecutorParams {
385 pub libraries: Dict<HashBytes, LibDescr>,
387 pub rand_seed: HashBytes,
389 pub block_unixtime: u32,
391 pub block_lt: u64,
393 pub prev_mc_block_id: Option<BlockId>,
395 pub vm_modifiers: tycho_vm::BehaviourModifiers,
397 pub disable_delete_frozen_accounts: bool,
402 pub charge_action_fees_on_fail: bool,
405 pub full_body_in_bounced: bool,
408 pub strict_extra_currency: bool,
410 pub authority_marks_enabled: bool,
412}
413
414pub struct UncommittedTransaction<'a, 's> {
416 original: &'s ShardAccount,
417 exec: ExecutorState<'a>,
418 in_msg: Option<Cell>,
419 info: Lazy<TxInfo>,
420}
421
422impl<'a, 's> UncommittedTransaction<'a, 's> {
423 #[inline]
424 pub fn with_info(
425 exec: ExecutorState<'a>,
426 original: &'s ShardAccount,
427 in_msg: Option<Cell>,
428 info: impl Into<TxInfo>,
429 ) -> Result<Self> {
430 let info = info.into();
431 let info = Lazy::new(&info)?;
432 Ok(Self {
433 original,
434 exec,
435 in_msg,
436 info,
437 })
438 }
439
440 pub fn build_uncommitted(&self) -> Result<Transaction, Error> {
445 thread_local! {
446 static EMPTY_STATE_UPDATE: Lazy<HashUpdate> = Lazy::new(&HashUpdate {
447 old: HashBytes::ZERO,
448 new: HashBytes::ZERO,
449 })
450 .unwrap();
451 }
452
453 self.build_transaction(self.exec.end_status, EMPTY_STATE_UPDATE.with(Clone::clone))
454 }
455
456 pub fn commit(mut self) -> Result<ExecutorOutput> {
458 let account_state;
460 let new_state_meta;
461 let end_status = match self.build_account_state()? {
462 None => {
463 account_state = CellBuilder::build_from(false)?;
465
466 new_state_meta = AccountMeta {
468 balance: CurrencyCollection::ZERO,
469 libraries: Dict::new(),
470 exists: false,
471 };
472
473 AccountStatus::NotExists
475 }
476 Some(state) => {
477 let prev_account_storage = 'prev: {
479 let mut cs = self.original.account.as_slice_allow_exotic();
480 if !cs.load_bit()? {
481 break 'prev None;
483 }
484 IntAddr::load_from(&mut cs)?;
487 let storage_info = StorageInfo::load_from(&mut cs)?;
489 Some((storage_info.used, cs))
491 };
492
493 let mut account_storage = CellBuilder::new();
495 account_storage.store_u64(self.exec.end_lt)?;
497 self.exec
499 .balance
500 .store_into(&mut account_storage, Cell::empty_context())?;
501 state.store_into(&mut account_storage, Cell::empty_context())?;
503
504 self.exec.storage_stat.used = compute_storage_used(
506 prev_account_storage,
507 account_storage.as_full_slice(),
508 &mut self.exec.cached_storage_stat,
509 self.exec.params.strict_extra_currency,
510 )?;
511
512 account_state = CellBuilder::build_from((
514 true, &self.exec.address, &self.exec.storage_stat, account_storage.as_full_slice(), ))?;
519
520 let libraries = match &state {
522 AccountState::Active(state) => state.libraries.clone(),
523 AccountState::Frozen(..) | AccountState::Uninit => Dict::new(),
524 };
525 new_state_meta = AccountMeta {
526 balance: self.exec.balance.clone(),
527 libraries,
528 exists: true,
529 };
530
531 state.status()
533 }
534 };
535
536 let state_update = Lazy::new(&HashUpdate {
538 old: *self.original.account.repr_hash(),
539 new: *account_state.repr_hash(),
540 })?;
541 let transaction = self
542 .build_transaction(end_status, state_update)
543 .and_then(|tx| Lazy::new(&tx))?;
544
545 let transaction_meta = TransactionMeta {
547 total_fees: self.exec.total_fees,
548 next_lt: self.exec.end_lt,
549 out_msgs: self.exec.out_msgs,
550 };
551
552 let new_state = ShardAccount {
554 account: unsafe { Lazy::from_raw_unchecked(account_state) },
556 last_trans_hash: *transaction.repr_hash(),
557 last_trans_lt: self.exec.start_lt,
558 };
559
560 Ok(ExecutorOutput {
562 new_state,
563 new_state_meta,
564 transaction,
565 transaction_meta,
566 burned: self.exec.burned,
567 })
568 }
569
570 fn build_account_state(&self) -> Result<Option<AccountState>> {
571 Ok(match self.exec.end_status {
572 AccountStatus::NotExists => None,
574 AccountStatus::Uninit if self.exec.balance.is_zero() => None,
576 AccountStatus::Uninit => Some(AccountState::Uninit),
578 AccountStatus::Active => {
580 debug_assert!(matches!(self.exec.state, AccountState::Active(_)));
581 Some(self.exec.state.clone())
582 }
583 AccountStatus::Frozen => {
585 let cell;
586 let frozen_hash = match &self.exec.state {
587 AccountState::Uninit => &self.exec.address.address,
591 AccountState::Active(state_init) => {
593 cell = CellBuilder::build_from(state_init)?;
594 cell.repr_hash()
595 }
596 AccountState::Frozen(hash_bytes) => hash_bytes,
598 };
599
600 Some(if frozen_hash == &self.exec.address.address {
602 AccountState::Uninit
603 } else {
604 AccountState::Frozen(*frozen_hash)
605 })
606 }
607 })
608 }
609
610 fn build_transaction(
611 &self,
612 end_status: AccountStatus,
613 state_update: Lazy<HashUpdate>,
614 ) -> Result<Transaction, Error> {
615 Ok(Transaction {
616 account: self.exec.address.address,
617 lt: self.exec.start_lt,
618 prev_trans_hash: self.original.last_trans_hash,
619 prev_trans_lt: self.original.last_trans_lt,
620 now: self.exec.params.block_unixtime,
621 out_msg_count: Uint15::new(self.exec.out_msgs.len() as _),
622 orig_status: self.exec.orig_status,
623 end_status,
624 in_msg: self.in_msg.clone(),
625 out_msgs: build_out_msgs(&self.exec.out_msgs)?,
626 total_fees: self.exec.total_fees.into(),
627 state_update,
628 info: self.info.clone(),
629 })
630 }
631}
632
633fn compute_storage_used(
634 mut prev: Option<(StorageUsed, CellSlice<'_>)>,
635 mut new_storage: CellSlice<'_>,
636 cache: &mut Option<OwnedExtStorageStat>,
637 without_extra_currencies: bool,
638) -> Result<StorageUsed> {
639 fn skip_extra(slice: &mut CellSlice<'_>) -> Result<bool, Error> {
640 let mut cs = *slice;
641 cs.skip_first(64, 0)?; let balance = CurrencyCollection::load_from(&mut cs)?;
643 Ok(if balance.other.is_empty() {
644 false
645 } else {
646 slice.skip_first(0, 1)?;
647 true
648 })
649 }
650
651 if without_extra_currencies {
652 if let Some((_, prev)) = &mut prev {
653 skip_extra(prev)?;
654 }
655 skip_extra(&mut new_storage)?;
656 }
657
658 if let Some((prev_used, prev_storage)) = prev {
660 'reuse: {
661 if prev_used.cells.is_zero()
663 || prev_used.bits.into_inner() < prev_storage.size_bits() as u64
664 {
665 break 'reuse;
666 }
667
668 if prev_storage.size_refs() != new_storage.size_refs() {
670 break 'reuse;
671 }
672
673 for (prev, new) in prev_storage.references().zip(new_storage.references()) {
675 if prev != new {
676 break 'reuse;
677 }
678 }
679
680 return Ok(StorageUsed {
682 bits: new_varuint56_truncate(
684 (prev_used.bits.into_inner() - prev_storage.size_bits() as u64)
685 .saturating_add(new_storage.size_bits() as u64),
686 ),
687 cells: prev_used.cells,
689 });
690 }
691 }
692
693 let cache = cache.get_or_insert_with(OwnedExtStorageStat::unlimited);
695 cache.set_unlimited();
696
697 for cell in new_storage.references().cloned() {
699 cache.add_cell(cell);
700 }
701 let stats = cache.stats();
702
703 Ok(StorageUsed {
705 cells: new_varuint56_truncate(stats.cell_count.saturating_add(1)),
706 bits: new_varuint56_truncate(stats.bit_count.saturating_add(new_storage.size_bits() as _)),
707 })
708}
709
710#[derive(Clone, Debug)]
712pub struct ExecutorOutput {
713 pub new_state: ShardAccount,
714 pub new_state_meta: AccountMeta,
715 pub transaction: Lazy<Transaction>,
716 pub transaction_meta: TransactionMeta,
717 pub burned: Tokens,
718}
719
720#[derive(Clone, Debug)]
722pub struct AccountMeta {
723 pub balance: CurrencyCollection,
724 pub libraries: Dict<HashBytes, SimpleLib>,
725 pub exists: bool,
726}
727
728#[derive(Clone, Debug)]
730pub struct TransactionMeta {
731 pub total_fees: Tokens,
733 pub out_msgs: Vec<Lazy<OwnedMessage>>,
735 pub next_lt: u64,
737}
738
739pub trait LoadMessage {
741 fn load_message_root(self) -> Result<Cell>;
742}
743
744impl<T: LoadMessage + Clone> LoadMessage for &T {
745 #[inline]
746 fn load_message_root(self) -> Result<Cell> {
747 T::load_message_root(T::clone(self))
748 }
749}
750
751impl LoadMessage for Cell {
752 #[inline]
753 fn load_message_root(self) -> Result<Cell> {
754 Ok(self)
755 }
756}
757
758impl<T: EquivalentRepr<OwnedMessage>> LoadMessage for Lazy<T> {
759 #[inline]
760 fn load_message_root(self) -> Result<Cell> {
761 Ok(self.into_inner())
762 }
763}
764
765impl LoadMessage for OwnedMessage {
766 #[inline]
767 fn load_message_root(self) -> Result<Cell> {
768 CellBuilder::build_from(self).context("failed to serialize inbound message")
769 }
770}
771
772impl LoadMessage for Message<'_> {
773 #[inline]
774 fn load_message_root(self) -> Result<Cell> {
775 CellBuilder::build_from(self).context("failed to serialize inbound message")
776 }
777}
778
779fn build_out_msgs(out_msgs: &[Lazy<OwnedMessage>]) -> Result<Dict<Uint15, Cell>, Error> {
780 dict::build_dict_from_sorted_iter(
781 out_msgs
782 .iter()
783 .enumerate()
784 .map(|(i, msg)| (Uint15::new(i as _), msg.inner().clone())),
785 Cell::empty_context(),
786 )
787 .map(Dict::from_raw)
788}
789
790#[cfg(test)]
791mod tests {
792 use std::rc::Rc;
793
794 use tycho_types::boc::BocRepr;
795 use tycho_types::models::{BlockchainConfig, MsgInfo, StateInit};
796
797 use super::*;
798
799 pub fn make_default_config() -> Rc<ParsedConfig> {
800 thread_local! {
801 pub static PARSED_CONFIG: Rc<ParsedConfig> = make_custom_config(|_| Ok(()));
802 }
803
804 PARSED_CONFIG.with(Clone::clone)
805 }
806
807 pub fn make_custom_config<F>(f: F) -> Rc<ParsedConfig>
808 where
809 F: FnOnce(&mut BlockchainConfig) -> anyhow::Result<()>,
810 {
811 let mut config: BlockchainConfig =
812 BocRepr::decode(include_bytes!("../res/config.boc")).unwrap();
813
814 config.params.set_global_id(100).unwrap();
815
816 config
818 .params
819 .set_size_limits(&ParsedConfig::DEFAULT_SIZE_LIMITS_CONFIG)
820 .unwrap();
821
822 f(&mut config).unwrap();
823
824 Rc::new(ParsedConfig::parse(config, u32::MAX).unwrap())
825 }
826
827 pub fn make_default_params() -> ExecutorParams {
828 ExecutorParams {
829 block_unixtime: 1738799198,
830 full_body_in_bounced: false,
831 strict_extra_currency: true,
832 vm_modifiers: tycho_vm::BehaviourModifiers {
833 chksig_always_succeed: true,
834 ..Default::default()
835 },
836 ..Default::default()
837 }
838 }
839
840 pub fn make_message(
841 info: impl Into<MsgInfo>,
842 init: Option<StateInit>,
843 body: Option<CellBuilder>,
844 ) -> Cell {
845 let body = match &body {
846 None => Cell::empty_cell_ref().as_slice_allow_exotic(),
847 Some(cell) => cell.as_full_slice(),
848 };
849 CellBuilder::build_from(Message {
850 info: info.into(),
851 init,
852 body,
853 layout: None,
854 })
855 .unwrap()
856 }
857
858 pub fn make_big_tree(depth: u8, count: &mut u16, target: u16) -> Cell {
859 *count += 1;
860
861 if depth == 0 {
862 CellBuilder::build_from(*count).unwrap()
863 } else {
864 let mut b = CellBuilder::new();
865 for _ in 0..4 {
866 if *count < target {
867 b.store_reference(make_big_tree(depth - 1, count, target))
868 .unwrap();
869 }
870 }
871 b.build().unwrap()
872 }
873 }
874
875 #[allow(unused)]
877 fn replay_tx(config: &str, state: &str, msg: &str, lt: u64, now: u32) -> Result<()> {
878 use tycho_types::models::*;
879
880 let mut config: BlockchainConfig = BocRepr::decode_base64(config)?;
881 let state: ShardAccount = BocRepr::decode_base64(state)?;
882 let msg = Boc::decode_base64(msg)?;
883
884 let mut workchains = Dict::<i32, _>::new();
885 workchains.set(0, WorkchainDescription {
886 enabled_since: 0,
887 actual_min_split: 0,
888 min_split: 0,
889 max_split: 0,
890 active: true,
891 accept_msgs: true,
892 zerostate_root_hash: HashBytes::ZERO,
893 zerostate_file_hash: HashBytes::ZERO,
894 version: 0,
895 format: WorkchainFormat::Basic(WorkchainFormatBasic {
896 vm_mode: 0,
897 vm_version: 0,
898 }),
899 })?;
900 config.params.set_workchains(&workchains)?;
901 config.params.remove(43)?;
902
903 let (is_external, address) = match msg.parse::<MsgInfo>()? {
904 MsgInfo::Int(info) => (false, info.dst),
905 MsgInfo::ExtIn(info) => (true, info.dst),
906 MsgInfo::ExtOut(_) => anyhow::bail!("unexpected message"),
907 };
908 let IntAddr::Std(address) = address else {
909 anyhow::bail!("unexpected address");
910 };
911
912 let params = ExecutorParams {
913 block_unixtime: now,
914 ..Default::default()
915 };
916
917 let parsed_config = ParsedConfig::parse(config, now).context("failed to parse config")?;
918 let output = Executor::new(¶ms, &parsed_config)
919 .with_min_lt(lt)
920 .begin_ordinary(&address, is_external, msg, &state)?
921 .commit()?;
922
923 let tx = output.transaction.load()?;
924 let tx_info = tx.load_info()?;
925 println!("{tx_info:#?}");
926 Ok(())
927 }
928}