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