Skip to main content

multiversx_sc_scenario/executor/debug/
catch_tx_panic.rs

1use multiversx_chain_vm::host::context::TxPanic;
2use multiversx_chain_vm_executor::{BreakpointValue, VMHooksEarlyExit};
3use multiversx_sc::{chain_core::types::ReturnCode, err_msg};
4
5/// Catches all thrown panics, as follows:
6/// - BreakpointValue is considered expected abortion of execution and already handled, for them it returns Ok;
7/// - TxPanic panics are considered VM failures with additional information. We are trying to get rid of them;
8/// - All other panics are treated as user errors;
9/// - The closure argument can also opt to return a TxPanic, without having to throw it. This will pe passed on as is.
10pub fn catch_tx_panic<F>(panic_message_flag: bool, f: F) -> Result<(), TxPanic>
11where
12    F: FnOnce() -> Result<(), TxPanic>,
13{
14    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
15    match result {
16        Ok(result) => result,
17        Err(panic_any) => {
18            if panic_any.downcast_ref::<BreakpointValue>().is_some() {
19                // breakpoints are considered to be already handled
20                Ok(())
21            } else if let Some(early_exit) = panic_any.downcast_ref::<VMHooksEarlyExit>() {
22                if early_exit.code == 0 {
23                    Ok(())
24                } else {
25                    Err(TxPanic::new(
26                        ReturnCode::from_u64(early_exit.code).unwrap(),
27                        &early_exit.message,
28                    ))
29                }
30            } else {
31                // fallback, general panics
32                Err(interpret_panic_as_tx_panic(panic_any, panic_message_flag))
33            }
34        }
35    }
36}
37
38/// Interprets a panic thrown during execution as a tx failure.
39/// Note: specific tx outcomes from the debugger are signalled via specific panic objects.
40fn interpret_panic_as_tx_panic(
41    panic_any: Box<dyn std::any::Any + std::marker::Send>,
42    panic_message_flag: bool,
43) -> TxPanic {
44    if let Some(panic_string) = panic_any.downcast_ref::<String>() {
45        return interpret_panic_str_as_tx_result(panic_string.as_str(), panic_message_flag);
46    }
47
48    if let Some(panic_string) = panic_any.downcast_ref::<&str>() {
49        return interpret_panic_str_as_tx_result(panic_string, panic_message_flag);
50    }
51
52    TxPanic::user_error("unknown panic object")
53}
54
55pub fn interpret_panic_str_as_tx_result(panic_str: &str, panic_message_flag: bool) -> TxPanic {
56    if panic_message_flag {
57        TxPanic::user_error(&format!("panic occurred: {panic_str}"))
58    } else {
59        TxPanic::user_error(err_msg::PANIC_OCCURRED)
60    }
61}