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