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