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 (@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
119pub 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#[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 #[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 unsafe {
211 core::arch::asm!("ebreak");
212 }
213 loop {}
214}
215
216pub fn bump() {
217 let p = SLOT_TRACING_COUNTER.as_ptr();
218 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}