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