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