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
18pub type ComputePhaseSmcInfo = tycho_vm::SmcInfoTonV11;
20
21pub struct ComputePhaseContext<'a, 'e> {
23 pub input: TransactionInput<'a>,
25 pub storage_fee: Tokens,
27 pub force_accept: bool,
31 pub stop_on_accept: bool,
34 pub inspector: Option<&'a mut ExecutorInspector<'e>>,
36}
37
38#[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#[derive(Debug)]
67pub struct ComputePhaseFull {
68 pub compute_phase: ComputePhase,
70 pub accepted: bool,
75 pub original_balance: CurrencyCollection,
77 pub new_state: StateInit,
79 pub actions: Cell,
81}
82
83impl ExecutorState<'_> {
84 pub fn compute_phase(
100 &mut self,
101 mut ctx: ComputePhaseContext<'_, '_>,
102 ) -> Result<ComputePhaseFull> {
103 let is_masterchain = self.address.is_masterchain();
104
105 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 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 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 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 let state_libs;
176 let msg_libs;
177 let msg_state_used;
178 match (ctx.input.in_msg_init(), &self.state) {
179 (None, AccountState::Uninit) => {
181 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
182 reason: ComputePhaseSkipReason::NoState,
183 });
184 return Ok(res);
185 }
186 (None, AccountState::Frozen { .. }) => {
188 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
189 reason: ComputePhaseSkipReason::BadState,
190 });
191 return Ok(res);
192 }
193 (None, AccountState::Active(StateInit { libraries, .. })) => {
195 state_libs = Some(libraries);
196 msg_libs = None;
197 msg_state_used = false;
198 }
199 (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 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
211 reason: ComputePhaseSkipReason::BadState,
212 });
213 return Ok(res);
214 }
215
216 let mut limits = self.config.size_limits.clone();
218 if is_masterchain && matches!(&self.state, AccountState::Uninit) {
219 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 res.new_state = from_msg.parsed.clone();
245 self.state = AccountState::Active(res.new_state.clone());
246 msg_state_used = true;
247
248 state_libs = None;
250 msg_libs = Some(from_msg.parsed.libraries.clone());
251 }
252 (Some(from_msg), AccountState::Active(StateInit { libraries, .. })) => {
253 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 msg_state_used = false;
263
264 state_libs = Some(libraries);
266 msg_libs = Some(from_msg.parsed.libraries.clone());
267 }
268 }
269
270 let unpacked_in_msg = match ctx.input.in_msg() {
272 Some(msg) => msg.make_tuple()?,
273 None => None,
274 };
275
276 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 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 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 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 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 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 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 gas_limit: new_varuint56_truncate(gas.limit),
413 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 let src_addr_slice = {
465 let mut cs = cs;
466 cs.skip_first(3, 0)?;
468 let mut addr_slice = cs;
469 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 [
496 raw block_id_to_tuple(prev_mc_block_id),
498 ]
499 ])
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 ¶ms,
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 assert_eq!(prev_balance, compute_phase.original_balance);
613 assert!(!compute_phase.accepted);
615 assert_eq!(state.state, prev_state);
617 assert_eq!(prev_end_status, state.end_status);
619 assert_eq!(compute_phase.actions, Cell::empty_cell());
621 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)); assert_eq!(compute_phase.gas_limit, VarUint56::new(0)); 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)); assert_eq!(compute_phase.vm_steps, 3); 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(¶ms, &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 assert_eq!(prev_balance, compute_phase.original_balance);
666 assert!(!compute_phase.accepted);
668 assert_eq!(state.state, prev_state);
670 assert_eq!(prev_end_status, state.end_status);
672 assert_eq!(compute_phase.actions, Cell::empty_cell());
674 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(¶ms, &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 assert_eq!(prev_balance, compute_phase.original_balance);
712 assert!(!compute_phase.accepted);
714 assert_eq!(state.state, prev_state);
716 assert_eq!(prev_end_status, state.end_status);
718 assert_eq!(compute_phase.actions, Cell::empty_cell());
720 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)); assert_eq!(compute_phase.gas_limit, VarUint56::new(0)); 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)); 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 ¶ms,
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 assert_eq!(prev_balance, compute_phase.original_balance);
775 assert!(compute_phase.accepted);
777 assert_eq!(state.state, prev_state);
779 assert_eq!(prev_end_status, state.end_status);
781 assert_eq!(compute_phase.actions, Cell::empty_cell());
783 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); 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); 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 ¶ms,
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 assert_eq!(prev_balance, compute_phase.original_balance);
849 assert!(compute_phase.accepted);
851 assert_eq!(state.state, prev_state);
853 assert_eq!(prev_end_status, state.end_status);
855 assert_eq!(compute_phase.actions, Cell::empty_cell());
857 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); 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 ¶ms,
902 &config,
903 &STUB_ADDR,
904 CurrencyCollection {
905 tokens: OK_BALANCE,
906 other: BTreeMap::from_iter([
907 (100u32, VarUint248::new(500)), ])
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 assert!(!compute_phase.accepted);
933 assert_eq!(state.state, prev_state);
935 assert_eq!(prev_end_status, state.end_status);
937 assert_eq!(compute_phase.actions, Cell::empty_cell());
939 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 ¶ms,
971 &config,
972 &StdAddr::new(-1, HashBytes::ZERO),
973 CurrencyCollection {
974 tokens: OK_BALANCE,
975 other: BTreeMap::from_iter([
976 (100u32, VarUint248::new(1000)), (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 assert_eq!(prev_balance, compute_phase.original_balance);
1002 assert!(compute_phase.accepted);
1004 assert_eq!(state.state, prev_state);
1006 assert_eq!(prev_end_status, state.end_status);
1008 assert_eq!(compute_phase.actions, Cell::empty_cell());
1010 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 ¶ms,
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 assert_eq!(prev_balance, compute_phase.original_balance);
1064 assert!(compute_phase.accepted);
1066 assert_eq!(state.state, prev_state);
1068 assert_eq!(prev_end_status, state.end_status);
1070 assert_eq!(
1072 compute_phase.actions,
1073 CellBuilder::build_from(0xdeafbeafu32)?
1074 );
1075 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 ¶ms,
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 assert_eq!(
1132 compute_phase.original_balance,
1133 CurrencyCollection::from(OK_BALANCE)
1134 );
1135 assert!(compute_phase.accepted);
1137 assert_eq!(state.state, prev_state);
1139 assert_eq!(prev_end_status, state.end_status);
1141 assert_eq!(compute_phase.actions, Cell::empty_cell());
1143 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); 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); 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 ¶ms,
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 assert_eq!(
1202 compute_phase.original_balance,
1203 CurrencyCollection::from(OK_BALANCE)
1204 );
1205 assert!(!compute_phase.accepted);
1207 assert_eq!(state.state, prev_state);
1209 assert_eq!(prev_end_status, state.end_status);
1211 assert_eq!(compute_phase.actions, Cell::empty_cell());
1213 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 ¶ms,
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 assert_eq!(compute_phase.original_balance, CurrencyCollection::ZERO);
1257 assert!(!compute_phase.accepted);
1259 assert_eq!(state.state, prev_state);
1261 assert_eq!(prev_end_status, state.end_status);
1263 assert_eq!(compute_phase.actions, Cell::empty_cell());
1265 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(¶ms, &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 assert_eq!(
1309 compute_phase.original_balance,
1310 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1311 );
1312 assert!(compute_phase.accepted);
1314 assert_eq!(state.state, AccountState::Active(state_init));
1316 assert_eq!(state.end_status, AccountStatus::Active);
1318 assert_eq!(compute_phase.actions, Cell::empty_cell());
1320 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); 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); 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(¶ms, &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 assert_eq!(
1380 compute_phase.original_balance,
1381 CurrencyCollection::from(OK_BALANCE)
1382 );
1383 assert!(compute_phase.accepted);
1385 assert_eq!(state.state, AccountState::Active(state_init));
1387 assert_eq!(state.end_status, AccountStatus::Active);
1389 assert_eq!(compute_phase.actions, Cell::empty_cell());
1391 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); 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); 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(¶ms, &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 assert_eq!(
1450 compute_phase.original_balance,
1451 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1452 );
1453 assert!(compute_phase.accepted);
1455 assert_eq!(state.state, AccountState::Active(state_init));
1457 assert_eq!(state.end_status, AccountStatus::Active);
1459 assert_eq!(compute_phase.actions, Cell::empty_cell());
1461 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); 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); 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(¶ms, &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 assert_eq!(compute_phase.original_balance, CurrencyCollection::ZERO);
1521 assert!(compute_phase.accepted);
1523 assert_eq!(state.state, AccountState::Active(state_init));
1525 assert_eq!(state.end_status, AccountStatus::Active);
1527 assert_eq!(compute_phase.actions, Cell::empty_cell());
1529 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); 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); 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(¶ms, &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 assert_eq!(
1588 compute_phase.original_balance,
1589 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1590 );
1591 assert!(!compute_phase.accepted);
1593 assert_eq!(state.state, prev_state);
1595 assert_eq!(state.end_status, AccountStatus::Frozen);
1597 assert_eq!(compute_phase.actions, Cell::empty_cell());
1599 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(¶ms, &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 assert_eq!(
1642 compute_phase.original_balance,
1643 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1644 );
1645 assert!(!compute_phase.accepted);
1647 assert_eq!(state.state, prev_state);
1649 assert_eq!(state.end_status, AccountStatus::Uninit);
1651 assert_eq!(compute_phase.actions, Cell::empty_cell());
1653 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(¶ms, &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 assert_eq!(
1701 compute_phase.original_balance,
1702 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1703 );
1704 assert!(!compute_phase.accepted);
1706 assert_eq!(state.state, prev_state);
1708 assert_eq!(state.end_status, AccountStatus::Frozen);
1710 assert_eq!(compute_phase.actions, Cell::empty_cell());
1712 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(¶ms, &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 assert_eq!(
1759 compute_phase.original_balance,
1760 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1761 );
1762 assert!(!compute_phase.accepted);
1764 assert_eq!(state.state, prev_state);
1766 assert_eq!(state.end_status, AccountStatus::Uninit);
1768 assert_eq!(compute_phase.actions, Cell::empty_cell());
1770 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 ¶ms,
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 assert_eq!(
1811 compute_phase.original_balance,
1812 CurrencyCollection::from(OK_BALANCE)
1813 );
1814 assert!(compute_phase.accepted);
1816 assert_eq!(state.state, prev_state);
1818 assert_eq!(prev_end_status, state.end_status);
1820 assert_eq!(compute_phase.actions, Cell::empty_cell());
1822 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(¶ms, &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 assert_eq!(
1888 compute_phase.original_balance,
1889 CurrencyCollection::from(OK_BALANCE)
1890 );
1891 assert!(compute_phase.accepted);
1893 assert_eq!(state.state, prev_state);
1895 assert_eq!(prev_end_status, state.end_status);
1897 assert_eq!(compute_phase.actions, Cell::empty_cell());
1899 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(¶ms, &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 assert_eq!(
2030 compute_phase.original_balance,
2031 CurrencyCollection::from(OK_BALANCE)
2032 );
2033 assert!(compute_phase.accepted);
2035 assert_eq!(state.state, prev_state);
2037 assert_eq!(prev_end_status, state.end_status);
2039 assert_eq!(compute_phase.actions, Cell::empty_cell());
2041 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}