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
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 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
108pub 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#[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 #[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 unsafe {
198 core::arch::asm!("ebreak");
199 }
200 loop {}
201}
202
203pub fn bump() {
204 let p = SLOT_TRACING_COUNTER.as_ptr();
205 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}