bobcat_panic/
lib.rs

1#![cfg_attr(not(feature = "std"), 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"))]
12use bobcat_host as impls;
13
14#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
15mod impls {
16    pub unsafe fn transient_store_bytes32(_: *const u8, _: *const u8) {}
17    pub unsafe fn transient_load_bytes32(_: *const u8, _: *const u8) {}
18}
19
20#[cfg(all(target_family = "wasm", target_os = "unknown"))]
21fn write_result_slice(s: &[u8]) {
22    unsafe { impls::write_result(s.as_ptr(), s.len()) }
23}
24
25//Panic(uint256)
26pub const PANIC_PREAMBLE_WORD: [u8; 32 + 4] = match const_hex::const_decode_to_array::<{ 32 + 4 }>(
27    b"4e487b710000000000000000000000000000000000000000000000000000000000000000",
28) {
29    Ok(v) => v,
30    Err(_) => panic!(),
31};
32
33//Error(string)
34pub const ERROR_PREAMBLE_OFFSET: [u8; 4 + 32] = match const_hex::const_decode_to_array::<{ 4 + 32 }>(
35    b"08c379a00000000000000000000000000000000000000000000000000000000000000020",
36) {
37    Ok(v) => v,
38    Err(_) => panic!(),
39};
40
41#[derive(Clone, Debug, PartialEq)]
42#[repr(u8)]
43pub enum PanicCodes {
44    DecodingError = 0,
45    OverflowOrUnderflow = 0x11,
46    DivByZero = 0x12,
47}
48
49#[cfg(all(target_family = "wasm", target_os = "unknown"))]
50pub fn panic_with_code(x: PanicCodes) -> ! {
51    let mut b = PANIC_PREAMBLE_WORD;
52    b[4 + 32 - 1] = x as u8;
53    write_result_slice(&b);
54    unsafe { impls::exit_early(1) }
55}
56
57#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
58pub fn panic_with_code(x: PanicCodes) -> ! {
59    panic!("panicked with code: {x:?}");
60}
61
62#[macro_export]
63macro_rules! define_panic_macros {
64    (
65        $(($error_msg:expr, $panic_code:ident)),* $(,)?
66    ) => {
67        $(
68            paste! {
69                #[macro_export]
70                macro_rules! [<panic_on_err_ $error_msg>] {
71                    ($e:expr, $msg:expr) => {{
72                        match $e {
73                            Some(v) => v,
74                            None => {
75                                #[cfg(feature = "msg-on-sdk-err")]
76                                panic!("{}: {}", $error_msg, $msg);
77                                #[cfg(not(feature = "msg-on-sdk-err"))]
78                                $crate::panic_with_code($crate::PanicCodes::$panic_code);
79                            }
80                        }
81                    }};
82                }
83            }
84        )*
85    };
86}
87
88define_panic_macros!(
89    ("overflow", OverflowOrUnderflow),
90    ("div_by_zero", DivByZero),
91);
92
93#[macro_export]
94macro_rules! panic_on_err_bad_decoding_bool {
95    ($msg:expr) => {{
96        #[cfg(feature = "msg-on-sdk-err")]
97        panic!("error decoding: {}", $msg);
98        #[cfg(not(feature = "msg-on-sdk-err"))]
99        $crate::panic_with_code($crate::PanicCodes::DecodingError);
100    }};
101    ($e:expr, $msg:expr) => {{
102        if !$e {
103            panic_on_err_bad_decoding_bool!($msg);
104        }
105    }};
106}
107
108#[allow(unused)]
109struct SliceWriter<'a>(&'a mut [u8], usize);
110
111impl<'a> Write for SliceWriter<'a> {
112    fn write_str(&mut self, s: &str) -> FmtResult {
113        let v = s.len().min(REVERT_BUF_SIZE.saturating_sub(self.1));
114        self.0[self.1..self.1 + v].copy_from_slice(&s.as_bytes()[..v]);
115        self.1 += v;
116        Ok(())
117    }
118}
119
120//uint256(keccak256(abi.encodePacked("bobcat.tracing.counter"))) - 1
121pub const SLOT_TRACING_COUNTER: [u8; 32] = [
122    0xad, 0x59, 0xcd, 0x5c, 0xcd, 0xcd, 0x00, 0x59, 0x2c, 0xd2, 0x06, 0xdc, 0x3b, 0xce, 0x83, 0xac,
123    0xe6, 0x1b, 0x8c, 0x80, 0xcb, 0xe9, 0xfd, 0x0d, 0x70, 0x09, 0x34, 0xba, 0x13, 0x78, 0x92, 0x22,
124];
125
126/// Revert buffer size that's used to write the panic. We can afford to
127/// use a large page here since a panic will consume all the gas anyway,
128/// and a user will see this during simulation hopefully.
129#[allow(unused)]
130const REVERT_BUF_SIZE: usize = 1024 * 2;
131
132#[cfg(all(feature = "panic-revert", feature = "panic-loc"))]
133compile_error!("panic-revert and panic-loc simultaneously enabled");
134
135#[cfg(all(feature = "panic-revert", feature = "panic-trace"))]
136compile_error!("panic-revert and panic-trace simultaneously enabled");
137
138#[cfg(all(feature = "panic-loc", feature = "panic-trace"))]
139compile_error!("panic-loc and panic-trace simultaneously enabled");
140
141#[derive(Debug, Clone, Copy, PartialEq)]
142#[repr(u8)]
143enum TracingDiscriminant {
144    Number = 0,
145    String = 1,
146}
147
148#[cfg(all(target_family = "wasm", target_os = "unknown"))]
149#[cfg_attr(all(feature = "panic", not(feature = "std")), panic_handler)]
150pub fn panic_handler(_msg: &core::panic::PanicInfo) -> ! {
151    #[cfg(feature = "console")]
152    {
153        let msg = alloc::format!("{_msg}");
154        unsafe { impls::log_txt(msg.as_ptr(), msg.len()) }
155    }
156    #[cfg(any(
157        feature = "panic-revert",
158        feature = "panic-loc",
159        feature = "panic-trace"
160    ))]
161    {
162        let mut buf = [0u8; REVERT_BUF_SIZE];
163        buf[..ERROR_PREAMBLE_OFFSET.len()].copy_from_slice(&ERROR_PREAMBLE_OFFSET);
164        let mut w = SliceWriter(&mut buf[ERROR_PREAMBLE_OFFSET.len() + 32..], 0);
165        #[cfg(feature = "panic-revert")]
166        {
167            write!(&mut w, "{_msg}").unwrap();
168        }
169        #[cfg(feature = "panic-loc")]
170        if let Some(loc) = _msg.location() {
171            write!(&mut w, "panic: {}:{}", loc.file(), loc.line()).unwrap();
172        } else {
173            write!(&mut w, "panic: unknown").unwrap();
174        }
175        #[cfg(feature = "panic-trace")]
176        {
177            let mut b = [0u8; 32];
178            unsafe { impls::transient_load_bytes32(SLOT_TRACING_COUNTER.as_ptr(), b.as_mut_ptr()) };
179            if b[0] == TracingDiscriminant::Number as u8 {
180                write!(
181                    &mut w,
182                    "trace no: {}",
183                    u32::from_be_bytes(b[1..32 - size_of::<u32>()].try_into().unwrap())
184                )
185            } else {
186                write!(&mut w, "trace str: {}", trace_key_to_str(&b))
187            }
188            .unwrap()
189        }
190        let len_msg = w.1;
191        let len_offset = ERROR_PREAMBLE_OFFSET.len();
192        buf[len_offset + 28..len_offset + 32].copy_from_slice(&(len_msg as u32).to_be_bytes());
193        let len_full = ERROR_PREAMBLE_OFFSET.len() + 32 + len_msg;
194        let len_padded = len_full + (32 - (len_full % 32)) % 32;
195        write_result_slice(&buf[..len_padded]);
196        unsafe { impls::exit_early(1) }
197    }
198    // Prefer the normal behaviour if the user hasn't opted into this
199    // feature. Maybe it's better to wipe out the revertdata if this happens,
200    // the other behaviour is different.
201    #[allow(unreachable_code)]
202    core::arch::wasm32::unreachable()
203}
204
205#[cfg(all(target_arch = "riscv32", target_os = "unknown"))]
206#[cfg_attr(all(feature = "panic", not(feature = "std")), panic_handler)]
207pub fn panic_handler(_: &core::panic::PanicInfo) -> ! {
208    // TODO: this needs to be fleshed out
209    unsafe {
210        core::arch::asm!("ebreak");
211    }
212    loop {}
213}
214
215pub fn bump() {
216    let p = SLOT_TRACING_COUNTER.as_ptr();
217    // We assume the execution counter here is always less than u32,
218    // so the upper part of the word could be dirty!
219    let mut b = [0u8; 32];
220    unsafe { impls::transient_load_bytes32(p, b.as_mut_ptr()) };
221    let v = u32::from_be_bytes(b[32 - size_of::<u32>()..].try_into().unwrap()) + 1;
222    b[32 - size_of::<u32>()..].copy_from_slice(&v.to_be_bytes());
223    b[0] = TracingDiscriminant::Number as u8;
224    unsafe { impls::transient_store_bytes32(p, b.as_ptr()) }
225}
226
227pub const fn trace_key_of_str(s: &str) -> [u8; 32] {
228    let bytes = s.as_bytes();
229    let mut b = [0u8; 32];
230    b[0] = TracingDiscriminant::String as u8;
231    let mut i = 0;
232    while i < bytes.len() && i < 31 {
233        b[i + 1] = bytes[i];
234        i += 1;
235    }
236    b
237}
238
239#[allow(unused)]
240const fn trace_key_to_str(b: &[u8; 32]) -> &str {
241    let mut i = 1;
242    while i < 32 {
243        if b[i] == 0 {
244            break;
245        }
246        i += 1;
247    }
248    unsafe {
249        let slice = core::slice::from_raw_parts(b.as_ptr().add(1), i - 1);
250        core::str::from_utf8_unchecked(slice)
251    }
252}
253
254pub fn trace(k: &str) {
255    let v = trace_key_of_str(k);
256    unsafe { impls::transient_store_bytes32(SLOT_TRACING_COUNTER.as_ptr(), v.as_ptr()) }
257}
258
259#[macro_export]
260macro_rules! trace_guard {
261    ($($body:tt)*) => {{
262        trace(concat!(file!(), ":", line!()));
263        $($body)*
264    }};
265}
266
267#[cfg(all(test, feature = "std"))]
268mod test {
269    use proptest::prelude::*;
270
271    use super::*;
272
273    proptest! {
274        #[test]
275        fn test_key_back_and_forth(x in proptest::string::string_regex("[0-9a-zA-Z]{0,31}").unwrap()) {
276            assert_eq!(&x, trace_key_to_str(&trace_key_of_str(&x)));
277        }
278    }
279}