1use anyhow::{Context, anyhow};
2use tycho_types::models::{
3 AccountStatus, BounceReason, ComputePhase, NewBounceComputePhaseInfo, OrdinaryTxInfo,
4};
5use tycho_types::num::Tokens;
6use tycho_types::prelude::*;
7
8use crate::error::{TxError, TxResult};
9use crate::phase::{
10 ActionPhaseContext, BouncePhaseContext, ComputePhaseContext, ComputePhaseFull,
11 StoragePhaseContext, TransactionInput,
12};
13use crate::{ExecutorInspector, ExecutorState};
14
15impl ExecutorState<'_> {
16 pub fn run_ordinary_transaction(
17 &mut self,
18 is_external: bool,
19 msg_root: Cell,
20 mut inspector: Option<&mut ExecutorInspector<'_>>,
21 ) -> TxResult<OrdinaryTxInfo> {
22 let mut msg = match self.receive_in_msg(msg_root) {
24 Ok(msg) if msg.is_external == is_external => msg,
25 Ok(_) => {
26 return Err(TxError::Fatal(anyhow!(
27 "received an unexpected inbound message"
28 )));
29 }
30 Err(_) if is_external => return Err(TxError::Skipped),
32 Err(e) => return Err(TxError::Fatal(e)),
33 };
34
35 let storage_phase;
38 let credit_phase;
39 if msg.bounce_enabled {
40 storage_phase = self
42 .storage_phase(StoragePhaseContext {
43 adjust_msg_balance: false,
44 received_message: Some(&mut msg),
45 inspector: inspector.as_deref_mut(),
46 })
47 .context("storage phase failed")?;
48
49 credit_phase = if is_external {
51 None
52 } else {
53 Some(self.credit_phase(&msg).context("credit phase failed")?)
54 };
55 } else {
56 credit_phase = if is_external {
58 None
59 } else {
60 Some(self.credit_phase(&msg).context("credit phase failed")?)
61 };
62
63 storage_phase = self
65 .storage_phase(StoragePhaseContext {
66 adjust_msg_balance: true,
67 received_message: Some(&mut msg),
68 inspector: inspector.as_deref_mut(),
69 })
70 .context("storage phase failed")?;
71 }
72
73 let ComputePhaseFull {
75 compute_phase,
76 accepted,
77 original_balance,
78 new_state,
79 actions,
80 } = self
81 .compute_phase(ComputePhaseContext {
82 input: TransactionInput::Ordinary(&msg),
83 storage_fee: storage_phase.storage_fees_collected,
84 force_accept: false,
85 stop_on_accept: false,
86 inspector: inspector.as_deref_mut(),
87 })
88 .context("compute phase failed")?;
89
90 if is_external && !accepted {
91 return Err(TxError::Skipped);
92 }
93
94 let mut aborted = true;
96 let mut state_exceeds_limits = false;
97 let mut bounce_required = false;
98 let mut action_fine = Tokens::ZERO;
99 let mut destroyed = false;
100
101 let mut action_phase = None;
102 if let ComputePhase::Executed(compute_phase) = &compute_phase
103 && compute_phase.success
104 {
105 let res = self
106 .action_phase(ActionPhaseContext {
107 received_message: Some(&mut msg),
108 original_balance,
109 new_state,
110 actions,
111 compute_phase,
112 inspector,
113 })
114 .context("action phase failed")?;
115
116 aborted = !res.action_phase.success;
117 state_exceeds_limits = res.state_exceeds_limits;
118 bounce_required = res.bounce;
119 action_fine = res.action_fine;
120 destroyed = self.end_status == AccountStatus::NotExists;
121
122 action_phase = Some(res.action_phase);
123 }
124
125 let mut bounce_phase = None;
127 if msg.bounce_enabled
128 && (!matches!(&compute_phase, ComputePhase::Executed(p) if p.success)
129 || state_exceeds_limits
130 || bounce_required)
131 {
132 debug_assert!(!is_external);
133
134 let reason = if let Some(phase) = &action_phase {
135 BounceReason::ActionPhaseFailed {
136 result_code: phase.result_code,
137 }
138 } else {
139 match &compute_phase {
140 ComputePhase::Executed(phase) => BounceReason::ComputePhaseFailed {
141 exit_code: phase.exit_code,
142 },
143 ComputePhase::Skipped(skipped) => {
144 BounceReason::ComputePhaseSkipped(skipped.reason)
145 }
146 }
147 };
148
149 let (gas_fees, compute_phase_info) = match &compute_phase {
150 ComputePhase::Executed(phase) => (
151 phase.gas_fees,
152 Some(NewBounceComputePhaseInfo {
153 gas_used: phase.gas_used.into_inner().try_into().unwrap_or(u32::MAX),
154 vm_steps: phase.vm_steps,
155 }),
156 ),
157 ComputePhase::Skipped(_) => (Tokens::ZERO, None),
158 };
159
160 bounce_phase = Some(
161 self.bounce_phase(BouncePhaseContext {
162 gas_fees,
163 action_fine,
164 received_message: &msg,
165 reason,
166 compute_phase_info,
167 })
168 .context("bounce phase failed")?,
169 );
170 }
171
172 Ok(OrdinaryTxInfo {
174 credit_first: !msg.bounce_enabled,
175 storage_phase: Some(storage_phase),
176 credit_phase,
177 compute_phase,
178 action_phase,
179 aborted,
180 bounce_phase,
181 destroyed,
182 })
183 }
184
185 pub fn check_ordinary_transaction(
186 &mut self,
187 msg_root: Cell,
188 mut inspector: Option<&mut ExecutorInspector<'_>>,
189 ) -> TxResult<()> {
190 let mut msg = match self.receive_in_msg(msg_root) {
192 Ok(msg) if msg.is_external => msg,
193 _ => return Err(TxError::Skipped),
194 };
195
196 let storage_phase = self
198 .storage_phase(StoragePhaseContext {
199 adjust_msg_balance: false,
200 received_message: Some(&mut msg),
201 inspector: inspector.as_deref_mut(),
202 })
203 .context("storage phase failed")?;
204
205 let ComputePhaseFull { accepted, .. } = self
207 .compute_phase(ComputePhaseContext {
208 input: TransactionInput::Ordinary(&msg),
209 storage_fee: storage_phase.storage_fees_collected,
210 force_accept: false,
211 stop_on_accept: true,
212 inspector,
213 })
214 .context("compute phase failed")?;
215
216 if !accepted {
217 return Err(TxError::Skipped);
218 }
219
220 Ok(())
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use anyhow::Result;
227 use tycho_asm_macros::tvmasm;
228 use tycho_types::cell::Lazy;
229 use tycho_types::models::{
230 Account, AccountState, AccountStatusChange, ComputePhaseSkipReason, CurrencyCollection,
231 ExtInMsgInfo, IntMsgInfo, MsgInfo, OptionalAccount, ShardAccount, SimpleLib, StateInit,
232 StdAddr, StorageInfo, StorageUsed, TxInfo,
233 };
234 use tycho_types::num::VarUint56;
235
236 use super::*;
237 use crate::tests::{make_default_config, make_default_params, make_message};
238 use crate::{Executor, PublicLibraryChange};
239
240 const STUB_ADDR: StdAddr = StdAddr::new(0, HashBytes::ZERO);
241 const MASTERCHAIN_ADDR: StdAddr = StdAddr::new(-1, HashBytes::ZERO);
242
243 fn make_library(id: u16) -> Cell {
244 CellBuilder::build_from(id).unwrap()
245 }
246
247 fn make_libraries<I>(items: I) -> Dict<HashBytes, SimpleLib>
248 where
249 I: IntoIterator<Item = (u16, bool)>,
250 {
251 let mut items = items
252 .into_iter()
253 .map(|(id, public)| {
254 let root = make_library(id);
255 (*root.repr_hash(), SimpleLib { root, public })
256 })
257 .collect::<Vec<_>>();
258 items.sort_unstable_by_key(|(hash, _)| *hash);
259 Dict::try_from_sorted_slice(&items).unwrap()
260 }
261
262 fn set_libraries(state: &mut ExecutorState<'_>, libraries: Dict<HashBytes, SimpleLib>) {
263 let AccountState::Active(state) = &mut state.state else {
264 panic!("expected active account state");
265 };
266 state.libraries = libraries;
267 }
268
269 fn assert_public_libs_diff(
270 actual: &ahash::HashMap<HashBytes, PublicLibraryChange>,
271 added: &[Cell],
272 removed: &[Cell],
273 ) {
274 let expected = added
275 .iter()
276 .cloned()
277 .map(|cell| (*cell.repr_hash(), PublicLibraryChange::Add(cell)))
278 .chain(
279 removed
280 .iter()
281 .map(|cell| (*cell.repr_hash(), PublicLibraryChange::Remove)),
282 )
283 .collect::<ahash::HashMap<_, _>>();
284 assert_eq!(actual, &expected);
285 }
286
287 pub fn make_uninit_with_balance<T: Into<CurrencyCollection>>(
288 address: &StdAddr,
289 balance: T,
290 ) -> ShardAccount {
291 ShardAccount {
292 account: Lazy::new(&OptionalAccount(Some(Account {
293 address: address.clone().into(),
294 storage_stat: StorageInfo::default(),
295 last_trans_lt: 1001,
296 balance: balance.into(),
297 state: AccountState::Uninit,
298 })))
299 .unwrap(),
300 last_trans_hash: HashBytes([0x11; 32]),
301 last_trans_lt: 1000,
302 }
303 }
304
305 #[test]
306 fn ever_wallet_deploys() -> Result<()> {
307 let config = make_default_config();
308 let params = make_default_params();
309
310 let code = Boc::decode(include_bytes!("../../res/ever_wallet_code.boc"))?;
311 let data = CellBuilder::build_from((HashBytes::ZERO, 0u64))?;
312
313 let state_init = StateInit {
314 split_depth: None,
315 special: None,
316 code: Some(code),
317 data: Some(data),
318 libraries: Dict::new(),
319 };
320 let address = StdAddr::new(0, *CellBuilder::build_from(&state_init)?.repr_hash());
321
322 let msg = make_message(
323 ExtInMsgInfo {
324 src: None,
325 dst: address.clone().into(),
326 import_fee: Tokens::ZERO,
327 },
328 Some(state_init),
329 Some({
330 let mut b = CellBuilder::new();
331 b.store_bit_one()?;
333 b.store_u256(&HashBytes::ZERO)?;
334 b.store_u256(&HashBytes::ZERO)?;
335 b.store_bit_one()?;
337 b.store_zeros(256)?;
338 b.store_u64((params.block_unixtime - 10) as u64 * 1000)?;
340 b.store_u32(params.block_unixtime + 40)?;
342 b.store_u32(0x4cee646c)?;
344 b.store_reference({
346 let mut b = CellBuilder::new();
347 address.store_into(&mut b, Cell::empty_context())?;
349 b.store_u128(10000000)?;
351 b.store_bit_zero()?;
353 b.store_u8(0b11)?;
355 b.store_reference(Cell::empty_cell())?;
357 b.build()?
359 })?;
360 b
362 }),
363 );
364
365 let state = make_uninit_with_balance(&address, CurrencyCollection::new(1_000_000_000));
366
367 let output = Executor::new(¶ms, config.as_ref())
368 .begin_ordinary(&address, true, &msg, &state)?
369 .commit()?;
370
371 println!("SHARD_STATE: {:#?}", output.new_state);
372 let account = output.new_state.load_account()?;
373 println!("ACCOUNT: {:#?}", account);
374
375 let tx = output.transaction.load()?;
376 println!("TX: {tx:#?}");
377 let info = tx.load_info()?;
378 println!("INFO: {info:#?}");
379
380 Ok(())
381 }
382
383 #[test]
384 fn freeze_account() -> Result<()> {
385 let params = make_default_params();
386 let config = make_default_config();
387
388 let code = tvmasm!(
389 r#"
390 NEWC INT 123 STUR 32 ENDC
391 POP c4
392 ACCEPT
393 "#
394 );
395 let mut state = ExecutorState::new_active(
396 ¶ms,
397 &config,
398 &STUB_ADDR,
399 Tokens::ZERO,
400 CellBuilder::build_from(u32::MIN)?,
401 code,
402 );
403 state.storage_stat = StorageInfo {
404 used: StorageUsed {
405 bits: VarUint56::new(128),
406 cells: VarUint56::new(1),
407 },
408 storage_extra: Default::default(),
409 last_paid: params.block_unixtime - 1000,
410 due_payment: Some(Tokens::new(2 * config.gas_prices.freeze_due_limit as u128)),
411 };
412
413 let info = state.run_ordinary_transaction(
414 false,
415 make_message(
416 IntMsgInfo {
417 src: STUB_ADDR.into(),
418 dst: STUB_ADDR.into(),
419 value: CurrencyCollection::new(1_000_000),
420 bounce: true,
421 ..Default::default()
422 },
423 None,
424 None,
425 ),
426 None,
427 )?;
428
429 assert!(!info.aborted);
430 assert_eq!(
431 info.storage_phase.unwrap().status_change,
432 AccountStatusChange::Frozen
433 );
434
435 let ComputePhase::Executed(compute_phase) = info.compute_phase else {
436 panic!("expected an executed compute phase");
437 };
438 assert!(compute_phase.success);
439
440 let action_phase = info.action_phase.unwrap();
441 assert!(action_phase.success);
442 assert_eq!(action_phase.messages_created, 0);
443
444 assert_eq!(info.bounce_phase, None);
445
446 assert_eq!(state.orig_status, AccountStatus::Active);
447 assert_eq!(state.end_status, AccountStatus::Frozen);
448 assert_eq!(state.balance, CurrencyCollection::ZERO);
449
450 assert_eq!(
451 state.state,
452 AccountState::Active(StateInit {
453 code: Boc::decode(code).map(Some)?,
454 data: CellBuilder::build_from(123u32).map(Some)?,
455 ..Default::default()
456 })
457 );
458
459 Ok(())
460 }
461
462 #[test]
463 fn deleted_account_removes_public_libs() -> Result<()> {
464 let params = make_default_params();
465 let config = make_default_config();
466
467 let code = tvmasm!(
468 r#"
469 ACCEPT
470 // This library only exists during the action phase and must not appear in the diff.
471 PUSHREF x{4444}
472 INT 2 SETLIBCODE
473 NEWC
474 // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
475 INT 0b011000 STUR 6
476 MYADDR
477 STSLICER
478 INT 0 STGRAMS
479 // extra:$0 ihr_fee:Tokens fwd_fee:Tokens created_lt:uint64 created_at:uint32
480 // 1 + 4 + 4 + 64 + 32
481 // init:none$0 body:left$0
482 // 1 + 1
483 INT 107 STZEROES
484 ENDC INT 160 SENDRAWMSG
485 "#
486 );
487 let public_lib_1 = make_library(0x1111);
488 let public_lib_2 = make_library(0x2222);
489 let private_lib = make_library(0x3333);
490
491 let mut state = ExecutorState::new_active(
492 ¶ms,
493 &config,
494 &MASTERCHAIN_ADDR,
495 Tokens::new(1_000_000_000),
496 Cell::empty_cell(),
497 code,
498 );
499 set_libraries(
500 &mut state,
501 make_libraries([(0x1111, true), (0x2222, true), (0x3333, false)]),
502 );
503
504 let mut inspector = ExecutorInspector::default();
505 let info = state.run_ordinary_transaction(
506 false,
507 make_message(
508 IntMsgInfo {
509 src: MASTERCHAIN_ADDR.into(),
510 dst: MASTERCHAIN_ADDR.into(),
511 value: CurrencyCollection::new(1_000_000_000),
512 ..Default::default()
513 },
514 None,
515 None,
516 ),
517 Some(&mut inspector),
518 )?;
519
520 assert!(info.destroyed);
521 assert_eq!(state.end_status, AccountStatus::NotExists);
522 assert_eq!(
523 info.action_phase.unwrap().status_change,
524 AccountStatusChange::Deleted
525 );
526 assert_public_libs_diff(&inspector.public_libs_diff, &[], &[
527 public_lib_1,
528 public_lib_2,
529 ]);
530 assert!(
531 !inspector
532 .public_libs_diff
533 .contains_key(private_lib.repr_hash())
534 );
535 assert!(
536 !inspector
537 .public_libs_diff
538 .contains_key(make_library(0x4444).repr_hash())
539 );
540
541 Ok(())
542 }
543
544 #[test]
545 fn frozen_account_removes_public_libs() -> Result<()> {
546 let params = make_default_params();
547 let config = make_default_config();
548 let public_lib_1 = make_library(0x1111);
549 let public_lib_2 = make_library(0x2222);
550 let private_lib = make_library(0x3333);
551
552 let mut state = ExecutorState::new_active(
553 ¶ms,
554 &config,
555 &MASTERCHAIN_ADDR,
556 Tokens::ZERO,
557 Cell::empty_cell(),
558 tvmasm!("ACCEPT"),
559 );
560 set_libraries(
561 &mut state,
562 make_libraries([(0x1111, true), (0x2222, true), (0x3333, false)]),
563 );
564 state.storage_stat = StorageInfo {
565 used: StorageUsed {
566 bits: VarUint56::new(128),
567 cells: VarUint56::new(1),
568 },
569 storage_extra: Default::default(),
570 last_paid: params.block_unixtime - 1000,
571 due_payment: Some(Tokens::new(
572 2 * config.mc_gas_prices.freeze_due_limit as u128,
573 )),
574 };
575
576 let mut inspector = ExecutorInspector::default();
577 let info = state.run_ordinary_transaction(
578 false,
579 make_message(
580 IntMsgInfo {
581 src: MASTERCHAIN_ADDR.into(),
582 dst: MASTERCHAIN_ADDR.into(),
583 value: CurrencyCollection::new(1_000_000_000),
584 bounce: true,
585 ..Default::default()
586 },
587 None,
588 None,
589 ),
590 Some(&mut inspector),
591 )?;
592
593 assert_eq!(
594 info.storage_phase.unwrap().status_change,
595 AccountStatusChange::Frozen
596 );
597 assert_eq!(state.end_status, AccountStatus::Frozen);
598 assert_public_libs_diff(&inspector.public_libs_diff, &[], &[
599 public_lib_1,
600 public_lib_2,
601 ]);
602 assert!(
603 !inspector
604 .public_libs_diff
605 .contains_key(private_lib.repr_hash())
606 );
607
608 Ok(())
609 }
610
611 #[test]
612 fn unfrozen_account_adds_public_libs() -> Result<()> {
613 let params = make_default_params();
614 let config = make_default_config();
615 let public_lib_1 = make_library(0x1111);
616 let public_lib_2 = make_library(0x2222);
617 let private_lib = make_library(0x3333);
618 let state_init = StateInit {
619 code: Some(Boc::decode(tvmasm!("ACCEPT"))?),
620 libraries: make_libraries([(0x1111, true), (0x2222, true), (0x3333, false)]),
621 ..Default::default()
622 };
623 let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
624 let mut state = ExecutorState::new_frozen(
625 ¶ms,
626 &config,
627 &MASTERCHAIN_ADDR,
628 Tokens::ZERO,
629 state_init_hash,
630 );
631
632 let mut inspector = ExecutorInspector::default();
633 let info = state.run_ordinary_transaction(
634 false,
635 make_message(
636 IntMsgInfo {
637 src: MASTERCHAIN_ADDR.into(),
638 dst: MASTERCHAIN_ADDR.into(),
639 value: CurrencyCollection::new(1_000_000_000),
640 ..Default::default()
641 },
642 Some(state_init),
643 None,
644 ),
645 Some(&mut inspector),
646 )?;
647
648 let ComputePhase::Executed(compute_phase) = info.compute_phase else {
649 panic!("expected an executed compute phase");
650 };
651 assert!(compute_phase.success);
652 assert!(compute_phase.account_activated);
653 assert_eq!(state.end_status, AccountStatus::Active);
654 assert_public_libs_diff(
655 &inspector.public_libs_diff,
656 &[public_lib_1, public_lib_2],
657 &[],
658 );
659 assert!(
660 !inspector
661 .public_libs_diff
662 .contains_key(private_lib.repr_hash())
663 );
664
665 Ok(())
666 }
667
668 #[test]
669 fn unfrozen_account_adds_public_libs_with_actions() -> Result<()> {
670 let params = make_default_params();
671 let config = make_default_config();
672 let removed_lib = make_library(0x1111);
673 let retained_lib = make_library(0x2222);
674 let added_lib = make_library(0x4444);
675 let state_init = StateInit {
676 code: Some(Boc::decode(tvmasm!(
677 r#"
678 ACCEPT
679 PUSHREF x{1111}
680 INT 0 SETLIBCODE
681 PUSHREF x{4444}
682 INT 2 SETLIBCODE
683 "#
684 ))?),
685 libraries: make_libraries([(0x1111, true), (0x2222, true), (0x3333, false)]),
686 ..Default::default()
687 };
688 let state_init_hash = *CellBuilder::build_from(&state_init)?.repr_hash();
689 let mut state = ExecutorState::new_frozen(
690 ¶ms,
691 &config,
692 &MASTERCHAIN_ADDR,
693 Tokens::ZERO,
694 state_init_hash,
695 );
696
697 let mut inspector = ExecutorInspector::default();
698 let info = state.run_ordinary_transaction(
699 false,
700 make_message(
701 IntMsgInfo {
702 src: MASTERCHAIN_ADDR.into(),
703 dst: MASTERCHAIN_ADDR.into(),
704 value: CurrencyCollection::new(1_000_000_000),
705 ..Default::default()
706 },
707 Some(state_init),
708 None,
709 ),
710 Some(&mut inspector),
711 )?;
712
713 let action_phase = info.action_phase.unwrap();
714 assert!(action_phase.success);
715 assert_eq!(action_phase.special_actions, 2);
716 let AccountState::Active(final_state) = &state.state else {
717 panic!("expected active account state");
718 };
719 assert_eq!(
720 final_state.libraries,
721 make_libraries([(0x2222, true), (0x3333, false), (0x4444, true),])
722 );
723 assert_public_libs_diff(&inspector.public_libs_diff, &[retained_lib, added_lib], &[]);
724 assert!(
725 !inspector
726 .public_libs_diff
727 .contains_key(removed_lib.repr_hash())
728 );
729
730 Ok(())
731 }
732
733 #[test]
734 fn deploy_with_libraries_fails() -> Result<()> {
735 let params = make_default_params();
736 let config = make_default_config();
737 let state_init = StateInit {
738 code: Some(Boc::decode(tvmasm!("ACCEPT"))?),
739 libraries: make_libraries([(0x1111, true)]),
740 ..Default::default()
741 };
742 let address = StdAddr::new(-1, *CellBuilder::build_from(&state_init)?.repr_hash());
743 let mut state = ExecutorState::new_uninit(¶ms, &config, &address, Tokens::ZERO);
744
745 let mut inspector = ExecutorInspector::default();
746 let info = state.run_ordinary_transaction(
747 false,
748 make_message(
749 IntMsgInfo {
750 src: address.clone().into(),
751 dst: address.clone().into(),
752 value: CurrencyCollection::new(1_000_000_000),
753 ..Default::default()
754 },
755 Some(state_init),
756 None,
757 ),
758 Some(&mut inspector),
759 )?;
760
761 let ComputePhase::Skipped(compute_phase) = info.compute_phase else {
762 panic!("expected a skipped compute phase");
763 };
764 assert_eq!(compute_phase.reason, ComputePhaseSkipReason::BadState);
765 assert_eq!(state.state, AccountState::Uninit);
766 assert_eq!(state.end_status, AccountStatus::Uninit);
767 assert!(inspector.public_libs_diff.is_empty());
768
769 Ok(())
770 }
771
772 #[test]
773 fn deploy_delete_in_same_tx() -> Result<()> {
774 let params = make_default_params();
775 let config = make_default_config();
776
777 let code = Boc::decode(tvmasm!(
778 r#"
779 ACCEPT
780 NEWC
781 // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
782 INT 0b011000 STUR 6
783 MYADDR
784 STSLICER
785 INT 0 STGRAMS
786 // extra:$0 ihr_fee:Tokens fwd_fee:Tokens created_lt:uint64 created_at:uint32
787 // 1 + 4 + 4 + 64 + 32
788 // init:none$0 body:left$0
789 // 1 + 1
790 INT 107 STZEROES
791 ENDC INT 160 SENDRAWMSG
792 "#
793 ))?;
794 let state_init = StateInit {
795 code: Some(code),
796 ..Default::default()
797 };
798
799 let address = StdAddr::new(0, *CellBuilder::build_from(&state_init)?.repr_hash());
800
801 let msg_balance = Tokens::new(1_000_000_000);
802 let msg = make_message(
803 IntMsgInfo {
804 src: address.clone().into(),
805 dst: address.clone().into(),
806 value: msg_balance.into(),
807 ..Default::default()
808 },
809 Some(state_init),
810 None,
811 );
812
813 let state = ShardAccount {
814 account: Lazy::new(&OptionalAccount::EMPTY)?,
815 last_trans_hash: HashBytes::ZERO,
816 last_trans_lt: 0,
817 };
818
819 let output = Executor::new(¶ms, config.as_ref())
820 .begin_ordinary(&address, false, msg, &state)?
821 .commit()?;
822
823 let new_account_state = output.new_state.load_account()?;
824 assert_eq!(new_account_state, None);
825
826 let tx = output.transaction.load()?;
827 assert_eq!(tx.orig_status, AccountStatus::NotExists);
828 assert_eq!(tx.end_status, AccountStatus::NotExists);
829
830 let TxInfo::Ordinary(info) = tx.load_info()? else {
831 panic!("expected an ordinary transaction info");
832 };
833 println!("{info:#?}");
834
835 assert!(!info.aborted);
836 assert!(info.destroyed);
837
838 let ComputePhase::Executed(compute_phase) = info.compute_phase else {
839 panic!("expected an executed compute phase");
840 };
841 assert!(compute_phase.success);
842 let action_phase = info.action_phase.unwrap();
843 assert!(action_phase.success);
844 assert_eq!(action_phase.total_actions, 1);
845 assert_eq!(action_phase.messages_created, 1);
846
847 assert_eq!(output.transaction_meta.out_msgs.len(), 1);
848 let out_msg = output.transaction_meta.out_msgs.last().unwrap().load()?;
849
850 {
851 let MsgInfo::Int(info) = out_msg.info else {
852 panic!("expected an internal outbound message");
853 };
854
855 assert_eq!(info.src, address.clone().into());
856 assert_eq!(info.dst, address.clone().into());
857 assert!(info.value.other.is_empty());
858 assert_eq!(
859 info.value.tokens,
860 msg_balance
861 - compute_phase.gas_fees
862 - action_phase.total_fwd_fees.unwrap_or_default()
863 );
864
865 assert_eq!(out_msg.init, None);
866 assert_eq!(out_msg.body, Default::default());
867 }
868
869 Ok(())
870 }
871}