bobcat_panic/
lib.rs

1#![no_std]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[allow(unused)]
7use core::fmt::{Result as FmtResult, Write};
8
9use paste::paste;
10
11#[cfg(all(target_family = "wasm", target_os = "unknown"))]
12mod wasm {
13    #[link(wasm_import_module = "console")]
14    #[cfg(feature = "console")]
15    unsafe extern "C" {
16        pub(crate) fn log_txt(ptr: *const u8, len: usize);
17    }
18
19    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
20    #[link(wasm_import_module = "vm_hooks")]
21    #[allow(unused)]
22    unsafe extern "C" {
23        pub(crate) fn exit_early(code: i32) -> !;
24        pub(crate) fn write_result(d: *const u8, l: usize);
25    }
26}
27
28#[cfg(all(target_family = "wasm", target_os = "unknown"))]
29fn write_result_slice(s: &[u8]) {
30    unsafe { wasm::write_result(s.as_ptr(), s.len()) }
31}
32
33//Panic(uint256)
34pub const PANIC_PREAMBLE_WORD: [u8; 32 + 4] = match const_hex::const_decode_to_array::<{ 32 + 4 }>(
35    b"4e487b710000000000000000000000000000000000000000000000000000000000000000",
36) {
37    Ok(v) => v,
38    Err(_) => panic!(),
39};
40
41//Error(string)
42pub const ERROR_PREAMBLE_OFFSET: [u8; 4 + 32] = match const_hex::const_decode_to_array::<{ 4 + 32 }>(
43    b"08c379a00000000000000000000000000000000000000000000000000000000000000020",
44) {
45    Ok(v) => v,
46    Err(_) => panic!(),
47};
48
49#[derive(Clone, Debug, PartialEq)]
50#[repr(u8)]
51pub enum PanicCodes {
52    OverflowOrUnderflow = 0x11,
53    DivByZero = 0x12,
54    DecodingError = 0x22,
55}
56
57#[cfg(all(target_family = "wasm", target_os = "unknown"))]
58pub fn panic_with_code(x: PanicCodes) -> ! {
59    let mut b = PANIC_PREAMBLE_WORD;
60    b[4 + 32 - 1] = x as u8;
61    write_result_slice(&b);
62    unsafe { wasm::exit_early(1) }
63}
64
65#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
66pub fn panic_with_code(x: PanicCodes) -> ! {
67    panic!("panicked with code: {x:?}");
68}
69
70#[macro_export]
71macro_rules! define_panic_macros {
72    (
73        $(($error_msg:expr, $panic_code:ident)),* $(,)?
74    ) => {
75        $(
76            paste! {
77                #[macro_export]
78                macro_rules! [<panic_on_err_ $error_msg>] {
79                    ($e:expr, $msg:expr) => {{
80                        match $e {
81                            Some(v) => v,
82                            None => {
83                                #[cfg(feature = "msg-on-sdk-err")]
84                                panic!("{}: {}", $error_msg, $msg);
85                                #[cfg(not(feature = "msg-on-sdk-err"))]
86                                $crate::panic_with_code($crate::PanicCodes::$panic_code);
87                            }
88                        }
89                    }};
90                }
91            }
92        )*
93    };
94}
95
96define_panic_macros!(
97    ("overflow", OverflowOrUnderflow),
98    ("div_by_zero", DivByZero),
99);
100
101#[macro_export]
102macro_rules! panic_on_err_bad_decoding_bool {
103    ($msg:expr) => {{
104        #[cfg(feature = "msg-on-sdk-err")]
105        panic!("error decoding: {}", $msg);
106        #[cfg(not(feature = "msg-on-sdk-err"))]
107        $crate::panic_with_code($crate::PanicCodes::DecodingError);
108    }};
109    ($e:expr, $msg:expr) => {{
110        if !$e {
111            panic_on_err_bad_decoding_bool!($msg);
112        }
113    }};
114}
115
116#[allow(unused)]
117struct SliceWriter<'a>(&'a mut [u8], usize);
118
119impl<'a> Write for SliceWriter<'a> {
120    fn write_str(&mut self, s: &str) -> FmtResult {
121        let v = s.len().min(REVERT_BUF_SIZE.saturating_sub(self.1));
122        self.0[self.1..self.1 + v].copy_from_slice(&s.as_bytes()[..v]);
123        self.1 += v;
124        Ok(())
125    }
126}
127
128/// Revert buffer size that's used to write the panic. We can afford to
129/// use a large page here since a panic will consume all the gas anyway,
130/// and a user will see this during simulation hopefully.
131#[allow(unused)]
132const REVERT_BUF_SIZE: usize = 1024;
133
134#[cfg(all(feature = "panic-revert", feature = "panic-loc"))]
135compile_error!("panic-revert and panic-loc simultaneously enabled");
136
137#[cfg(all(target_family = "wasm", target_os = "unknown"))]
138#[cfg_attr(all(feature = "panic", not(feature = "std")), panic_handler)]
139pub fn panic_handler(_msg: &core::panic::PanicInfo) -> ! {
140    #[cfg(feature = "console")]
141    {
142        let msg = alloc::format!("{_msg}");
143        unsafe { wasm::log_txt(msg.as_ptr(), msg.len()) }
144    }
145    #[cfg(any(feature = "panic-revert", feature = "panic-loc"))]
146    {
147        let mut buf = [0u8; REVERT_BUF_SIZE];
148        buf[..ERROR_PREAMBLE_OFFSET.len()].copy_from_slice(&ERROR_PREAMBLE_OFFSET);
149        let mut w = SliceWriter(&mut buf[ERROR_PREAMBLE_OFFSET.len() + 32..], 0);
150        #[cfg(feature = "panic-revert")]
151        {
152            write!(&mut w, "{_msg}").unwrap();
153        }
154        #[cfg(feature = "panic-loc")]
155        if let Some(loc) = _msg.location() {
156            write!(&mut w, "panic: {}:{}", loc.file(), loc.line()).unwrap();
157        } else {
158            write!(&mut w, "panic: unknown").unwrap();
159        }
160        let len_msg = w.1;
161        let len_offset = ERROR_PREAMBLE_OFFSET.len();
162        buf[len_offset + 28..len_offset + 32].copy_from_slice(&(len_msg as u32).to_be_bytes());
163        let len_full = ERROR_PREAMBLE_OFFSET.len() + 32 + len_msg;
164        let len_padded = len_full + (32 - (len_full % 32)) % 32;
165        write_result_slice(&buf[..len_padded]);
166        unsafe { wasm::exit_early(1) }
167    }
168    // Prefer the normal behaviour if the user hasn't opted into this
169    // feature. Maybe it's better to wipe out the revertdata if this happens,
170    // the other behaviour is different.
171    #[allow(unreachable_code)]
172    core::arch::wasm32::unreachable()
173}