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 let (msg_balance_remaining, is_external) = match ctx.input.in_msg() {
128 Some(msg) => (msg.balance_remaining.clone(), msg.is_external),
129 None => (CurrencyCollection::ZERO, false),
130 };
131
132 let gas = if unlikely(ctx.force_accept) {
133 tycho_vm::GasParams::getter()
134 } else {
135 self.config.compute_gas_params(
136 &self.balance.tokens,
137 &msg_balance_remaining.tokens,
138 self.is_special,
139 is_masterchain,
140 ctx.input.is_ordinary(),
141 is_external,
142 )
143 };
144 if gas.limit == 0 && gas.credit == 0 {
145 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
146 reason: ComputePhaseSkipReason::NoGas,
147 });
148 return Ok(res);
149 }
150
151 let state_libs;
153 let msg_libs;
154 let msg_state_used;
155 match (ctx.input.in_msg_init(), &self.state) {
156 (None, AccountState::Uninit) => {
158 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
159 reason: ComputePhaseSkipReason::NoState,
160 });
161 return Ok(res);
162 }
163 (None, AccountState::Frozen { .. }) => {
165 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
166 reason: ComputePhaseSkipReason::BadState,
167 });
168 return Ok(res);
169 }
170 (None, AccountState::Active(StateInit { libraries, .. })) => {
172 state_libs = Some(libraries);
173 msg_libs = None;
174 msg_state_used = false;
175 }
176 (Some(from_msg), AccountState::Uninit | AccountState::Frozen(..)) => {
178 let target_hash = if let AccountState::Frozen(old_hash) = &self.state {
179 old_hash
180 } else {
181 &self.address.address
182 };
183
184 if from_msg.root_hash() != target_hash || from_msg.parsed.split_depth.is_some() {
185 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
188 reason: ComputePhaseSkipReason::BadState,
189 });
190 return Ok(res);
191 }
192
193 let mut limits = self.config.size_limits.clone();
195 if is_masterchain && matches!(&self.state, AccountState::Uninit) {
196 limits.max_acc_public_libraries = 0;
198 }
199
200 if matches!(
201 check_state_limits_diff(
202 &res.new_state,
203 &from_msg.parsed,
204 &limits,
205 is_masterchain,
206 &mut self.cached_storage_stat,
207 ),
208 StateLimitsResult::Exceeds
209 ) {
210 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
211 reason: ComputePhaseSkipReason::BadState,
212 });
213 return Ok(res);
214 }
215
216 res.new_state = from_msg.parsed.clone();
222 self.state = AccountState::Active(res.new_state.clone());
223 msg_state_used = true;
224
225 state_libs = None;
227 msg_libs = Some(from_msg.parsed.libraries.clone());
228 }
229 (Some(from_msg), AccountState::Active(StateInit { libraries, .. })) => {
230 if is_external && from_msg.root_hash() != &self.address.address {
232 res.compute_phase = ComputePhase::Skipped(SkippedComputePhase {
233 reason: ComputePhaseSkipReason::BadState,
234 });
235 return Ok(res);
236 }
237
238 msg_state_used = false;
240
241 state_libs = Some(libraries);
243 msg_libs = Some(from_msg.parsed.libraries.clone());
244 }
245 }
246
247 let unpacked_in_msg = match ctx.input.in_msg() {
249 Some(msg) => msg.make_tuple()?,
250 None => None,
251 };
252
253 let stack = self.prepare_vm_stack(ctx.input);
255
256 let code = res.new_state.code.clone();
257
258 let smc_info = SmcInfoBase::new()
259 .with_now(self.params.block_unixtime)
260 .with_block_lt(self.params.block_lt)
261 .with_tx_lt(self.start_lt)
262 .with_mixed_rand_seed(&self.params.rand_seed, &self.address.address)
263 .with_account_balance(self.balance.clone())
264 .with_account_addr(self.address.clone().into())
265 .with_config(self.config.raw.params.clone())
266 .require_ton_v4()
267 .with_code(code.clone().unwrap_or_default())
268 .with_message_balance(msg_balance_remaining.clone())
269 .with_storage_fees(ctx.storage_fee)
270 .require_ton_v6()
271 .with_unpacked_config(self.config.unpacked.as_tuple())
272 .require_ton_v11()
273 .with_unpacked_in_msg(unpacked_in_msg);
274
275 let libraries = (msg_libs, state_libs, &self.params.libraries);
276 let mut vm = VmState::builder()
277 .with_smc_info(smc_info)
278 .with_code(code)
279 .with_data(res.new_state.data.clone().unwrap_or_default())
280 .with_libraries(&libraries)
281 .with_init_selector(false)
282 .with_raw_stack(stack)
283 .with_gas(gas)
284 .with_modifiers(self.params.vm_modifiers)
285 .build();
286
287 let mut inspector_actions = None;
289 let mut inspector_exit_code = None;
290 let mut inspector_total_gas_used = None;
291 if let Some(inspector) = ctx.inspector {
292 inspector_actions = Some(&mut inspector.actions);
293 inspector_exit_code = Some(&mut inspector.exit_code);
294 inspector_total_gas_used = Some(&mut inspector.total_gas_used);
295 if let Some(debug) = inspector.debug.as_deref_mut() {
296 vm.debug = Some(debug);
297 }
298 }
299
300 let exit_code = !vm.run();
302
303 if let Some(inspector_exit_code) = inspector_exit_code {
304 *inspector_exit_code = Some(exit_code);
305 }
306
307 let consumed_paid_gas = vm.gas.consumed();
308 if let Some(total_gas_used) = inspector_total_gas_used {
309 *total_gas_used = consumed_paid_gas.saturating_add(vm.gas.free_gas_consumed());
310 }
311
312 res.accepted = ctx.force_accept || vm.gas.credit() == 0;
314 debug_assert!(
315 is_external || res.accepted,
316 "internal messages must be accepted"
317 );
318
319 let success = res.accepted && vm.committed_state.is_some();
320
321 let gas_used = std::cmp::min(consumed_paid_gas, vm.gas.limit());
322 let gas_fees = if res.accepted && !self.is_special {
323 self.config
324 .gas_prices(is_masterchain)
325 .compute_gas_fee(gas_used)
326 } else {
327 Tokens::ZERO
329 };
330
331 let mut account_activated = false;
332 if res.accepted && msg_state_used {
333 account_activated = self.orig_status != AccountStatus::Active;
334 self.end_status = AccountStatus::Active;
335 }
336
337 if let Some(committed) = vm.committed_state {
338 if res.accepted {
339 res.new_state.data = Some(committed.c4);
340 res.actions = committed.c5;
341
342 if let Some(actions) = inspector_actions {
344 *actions = Some(res.actions.clone());
345 }
346 }
347 }
348
349 self.balance.try_sub_assign_tokens(gas_fees)?;
350 self.total_fees.try_add_assign(gas_fees)?;
351
352 res.compute_phase = ComputePhase::Executed(ExecutedComputePhase {
353 success,
354 msg_state_used,
355 account_activated,
356 gas_fees,
357 gas_used: new_varuint56_truncate(gas_used),
358 gas_limit: new_varuint56_truncate(gas.limit),
360 gas_credit: (gas.credit != 0).then(|| new_varuint24_truncate(gas.credit)),
362 mode: 0,
363 exit_code,
364 exit_arg: if success {
365 None
366 } else {
367 vm.stack.get_exit_arg().filter(|x| *x != 0)
368 },
369 vm_steps: vm.steps.try_into().unwrap_or(u32::MAX),
370 vm_init_state_hash: HashBytes::ZERO,
371 vm_final_state_hash: HashBytes::ZERO,
372 });
373 Ok(res)
374 }
375
376 fn prepare_vm_stack(&self, input: TransactionInput<'_>) -> SafeRc<Stack> {
377 SafeRc::new(Stack::with_items(match input {
378 TransactionInput::Ordinary(msg) => {
379 tuple![
380 int self.balance.tokens,
381 int msg.balance_remaining.tokens,
382 cell msg.root.clone(),
383 slice msg.body.clone(),
384 int if msg.is_external { -1 } else { 0 },
385 ]
386 }
387 TransactionInput::TickTock(ty) => {
388 tuple![
389 int self.balance.tokens,
390 int BigInt::from_bytes_be(Sign::Plus, self.address.address.as_array()),
391 int match ty {
392 TickTock::Tick => 0,
393 TickTock::Tock => -1,
394 },
395 int -2,
396 ]
397 }
398 }))
399 }
400}
401
402impl ReceivedMessage {
403 fn make_tuple(&self) -> Result<Option<SafeRc<Tuple>>, tycho_types::error::Error> {
404 let mut cs = self.root.as_slice()?;
405 if MsgType::load_from(&mut cs)? != MsgType::Int {
406 return Ok(None);
407 }
408
409 let src_addr_slice = {
411 let mut cs = cs;
412 cs.skip_first(3, 0)?;
414 let mut addr_slice = cs;
415 IntAddr::load_from(&mut cs)?;
417 addr_slice.skip_last(cs.size_bits(), cs.size_refs())?;
418 addr_slice.range()
419 };
420
421 let info = IntMsgInfo::load_from(&mut cs)?;
422
423 let unpacked = UnpackedInMsgSmcInfo {
424 bounce: info.bounce,
425 bounced: info.bounced,
426 src_addr: (src_addr_slice, self.root.clone()).into(),
427 fwd_fee: info.fwd_fee,
428 created_lt: info.created_lt,
429 created_at: info.created_at,
430 original_value: info.value.tokens,
431 remaining_value: self.balance_remaining.clone(),
432 state_init: self.init.as_ref().map(|init| init.root.clone()),
433 };
434 Ok(Some(unpacked.into_tuple()))
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use tycho_asm_macros::tvmasm;
441 use tycho_types::models::{ExtInMsgInfo, IntMsgInfo, LibDescr, SimpleLib, StdAddr};
442 use tycho_types::num::{VarUint24, VarUint56};
443
444 use super::*;
445 use crate::tests::{make_default_config, make_default_params, make_message};
446
447 const STUB_ADDR: StdAddr = StdAddr::new(0, HashBytes::ZERO);
448 const OK_BALANCE: Tokens = Tokens::new(1_000_000_000);
449
450 fn empty_ext_in_msg(addr: &StdAddr) -> Cell {
451 make_message(
452 ExtInMsgInfo {
453 dst: addr.clone().into(),
454 ..Default::default()
455 },
456 None,
457 None,
458 )
459 }
460
461 fn empty_int_msg(addr: &StdAddr, value: impl Into<CurrencyCollection>) -> Cell {
462 make_message(
463 IntMsgInfo {
464 src: addr.clone().into(),
465 dst: addr.clone().into(),
466 value: value.into(),
467 ..Default::default()
468 },
469 None,
470 None,
471 )
472 }
473
474 fn simple_state(code: &[u8]) -> StateInit {
475 StateInit {
476 split_depth: None,
477 special: None,
478 code: Some(Boc::decode(code).unwrap()),
479 data: None,
480 libraries: Dict::new(),
481 }
482 }
483
484 fn init_tracing() {
485 tracing_subscriber::fmt::fmt()
486 .with_env_filter("tycho_vm=trace")
487 .with_writer(tracing_subscriber::fmt::TestWriter::new)
488 .try_init()
489 .ok();
490 }
491
492 fn make_lib_ref(code: &DynCell) -> Cell {
493 let mut b = CellBuilder::new();
494 b.set_exotic(true);
495 b.store_u8(CellType::LibraryReference.to_byte()).unwrap();
496 b.store_u256(code.repr_hash()).unwrap();
497 b.build().unwrap()
498 }
499
500 #[test]
501 fn ext_in_run_no_accept() -> Result<()> {
502 let params = make_default_params();
503 let config = make_default_config();
504 let mut state = ExecutorState::new_active(
505 ¶ms,
506 &config,
507 &STUB_ADDR,
508 OK_BALANCE,
509 Cell::empty_cell(),
510 tvmasm!("INT 123 NOP"),
511 );
512
513 let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
514
515 let prev_balance = state.balance.clone();
516 let prev_state = state.state.clone();
517 let prev_total_fees = state.total_fees;
518 let prev_end_status = state.end_status;
519
520 let compute_phase = state.compute_phase(ComputePhaseContext {
521 input: TransactionInput::Ordinary(&msg),
522 storage_fee: Tokens::ZERO,
523 force_accept: false,
524 inspector: None,
525 })?;
526
527 assert_eq!(prev_balance, compute_phase.original_balance);
529 assert!(!compute_phase.accepted);
531 assert_eq!(state.state, prev_state);
533 assert_eq!(prev_end_status, state.end_status);
535 assert_eq!(compute_phase.actions, Cell::empty_cell());
537 assert_eq!(state.total_fees, prev_total_fees);
539 assert_eq!(state.balance, prev_balance);
540
541 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
542 panic!("expected executed compute phase");
543 };
544
545 assert!(!compute_phase.success);
546 assert!(!compute_phase.msg_state_used);
547 assert!(!compute_phase.account_activated);
548 assert_eq!(compute_phase.gas_fees, Tokens::ZERO);
549 assert_eq!(compute_phase.gas_used, VarUint56::new(0)); assert_eq!(compute_phase.gas_limit, VarUint56::new(0)); assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
552 assert_eq!(compute_phase.exit_code, 0);
553 assert_eq!(compute_phase.exit_arg, Some(123)); assert_eq!(compute_phase.vm_steps, 3); Ok(())
557 }
558
559 #[test]
560 fn ext_in_run_uninit_no_accept() -> Result<()> {
561 let params = make_default_params();
562 let config = make_default_config();
563 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
564
565 let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
566
567 let prev_balance = state.balance.clone();
568 let prev_state = state.state.clone();
569 let prev_total_fees = state.total_fees;
570 let prev_end_status = state.end_status;
571
572 let compute_phase = state.compute_phase(ComputePhaseContext {
573 input: TransactionInput::Ordinary(&msg),
574 storage_fee: Tokens::ZERO,
575 force_accept: false,
576 inspector: None,
577 })?;
578
579 assert_eq!(prev_balance, compute_phase.original_balance);
581 assert!(!compute_phase.accepted);
583 assert_eq!(state.state, prev_state);
585 assert_eq!(prev_end_status, state.end_status);
587 assert_eq!(compute_phase.actions, Cell::empty_cell());
589 assert_eq!(state.total_fees, prev_total_fees);
591 assert_eq!(state.balance, prev_balance);
592
593 let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
594 panic!("expected skipped compute phase");
595 };
596 assert_eq!(compute_phase.reason, ComputePhaseSkipReason::NoState);
597
598 Ok(())
599 }
600
601 #[test]
602 fn ext_in_run_no_code_no_accept() -> Result<()> {
603 let params = make_default_params();
604 let config = make_default_config();
605 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
606 state.state = AccountState::Active(StateInit::default());
607 state.orig_status = AccountStatus::Active;
608 state.end_status = AccountStatus::Active;
609
610 let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
611
612 let prev_balance = state.balance.clone();
613 let prev_state = state.state.clone();
614 let prev_total_fees = state.total_fees;
615 let prev_end_status = state.end_status;
616
617 let compute_phase = state.compute_phase(ComputePhaseContext {
618 input: TransactionInput::Ordinary(&msg),
619 storage_fee: Tokens::ZERO,
620 force_accept: false,
621 inspector: None,
622 })?;
623
624 assert_eq!(prev_balance, compute_phase.original_balance);
626 assert!(!compute_phase.accepted);
628 assert_eq!(state.state, prev_state);
630 assert_eq!(prev_end_status, state.end_status);
632 assert_eq!(compute_phase.actions, Cell::empty_cell());
634 assert_eq!(state.total_fees, prev_total_fees);
636 assert_eq!(state.balance, prev_balance);
637
638 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
639 panic!("expected executed compute phase");
640 };
641
642 assert!(!compute_phase.success);
643 assert!(!compute_phase.msg_state_used);
644 assert!(!compute_phase.account_activated);
645 assert_eq!(compute_phase.gas_fees, Tokens::ZERO);
646 assert_eq!(compute_phase.gas_used, VarUint56::new(0)); assert_eq!(compute_phase.gas_limit, VarUint56::new(0)); assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
649 assert_eq!(
650 compute_phase.exit_code,
651 tycho_vm::VmException::Fatal.as_exit_code()
652 );
653 assert_eq!(compute_phase.exit_arg, Some(-1)); assert_eq!(compute_phase.vm_steps, 0);
655
656 Ok(())
657 }
658
659 #[test]
660 fn ext_in_accept_no_commit() -> Result<()> {
661 let params = make_default_params();
662 let config = make_default_config();
663 let mut state = ExecutorState::new_active(
664 ¶ms,
665 &config,
666 &STUB_ADDR,
667 OK_BALANCE,
668 Cell::empty_cell(),
669 tvmasm!("ACCEPT THROW 42"),
670 );
671
672 let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
673
674 let prev_balance = state.balance.clone();
675 let prev_state = state.state.clone();
676 let prev_total_fees = state.total_fees;
677 let prev_end_status = state.end_status;
678
679 let compute_phase = state.compute_phase(ComputePhaseContext {
680 input: TransactionInput::Ordinary(&msg),
681 storage_fee: Tokens::ZERO,
682 force_accept: false,
683 inspector: None,
684 })?;
685
686 assert_eq!(prev_balance, compute_phase.original_balance);
688 assert!(compute_phase.accepted);
690 assert_eq!(state.state, prev_state);
692 assert_eq!(prev_end_status, state.end_status);
694 assert_eq!(compute_phase.actions, Cell::empty_cell());
696 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
698 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
699 assert_eq!(state.balance.other, prev_balance.other);
700 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
701
702 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
703 panic!("expected executed compute phase");
704 };
705
706 assert!(!compute_phase.success);
707 assert!(!compute_phase.msg_state_used);
708 assert!(!compute_phase.account_activated);
709 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
710 assert_eq!(compute_phase.gas_used, (10 + 16) * 2 + 50); assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
712 assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
713 assert_eq!(compute_phase.exit_code, 42);
714 assert_eq!(compute_phase.exit_arg, None);
715 assert_eq!(compute_phase.vm_steps, 2); Ok(())
718 }
719
720 #[test]
721 fn ext_in_accept_invalid_commit() -> Result<()> {
722 init_tracing();
723 let params = make_default_params();
724 let config = make_default_config();
725
726 let mut state = ExecutorState::new_active(
727 ¶ms,
728 &config,
729 &STUB_ADDR,
730 OK_BALANCE,
731 Cell::empty_cell(),
732 tvmasm!(
733 r#"
734 ACCEPT
735 NEWC
736 INT 1 STUR 8
737 INT 7 STUR 8
738 INT 816 STZEROES
739 TRUE ENDXC
740 POP c5
741 "#
742 ),
743 );
744
745 let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
746
747 let prev_balance = state.balance.clone();
748 let prev_state = state.state.clone();
749 let prev_total_fees = state.total_fees;
750 let prev_end_status = state.end_status;
751
752 let compute_phase = state.compute_phase(ComputePhaseContext {
753 input: TransactionInput::Ordinary(&msg),
754 storage_fee: Tokens::ZERO,
755 force_accept: false,
756 inspector: None,
757 })?;
758
759 assert_eq!(prev_balance, compute_phase.original_balance);
761 assert!(compute_phase.accepted);
763 assert_eq!(state.state, prev_state);
765 assert_eq!(prev_end_status, state.end_status);
767 assert_eq!(compute_phase.actions, Cell::empty_cell());
769 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
771 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
772 assert_eq!(state.balance.other, prev_balance.other);
773 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
774
775 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
776 panic!("expected executed compute phase");
777 };
778
779 assert!(!compute_phase.success);
780 assert!(!compute_phase.msg_state_used);
781 assert!(!compute_phase.account_activated);
782 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
783 assert_eq!(compute_phase.gas_used, 783);
784 assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
785 assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
786 assert_eq!(
787 compute_phase.exit_code,
788 tycho_vm::VmException::CellOverflow as i32
789 );
790 assert_eq!(compute_phase.exit_arg, None);
791 assert_eq!(compute_phase.vm_steps, 12); Ok(())
794 }
795
796 #[test]
797 fn ext_in_accept_simple() -> Result<()> {
798 let params = make_default_params();
799 let config = make_default_config();
800 let mut state = ExecutorState::new_active(
801 ¶ms,
802 &config,
803 &STUB_ADDR,
804 OK_BALANCE,
805 Cell::empty_cell(),
806 tvmasm!("ACCEPT NEWC INT 0xdeafbeaf STUR 32 ENDC POP c5"),
807 );
808
809 let msg = state.receive_in_msg(empty_ext_in_msg(&state.address))?;
810
811 let prev_balance = state.balance.clone();
812 let prev_state = state.state.clone();
813 let prev_total_fees = state.total_fees;
814 let prev_end_status = state.end_status;
815
816 let compute_phase = state.compute_phase(ComputePhaseContext {
817 input: TransactionInput::Ordinary(&msg),
818 storage_fee: Tokens::ZERO,
819 force_accept: false,
820 inspector: None,
821 })?;
822
823 assert_eq!(prev_balance, compute_phase.original_balance);
825 assert!(compute_phase.accepted);
827 assert_eq!(state.state, prev_state);
829 assert_eq!(prev_end_status, state.end_status);
831 assert_eq!(
833 compute_phase.actions,
834 CellBuilder::build_from(0xdeafbeafu32)?
835 );
836 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
838 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
839 assert_eq!(state.balance.other, prev_balance.other);
840 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
841
842 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
843 panic!("expected executed compute phase");
844 };
845
846 assert!(compute_phase.success);
847 assert!(!compute_phase.msg_state_used);
848 assert!(!compute_phase.account_activated);
849 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
850 assert_eq!(compute_phase.gas_used, 650);
851 assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
852 assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
853 assert_eq!(compute_phase.exit_code, 0);
854 assert_eq!(compute_phase.exit_arg, None);
855 assert_eq!(compute_phase.vm_steps, 7);
856
857 Ok(())
858 }
859
860 #[test]
861 fn internal_accept_simple() -> Result<()> {
862 let params = make_default_params();
863 let config = make_default_config();
864 let mut state = ExecutorState::new_active(
865 ¶ms,
866 &config,
867 &STUB_ADDR,
868 OK_BALANCE,
869 Cell::empty_cell(),
870 tvmasm!("ACCEPT"),
871 );
872
873 let msg =
874 state.receive_in_msg(empty_int_msg(&state.address, Tokens::new(1_000_000_000)))?;
875
876 state.credit_phase(&msg)?;
877
878 let prev_balance = state.balance.clone();
879 let prev_state = state.state.clone();
880 let prev_total_fees = state.total_fees;
881 let prev_end_status = state.end_status;
882
883 let compute_phase = state.compute_phase(ComputePhaseContext {
884 input: TransactionInput::Ordinary(&msg),
885 storage_fee: Tokens::ZERO,
886 force_accept: false,
887 inspector: None,
888 })?;
889
890 assert_eq!(
892 compute_phase.original_balance,
893 CurrencyCollection::from(OK_BALANCE)
894 );
895 assert!(compute_phase.accepted);
897 assert_eq!(state.state, prev_state);
899 assert_eq!(prev_end_status, state.end_status);
901 assert_eq!(compute_phase.actions, Cell::empty_cell());
903 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
905 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
906 assert_eq!(state.balance.other, prev_balance.other);
907 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
908
909 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
910 panic!("expected executed compute phase");
911 };
912
913 assert!(compute_phase.success);
914 assert!(!compute_phase.msg_state_used);
915 assert!(!compute_phase.account_activated);
916 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
917 assert_eq!(compute_phase.gas_used, (10 + 16) + 5); assert_eq!(
919 compute_phase.gas_limit,
920 VarUint56::new(config.gas_prices.gas_limit)
921 );
922 assert_eq!(compute_phase.gas_credit, None);
923 assert_eq!(compute_phase.exit_code, 0);
924 assert_eq!(compute_phase.exit_arg, None);
925 assert_eq!(compute_phase.vm_steps, 2); Ok(())
928 }
929
930 #[test]
931 fn internal_no_accept() -> Result<()> {
932 let params = make_default_params();
933 let config = make_default_config();
934 let mut state = ExecutorState::new_active(
935 ¶ms,
936 &config,
937 &STUB_ADDR,
938 OK_BALANCE,
939 Cell::empty_cell(),
940 tvmasm!("NOP"),
941 );
942
943 let msg = state.receive_in_msg(empty_int_msg(&state.address, Tokens::new(1)))?;
944
945 state.credit_phase(&msg)?;
946
947 let prev_balance = state.balance.clone();
948 let prev_state = state.state.clone();
949 let prev_total_fees = state.total_fees;
950 let prev_end_status = state.end_status;
951
952 let compute_phase = state.compute_phase(ComputePhaseContext {
953 input: TransactionInput::Ordinary(&msg),
954 storage_fee: Tokens::ZERO,
955 force_accept: false,
956 inspector: None,
957 })?;
958
959 assert_eq!(
961 compute_phase.original_balance,
962 CurrencyCollection::from(OK_BALANCE)
963 );
964 assert!(!compute_phase.accepted);
966 assert_eq!(state.state, prev_state);
968 assert_eq!(prev_end_status, state.end_status);
970 assert_eq!(compute_phase.actions, Cell::empty_cell());
972 assert_eq!(state.total_fees, prev_total_fees);
974 assert_eq!(state.balance, prev_balance);
975
976 let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
977 panic!("expected skipped compute phase");
978 };
979 assert_eq!(compute_phase.reason, ComputePhaseSkipReason::NoGas);
980
981 Ok(())
982 }
983
984 #[test]
985 fn internal_no_accept_empty_balance() -> Result<()> {
986 let params = make_default_params();
987 let config = make_default_config();
988 let mut state = ExecutorState::new_active(
989 ¶ms,
990 &config,
991 &STUB_ADDR,
992 Tokens::ZERO,
993 Cell::empty_cell(),
994 tvmasm!("NOP"),
995 );
996
997 let msg = state.receive_in_msg(empty_int_msg(&state.address, Tokens::ZERO))?;
998
999 state.credit_phase(&msg)?;
1000
1001 let prev_balance = state.balance.clone();
1002 let prev_state = state.state.clone();
1003 let prev_total_fees = state.total_fees;
1004 let prev_end_status = state.end_status;
1005
1006 let compute_phase = state.compute_phase(ComputePhaseContext {
1007 input: TransactionInput::Ordinary(&msg),
1008 storage_fee: Tokens::ZERO,
1009 force_accept: false,
1010 inspector: None,
1011 })?;
1012
1013 assert_eq!(compute_phase.original_balance, CurrencyCollection::ZERO);
1015 assert!(!compute_phase.accepted);
1017 assert_eq!(state.state, prev_state);
1019 assert_eq!(prev_end_status, state.end_status);
1021 assert_eq!(compute_phase.actions, Cell::empty_cell());
1023 assert_eq!(state.total_fees, prev_total_fees);
1025 assert_eq!(state.balance, prev_balance);
1026
1027 let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1028 panic!("expected skipped compute phase");
1029 };
1030 assert_eq!(compute_phase.reason, ComputePhaseSkipReason::NoGas);
1031
1032 Ok(())
1033 }
1034
1035 #[test]
1036 fn ext_in_deploy_account() -> Result<()> {
1037 let state_init = simple_state(tvmasm!("ACCEPT"));
1038 let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
1039 let addr = StdAddr::new(0, state_init_hash);
1040
1041 let params = make_default_params();
1042 let config = make_default_config();
1043 let mut state = ExecutorState::new_uninit(¶ms, &config, &addr, OK_BALANCE);
1044
1045 let msg = state.receive_in_msg(make_message(
1046 ExtInMsgInfo {
1047 dst: addr.clone().into(),
1048 ..Default::default()
1049 },
1050 Some(state_init.clone()),
1051 None,
1052 ))?;
1053
1054 let prev_balance = state.balance.clone();
1055 let prev_total_fees = state.total_fees;
1056
1057 let compute_phase = state.compute_phase(ComputePhaseContext {
1058 input: TransactionInput::Ordinary(&msg),
1059 storage_fee: Tokens::ZERO,
1060 force_accept: false,
1061 inspector: None,
1062 })?;
1063
1064 assert_eq!(
1066 compute_phase.original_balance,
1067 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1068 );
1069 assert!(compute_phase.accepted);
1071 assert_eq!(state.state, AccountState::Active(state_init));
1073 assert_eq!(state.end_status, AccountStatus::Active);
1075 assert_eq!(compute_phase.actions, Cell::empty_cell());
1077 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1079 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1080 assert_eq!(state.balance.other, prev_balance.other);
1081 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1082
1083 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1084 panic!("expected executed compute phase");
1085 };
1086
1087 assert!(compute_phase.success);
1088 assert!(compute_phase.msg_state_used);
1089 assert!(compute_phase.account_activated);
1090 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1091 assert_eq!(compute_phase.gas_used, (10 + 16) + 5); assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
1093 assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
1094 assert_eq!(compute_phase.exit_code, 0);
1095 assert_eq!(compute_phase.exit_arg, None);
1096 assert_eq!(compute_phase.vm_steps, 2); Ok(())
1099 }
1100
1101 #[test]
1102 fn internal_deploy_account() -> Result<()> {
1103 let state_init = simple_state(tvmasm!("ACCEPT"));
1104 let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
1105 let addr = StdAddr::new(0, state_init_hash);
1106
1107 let params = make_default_params();
1108 let config = make_default_config();
1109 let mut state = ExecutorState::new_uninit(¶ms, &config, &addr, OK_BALANCE);
1110
1111 let msg = state.receive_in_msg(make_message(
1112 IntMsgInfo {
1113 src: addr.clone().into(),
1114 dst: addr.clone().into(),
1115 value: Tokens::new(1_000_000_000).into(),
1116 ..Default::default()
1117 },
1118 Some(state_init.clone()),
1119 None,
1120 ))?;
1121
1122 state.credit_phase(&msg)?;
1123
1124 let prev_balance = state.balance.clone();
1125 let prev_total_fees = state.total_fees;
1126
1127 let compute_phase = state.compute_phase(ComputePhaseContext {
1128 input: TransactionInput::Ordinary(&msg),
1129 storage_fee: Tokens::ZERO,
1130 force_accept: false,
1131 inspector: None,
1132 })?;
1133
1134 assert_eq!(
1136 compute_phase.original_balance,
1137 CurrencyCollection::from(OK_BALANCE)
1138 );
1139 assert!(compute_phase.accepted);
1141 assert_eq!(state.state, AccountState::Active(state_init));
1143 assert_eq!(state.end_status, AccountStatus::Active);
1145 assert_eq!(compute_phase.actions, Cell::empty_cell());
1147 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1149 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1150 assert_eq!(state.balance.other, prev_balance.other);
1151 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1152
1153 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1154 panic!("expected executed compute phase");
1155 };
1156
1157 assert!(compute_phase.success);
1158 assert!(compute_phase.msg_state_used);
1159 assert!(compute_phase.account_activated);
1160 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1161 assert_eq!(compute_phase.gas_used, (10 + 16) + 5); assert_eq!(
1163 compute_phase.gas_limit,
1164 VarUint56::new(config.gas_prices.gas_limit)
1165 );
1166 assert_eq!(compute_phase.gas_credit, None);
1167 assert_eq!(compute_phase.exit_code, 0);
1168 assert_eq!(compute_phase.exit_arg, None);
1169 assert_eq!(compute_phase.vm_steps, 2); Ok(())
1172 }
1173
1174 #[test]
1175 fn ext_in_unfreeze_account() -> Result<()> {
1176 let state_init = simple_state(tvmasm!("ACCEPT"));
1177 let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
1178
1179 let params = make_default_params();
1180 let config = make_default_config();
1181 let mut state =
1182 ExecutorState::new_frozen(¶ms, &config, &STUB_ADDR, OK_BALANCE, state_init_hash);
1183
1184 let msg = state.receive_in_msg(make_message(
1185 ExtInMsgInfo {
1186 dst: STUB_ADDR.into(),
1187 ..Default::default()
1188 },
1189 Some(state_init.clone()),
1190 None,
1191 ))?;
1192
1193 let prev_balance = state.balance.clone();
1194 let prev_total_fees = state.total_fees;
1195
1196 let compute_phase = state.compute_phase(ComputePhaseContext {
1197 input: TransactionInput::Ordinary(&msg),
1198 storage_fee: Tokens::ZERO,
1199 force_accept: false,
1200 inspector: None,
1201 })?;
1202
1203 assert_eq!(
1205 compute_phase.original_balance,
1206 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1207 );
1208 assert!(compute_phase.accepted);
1210 assert_eq!(state.state, AccountState::Active(state_init));
1212 assert_eq!(state.end_status, AccountStatus::Active);
1214 assert_eq!(compute_phase.actions, Cell::empty_cell());
1216 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1218 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1219 assert_eq!(state.balance.other, prev_balance.other);
1220 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1221
1222 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1223 panic!("expected executed compute phase");
1224 };
1225
1226 assert!(compute_phase.success);
1227 assert!(compute_phase.msg_state_used);
1228 assert!(compute_phase.account_activated);
1229 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1230 assert_eq!(compute_phase.gas_used, (10 + 16) + 5); assert_eq!(compute_phase.gas_limit, VarUint56::ZERO);
1232 assert_eq!(compute_phase.gas_credit, Some(VarUint24::new(10_000)));
1233 assert_eq!(compute_phase.exit_code, 0);
1234 assert_eq!(compute_phase.exit_arg, None);
1235 assert_eq!(compute_phase.vm_steps, 2); Ok(())
1238 }
1239
1240 #[test]
1241 fn internal_unfreeze_account() -> Result<()> {
1242 let state_init = simple_state(tvmasm!("ACCEPT"));
1243 let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
1244
1245 let params = make_default_params();
1246 let config = make_default_config();
1247 let mut state =
1248 ExecutorState::new_frozen(¶ms, &config, &STUB_ADDR, Tokens::ZERO, state_init_hash);
1249
1250 let msg = state.receive_in_msg(make_message(
1251 IntMsgInfo {
1252 src: STUB_ADDR.into(),
1253 dst: STUB_ADDR.into(),
1254 value: Tokens::new(1_000_000_000).into(),
1255 ..Default::default()
1256 },
1257 Some(state_init.clone()),
1258 None,
1259 ))?;
1260
1261 state.credit_phase(&msg)?;
1262
1263 let prev_balance = state.balance.clone();
1264 let prev_total_fees = state.total_fees;
1265
1266 let compute_phase = state.compute_phase(ComputePhaseContext {
1267 input: TransactionInput::Ordinary(&msg),
1268 storage_fee: Tokens::ZERO,
1269 force_accept: false,
1270 inspector: None,
1271 })?;
1272
1273 assert_eq!(compute_phase.original_balance, CurrencyCollection::ZERO);
1275 assert!(compute_phase.accepted);
1277 assert_eq!(state.state, AccountState::Active(state_init));
1279 assert_eq!(state.end_status, AccountStatus::Active);
1281 assert_eq!(compute_phase.actions, Cell::empty_cell());
1283 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1285 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1286 assert_eq!(state.balance.other, prev_balance.other);
1287 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1288
1289 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1290 panic!("expected executed compute phase");
1291 };
1292
1293 assert!(compute_phase.success);
1294 assert!(compute_phase.msg_state_used);
1295 assert!(compute_phase.account_activated);
1296 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1297 assert_eq!(compute_phase.gas_used, (10 + 16) + 5); assert_eq!(
1299 compute_phase.gas_limit,
1300 VarUint56::new(config.gas_prices.gas_limit)
1301 );
1302 assert_eq!(compute_phase.gas_credit, None);
1303 assert_eq!(compute_phase.exit_code, 0);
1304 assert_eq!(compute_phase.exit_arg, None);
1305 assert_eq!(compute_phase.vm_steps, 2); Ok(())
1308 }
1309
1310 #[test]
1311 fn ext_in_unfreeze_hash_mismatch() -> Result<()> {
1312 let state_init = simple_state(tvmasm!("ACCEPT"));
1313
1314 let params = make_default_params();
1315 let config = make_default_config();
1316 let mut state =
1317 ExecutorState::new_frozen(¶ms, &config, &STUB_ADDR, OK_BALANCE, HashBytes::ZERO);
1318
1319 let msg = state.receive_in_msg(make_message(
1320 ExtInMsgInfo {
1321 dst: STUB_ADDR.into(),
1322 ..Default::default()
1323 },
1324 Some(state_init.clone()),
1325 None,
1326 ))?;
1327
1328 let prev_state = state.state.clone();
1329 let prev_balance = state.balance.clone();
1330 let prev_total_fees = state.total_fees;
1331
1332 let compute_phase = state.compute_phase(ComputePhaseContext {
1333 input: TransactionInput::Ordinary(&msg),
1334 storage_fee: Tokens::ZERO,
1335 force_accept: false,
1336 inspector: None,
1337 })?;
1338
1339 assert_eq!(
1341 compute_phase.original_balance,
1342 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1343 );
1344 assert!(!compute_phase.accepted);
1346 assert_eq!(state.state, prev_state);
1348 assert_eq!(state.end_status, AccountStatus::Frozen);
1350 assert_eq!(compute_phase.actions, Cell::empty_cell());
1352 assert_eq!(state.total_fees, prev_total_fees);
1354 assert_eq!(state.balance, prev_balance);
1355
1356 let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1357 panic!("expected skipped compute phase");
1358 };
1359 assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
1360
1361 Ok(())
1362 }
1363
1364 #[test]
1365 fn ext_in_deploy_hash_mismatch() -> Result<()> {
1366 let state_init = simple_state(tvmasm!("ACCEPT"));
1367
1368 let params = make_default_params();
1369 let config = make_default_config();
1370 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1371
1372 let msg = state.receive_in_msg(make_message(
1373 ExtInMsgInfo {
1374 dst: STUB_ADDR.into(),
1375 ..Default::default()
1376 },
1377 Some(state_init.clone()),
1378 None,
1379 ))?;
1380
1381 let prev_state = state.state.clone();
1382 let prev_balance = state.balance.clone();
1383 let prev_total_fees = state.total_fees;
1384
1385 let compute_phase = state.compute_phase(ComputePhaseContext {
1386 input: TransactionInput::Ordinary(&msg),
1387 storage_fee: Tokens::ZERO,
1388 force_accept: false,
1389 inspector: None,
1390 })?;
1391
1392 assert_eq!(
1394 compute_phase.original_balance,
1395 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1396 );
1397 assert!(!compute_phase.accepted);
1399 assert_eq!(state.state, prev_state);
1401 assert_eq!(state.end_status, AccountStatus::Uninit);
1403 assert_eq!(compute_phase.actions, Cell::empty_cell());
1405 assert_eq!(state.total_fees, prev_total_fees);
1407 assert_eq!(state.balance, prev_balance);
1408
1409 let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1410 panic!("expected skipped compute phase");
1411 };
1412 assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
1413
1414 Ok(())
1415 }
1416
1417 #[test]
1418 fn internal_unfreeze_hash_mismatch() -> Result<()> {
1419 let state_init = simple_state(tvmasm!("ACCEPT"));
1420
1421 let params = make_default_params();
1422 let config = make_default_config();
1423 let mut state =
1424 ExecutorState::new_frozen(¶ms, &config, &STUB_ADDR, OK_BALANCE, HashBytes::ZERO);
1425
1426 let msg = state.receive_in_msg(make_message(
1427 IntMsgInfo {
1428 src: STUB_ADDR.into(),
1429 dst: STUB_ADDR.into(),
1430 value: Tokens::new(1_000_000_000).into(),
1431 ..Default::default()
1432 },
1433 Some(state_init.clone()),
1434 None,
1435 ))?;
1436
1437 state.credit_phase(&msg)?;
1438
1439 let prev_state = state.state.clone();
1440 let prev_balance = state.balance.clone();
1441 let prev_total_fees = state.total_fees;
1442
1443 let compute_phase = state.compute_phase(ComputePhaseContext {
1444 input: TransactionInput::Ordinary(&msg),
1445 storage_fee: Tokens::ZERO,
1446 force_accept: false,
1447 inspector: None,
1448 })?;
1449
1450 assert_eq!(
1452 compute_phase.original_balance,
1453 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1454 );
1455 assert!(!compute_phase.accepted);
1457 assert_eq!(state.state, prev_state);
1459 assert_eq!(state.end_status, AccountStatus::Frozen);
1461 assert_eq!(compute_phase.actions, Cell::empty_cell());
1463 assert_eq!(state.total_fees, prev_total_fees);
1465 assert_eq!(state.balance, prev_balance);
1466
1467 let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1468 panic!("expected skipped compute phase");
1469 };
1470 assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
1471
1472 Ok(())
1473 }
1474
1475 #[test]
1476 fn internal_deploy_hash_mismatch() -> Result<()> {
1477 let state_init = simple_state(tvmasm!("ACCEPT"));
1478
1479 let params = make_default_params();
1480 let config = make_default_config();
1481 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1482
1483 let msg = state.receive_in_msg(make_message(
1484 IntMsgInfo {
1485 src: STUB_ADDR.into(),
1486 dst: STUB_ADDR.into(),
1487 value: Tokens::new(1_000_000_000).into(),
1488 ..Default::default()
1489 },
1490 Some(state_init.clone()),
1491 None,
1492 ))?;
1493
1494 state.credit_phase(&msg)?;
1495
1496 let prev_state = state.state.clone();
1497 let prev_balance = state.balance.clone();
1498 let prev_total_fees = state.total_fees;
1499
1500 let compute_phase = state.compute_phase(ComputePhaseContext {
1501 input: TransactionInput::Ordinary(&msg),
1502 storage_fee: Tokens::ZERO,
1503 force_accept: false,
1504 inspector: None,
1505 })?;
1506
1507 assert_eq!(
1509 compute_phase.original_balance,
1510 CurrencyCollection::from(OK_BALANCE - prev_total_fees)
1511 );
1512 assert!(!compute_phase.accepted);
1514 assert_eq!(state.state, prev_state);
1516 assert_eq!(state.end_status, AccountStatus::Uninit);
1518 assert_eq!(compute_phase.actions, Cell::empty_cell());
1520 assert_eq!(state.total_fees, prev_total_fees);
1522 assert_eq!(state.balance, prev_balance);
1523
1524 let ComputePhase::Skipped(compute_phase) = compute_phase.compute_phase else {
1525 panic!("expected skipped compute phase");
1526 };
1527 assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
1528
1529 Ok(())
1530 }
1531
1532 #[test]
1533 fn tick_special() -> Result<()> {
1534 init_tracing();
1535 let params = make_default_params();
1536 let config = make_default_config();
1537 let mut state = ExecutorState::new_active(
1538 ¶ms,
1539 &config,
1540 &STUB_ADDR,
1541 OK_BALANCE,
1542 Cell::empty_cell(),
1543 tvmasm!("DROP THROWIF 42 ACCEPT"),
1544 );
1545
1546 let prev_balance = state.balance.clone();
1547 let prev_state = state.state.clone();
1548 let prev_total_fees = state.total_fees;
1549 let prev_end_status = state.end_status;
1550
1551 let compute_phase = state.compute_phase(ComputePhaseContext {
1552 input: TransactionInput::TickTock(TickTock::Tick),
1553 storage_fee: Tokens::ZERO,
1554 force_accept: false,
1555 inspector: None,
1556 })?;
1557
1558 assert_eq!(
1560 compute_phase.original_balance,
1561 CurrencyCollection::from(OK_BALANCE)
1562 );
1563 assert!(compute_phase.accepted);
1565 assert_eq!(state.state, prev_state);
1567 assert_eq!(prev_end_status, state.end_status);
1569 assert_eq!(compute_phase.actions, Cell::empty_cell());
1571 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1573 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1574 assert_eq!(state.balance.other, prev_balance.other);
1575 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1576
1577 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1578 panic!("expected executed compute phase");
1579 };
1580
1581 assert!(compute_phase.success);
1582 assert!(!compute_phase.msg_state_used);
1583 assert!(!compute_phase.account_activated);
1584 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1585 assert_eq!(compute_phase.gas_used, 75);
1586 assert_eq!(
1587 compute_phase.gas_limit,
1588 VarUint56::new(config.gas_prices.gas_limit)
1589 );
1590 assert_eq!(compute_phase.gas_credit, None);
1591 assert_eq!(compute_phase.exit_code, 0);
1592 assert_eq!(compute_phase.exit_arg, None);
1593 assert_eq!(compute_phase.vm_steps, 4);
1594
1595 Ok(())
1596 }
1597
1598 #[test]
1599 fn code_as_library() -> Result<()> {
1600 init_tracing();
1601 let mut params = make_default_params();
1602 let config = make_default_config();
1603
1604 let code = Boc::decode(tvmasm!("DROP THROWIF 42 ACCEPT"))?;
1605 params.libraries.set(code.repr_hash(), LibDescr {
1606 lib: code.clone(),
1607 publishers: {
1608 let mut p = Dict::new();
1609 p.set(HashBytes::ZERO, ())?;
1610 p
1611 },
1612 })?;
1613
1614 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
1615 state.state = AccountState::Active(StateInit {
1616 code: Some(make_lib_ref(code.as_ref())),
1617 ..Default::default()
1618 });
1619 state.orig_status = AccountStatus::Active;
1620 state.end_status = AccountStatus::Active;
1621
1622 let prev_balance = state.balance.clone();
1623 let prev_state = state.state.clone();
1624 let prev_total_fees = state.total_fees;
1625 let prev_end_status = state.end_status;
1626
1627 let compute_phase = state.compute_phase(ComputePhaseContext {
1628 input: TransactionInput::TickTock(TickTock::Tick),
1629 storage_fee: Tokens::ZERO,
1630 force_accept: false,
1631 inspector: None,
1632 })?;
1633
1634 assert_eq!(
1636 compute_phase.original_balance,
1637 CurrencyCollection::from(OK_BALANCE)
1638 );
1639 assert!(compute_phase.accepted);
1641 assert_eq!(state.state, prev_state);
1643 assert_eq!(prev_end_status, state.end_status);
1645 assert_eq!(compute_phase.actions, Cell::empty_cell());
1647 let expected_gas_fee = Tokens::new(config.gas_prices.flat_gas_price as _);
1649 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1650 assert_eq!(state.balance.other, prev_balance.other);
1651 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1652
1653 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1654 panic!("expected executed compute phase");
1655 };
1656
1657 assert!(compute_phase.success);
1658 assert!(!compute_phase.msg_state_used);
1659 assert!(!compute_phase.account_activated);
1660 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1661 assert_eq!(compute_phase.gas_used, 285);
1662 assert_eq!(
1663 compute_phase.gas_limit,
1664 VarUint56::new(config.gas_prices.gas_limit)
1665 );
1666 assert_eq!(compute_phase.gas_credit, None);
1667 assert_eq!(compute_phase.exit_code, 0);
1668 assert_eq!(compute_phase.exit_arg, None);
1669 assert_eq!(compute_phase.vm_steps, 5);
1670
1671 Ok(())
1672 }
1673
1674 #[test]
1675 fn internal_use_libraries() -> Result<()> {
1676 init_tracing();
1677 let mut params = make_default_params();
1678 let config = make_default_config();
1679
1680 let state_lib_code = Boc::decode(tvmasm!("THROWIF 42 INT 0"))?;
1681 let account_lib_code = Boc::decode(tvmasm!("THROWIF 43 INT -1"))?;
1682 let msg_lib_code = Boc::decode(tvmasm!("THROWIFNOT 44"))?;
1683
1684 params.libraries.set(HashBytes::ZERO, LibDescr {
1685 lib: state_lib_code.clone(),
1686 publishers: {
1687 let mut p = Dict::new();
1688 p.set(HashBytes([0x00; 32]), ())?;
1689 p
1690 },
1691 })?;
1692
1693 let msg_state_init = StateInit {
1694 libraries: {
1695 let mut p = Dict::new();
1696 p.set(HashBytes([0x22; 32]), SimpleLib {
1697 public: false,
1698 root: msg_lib_code,
1699 })?;
1700 p
1701 },
1702 ..Default::default()
1703 };
1704 let addr = StdAddr::new(0, *CellBuilder::build_from(&msg_state_init)?.repr_hash());
1705
1706 let mut state = ExecutorState::new_uninit(¶ms, &config, &addr, OK_BALANCE);
1707 state.state = AccountState::Active(StateInit {
1708 code: Some(Boc::decode(tvmasm!(
1709 r#"
1710 ACCEPT
1711 PUSHCONT {
1712 DUMPSTK
1713 XLOAD
1714 CTOS
1715 BLESS
1716 EXECUTE
1717 }
1718 // Execute state lib code
1719 OVER
1720 PUSHREF @{0000000000000000000000000000000000000000000000000000000000000000}
1721 PUSH s2
1722 EXECUTE
1723 // Execute account lib code
1724 PUSHREF @{1111111111111111111111111111111111111111111111111111111111111111}
1725 PUSH s2
1726 EXECUTE
1727 // Execute msg lib code
1728 PUSHREF @{2222222222222222222222222222222222222222222222222222222222222222}
1729 ROT
1730 EXECUTE
1731 "#
1732 ))?),
1733 libraries: {
1734 let mut p = Dict::new();
1735 p.set(HashBytes([0x11; 32]), SimpleLib {
1736 public: false,
1737 root: account_lib_code,
1738 })?;
1739 p
1740 },
1741 ..Default::default()
1742 });
1743 state.orig_status = AccountStatus::Active;
1744 state.end_status = AccountStatus::Active;
1745
1746 let msg = state.receive_in_msg(make_message(
1747 IntMsgInfo {
1748 src: addr.clone().into(),
1749 dst: addr.clone().into(),
1750 value: Tokens::new(1_000_000_000).into(),
1751 ..Default::default()
1752 },
1753 Some(msg_state_init),
1754 None,
1755 ))?;
1756
1757 state.credit_phase(&msg)?;
1758
1759 let prev_balance = state.balance.clone();
1760 let prev_state = state.state.clone();
1761 let prev_total_fees = state.total_fees;
1762 let prev_end_status = state.end_status;
1763
1764 let compute_phase = state.compute_phase(ComputePhaseContext {
1765 input: TransactionInput::Ordinary(&msg),
1766 storage_fee: Tokens::ZERO,
1767 force_accept: false,
1768 inspector: None,
1769 })?;
1770
1771 assert_eq!(
1773 compute_phase.original_balance,
1774 CurrencyCollection::from(OK_BALANCE)
1775 );
1776 assert!(compute_phase.accepted);
1778 assert_eq!(state.state, prev_state);
1780 assert_eq!(prev_end_status, state.end_status);
1782 assert_eq!(compute_phase.actions, Cell::empty_cell());
1784 let expected_gas_fee = Tokens::new(1315000);
1786 assert_eq!(state.total_fees, prev_total_fees + expected_gas_fee);
1787 assert_eq!(state.balance.other, prev_balance.other);
1788 assert_eq!(state.balance.tokens, prev_balance.tokens - expected_gas_fee);
1789
1790 let ComputePhase::Executed(compute_phase) = compute_phase.compute_phase else {
1791 panic!("expected executed compute phase");
1792 };
1793
1794 assert!(compute_phase.success);
1795 assert!(!compute_phase.msg_state_used);
1796 assert!(!compute_phase.account_activated);
1797 assert_eq!(compute_phase.gas_fees, expected_gas_fee);
1798 assert_eq!(compute_phase.gas_used, 1315);
1799 assert_eq!(
1800 compute_phase.gas_limit,
1801 VarUint56::new(config.gas_prices.gas_limit)
1802 );
1803 assert_eq!(compute_phase.gas_credit, None);
1804 assert_eq!(compute_phase.exit_code, 0);
1805 assert_eq!(compute_phase.exit_arg, None);
1806 assert_eq!(compute_phase.vm_steps, 39);
1807
1808 Ok(())
1809 }
1810}