tycho_executor/phase/
compute.rs

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