Skip to main content

tycho_executor/tx/
ordinary.rs

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        // Receive inbound message.
23        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            // Invalid external messages can be safely skipped.
31            Err(_) if is_external => return Err(TxError::Skipped),
32            Err(e) => return Err(TxError::Fatal(e)),
33        };
34
35        // Order of credit and storage phases depends on the `bounce` flag
36        // of the inbound message.
37        let storage_phase;
38        let credit_phase;
39        if msg.bounce_enabled {
40            // Run storage phase.
41            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            // Run credit phase (only for internal messages).
50            credit_phase = if is_external {
51                None
52            } else {
53                Some(self.credit_phase(&msg).context("credit phase failed")?)
54            };
55        } else {
56            // Run credit phase (only for internal messages).
57            credit_phase = if is_external {
58                None
59            } else {
60                Some(self.credit_phase(&msg).context("credit phase failed")?)
61            };
62
63            // Run storage phase.
64            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        // Run compute phase.
74        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        // Run action phase only if compute phase succeeded.
95        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        // Run bounce phase for internal messages if something failed.
126        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        // Build transaction info.
173        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        // Receive phase
191        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        // Storage phase
197        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        // Compute phase with partial execution
206        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                // just$1 Signature
332                b.store_bit_one()?;
333                b.store_u256(&HashBytes::ZERO)?;
334                b.store_u256(&HashBytes::ZERO)?;
335                // just$1 Pubkey
336                b.store_bit_one()?;
337                b.store_zeros(256)?;
338                // header_time:u64
339                b.store_u64((params.block_unixtime - 10) as u64 * 1000)?;
340                // header_expire:u32
341                b.store_u32(params.block_unixtime + 40)?;
342                // sendTransaction
343                b.store_u32(0x4cee646c)?;
344                // ...
345                b.store_reference({
346                    let mut b = CellBuilder::new();
347                    // dest:address
348                    address.store_into(&mut b, Cell::empty_context())?;
349                    // value:uint128
350                    b.store_u128(10000000)?;
351                    // bounce:false
352                    b.store_bit_zero()?;
353                    // mode:uint8
354                    b.store_u8(0b11)?;
355                    // payload:cell
356                    b.store_reference(Cell::empty_cell())?;
357                    //
358                    b.build()?
359                })?;
360                //
361                b
362            }),
363        );
364
365        let state = make_uninit_with_balance(&address, CurrencyCollection::new(1_000_000_000));
366
367        let output = Executor::new(&params, 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            &params,
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            &params,
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            &params,
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            &params,
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            &params,
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(&params, &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(&params, 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}