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
13pub 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
21pub 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
110pub 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#[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 #[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 unsafe {
202 core::arch::asm!("ebreak");
203 }
204 loop {}
205}
206
207pub fn bump() {
208 let p = SLOT_TRACING_COUNTER.as_ptr();
209 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}