Skip to main content

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