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