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