Skip to main content

tycho_executor/phase/
compute.rs

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