Skip to main content

tycho_executor/phase/
compute.rs

1use anyhow::Result;
2use tycho_types::models::{
3    AccountState, AccountStatus, BlockId, ComputeGasParams, ComputePhase, ComputePhaseSkipReason,
4    CurrencyCollection, ExecutedComputePhase, IntAddr, IntMsgInfo, MsgType, SkippedComputePhase,
5    StateInit, TickTock,
6};
7use tycho_types::num::Tokens;
8use tycho_types::prelude::*;
9use tycho_vm::{SafeRc, SmcInfoBase, Stack, Tuple, UnpackedInMsgSmcInfo, VmState, tuple};
10
11use crate::phase::receive::{MsgStateInit, ReceivedMessage};
12use crate::util::{
13    StateLimitsResult, check_state_limits_diff, new_varuint24_truncate, new_varuint56_truncate,
14    unlikely,
15};
16use crate::{ExecutorInspector, ExecutorState, PublicLibraryChange};
17
18/// The exact type of smart contract info used for C7.
19pub type ComputePhaseSmcInfo = tycho_vm::SmcInfoTonV11;
20
21/// Compute phase input context.
22pub struct ComputePhaseContext<'a, 'e> {
23    /// Parsed transaction input.
24    pub input: TransactionInput<'a>,
25    /// Fees collected during the storage phase.
26    pub storage_fee: Tokens,
27    /// Accept message even without opcode.
28    ///
29    /// Should only be used as part of the `run_local` stuff.
30    pub force_accept: bool,
31    /// Only checks whether the contract accepts the message, without running
32    /// the rest of the contract body.
33    pub stop_on_accept: bool,
34    /// Executor inspector.
35    pub inspector: Option<&'a mut ExecutorInspector<'e>>,
36}
37
38/// Parsed transaction input.
39#[derive(Debug, Clone, Copy)]
40pub enum TransactionInput<'a> {
41    Ordinary(&'a ReceivedMessage),
42    TickTock(TickTock),
43}
44
45impl<'a> TransactionInput<'a> {
46    const fn is_ordinary(&self) -> bool {
47        matches!(self, Self::Ordinary(_))
48    }
49
50    fn in_msg(&self) -> Option<&'a ReceivedMessage> {
51        match self {
52            Self::Ordinary(msg) => Some(msg),
53            Self::TickTock(_) => None,
54        }
55    }
56
57    fn in_msg_init(&self) -> Option<&'a MsgStateInit> {
58        match self {
59            Self::Ordinary(msg) => msg.init.as_ref(),
60            Self::TickTock(_) => None,
61        }
62    }
63}
64
65/// Executed compute phase with additional info.
66#[derive(Debug)]
67pub struct ComputePhaseFull {
68    /// Resulting comput phase.
69    pub compute_phase: ComputePhase,
70    /// Whether the inbound message was accepted.
71    ///
72    /// NOTE: Message can be accepted even without a committed state,
73    /// so we can't use [`ExecutedComputePhase::success`].
74    pub accepted: bool,
75    /// Original account balance before this phase.
76    pub original_balance: CurrencyCollection,
77    /// New account state.
78    pub new_state: StateInit,
79    /// Resulting actions list.
80    pub actions: Cell,
81}
82
83impl ExecutorState<'_> {
84    /// Compute phase of ordinary or ticktock transactions.
85    ///
86    /// - Tries to deploy or unfreeze account if it was [`Uninit`] or [`Frozen`];
87    /// - Executes contract code and produces a new account state;
88    /// - Produces an action list on successful execution;
89    /// - External messages can be ignored if they were not accepted;
90    /// - Necessary for all types of messages or even without them;
91    ///
92    /// Returns an executed [`ComputePhase`] with extra data.
93    ///
94    /// Fails only on account balance overflow. This should not happen on networks
95    /// with valid value flow.
96    ///
97    /// [`Uninit`]: AccountState::Uninit
98    /// [`Frozen`]: AccountState::Frozen
99    pub fn compute_phase(
100        &mut self,
101        mut ctx: ComputePhaseContext<'_, '_>,
102    ) -> Result<ComputePhaseFull> {
103        let is_masterchain = self.address.is_masterchain();
104
105        // Compute original balance for the action phase.
106        let mut original_balance = self.balance.clone();
107        if let Some(msg) = ctx.input.in_msg() {
108            original_balance.try_sub_assign(&msg.balance_remaining)?;
109        }
110
111        // Prepare full phase output.
112        let new_state = if let AccountState::Active(current) = &self.state {
113            current.clone()
114        } else {
115            Default::default()
116        };
117
118        let mut res = ComputePhaseFull {
119            compute_phase: ComputePhase::Skipped(SkippedComputePhase {
120                reason: ComputePhaseSkipReason::NoGas,
121            }),
122            accepted: false,
123            original_balance,
124            new_state,
125            actions: Cell::empty_cell(),
126        };
127
128        // Compute VM gas limits.
129        if self.balance.tokens.is_zero() {
130            res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
131                reason: ComputePhaseSkipReason::NoGas,
132            });
133            return Ok(res);
134        }
135
136        // Skip compute phase for accounts suspended by authority marks.
137        if self.is_suspended_by_marks {
138            res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
139                reason: ComputePhaseSkipReason::Suspended,
140            });
141            return Ok(res);
142        }
143
144        let (msg_balance_remaining, is_external) = match ctx.input.in_msg() {
145            Some(msg) => (msg.balance_remaining.clone(), msg.is_external),
146            None => (CurrencyCollection::ZERO, false),
147        };
148
149        let gas = if unlikely(ctx.force_accept) {
150            tycho_vm::GasParams::getter()
151        } else {
152            let prices = self.config.gas_prices(is_masterchain);
153            let computed = prices.compute_gas_params(ComputeGasParams {
154                account_balance: &self.balance.tokens,
155                message_balance: &msg_balance_remaining.tokens,
156                is_special: self.is_special,
157                is_tx_ordinary: ctx.input.is_ordinary(),
158                is_in_msg_external: is_external,
159            });
160            tycho_vm::GasParams {
161                max: computed.max,
162                limit: computed.limit,
163                credit: computed.credit,
164                price: prices.gas_price,
165            }
166        };
167        if gas.limit == 0 && gas.credit == 0 {
168            res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
169                reason: ComputePhaseSkipReason::NoGas,
170            });
171            return Ok(res);
172        }
173
174        // Apply internal message state.
175        let state_libs;
176        let msg_libs;
177        let msg_state_used;
178        match (ctx.input.in_msg_init(), &self.state) {
179            // Uninit account cannot run anything without deploy.
180            (None, AccountState::Uninit) => {
181                res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
182                    reason: ComputePhaseSkipReason::NoState,
183                });
184                return Ok(res);
185            }
186            // Frozen account cannot run anything until receives its old state.
187            (None, AccountState::Frozen { .. }) => {
188                res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
189                    reason: ComputePhaseSkipReason::BadState,
190                });
191                return Ok(res);
192            }
193            // Active account simply runs its code. (use libraries from its state).
194            (None, AccountState::Active(StateInit { libraries, .. })) => {
195                state_libs = Some(libraries);
196                msg_libs = None;
197                msg_state_used = false;
198            }
199            // Received a new state init for an uninit account or an old state for a frozen account.
200            (Some(from_msg), AccountState::Uninit | AccountState::Frozen(..)) => {
201                let target_hash = if let AccountState::Frozen(old_hash) = &self.state {
202                    old_hash
203                } else {
204                    &self.address.address
205                };
206
207                if from_msg.root_hash() != target_hash || from_msg.parsed.split_depth.is_some() {
208                    // State hash mismatch, cannot use this state.
209                    // We also forbid using `split_depth` (for now).
210                    res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
211                        reason: ComputePhaseSkipReason::BadState,
212                    });
213                    return Ok(res);
214                }
215
216                // Check if we can use the new state from the message.
217                let mut limits = self.config.size_limits.clone();
218                if is_masterchain && matches!(&self.state, AccountState::Uninit) {
219                    // Forbid public libraries when deploying, allow for unfreezing.
220                    limits.max_acc_public_libraries = 0;
221                }
222
223                if matches!(
224                    check_state_limits_diff(
225                        &res.new_state,
226                        &from_msg.parsed,
227                        &limits,
228                        is_masterchain,
229                        &mut self.cached_storage_stat,
230                    ),
231                    StateLimitsResult::Exceeds
232                ) {
233                    res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
234                        reason: ComputePhaseSkipReason::BadState,
235                    });
236                    return Ok(res);
237                }
238
239                // NOTE: At this point the `cached_storage_stat` will always contain
240                // visited cells because previous state was not active and we
241                // handled a case when check overflowed.
242
243                // Use state
244                res.new_state = from_msg.parsed.clone();
245                self.state = AccountState::Active(res.new_state.clone());
246                msg_state_used = true;
247
248                // Use libraries.
249                state_libs = None;
250                msg_libs = Some(from_msg.parsed.libraries.clone());
251            }
252            (Some(from_msg), AccountState::Active(StateInit { libraries, .. })) => {
253                // Check if a state from the external message has the correct hash.
254                if is_external && from_msg.root_hash() != &self.address.address {
255                    res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
256                        reason: ComputePhaseSkipReason::BadState,
257                    });
258                    return Ok(res);
259                }
260
261                // We use only libraries here.
262                msg_state_used = false;
263
264                // Use libraries.
265                state_libs = Some(libraries);
266                msg_libs = Some(from_msg.parsed.libraries.clone());
267            }
268        }
269
270        // Unpack internal message.
271        let unpacked_in_msg = match ctx.input.in_msg() {
272            Some(msg) => msg.make_tuple()?,
273            None => None,
274        };
275
276        // Build VM stack and register info.
277        let stack = self.prepare_vm_stack(ctx.input);
278
279        let code = res.new_state.code.clone();
280
281        let prev_blocks = self
282            .params
283            .prev_mc_block_id
284            .as_ref()
285            .map(make_prev_blocks_tuple)
286            .unwrap_or_default();
287
288        let mut smc_info = SmcInfoBase::new()
289            .with_now(self.params.block_unixtime)
290            .with_block_lt(self.params.block_lt)
291            .with_tx_lt(self.start_lt)
292            .with_mixed_rand_seed(&self.params.rand_seed, &self.address.address)
293            .with_account_balance(self.balance.clone())
294            .with_account_addr(self.address.clone().into())
295            .with_config(self.config.raw.params.clone())
296            .require_ton_v4()
297            .with_code(code.clone().unwrap_or_default())
298            .with_message_balance(msg_balance_remaining.clone())
299            .with_prev_blocks_info(prev_blocks)
300            .with_storage_fees(ctx.storage_fee)
301            .require_ton_v6()
302            .with_unpacked_config(self.config.unpacked.as_tuple())
303            .with_due_payment(self.storage_stat.due_payment.unwrap_or_default())
304            .require_ton_v11()
305            .with_unpacked_in_msg(unpacked_in_msg);
306
307        // NOTE: Not all versions change the smc info, so we need to bump
308        // this value manually.
309        let real_version = tycho_vm::VmVersion::Ton(12);
310
311        if let Some(inspector) = ctx.inspector.as_deref_mut()
312            && let Some(modify_smc_info) = inspector.modify_smc_info.as_deref_mut()
313        {
314            modify_smc_info(&mut smc_info)?;
315        }
316
317        let libraries = (msg_libs, state_libs, &self.params.libraries);
318
319        let mut modifiers = self.params.vm_modifiers;
320        modifiers.stop_on_accept |= ctx.stop_on_accept;
321
322        let mut vm = VmState::builder()
323            .with_smc_info(smc_info)
324            .with_version(real_version)
325            .with_code(code)
326            .with_data(res.new_state.data.clone().unwrap_or_default())
327            .with_libraries(&libraries)
328            .with_init_selector(false)
329            .with_raw_stack(stack)
330            .with_gas(gas)
331            .with_modifiers(modifiers)
332            .build();
333
334        // Connect inspected output as debug.
335        let mut inspector_actions = None;
336        let mut inspector_exit_code = None;
337        let mut inspector_total_gas_used = None;
338        let mut inspector_public_libs_diff = None;
339        let mut missing_library = None;
340        if let Some(inspector) = ctx.inspector {
341            inspector_actions = Some(&mut inspector.actions);
342            inspector_exit_code = Some(&mut inspector.exit_code);
343            inspector_total_gas_used = Some(&mut inspector.total_gas_used);
344            inspector_public_libs_diff = Some(&mut inspector.public_libs_diff);
345            missing_library = Some(&mut inspector.missing_library);
346            if let Some(debug) = inspector.debug.as_deref_mut() {
347                vm.debug = Some(debug);
348            }
349        }
350
351        // Run VM.
352        let exit_code = !vm.run();
353
354        if let Some(inspector_exit_code) = inspector_exit_code {
355            *inspector_exit_code = Some(exit_code);
356        }
357
358        let consumed_paid_gas = vm.gas.consumed();
359        if let Some(total_gas_used) = inspector_total_gas_used {
360            *total_gas_used = consumed_paid_gas.saturating_add(vm.gas.free_gas_consumed());
361        }
362
363        // Parse VM state.
364        res.accepted = ctx.force_accept || vm.gas.credit() == 0;
365        debug_assert!(
366            is_external || res.accepted,
367            "internal messages must be accepted"
368        );
369
370        let success = res.accepted && vm.committed_state.is_some();
371
372        let gas_used = std::cmp::min(consumed_paid_gas, vm.gas.limit());
373        let gas_fees = if res.accepted && !self.is_special {
374            self.config
375                .gas_prices(is_masterchain)
376                .compute_gas_fee(gas_used)
377        } else {
378            // We don't add any fees for messages that were not accepted.
379            Tokens::ZERO
380        };
381
382        let mut account_activated = false;
383        if res.accepted && msg_state_used {
384            account_activated = self.orig_status != AccountStatus::Active;
385            self.end_status = AccountStatus::Active;
386
387            // Apply libraries diff on account unfreeze.
388            if is_masterchain
389                && self.orig_status == AccountStatus::Frozen
390                && let Some(diff) = inspector_public_libs_diff
391            {
392                for entry in res.new_state.libraries.values() {
393                    let lib = entry?;
394                    if lib.public {
395                        diff.insert(*lib.root.repr_hash(), PublicLibraryChange::Add(lib.root));
396                    }
397                }
398            }
399        }
400
401        if let Some(committed) = vm.committed_state
402            && res.accepted
403        {
404            res.new_state.data = Some(committed.c4);
405            res.actions = committed.c5;
406
407            // Set inspector actions.
408            if let Some(actions) = inspector_actions {
409                *actions = Some(res.actions.clone());
410            }
411        }
412
413        if let Some(missing_library) = missing_library {
414            *missing_library = vm.gas.missing_library();
415        }
416
417        self.balance.try_sub_assign_tokens(gas_fees)?;
418        self.total_fees.try_add_assign(gas_fees)?;
419
420        res.compute_phase = ComputePhase::Executed(ExecutedComputePhase {
421            success,
422            msg_state_used,
423            account_activated,
424            gas_fees,
425            gas_used: new_varuint56_truncate(gas_used),
426            // NOTE: Initial value is stored here (not `vm.gas.limit()`).
427            gas_limit: new_varuint56_truncate(gas.limit),
428            // NOTE: Initial value is stored here (not `vm.gas.credit()`).
429            gas_credit: (gas.credit != 0).then(|| new_varuint24_truncate(gas.credit)),
430            mode: 0,
431            exit_code,
432            exit_arg: if success {
433                None
434            } else {
435                vm.stack.get_exit_arg().filter(|x| *x != 0)
436            },
437            vm_steps: vm.steps.try_into().unwrap_or(u32::MAX),
438            vm_init_state_hash: HashBytes::ZERO,
439            vm_final_state_hash: HashBytes::ZERO,
440        });
441
442        Ok(res)
443    }
444
445    fn prepare_vm_stack(&self, input: TransactionInput<'_>) -> SafeRc<Stack> {
446        SafeRc::new(Stack::with_items(match input {
447            TransactionInput::Ordinary(msg) => {
448                tuple![
449                    int self.balance.tokens,
450                    int msg.balance_remaining.tokens,
451                    cell msg.root.clone(),
452                    slice msg.body.clone(),
453                    int if msg.is_external { -1 } else { 0 },
454                ]
455            }
456            TransactionInput::TickTock(ty) => {
457                tuple![
458                    int self.balance.tokens,
459                    int self.address.address.as_bigint(),
460                    int match ty {
461                        TickTock::Tick => 0,
462                        TickTock::Tock => -1,
463                    },
464                    int -2,
465                ]
466            }
467        }))
468    }
469}
470
471impl ReceivedMessage {
472    fn make_tuple(&self) -> Result<Option<SafeRc<Tuple>>, tycho_types::error::Error> {
473        let mut cs = self.root.as_slice()?;
474        if MsgType::load_from(&mut cs)? != MsgType::Int {
475            return Ok(None);
476        }
477
478        // Get `src` addr range.
479        let src_addr_slice = {
480            let mut cs = cs;
481            // Skip flags.
482            cs.skip_first(3, 0)?;
483            let mut addr_slice = cs;
484            // Read `src`.
485            IntAddr::load_from(&mut cs)?;
486            addr_slice.skip_last(cs.size_bits(), cs.size_refs())?;
487            addr_slice.range()
488        };
489
490        let info = IntMsgInfo::load_from(&mut cs)?;
491
492        let unpacked = UnpackedInMsgSmcInfo {
493            bounce: info.bounce,
494            bounced: info.bounced,
495            src_addr: (src_addr_slice, self.root.clone()).into(),
496            fwd_fee: info.fwd_fee,
497            created_lt: info.created_lt,
498            created_at: info.created_at,
499            original_value: info.value.tokens,
500            remaining_value: self.balance_remaining.clone(),
501            state_init: self.init.as_ref().map(|init| init.root.clone()),
502        };
503        Ok(Some(unpacked.into_tuple()))
504    }
505}
506
507fn make_prev_blocks_tuple(prev_mc_block_id: &BlockId) -> SafeRc<tycho_vm::Tuple> {
508    SafeRc::new(tuple![
509        // last_mc_blocks:[BlockId...]
510        [
511            // Only the previous is used for now
512            raw block_id_to_tuple(prev_mc_block_id),
513        ]
514        // TODO: prev_key_block:BlockId
515        // TODO: last_mc_blocks_100[BlockId...]
516    ])
517}
518
519fn block_id_to_tuple(block_id: &BlockId) -> SafeRc<tycho_vm::Tuple> {
520    SafeRc::new(tuple![
521        int block_id.shard.workchain(),
522        int block_id.shard.prefix(),
523        int block_id.seqno,
524        int block_id.root_hash.as_bigint(),
525        int block_id.file_hash.as_bigint(),
526    ])
527}
528
529#[cfg(test)]
530mod tests {
531    use std::collections::BTreeMap;
532
533    use tycho_asm_macros::tvmasm;
534    use tycho_types::models::{
535        AuthorityMarksConfig, ExtInMsgInfo, IntMsgInfo, LibDescr, SimpleLib, StdAddr,
536    };
537    use tycho_types::num::{VarUint24, VarUint56, VarUint248};
538
539    use super::*;
540    use crate::ExecutorParams;
541    use crate::tests::{
542        make_custom_config, make_default_config, make_default_params, make_message,
543    };
544
545    const STUB_ADDR: StdAddr = StdAddr::new(0, HashBytes::ZERO);
546    const OK_BALANCE: Tokens = Tokens::new(1_000_000_000);
547
548    fn empty_ext_in_msg(addr: &StdAddr) -> Cell {
549        make_message(
550            ExtInMsgInfo {
551                dst: addr.clone().into(),
552                ..Default::default()
553            },
554            None,
555            None,
556        )
557    }
558
559    fn empty_int_msg(addr: &StdAddr, value: impl Into<CurrencyCollection>) -> Cell {
560        make_message(
561            IntMsgInfo {
562                src: addr.clone().into(),
563                dst: addr.clone().into(),
564                value: value.into(),
565                ..Default::default()
566            },
567            None,
568            None,
569        )
570    }
571
572    fn simple_state(code: &[u8]) -> StateInit {
573        StateInit {
574            split_depth: None,
575            special: None,
576            code: Some(Boc::decode(code).unwrap()),
577            data: None,
578            libraries: Dict::new(),
579        }
580    }
581
582    fn init_tracing() {
583        tracing_subscriber::fmt::fmt()
584            .with_env_filter("tycho_vm=trace")
585            .with_writer(tracing_subscriber::fmt::TestWriter::new)
586            .try_init()
587            .ok();
588    }
589
590    fn make_lib_ref(code: &DynCell) -> Cell {
591        let mut b = CellBuilder::new();
592        b.set_exotic(true);
593        b.store_u8(CellType::LibraryReference.to_byte()).unwrap();
594        b.store_u256(code.repr_hash()).unwrap();
595        b.build().unwrap()
596    }
597
598    #[test]
599    fn ext_in_run_no_accept() -> Result<()> {
600        let params = make_default_params();
601        let config = make_default_config();
602        let mut state = ExecutorState::new_active(
603            &params,
604            &config,
605            &STUB_ADDR,
606            OK_BALANCE,
607            Cell::empty_cell(),
608            tvmasm!("INT 123 NOP"),
609        );
610
611        let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
612
613        let prev_balance = state.balance.clone();
614        let prev_state = state.state.clone();
615        let prev_total_fees = state.total_fees;
616        let prev_end_status = state.end_status;
617
618        let compute_phase = state.compute_phase(ComputePhaseContext {
619            input: TransactionInput::Ordinary(&msg),
620            storage_fee: Tokens::ZERO,
621            force_accept: false,
622            stop_on_accept: false,
623            inspector: None,
624        })?;
625
626        // Original balance must be correct.
627        assert_eq!(prev_balance, compute_phase.original_balance);
628        // Message must not be accepted.
629        assert!(!compute_phase.accepted);
630        // State must not change.
631        assert_eq!(state.state, prev_state);
632        // Status must not change.
633        assert_eq!(prev_end_status, state.end_status);
634        // No actions must be produced.
635        assert_eq!(compute_phase.actions, Cell::empty_cell());
636        // No fees must be paid when message was not accepted.
637        assert_eq!(state.total_fees, prev_total_fees);
638        assert_eq!(state.balance, prev_balance);
639
640        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
641            panic!("expected executed compute phase");
642        };
643
644        assert!(!compute_phase.success);
645        assert!(!compute_phase.msg_state_used);
646        assert!(!compute_phase.account_activated);
647        assert_eq!(compute_phase.gas_fees, Tokens::ZERO);
648        assert_eq!(compute_phase.gas_used, VarUint56::new(0)); // only credit was used
649        assert_eq!(compute_phase.gas_limit, VarUint56::new(0)); // zero, for external messages before accept
650        assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
651        assert_eq!(compute_phase.exit_code, 0);
652        assert_eq!(compute_phase.exit_arg, Some(123)); // top int is treated as exit arg if !success
653        assert_eq!(compute_phase.vm_steps, 3); // pushint, nop, implicit ret
654
655        Ok(())
656    }
657
658    #[test]
659    fn ext_in_run_uninit_no_accept() -> Result<()> {
660        let params = make_default_params();
661        let config = make_default_config();
662        let mut state = ExecutorState::new_uninit(&params, &config, &STUB_ADDR, OK_BALANCE);
663
664        let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
665
666        let prev_balance = state.balance.clone();
667        let prev_state = state.state.clone();
668        let prev_total_fees = state.total_fees;
669        let prev_end_status = state.end_status;
670
671        let compute_phase = state.compute_phase(ComputePhaseContext {
672            input: TransactionInput::Ordinary(&msg),
673            storage_fee: Tokens::ZERO,
674            force_accept: false,
675            stop_on_accept: false,
676            inspector: None,
677        })?;
678
679        // Original balance must be correct.
680        assert_eq!(prev_balance, compute_phase.original_balance);
681        // Message must not be accepted.
682        assert!(!compute_phase.accepted);
683        // State must not change.
684        assert_eq!(state.state, prev_state);
685        // Status must not change.
686        assert_eq!(prev_end_status, state.end_status);
687        // No actions must be produced.
688        assert_eq!(compute_phase.actions, Cell::empty_cell());
689        // No fees must be paid when message was not accepted.
690        assert_eq!(state.total_fees, prev_total_fees);
691        assert_eq!(state.balance, prev_balance);
692
693        let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
694            panic!("expected skipped compute phase");
695        };
696        assert_eq!(compute_phase.reason, ComputePhaseSkipReason::NoState);
697
698        Ok(())
699    }
700
701    #[test]
702    fn ext_in_run_no_code_no_accept() -> Result<()> {
703        let params = make_default_params();
704        let config = make_default_config();
705        let mut state = ExecutorState::new_uninit(&params, &config, &STUB_ADDR, OK_BALANCE);
706        state.state = AccountState::Active(StateInit::default());
707        state.orig_status = AccountStatus::Active;
708        state.end_status = AccountStatus::Active;
709
710        let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
711
712        let prev_balance = state.balance.clone();
713        let prev_state = state.state.clone();
714        let prev_total_fees = state.total_fees;
715        let prev_end_status = state.end_status;
716
717        let compute_phase = state.compute_phase(ComputePhaseContext {
718            input: TransactionInput::Ordinary(&msg),
719            storage_fee: Tokens::ZERO,
720            force_accept: false,
721            stop_on_accept: false,
722            inspector: None,
723        })?;
724
725        // Original balance must be correct.
726        assert_eq!(prev_balance, compute_phase.original_balance);
727        // Message must not be accepted.
728        assert!(!compute_phase.accepted);
729        // State must not change.
730        assert_eq!(state.state, prev_state);
731        // Status must not change.
732        assert_eq!(prev_end_status, state.end_status);
733        // No actions must be produced.
734        assert_eq!(compute_phase.actions, Cell::empty_cell());
735        // No fees must be paid when message was not accepted.
736        assert_eq!(state.total_fees, prev_total_fees);
737        assert_eq!(state.balance, prev_balance);
738
739        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
740            panic!("expected executed compute phase");
741        };
742
743        assert!(!compute_phase.success);
744        assert!(!compute_phase.msg_state_used);
745        assert!(!compute_phase.account_activated);
746        assert_eq!(compute_phase.gas_fees, Tokens::ZERO);
747        assert_eq!(compute_phase.gas_used, VarUint56::new(0)); // only credit was used
748        assert_eq!(compute_phase.gas_limit, VarUint56::new(0)); // zero, for external messages before accept
749        assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
750        assert_eq!(
751            compute_phase.exit_code,
752            tycho_vm::VmException::Fatal.as_exit_code()
753        );
754        assert_eq!(compute_phase.exit_arg, Some(-1)); // top int is treated as exit arg if !success
755        assert_eq!(compute_phase.vm_steps, 0);
756
757        Ok(())
758    }
759
760    #[test]
761    fn ext_in_accept_no_commit() -> Result<()> {
762        let params = make_default_params();
763        let config = make_default_config();
764        let mut state = ExecutorState::new_active(
765            &params,
766            &config,
767            &STUB_ADDR,
768            OK_BALANCE,
769            Cell::empty_cell(),
770            tvmasm!("ACCEPT THROW 42"),
771        );
772
773        let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
774
775        let prev_balance = state.balance.clone();
776        let prev_state = state.state.clone();
777        let prev_total_fees = state.total_fees;
778        let prev_end_status = state.end_status;
779
780        let compute_phase = state.compute_phase(ComputePhaseContext {
781            input: TransactionInput::Ordinary(&msg),
782            storage_fee: Tokens::ZERO,
783            force_accept: false,
784            stop_on_accept: false,
785            inspector: None,
786        })?;
787
788        // Original balance must be correct.
789        assert_eq!(prev_balance, compute_phase.original_balance);
790        // Message must be accepted.
791        assert!(compute_phase.accepted);
792        // State must not change.
793        assert_eq!(state.state, prev_state);
794        // Status must not change.
795        assert_eq!(prev_end_status, state.end_status);
796        // No actions must be produced.
797        assert_eq!(compute_phase.actions, Cell::empty_cell());
798        // Fees must be paid.
799        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
800        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
801        assert_eq!(state.balance.other, prev_balance.other);
802        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
803
804        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
805            panic!("expected executed compute phase");
806        };
807
808        assert!(!compute_phase.success);
809        assert!(!compute_phase.msg_state_used);
810        assert!(!compute_phase.account_activated);
811        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
812        assert_eq!(compute_phase.gas_used, (10 + 16) * 2 + 50); // two 16bit opcodes + exception
813        assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
814        assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
815        assert_eq!(compute_phase.exit_code, 42);
816        assert_eq!(compute_phase.exit_arg, None);
817        assert_eq!(compute_phase.vm_steps, 2); // accept, throw
818
819        Ok(())
820    }
821
822    #[test]
823    fn ext_in_accept_invalid_commit() -> Result<()> {
824        init_tracing();
825        let params = make_default_params();
826        let config = make_default_config();
827
828        let mut state = ExecutorState::new_active(
829            &params,
830            &config,
831            &STUB_ADDR,
832            OK_BALANCE,
833            Cell::empty_cell(),
834            tvmasm!(
835                r#"
836                ACCEPT
837                NEWC
838                INT 1 STUR 8
839                INT 7 STUR 8
840                INT 816 STZEROES
841                TRUE ENDXC
842                POP c5
843                "#
844            ),
845        );
846
847        let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
848
849        let prev_balance = state.balance.clone();
850        let prev_state = state.state.clone();
851        let prev_total_fees = state.total_fees;
852        let prev_end_status = state.end_status;
853
854        let compute_phase = state.compute_phase(ComputePhaseContext {
855            input: TransactionInput::Ordinary(&msg),
856            storage_fee: Tokens::ZERO,
857            force_accept: false,
858            stop_on_accept: false,
859            inspector: None,
860        })?;
861
862        // Original balance must be correct.
863        assert_eq!(prev_balance, compute_phase.original_balance);
864        // Message must be accepted.
865        assert!(compute_phase.accepted);
866        // State must not change.
867        assert_eq!(state.state, prev_state);
868        // Status must not change.
869        assert_eq!(prev_end_status, state.end_status);
870        // No (VALID) actions must be produced.
871        assert_eq!(compute_phase.actions, Cell::empty_cell());
872        // Fees must be paid.
873        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
874        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
875        assert_eq!(state.balance.other, prev_balance.other);
876        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
877
878        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
879            panic!("expected executed compute phase");
880        };
881
882        assert!(!compute_phase.success);
883        assert!(!compute_phase.msg_state_used);
884        assert!(!compute_phase.account_activated);
885        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
886        assert_eq!(compute_phase.gas_used, 783);
887        assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
888        assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
889        assert_eq!(
890            compute_phase.exit_code,
891            tycho_vm::VmException::CellOverflow as i32
892        );
893        assert_eq!(compute_phase.exit_arg, None);
894        assert_eq!(compute_phase.vm_steps, 12); // accept, throw
895
896        Ok(())
897    }
898
899    #[test]
900    fn suspended_account_skips_compute() -> Result<()> {
901        let params = ExecutorParams {
902            authority_marks_enabled: true,
903            ..make_default_params()
904        };
905
906        let config = make_custom_config(|config| {
907            config.set_authority_marks_config(&AuthorityMarksConfig {
908                authority_addresses: Dict::new(),
909                black_mark_id: 100,
910                white_mark_id: 101,
911            })?;
912            Ok(())
913        });
914
915        let mut state = ExecutorState::new_active(
916            &params,
917            &config,
918            &STUB_ADDR,
919            CurrencyCollection {
920                tokens: OK_BALANCE,
921                other: BTreeMap::from_iter([
922                    (100u32, VarUint248::new(500)), // blocked by black marks
923                ])
924                .try_into()?,
925            },
926            CellBuilder::build_from(u32::MIN)?,
927            tvmasm!("ACCEPT"),
928        );
929
930        let msg = state.receive_in_msg(empty_int_msg(&STUB_ADDR, OK_BALANCE))?;
931        state.credit_phase(&msg)?;
932
933        let prev_balance = state.balance.clone();
934        let prev_state = state.state.clone();
935        let prev_total_fees = state.total_fees;
936        let prev_end_status = state.end_status;
937
938        let compute_phase = state.compute_phase(ComputePhaseContext {
939            input: TransactionInput::Ordinary(&msg),
940            storage_fee: Tokens::ZERO,
941            force_accept: false,
942            stop_on_accept: false,
943            inspector: None,
944        })?;
945
946        // Message must not be accepted.
947        assert!(!compute_phase.accepted);
948        // State must not change.
949        assert_eq!(state.state, prev_state);
950        // Status must not change.
951        assert_eq!(prev_end_status, state.end_status);
952        // No actions must be produced.
953        assert_eq!(compute_phase.actions, Cell::empty_cell());
954        // No fees must be paid when message was not accepted.
955        assert_eq!(state.total_fees, prev_total_fees);
956        assert_eq!(state.balance, prev_balance);
957
958        let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
959            panic!("expected skipped compute phase");
960        };
961        assert_eq!(compute_phase.reason, ComputePhaseSkipReason::Suspended);
962
963        Ok(())
964    }
965
966    #[test]
967    fn cant_suspend_authority_address() -> Result<()> {
968        let params = ExecutorParams {
969            authority_marks_enabled: true,
970            ..make_default_params()
971        };
972        let config = make_custom_config(|config| {
973            config.set_authority_marks_config(&AuthorityMarksConfig {
974                authority_addresses: Dict::try_from_btree(&BTreeMap::from_iter([(
975                    HashBytes::ZERO,
976                    (),
977                )]))?,
978                black_mark_id: 100,
979                white_mark_id: 101,
980            })?;
981            Ok(())
982        });
983
984        let mut state = ExecutorState::new_active(
985            &params,
986            &config,
987            &StdAddr::new(-1, HashBytes::ZERO),
988            CurrencyCollection {
989                tokens: OK_BALANCE,
990                other: BTreeMap::from_iter([
991                    (100u32, VarUint248::new(1000)), // more black barks than white
992                    (101u32, VarUint248::new(100)),
993                ])
994                .try_into()?,
995            },
996            Cell::default(),
997            tvmasm!("ACCEPT"),
998        );
999
1000        let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
1001
1002        let prev_balance = state.balance.clone();
1003        let prev_state = state.state.clone();
1004        let prev_total_fees = state.total_fees;
1005        let prev_end_status = state.end_status;
1006
1007        let compute_phase = state.compute_phase(ComputePhaseContext {
1008            input: TransactionInput::Ordinary(&msg),
1009            storage_fee: Tokens::ZERO,
1010            force_accept: false,
1011            stop_on_accept: false,
1012            inspector: None,
1013        })?;
1014
1015        // Original balance must be correct.
1016        assert_eq!(prev_balance, compute_phase.original_balance);
1017        // Message must be accepted.
1018        assert!(compute_phase.accepted);
1019        // State must not change.
1020        assert_eq!(state.state, prev_state);
1021        // Status must not change.
1022        assert_eq!(prev_end_status, state.end_status);
1023        // No actions must be produced.
1024        assert_eq!(compute_phase.actions, Cell::empty_cell());
1025        // Fees must be paid.
1026        let expected_gas_fee = Tokens::new(config.mc_gas_prices.flat_gas_price as _);
1027        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1028        assert_eq!(state.balance.other, prev_balance.other);
1029        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1030
1031        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1032            panic!("expected executed compute phase");
1033        };
1034
1035        assert!(compute_phase.success);
1036        assert!(!compute_phase.msg_state_used);
1037        assert!(!compute_phase.account_activated);
1038        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1039        assert_eq!(compute_phase.gas_used, 31);
1040        assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
1041        assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
1042        assert_eq!(compute_phase.exit_code, 0);
1043        assert_eq!(compute_phase.exit_arg, None);
1044        assert_eq!(compute_phase.vm_steps, 2);
1045
1046        Ok(())
1047    }
1048
1049    #[test]
1050    fn ext_in_accept_simple() -> Result<()> {
1051        let params = make_default_params();
1052        let config = make_default_config();
1053        let mut state = ExecutorState::new_active(
1054            &params,
1055            &config,
1056            &STUB_ADDR,
1057            OK_BALANCE,
1058            Cell::empty_cell(),
1059            tvmasm!("ACCEPT NEWC INT 0xdeafbeaf STUR 32 ENDC POP c5"),
1060        );
1061
1062        let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
1063
1064        let prev_balance = state.balance.clone();
1065        let prev_state = state.state.clone();
1066        let prev_total_fees = state.total_fees;
1067        let prev_end_status = state.end_status;
1068
1069        let compute_phase = state.compute_phase(ComputePhaseContext {
1070            input: TransactionInput::Ordinary(&msg),
1071            storage_fee: Tokens::ZERO,
1072            force_accept: false,
1073            stop_on_accept: false,
1074            inspector: None,
1075        })?;
1076
1077        // Original balance must be correct.
1078        assert_eq!(prev_balance, compute_phase.original_balance);
1079        // Message must be accepted.
1080        assert!(compute_phase.accepted);
1081        // State must not change.
1082        assert_eq!(state.state, prev_state);
1083        // Status must not change.
1084        assert_eq!(prev_end_status, state.end_status);
1085        // No actions must be produced.
1086        assert_eq!(
1087            compute_phase.actions,
1088            CellBuilder::build_from(0xdeafbeafu32)?
1089        );
1090        // Fees must be paid.
1091        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1092        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1093        assert_eq!(state.balance.other, prev_balance.other);
1094        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1095
1096        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1097            panic!("expected executed compute phase");
1098        };
1099
1100        assert!(compute_phase.success);
1101        assert!(!compute_phase.msg_state_used);
1102        assert!(!compute_phase.account_activated);
1103        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1104        assert_eq!(compute_phase.gas_used, 650);
1105        assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
1106        assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
1107        assert_eq!(compute_phase.exit_code, 0);
1108        assert_eq!(compute_phase.exit_arg, None);
1109        assert_eq!(compute_phase.vm_steps, 7);
1110
1111        Ok(())
1112    }
1113
1114    #[test]
1115    fn internal_accept_simple() -> Result<()> {
1116        let params = make_default_params();
1117        let config = make_default_config();
1118        let mut state = ExecutorState::new_active(
1119            &params,
1120            &config,
1121            &STUB_ADDR,
1122            OK_BALANCE,
1123            Cell::empty_cell(),
1124            tvmasm!("ACCEPT"),
1125        );
1126
1127        let msg =
1128            state.receive_in_msg(empty_int_msg(&state.address, Tokens::new(1_000_000_000)))?;
1129
1130        state.credit_phase(&msg)?;
1131
1132        let prev_balance = state.balance.clone();
1133        let prev_state = state.state.clone();
1134        let prev_total_fees = state.total_fees;
1135        let prev_end_status = state.end_status;
1136
1137        let compute_phase = state.compute_phase(ComputePhaseContext {
1138            input: TransactionInput::Ordinary(&msg),
1139            storage_fee: Tokens::ZERO,
1140            force_accept: false,
1141            stop_on_accept: false,
1142            inspector: None,
1143        })?;
1144
1145        // Original balance must be correct.
1146        assert_eq!(
1147            compute_phase.original_balance,
1148            CurrencyCollection::from(OK_BALANCE)
1149        );
1150        // Message must be accepted.
1151        assert!(compute_phase.accepted);
1152        // State must not change.
1153        assert_eq!(state.state, prev_state);
1154        // Status must not change.
1155        assert_eq!(prev_end_status, state.end_status);
1156        // No actions must be produced.
1157        assert_eq!(compute_phase.actions, Cell::empty_cell());
1158        // Fees must be paid.
1159        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1160        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1161        assert_eq!(state.balance.other, prev_balance.other);
1162        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1163
1164        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1165            panic!("expected executed compute phase");
1166        };
1167
1168        assert!(compute_phase.success);
1169        assert!(!compute_phase.msg_state_used);
1170        assert!(!compute_phase.account_activated);
1171        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1172        assert_eq!(compute_phase.gas_used, (10 + 16) + 5); // two 16bit opcodes + implicit ret
1173        assert_eq!(
1174            compute_phase.gas_limit,
1175            VarUint56::new(config.gas_prices.gas_limit)
1176        );
1177        assert_eq!(compute_phase.gas_credit, None);
1178        assert_eq!(compute_phase.exit_code, 0);
1179        assert_eq!(compute_phase.exit_arg, None);
1180        assert_eq!(compute_phase.vm_steps, 2); // accept, implicit ret
1181
1182        Ok(())
1183    }
1184
1185    #[test]
1186    fn internal_no_accept() -> Result<()> {
1187        let params = make_default_params();
1188        let config = make_default_config();
1189        let mut state = ExecutorState::new_active(
1190            &params,
1191            &config,
1192            &STUB_ADDR,
1193            OK_BALANCE,
1194            Cell::empty_cell(),
1195            tvmasm!("NOP"),
1196        );
1197
1198        let msg = state.receive_in_msg(empty_int_msg(&state.address, Tokens::new(1)))?;
1199
1200        state.credit_phase(&msg)?;
1201
1202        let prev_balance = state.balance.clone();
1203        let prev_state = state.state.clone();
1204        let prev_total_fees = state.total_fees;
1205        let prev_end_status = state.end_status;
1206
1207        let compute_phase = state.compute_phase(ComputePhaseContext {
1208            input: TransactionInput::Ordinary(&msg),
1209            storage_fee: Tokens::ZERO,
1210            force_accept: false,
1211            stop_on_accept: false,
1212            inspector: None,
1213        })?;
1214
1215        // Original balance must be correct.
1216        assert_eq!(
1217            compute_phase.original_balance,
1218            CurrencyCollection::from(OK_BALANCE)
1219        );
1220        // Message must not be accepted.
1221        assert!(!compute_phase.accepted);
1222        // State must not change.
1223        assert_eq!(state.state, prev_state);
1224        // Status must not change.
1225        assert_eq!(prev_end_status, state.end_status);
1226        // No actions must be produced.
1227        assert_eq!(compute_phase.actions, Cell::empty_cell());
1228        // No fees must be paid when message was not accepted.
1229        assert_eq!(state.total_fees, prev_total_fees);
1230        assert_eq!(state.balance, prev_balance);
1231
1232        let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1233            panic!("expected skipped compute phase");
1234        };
1235        assert_eq!(compute_phase.reason, ComputePhaseSkipReason::NoGas);
1236
1237        Ok(())
1238    }
1239
1240    #[test]
1241    fn internal_no_accept_empty_balance() -> Result<()> {
1242        let params = make_default_params();
1243        let config = make_default_config();
1244        let mut state = ExecutorState::new_active(
1245            &params,
1246            &config,
1247            &STUB_ADDR,
1248            Tokens::ZERO,
1249            Cell::empty_cell(),
1250            tvmasm!("NOP"),
1251        );
1252
1253        let msg = state.receive_in_msg(empty_int_msg(&state.address, Tokens::ZERO))?;
1254
1255        state.credit_phase(&msg)?;
1256
1257        let prev_balance = state.balance.clone();
1258        let prev_state = state.state.clone();
1259        let prev_total_fees = state.total_fees;
1260        let prev_end_status = state.end_status;
1261
1262        let compute_phase = state.compute_phase(ComputePhaseContext {
1263            input: TransactionInput::Ordinary(&msg),
1264            storage_fee: Tokens::ZERO,
1265            force_accept: false,
1266            stop_on_accept: false,
1267            inspector: None,
1268        })?;
1269
1270        // Original balance must be correct.
1271        assert_eq!(compute_phase.original_balance, CurrencyCollection::ZERO);
1272        // Message must not be accepted.
1273        assert!(!compute_phase.accepted);
1274        // State must not change.
1275        assert_eq!(state.state, prev_state);
1276        // Status must not change.
1277        assert_eq!(prev_end_status, state.end_status);
1278        // No actions must be produced.
1279        assert_eq!(compute_phase.actions, Cell::empty_cell());
1280        // No fees must be paid when message was not accepted.
1281        assert_eq!(state.total_fees, prev_total_fees);
1282        assert_eq!(state.balance, prev_balance);
1283
1284        let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1285            panic!("expected skipped compute phase");
1286        };
1287        assert_eq!(compute_phase.reason, ComputePhaseSkipReason::NoGas);
1288
1289        Ok(())
1290    }
1291
1292    #[test]
1293    fn ext_in_deploy_account() -> Result<()> {
1294        let state_init = simple_state(tvmasm!("ACCEPT"));
1295        let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
1296        let addr = StdAddr::new(0, state_init_hash);
1297
1298        let params = make_default_params();
1299        let config = make_default_config();
1300        let mut state = ExecutorState::new_uninit(&params, &config, &addr, OK_BALANCE);
1301
1302        let msg = state.receive_in_msg(make_message(
1303            ExtInMsgInfo {
1304                dst: addr.clone().into(),
1305                ..Default::default()
1306            },
1307            Some(state_init.clone()),
1308            None,
1309        ))?;
1310
1311        let prev_balance = state.balance.clone();
1312        let prev_total_fees = state.total_fees;
1313
1314        let compute_phase = state.compute_phase(ComputePhaseContext {
1315            input: TransactionInput::Ordinary(&msg),
1316            storage_fee: Tokens::ZERO,
1317            force_accept: false,
1318            stop_on_accept: false,
1319            inspector: None,
1320        })?;
1321
1322        // Original balance must be correct.
1323        assert_eq!(
1324            compute_phase.original_balance,
1325            CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1326        );
1327        // Message must be accepted.
1328        assert!(compute_phase.accepted);
1329        // State must change.
1330        assert_eq!(state.state, AccountState::Active(state_init));
1331        // Status must change.
1332        assert_eq!(state.end_status, AccountStatus::Active);
1333        // No actions must be produced.
1334        assert_eq!(compute_phase.actions, Cell::empty_cell());
1335        // Fees must be paid.
1336        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1337        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1338        assert_eq!(state.balance.other, prev_balance.other);
1339        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1340
1341        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1342            panic!("expected executed compute phase");
1343        };
1344
1345        assert!(compute_phase.success);
1346        assert!(compute_phase.msg_state_used);
1347        assert!(compute_phase.account_activated);
1348        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1349        assert_eq!(compute_phase.gas_used, (10 + 16) + 5); // two 16bit opcodes + implicit ret
1350        assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
1351        assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
1352        assert_eq!(compute_phase.exit_code, 0);
1353        assert_eq!(compute_phase.exit_arg, None);
1354        assert_eq!(compute_phase.vm_steps, 2); // accept, implicit ret
1355
1356        Ok(())
1357    }
1358
1359    #[test]
1360    fn internal_deploy_account() -> Result<()> {
1361        let state_init = simple_state(tvmasm!("ACCEPT"));
1362        let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
1363        let addr = StdAddr::new(0, state_init_hash);
1364
1365        let params = make_default_params();
1366        let config = make_default_config();
1367        let mut state = ExecutorState::new_uninit(&params, &config, &addr, OK_BALANCE);
1368
1369        let msg = state.receive_in_msg(make_message(
1370            IntMsgInfo {
1371                src: addr.clone().into(),
1372                dst: addr.clone().into(),
1373                value: Tokens::new(1_000_000_000).into(),
1374                ..Default::default()
1375            },
1376            Some(state_init.clone()),
1377            None,
1378        ))?;
1379
1380        state.credit_phase(&msg)?;
1381
1382        let prev_balance = state.balance.clone();
1383        let prev_total_fees = state.total_fees;
1384
1385        let compute_phase = state.compute_phase(ComputePhaseContext {
1386            input: TransactionInput::Ordinary(&msg),
1387            storage_fee: Tokens::ZERO,
1388            force_accept: false,
1389            stop_on_accept: false,
1390            inspector: None,
1391        })?;
1392
1393        // Original balance must be correct.
1394        assert_eq!(
1395            compute_phase.original_balance,
1396            CurrencyCollection::from(OK_BALANCE)
1397        );
1398        // Message must be accepted.
1399        assert!(compute_phase.accepted);
1400        // State must change.
1401        assert_eq!(state.state, AccountState::Active(state_init));
1402        // Status must change.
1403        assert_eq!(state.end_status, AccountStatus::Active);
1404        // No actions must be produced.
1405        assert_eq!(compute_phase.actions, Cell::empty_cell());
1406        // Fees must be paid.
1407        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1408        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1409        assert_eq!(state.balance.other, prev_balance.other);
1410        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1411
1412        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1413            panic!("expected executed compute phase");
1414        };
1415
1416        assert!(compute_phase.success);
1417        assert!(compute_phase.msg_state_used);
1418        assert!(compute_phase.account_activated);
1419        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1420        assert_eq!(compute_phase.gas_used, (10 + 16) + 5); // two 16bit opcodes + implicit ret
1421        assert_eq!(
1422            compute_phase.gas_limit,
1423            VarUint56::new(config.gas_prices.gas_limit)
1424        );
1425        assert_eq!(compute_phase.gas_credit, None);
1426        assert_eq!(compute_phase.exit_code, 0);
1427        assert_eq!(compute_phase.exit_arg, None);
1428        assert_eq!(compute_phase.vm_steps, 2); // accept, implicit ret
1429
1430        Ok(())
1431    }
1432
1433    #[test]
1434    fn ext_in_unfreeze_account() -> Result<()> {
1435        let state_init = simple_state(tvmasm!("ACCEPT"));
1436        let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
1437
1438        let params = make_default_params();
1439        let config = make_default_config();
1440        let mut state =
1441            ExecutorState::new_frozen(&params, &config, &STUB_ADDR, OK_BALANCE, state_init_hash);
1442
1443        let msg = state.receive_in_msg(make_message(
1444            ExtInMsgInfo {
1445                dst: STUB_ADDR.into(),
1446                ..Default::default()
1447            },
1448            Some(state_init.clone()),
1449            None,
1450        ))?;
1451
1452        let prev_balance = state.balance.clone();
1453        let prev_total_fees = state.total_fees;
1454
1455        let compute_phase = state.compute_phase(ComputePhaseContext {
1456            input: TransactionInput::Ordinary(&msg),
1457            storage_fee: Tokens::ZERO,
1458            force_accept: false,
1459            stop_on_accept: false,
1460            inspector: None,
1461        })?;
1462
1463        // Original balance must be correct.
1464        assert_eq!(
1465            compute_phase.original_balance,
1466            CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1467        );
1468        // Message must be accepted.
1469        assert!(compute_phase.accepted);
1470        // State must change.
1471        assert_eq!(state.state, AccountState::Active(state_init));
1472        // Status must change.
1473        assert_eq!(state.end_status, AccountStatus::Active);
1474        // No actions must be produced.
1475        assert_eq!(compute_phase.actions, Cell::empty_cell());
1476        // Fees must be paid.
1477        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1478        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1479        assert_eq!(state.balance.other, prev_balance.other);
1480        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1481
1482        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1483            panic!("expected executed compute phase");
1484        };
1485
1486        assert!(compute_phase.success);
1487        assert!(compute_phase.msg_state_used);
1488        assert!(compute_phase.account_activated);
1489        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1490        assert_eq!(compute_phase.gas_used, (10 + 16) + 5); // two 16bit opcodes + implicit ret
1491        assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
1492        assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
1493        assert_eq!(compute_phase.exit_code, 0);
1494        assert_eq!(compute_phase.exit_arg, None);
1495        assert_eq!(compute_phase.vm_steps, 2); // accept, implicit ret
1496
1497        Ok(())
1498    }
1499
1500    #[test]
1501    fn internal_unfreeze_account() -> Result<()> {
1502        let state_init = simple_state(tvmasm!("ACCEPT"));
1503        let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
1504
1505        let params = make_default_params();
1506        let config = make_default_config();
1507        let mut state =
1508            ExecutorState::new_frozen(&params, &config, &STUB_ADDR, Tokens::ZERO, state_init_hash);
1509
1510        let msg = state.receive_in_msg(make_message(
1511            IntMsgInfo {
1512                src: STUB_ADDR.into(),
1513                dst: STUB_ADDR.into(),
1514                value: Tokens::new(1_000_000_000).into(),
1515                ..Default::default()
1516            },
1517            Some(state_init.clone()),
1518            None,
1519        ))?;
1520
1521        state.credit_phase(&msg)?;
1522
1523        let prev_balance = state.balance.clone();
1524        let prev_total_fees = state.total_fees;
1525
1526        let compute_phase = state.compute_phase(ComputePhaseContext {
1527            input: TransactionInput::Ordinary(&msg),
1528            storage_fee: Tokens::ZERO,
1529            force_accept: false,
1530            stop_on_accept: false,
1531            inspector: None,
1532        })?;
1533
1534        // Original balance must be correct.
1535        assert_eq!(compute_phase.original_balance, CurrencyCollection::ZERO);
1536        // Message must be accepted.
1537        assert!(compute_phase.accepted);
1538        // State must change.
1539        assert_eq!(state.state, AccountState::Active(state_init));
1540        // Status must change to active.
1541        assert_eq!(state.end_status, AccountStatus::Active);
1542        // No actions must be produced.
1543        assert_eq!(compute_phase.actions, Cell::empty_cell());
1544        // Fees must be paid.
1545        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1546        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1547        assert_eq!(state.balance.other, prev_balance.other);
1548        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1549
1550        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1551            panic!("expected executed compute phase");
1552        };
1553
1554        assert!(compute_phase.success);
1555        assert!(compute_phase.msg_state_used);
1556        assert!(compute_phase.account_activated);
1557        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1558        assert_eq!(compute_phase.gas_used, (10 + 16) + 5); // two 16bit opcodes + implicit ret
1559        assert_eq!(
1560            compute_phase.gas_limit,
1561            VarUint56::new(config.gas_prices.gas_limit)
1562        );
1563        assert_eq!(compute_phase.gas_credit, None);
1564        assert_eq!(compute_phase.exit_code, 0);
1565        assert_eq!(compute_phase.exit_arg, None);
1566        assert_eq!(compute_phase.vm_steps, 2); // accept, implicit ret
1567
1568        Ok(())
1569    }
1570
1571    #[test]
1572    fn ext_in_unfreeze_hash_mismatch() -> Result<()> {
1573        let state_init = simple_state(tvmasm!("ACCEPT"));
1574
1575        let params = make_default_params();
1576        let config = make_default_config();
1577        let mut state =
1578            ExecutorState::new_frozen(&params, &config, &STUB_ADDR, OK_BALANCE, HashBytes::ZERO);
1579
1580        let msg = state.receive_in_msg(make_message(
1581            ExtInMsgInfo {
1582                dst: STUB_ADDR.into(),
1583                ..Default::default()
1584            },
1585            Some(state_init.clone()),
1586            None,
1587        ))?;
1588
1589        let prev_state = state.state.clone();
1590        let prev_balance = state.balance.clone();
1591        let prev_total_fees = state.total_fees;
1592
1593        let compute_phase = state.compute_phase(ComputePhaseContext {
1594            input: TransactionInput::Ordinary(&msg),
1595            storage_fee: Tokens::ZERO,
1596            force_accept: false,
1597            stop_on_accept: false,
1598            inspector: None,
1599        })?;
1600
1601        // Original balance must be correct.
1602        assert_eq!(
1603            compute_phase.original_balance,
1604            CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1605        );
1606        // Message must not be accepted.
1607        assert!(!compute_phase.accepted);
1608        // State must not change.
1609        assert_eq!(state.state, prev_state);
1610        // Status must not change.
1611        assert_eq!(state.end_status, AccountStatus::Frozen);
1612        // No actions must be produced.
1613        assert_eq!(compute_phase.actions, Cell::empty_cell());
1614        // Fees must not be paid.
1615        assert_eq!(state.total_fees, prev_total_fees);
1616        assert_eq!(state.balance, prev_balance);
1617
1618        let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1619            panic!("expected skipped compute phase");
1620        };
1621        assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
1622
1623        Ok(())
1624    }
1625
1626    #[test]
1627    fn ext_in_deploy_hash_mismatch() -> Result<()> {
1628        let state_init = simple_state(tvmasm!("ACCEPT"));
1629
1630        let params = make_default_params();
1631        let config = make_default_config();
1632        let mut state = ExecutorState::new_uninit(&params, &config, &STUB_ADDR, OK_BALANCE);
1633
1634        let msg = state.receive_in_msg(make_message(
1635            ExtInMsgInfo {
1636                dst: STUB_ADDR.into(),
1637                ..Default::default()
1638            },
1639            Some(state_init.clone()),
1640            None,
1641        ))?;
1642
1643        let prev_state = state.state.clone();
1644        let prev_balance = state.balance.clone();
1645        let prev_total_fees = state.total_fees;
1646
1647        let compute_phase = state.compute_phase(ComputePhaseContext {
1648            input: TransactionInput::Ordinary(&msg),
1649            storage_fee: Tokens::ZERO,
1650            force_accept: false,
1651            stop_on_accept: false,
1652            inspector: None,
1653        })?;
1654
1655        // Original balance must be correct.
1656        assert_eq!(
1657            compute_phase.original_balance,
1658            CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1659        );
1660        // Message must not be accepted.
1661        assert!(!compute_phase.accepted);
1662        // State must not change.
1663        assert_eq!(state.state, prev_state);
1664        // Status must not change.
1665        assert_eq!(state.end_status, AccountStatus::Uninit);
1666        // No actions must be produced.
1667        assert_eq!(compute_phase.actions, Cell::empty_cell());
1668        // Fees must not be paid.
1669        assert_eq!(state.total_fees, prev_total_fees);
1670        assert_eq!(state.balance, prev_balance);
1671
1672        let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1673            panic!("expected skipped compute phase");
1674        };
1675        assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
1676
1677        Ok(())
1678    }
1679
1680    #[test]
1681    fn internal_unfreeze_hash_mismatch() -> Result<()> {
1682        let state_init = simple_state(tvmasm!("ACCEPT"));
1683
1684        let params = make_default_params();
1685        let config = make_default_config();
1686        let mut state =
1687            ExecutorState::new_frozen(&params, &config, &STUB_ADDR, OK_BALANCE, HashBytes::ZERO);
1688
1689        let msg = state.receive_in_msg(make_message(
1690            IntMsgInfo {
1691                src: STUB_ADDR.into(),
1692                dst: STUB_ADDR.into(),
1693                value: Tokens::new(1_000_000_000).into(),
1694                ..Default::default()
1695            },
1696            Some(state_init.clone()),
1697            None,
1698        ))?;
1699
1700        state.credit_phase(&msg)?;
1701
1702        let prev_state = state.state.clone();
1703        let prev_balance = state.balance.clone();
1704        let prev_total_fees = state.total_fees;
1705
1706        let compute_phase = state.compute_phase(ComputePhaseContext {
1707            input: TransactionInput::Ordinary(&msg),
1708            storage_fee: Tokens::ZERO,
1709            force_accept: false,
1710            stop_on_accept: false,
1711            inspector: None,
1712        })?;
1713
1714        // Original balance must be correct.
1715        assert_eq!(
1716            compute_phase.original_balance,
1717            CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1718        );
1719        // Message must not be accepted.
1720        assert!(!compute_phase.accepted);
1721        // State must not change.
1722        assert_eq!(state.state, prev_state);
1723        // Status must not change.
1724        assert_eq!(state.end_status, AccountStatus::Frozen);
1725        // No actions must be produced.
1726        assert_eq!(compute_phase.actions, Cell::empty_cell());
1727        // Fees must not be paid.
1728        assert_eq!(state.total_fees, prev_total_fees);
1729        assert_eq!(state.balance, prev_balance);
1730
1731        let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1732            panic!("expected skipped compute phase");
1733        };
1734        assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
1735
1736        Ok(())
1737    }
1738
1739    #[test]
1740    fn internal_deploy_hash_mismatch() -> Result<()> {
1741        let state_init = simple_state(tvmasm!("ACCEPT"));
1742
1743        let params = make_default_params();
1744        let config = make_default_config();
1745        let mut state = ExecutorState::new_uninit(&params, &config, &STUB_ADDR, OK_BALANCE);
1746
1747        let msg = state.receive_in_msg(make_message(
1748            IntMsgInfo {
1749                src: STUB_ADDR.into(),
1750                dst: STUB_ADDR.into(),
1751                value: Tokens::new(1_000_000_000).into(),
1752                ..Default::default()
1753            },
1754            Some(state_init.clone()),
1755            None,
1756        ))?;
1757
1758        state.credit_phase(&msg)?;
1759
1760        let prev_state = state.state.clone();
1761        let prev_balance = state.balance.clone();
1762        let prev_total_fees = state.total_fees;
1763
1764        let compute_phase = state.compute_phase(ComputePhaseContext {
1765            input: TransactionInput::Ordinary(&msg),
1766            storage_fee: Tokens::ZERO,
1767            force_accept: false,
1768            stop_on_accept: false,
1769            inspector: None,
1770        })?;
1771
1772        // Original balance must be correct.
1773        assert_eq!(
1774            compute_phase.original_balance,
1775            CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1776        );
1777        // Message must not be accepted.
1778        assert!(!compute_phase.accepted);
1779        // State must not change.
1780        assert_eq!(state.state, prev_state);
1781        // Status must not change.
1782        assert_eq!(state.end_status, AccountStatus::Uninit);
1783        // No actions must be produced.
1784        assert_eq!(compute_phase.actions, Cell::empty_cell());
1785        // Fees must not be paid.
1786        assert_eq!(state.total_fees, prev_total_fees);
1787        assert_eq!(state.balance, prev_balance);
1788
1789        let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1790            panic!("expected skipped compute phase");
1791        };
1792        assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
1793
1794        Ok(())
1795    }
1796
1797    #[test]
1798    fn tick_special() -> Result<()> {
1799        init_tracing();
1800        let params = make_default_params();
1801        let config = make_default_config();
1802        let mut state = ExecutorState::new_active(
1803            &params,
1804            &config,
1805            &STUB_ADDR,
1806            OK_BALANCE,
1807            Cell::empty_cell(),
1808            tvmasm!("DROP THROWIF 42 ACCEPT"),
1809        );
1810
1811        let prev_balance = state.balance.clone();
1812        let prev_state = state.state.clone();
1813        let prev_total_fees = state.total_fees;
1814        let prev_end_status = state.end_status;
1815
1816        let compute_phase = state.compute_phase(ComputePhaseContext {
1817            input: TransactionInput::TickTock(TickTock::Tick),
1818            storage_fee: Tokens::ZERO,
1819            force_accept: false,
1820            stop_on_accept: false,
1821            inspector: None,
1822        })?;
1823
1824        // Original balance must be correct.
1825        assert_eq!(
1826            compute_phase.original_balance,
1827            CurrencyCollection::from(OK_BALANCE)
1828        );
1829        // Message must be accepted.
1830        assert!(compute_phase.accepted);
1831        // State must not change.
1832        assert_eq!(state.state, prev_state);
1833        // Status must not change.
1834        assert_eq!(prev_end_status, state.end_status);
1835        // No actions must be produced.
1836        assert_eq!(compute_phase.actions, Cell::empty_cell());
1837        // Fees must be paid.
1838        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1839        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1840        assert_eq!(state.balance.other, prev_balance.other);
1841        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1842
1843        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1844            panic!("expected executed compute phase");
1845        };
1846
1847        assert!(compute_phase.success);
1848        assert!(!compute_phase.msg_state_used);
1849        assert!(!compute_phase.account_activated);
1850        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1851        assert_eq!(compute_phase.gas_used, 75);
1852        assert_eq!(
1853            compute_phase.gas_limit,
1854            VarUint56::new(config.gas_prices.gas_limit)
1855        );
1856        assert_eq!(compute_phase.gas_credit, None);
1857        assert_eq!(compute_phase.exit_code, 0);
1858        assert_eq!(compute_phase.exit_arg, None);
1859        assert_eq!(compute_phase.vm_steps, 4);
1860
1861        Ok(())
1862    }
1863
1864    #[test]
1865    fn code_as_library() -> Result<()> {
1866        init_tracing();
1867        let mut params = make_default_params();
1868        let config = make_default_config();
1869
1870        let code = Boc::decode(tvmasm!("DROP THROWIF 42 ACCEPT"))?;
1871        params.libraries.set(code.repr_hash(), LibDescr {
1872            lib: code.clone(),
1873            publishers: {
1874                let mut p = Dict::new();
1875                p.set(HashBytes::ZERO, ())?;
1876                p
1877            },
1878        })?;
1879
1880        let mut state = ExecutorState::new_uninit(&params, &config, &STUB_ADDR, OK_BALANCE);
1881        state.state = AccountState::Active(StateInit {
1882            code: Some(make_lib_ref(code.as_ref())),
1883            ..Default::default()
1884        });
1885        state.orig_status = AccountStatus::Active;
1886        state.end_status = AccountStatus::Active;
1887
1888        let prev_balance = state.balance.clone();
1889        let prev_state = state.state.clone();
1890        let prev_total_fees = state.total_fees;
1891        let prev_end_status = state.end_status;
1892
1893        let compute_phase = state.compute_phase(ComputePhaseContext {
1894            input: TransactionInput::TickTock(TickTock::Tick),
1895            storage_fee: Tokens::ZERO,
1896            force_accept: false,
1897            stop_on_accept: false,
1898            inspector: None,
1899        })?;
1900
1901        // Original balance must be correct.
1902        assert_eq!(
1903            compute_phase.original_balance,
1904            CurrencyCollection::from(OK_BALANCE)
1905        );
1906        // Message must be accepted.
1907        assert!(compute_phase.accepted);
1908        // State must not change.
1909        assert_eq!(state.state, prev_state);
1910        // Status must not change.
1911        assert_eq!(prev_end_status, state.end_status);
1912        // No actions must be produced.
1913        assert_eq!(compute_phase.actions, Cell::empty_cell());
1914        // Fees must be paid.
1915        let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1916        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1917        assert_eq!(state.balance.other, prev_balance.other);
1918        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1919
1920        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1921            panic!("expected executed compute phase");
1922        };
1923
1924        assert!(compute_phase.success);
1925        assert!(!compute_phase.msg_state_used);
1926        assert!(!compute_phase.account_activated);
1927        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1928        assert_eq!(compute_phase.gas_used, 185);
1929        assert_eq!(
1930            compute_phase.gas_limit,
1931            VarUint56::new(config.gas_prices.gas_limit)
1932        );
1933        assert_eq!(compute_phase.gas_credit, None);
1934        assert_eq!(compute_phase.exit_code, 0);
1935        assert_eq!(compute_phase.exit_arg, None);
1936        assert_eq!(compute_phase.vm_steps, 5);
1937
1938        Ok(())
1939    }
1940
1941    #[test]
1942    fn internal_use_libraries() -> Result<()> {
1943        init_tracing();
1944        let mut params = make_default_params();
1945        let config = make_default_config();
1946
1947        let state_lib_code = Boc::decode(tvmasm!("THROWIF 42 INT 0"))?;
1948        let account_lib_code = Boc::decode(tvmasm!("THROWIF 43 INT -1"))?;
1949        let msg_lib_code = Boc::decode(tvmasm!("THROWIFNOT 44"))?;
1950
1951        println!("State lib hash: {}", state_lib_code.repr_hash());
1952        println!("Account lib hash: {}", account_lib_code.repr_hash());
1953        println!("Message lib hash: {}", msg_lib_code.repr_hash());
1954
1955        params.libraries.set(state_lib_code.repr_hash(), LibDescr {
1956            lib: state_lib_code.clone(),
1957            publishers: {
1958                let mut p = Dict::new();
1959                p.set(HashBytes([0x00; 32]), ())?;
1960                p
1961            },
1962        })?;
1963
1964        let msg_state_init = StateInit {
1965            libraries: {
1966                let mut p = Dict::new();
1967                p.set(msg_lib_code.repr_hash(), SimpleLib {
1968                    public: false,
1969                    root: msg_lib_code.clone(),
1970                })?;
1971                p
1972            },
1973            ..Default::default()
1974        };
1975        let addr = StdAddr::new(0, *CellBuilder::build_from(&msg_state_init)?.repr_hash());
1976
1977        let mut state = ExecutorState::new_uninit(&params, &config, &addr, OK_BALANCE);
1978        state.state = AccountState::Active(StateInit {
1979            code: Some(Boc::decode(tvmasm!(
1980                r#"
1981                ACCEPT
1982                PUSHCONT {
1983                    DUMPSTK
1984                    XLOAD
1985                    CTOS
1986                    BLESS
1987                    EXECUTE
1988                }
1989                // Execute state lib code
1990                OVER
1991                PUSHREF @{f3898d9454fc288ac160d46487de1303db2244da85d6908356b89090c7839e4a}
1992                PUSH s2
1993                EXECUTE
1994                // Execute account lib code
1995                PUSHREF @{d8b2b44711e73fece0f00b41be3aef8f3850169a7834852e1c8abf80c228cf57}
1996                PUSH s2
1997                EXECUTE
1998                // Execute msg lib code
1999                PUSHREF @{bc17092cb9d0bad5c5a523cd866d37f20d3783de23b891e48b903f7bca470998}
2000                ROT
2001                EXECUTE
2002                "#
2003            ))?),
2004            libraries: {
2005                let mut p = Dict::new();
2006                p.set(account_lib_code.repr_hash(), SimpleLib {
2007                    public: false,
2008                    root: account_lib_code.clone(),
2009                })?;
2010                p
2011            },
2012            ..Default::default()
2013        });
2014        state.orig_status = AccountStatus::Active;
2015        state.end_status = AccountStatus::Active;
2016
2017        let msg = state.receive_in_msg(make_message(
2018            IntMsgInfo {
2019                src: addr.clone().into(),
2020                dst: addr.clone().into(),
2021                value: Tokens::new(1_000_000_000).into(),
2022                ..Default::default()
2023            },
2024            Some(msg_state_init),
2025            None,
2026        ))?;
2027
2028        state.credit_phase(&msg)?;
2029
2030        let prev_balance = state.balance.clone();
2031        let prev_state = state.state.clone();
2032        let prev_total_fees = state.total_fees;
2033        let prev_end_status = state.end_status;
2034
2035        let compute_phase = state.compute_phase(ComputePhaseContext {
2036            input: TransactionInput::Ordinary(&msg),
2037            storage_fee: Tokens::ZERO,
2038            force_accept: false,
2039            stop_on_accept: false,
2040            inspector: None,
2041        })?;
2042
2043        // Original balance must be correct.
2044        assert_eq!(
2045            compute_phase.original_balance,
2046            CurrencyCollection::from(OK_BALANCE)
2047        );
2048        // Message must be accepted.
2049        assert!(compute_phase.accepted);
2050        // State must not change.
2051        assert_eq!(state.state, prev_state);
2052        // Status must not change.
2053        assert_eq!(prev_end_status, state.end_status);
2054        // No actions must be produced.
2055        assert_eq!(compute_phase.actions, Cell::empty_cell());
2056        // Fees must be paid.
2057        let expected_gas_fee = Tokens::new(1315000);
2058        assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
2059        assert_eq!(state.balance.other, prev_balance.other);
2060        assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
2061
2062        let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
2063            panic!("expected executed compute phase");
2064        };
2065
2066        assert!(compute_phase.success);
2067        assert!(!compute_phase.msg_state_used);
2068        assert!(!compute_phase.account_activated);
2069        assert_eq!(compute_phase.gas_fees, expected_gas_fee);
2070        assert_eq!(compute_phase.gas_used, 1315);
2071        assert_eq!(
2072            compute_phase.gas_limit,
2073            VarUint56::new(config.gas_prices.gas_limit)
2074        );
2075        assert_eq!(compute_phase.gas_credit, None);
2076        assert_eq!(compute_phase.exit_code, 0);
2077        assert_eq!(compute_phase.exit_arg, None);
2078        assert_eq!(compute_phase.vm_steps, 39);
2079
2080        Ok(())
2081    }
2082}