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
11#[cfg(all(target_family = "wasm", target_os = "unknown"))]
12use bobcat_host as impls;
13
14#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
15mod impls {
16 pub unsafe fn transient_store_bytes32(_: *const u8, _: *const u8) {}
17 pub unsafe fn transient_load_bytes32(_: *const u8, _: *const u8) {}
18}
19
20#[cfg(all(target_family = "wasm", target_os = "unknown"))]
21fn write_result_slice(s: &[u8]) {
22 unsafe { impls::write_result(s.as_ptr(), s.len()) }
23}
24
25pub const PANIC_PREAMBLE_WORD: [u8; 32 + 4] = match const_hex::const_decode_to_array::<{ 32 + 4 }>(
27 b"4e487b710000000000000000000000000000000000000000000000000000000000000000",
28) {
29 Ok(v) => v,
30 Err(_) => panic!(),
31};
32
33pub const ERROR_PREAMBLE_OFFSET: [u8; 4 + 32] = match const_hex::const_decode_to_array::<{ 4 + 32 }>(
35 b"08c379a00000000000000000000000000000000000000000000000000000000000000020",
36) {
37 Ok(v) => v,
38 Err(_) => panic!(),
39};
40
41#[derive(Clone, Debug, PartialEq)]
42#[repr(u8)]
43pub enum PanicCodes {
44 DecodingError = 0,
45 OverflowOrUnderflow = 0x11,
46 DivByZero = 0x12,
47}
48
49#[cfg(all(target_family = "wasm", target_os = "unknown"))]
50pub fn panic_with_code(x: PanicCodes) -> ! {
51 let mut b = PANIC_PREAMBLE_WORD;
52 b[4 + 32 - 1] = x as u8;
53 write_result_slice(&b);
54 unsafe { impls::exit_early(1) }
55}
56
57#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
58pub fn panic_with_code(x: PanicCodes) -> ! {
59 panic!("panicked with code: {x:?}");
60}
61
62#[macro_export]
63macro_rules! define_panic_macros {
64 (
65 $(($error_msg:expr, $panic_code:ident)),* $(,)?
66 ) => {
67 $(
68 paste! {
69 #[macro_export]
70 macro_rules! [<panic_on_err_ $error_msg>] {
71 ($e:expr, $msg:expr) => {{
72 match $e {
73 Some(v) => v,
74 None => {
75 #[cfg(feature = "msg-on-sdk-err")]
76 panic!("{}: {}", $error_msg, $msg);
77 #[cfg(not(feature = "msg-on-sdk-err"))]
78 $crate::panic_with_code($crate::PanicCodes::$panic_code);
79 }
80 }
81 }};
82 }
83 }
84 )*
85 };
86}
87
88define_panic_macros!(
89 ("overflow", OverflowOrUnderflow),
90 ("div_by_zero", DivByZero),
91);
92
93#[macro_export]
94macro_rules! panic_on_err_bad_decoding_bool {
95 ($msg:expr) => {{
96 #[cfg(feature = "msg-on-sdk-err")]
97 panic!("error decoding: {}", $msg);
98 #[cfg(not(feature = "msg-on-sdk-err"))]
99 $crate::panic_with_code($crate::PanicCodes::DecodingError);
100 }};
101 ($e:expr, $msg:expr) => {{
102 if !$e {
103 panic_on_err_bad_decoding_bool!($msg);
104 }
105 }};
106}
107
108#[allow(unused)]
109struct SliceWriter<'a>(&'a mut [u8], usize);
110
111impl<'a> Write for SliceWriter<'a> {
112 fn write_str(&mut self, s: &str) -> FmtResult {
113 let v = s.len().min(REVERT_BUF_SIZE.saturating_sub(self.1));
114 self.0[self.1..self.1 + v].copy_from_slice(&s.as_bytes()[..v]);
115 self.1 += v;
116 Ok(())
117 }
118}
119
120pub const SLOT_TRACING_COUNTER: [u8; 32] = [
122 0xad, 0x59, 0xcd, 0x5c, 0xcd, 0xcd, 0x00, 0x59, 0x2c, 0xd2, 0x06, 0xdc, 0x3b, 0xce, 0x83, 0xac,
123 0xe6, 0x1b, 0x8c, 0x80, 0xcb, 0xe9, 0xfd, 0x0d, 0x70, 0x09, 0x34, 0xba, 0x13, 0x78, 0x92, 0x22,
124];
125
126#[allow(unused)]
130const REVERT_BUF_SIZE: usize = 1024 * 2;
131
132#[cfg(all(feature = "panic-revert", feature = "panic-loc"))]
133compile_error!("panic-revert and panic-loc simultaneously enabled");
134
135#[cfg(all(feature = "panic-revert", feature = "panic-trace"))]
136compile_error!("panic-revert and panic-trace simultaneously enabled");
137
138#[cfg(all(feature = "panic-loc", feature = "panic-trace"))]
139compile_error!("panic-loc and panic-trace simultaneously enabled");
140
141#[derive(Debug, Clone, Copy, PartialEq)]
142#[repr(u8)]
143enum TracingDiscriminant {
144 Number = 0,
145 String = 1,
146}
147
148#[cfg(all(target_family = "wasm", target_os = "unknown"))]
149#[cfg_attr(all(feature = "panic", not(feature = "std")), panic_handler)]
150pub fn panic_handler(_msg: &core::panic::PanicInfo) -> ! {
151 #[cfg(feature = "console")]
152 {
153 let msg = alloc::format!("{_msg}");
154 unsafe { impls::log_txt(msg.as_ptr(), msg.len()) }
155 }
156 #[cfg(any(
157 feature = "panic-revert",
158 feature = "panic-loc",
159 feature = "panic-trace"
160 ))]
161 {
162 let mut buf = [0u8; REVERT_BUF_SIZE];
163 buf[..ERROR_PREAMBLE_OFFSET.len()].copy_from_slice(&ERROR_PREAMBLE_OFFSET);
164 let mut w = SliceWriter(&mut buf[ERROR_PREAMBLE_OFFSET.len() + 32..], 0);
165 #[cfg(feature = "panic-revert")]
166 {
167 write!(&mut w, "{_msg}").unwrap();
168 }
169 #[cfg(feature = "panic-loc")]
170 if let Some(loc) = _msg.location() {
171 write!(&mut w, "panic: {}:{}", loc.file(), loc.line()).unwrap();
172 } else {
173 write!(&mut w, "panic: unknown").unwrap();
174 }
175 #[cfg(feature = "panic-trace")]
176 {
177 let mut b = [0u8; 32];
178 unsafe { impls::transient_load_bytes32(SLOT_TRACING_COUNTER.as_ptr(), b.as_mut_ptr()) };
179 if b[0] == TracingDiscriminant::Number as u8 {
180 write!(
181 &mut w,
182 "trace no: {}",
183 u32::from_be_bytes(b[1..32 - size_of::<u32>()].try_into().unwrap())
184 )
185 } else {
186 write!(&mut w, "trace str: {}", trace_key_to_str(&b))
187 }
188 .unwrap()
189 }
190 let len_msg = w.1;
191 let len_offset = ERROR_PREAMBLE_OFFSET.len();
192 buf[len_offset + 28..len_offset + 32].copy_from_slice(&(len_msg as u32).to_be_bytes());
193 let len_full = ERROR_PREAMBLE_OFFSET.len() + 32 + len_msg;
194 let len_padded = len_full + (32 - (len_full % 32)) % 32;
195 write_result_slice(&buf[..len_padded]);
196 unsafe { impls::exit_early(1) }
197 }
198 #[allow(unreachable_code)]
202 core::arch::wasm32::unreachable()
203}
204
205#[cfg(all(target_arch = "riscv32", target_os = "unknown"))]
206#[cfg_attr(all(feature = "panic", not(feature = "std")), panic_handler)]
207pub fn panic_handler(_: &core::panic::PanicInfo) -> ! {
208 unsafe {
210 core::arch::asm!("ebreak");
211 }
212 loop {}
213}
214
215pub fn bump() {
216 let p = SLOT_TRACING_COUNTER.as_ptr();
217 let mut b = [0u8; 32];
220 unsafe { impls::transient_load_bytes32(p, b.as_mut_ptr()) };
221 let v = u32::from_be_bytes(b[32 - size_of::<u32>()..].try_into().unwrap()) + 1;
222 b[32 - size_of::<u32>()..].copy_from_slice(&v.to_be_bytes());
223 b[0] = TracingDiscriminant::Number as u8;
224 unsafe { impls::transient_store_bytes32(p, b.as_ptr()) }
225}
226
227pub const fn trace_key_of_str(s: &str) -> [u8; 32] {
228 let bytes = s.as_bytes();
229 let mut b = [0u8; 32];
230 b[0] = TracingDiscriminant::String as u8;
231 let mut i = 0;
232 while i < bytes.len() && i < 31 {
233 b[i + 1] = bytes[i];
234 i += 1;
235 }
236 b
237}
238
239#[allow(unused)]
240const fn trace_key_to_str(b: &[u8; 32]) -> &str {
241 let mut i = 1;
242 while i < 32 {
243 if b[i] == 0 {
244 break;
245 }
246 i += 1;
247 }
248 unsafe {
249 let slice = core::slice::from_raw_parts(b.as_ptr().add(1), i - 1);
250 core::str::from_utf8_unchecked(slice)
251 }
252}
253
254pub fn trace(k: &str) {
255 let v = trace_key_of_str(k);
256 unsafe { impls::transient_store_bytes32(SLOT_TRACING_COUNTER.as_ptr(), v.as_ptr()) }
257}
258
259#[macro_export]
260macro_rules! trace_guard {
261 ($($body:tt)*) => {{
262 trace(concat!(file!(), ":", line!()));
263 $($body)*
264 }};
265}
266
267#[cfg(all(test, feature = "std"))]
268mod test {
269 use proptest::prelude::*;
270
271 use super::*;
272
273 proptest! {
274 #[test]
275 fn test_key_back_and_forth(x in proptest::string::string_regex("[0-9a-zA-Z]{0,31}").unwrap()) {
276 assert_eq!(&x, trace_key_to_str(&trace_key_of_str(&x)));
277 }
278 }
279}