1use crate::exn::Exn;
31#[cfg(feature = "alloc")]
32use crate::exn::{CallingError, FaultString, LibraryExn};
33use crate::xdr::*;
34use thruput_exn::RawExceptionRef;
35
36impl crate::re_throw::ReThrow for RawExceptionRef<'_> {
40 #[inline]
41 #[cfg_attr(not(target_arch = "riscv32"), track_caller)]
42 fn re_throw(self) -> ! {
43 #[cfg(all(not(target_arch = "riscv32"), feature = "std"))]
44 crate::contract_op::abort_breadcrumb("Re-throwing", "Location", &self);
45
46 crate::contract_op::contract_abort(self.wire())
47 }
48}
49
50impl From<RawExceptionRef<'_>> for RawException {
54 fn from(exn: RawExceptionRef<'_>) -> Self {
55 RawException::own_wire(&exn)
56 }
57}
58
59fn fmt_exn(
61 f: &mut core::fmt::Formatter,
62 name: &str,
63 selector: u64,
64 payload: &[u8],
65) -> core::fmt::Result {
66 write!(
67 f,
68 "{name} {{ selector: 0x{selector:016X}, payload: {} bytes",
69 payload.len(),
70 )?;
71 #[cfg(not(target_arch = "riscv32"))]
72 write!(f, ", hex: {}", crate::hex::hex_encode(&payload))?;
73 write!(f, " }}")
74}
75
76pub struct RawException {
91 selector: u64,
92 wire: crate::call::Buf,
95 payload_start: usize,
96}
97
98impl RawException {
99 fn own_wire(exn: &RawExceptionRef<'_>) -> Self {
105 let selector = exn.selector();
106 let payload_start = exn.payload_start();
107 let wire = exn.wire();
108 #[cfg(feature = "alloc")]
109 let wire = wire.to_vec();
110 #[cfg(not(feature = "alloc"))]
111 let wire = {
112 let mut buf = crate::call::Buf::new();
113 if buf.extend_from_slice(wire).is_err() {
114 crate::contract_op::contract_abort(wire);
118 }
119 buf
120 };
121 Self {
122 selector,
123 wire,
124 payload_start,
125 }
126 }
127
128 #[inline]
129 pub fn selector(&self) -> u64 {
130 self.selector
131 }
132
133 #[inline]
135 pub fn payload(&self) -> &[u8] {
136 &self.wire.as_slice()[self.payload_start..]
137 }
138
139 pub(crate) fn as_ref_exn(&self) -> RawExceptionRef<'_> {
143 RawExceptionRef::from_parts(
144 self.selector,
145 self.wire.as_slice(),
146 self.payload_start,
147 )
148 }
149
150 pub fn try_as<T: Exn + ReadXDROwned>(&self) -> Option<T> {
151 self.as_ref_exn().try_as()
152 }
153
154 pub fn try_decode<T: Exn + ReadXDROwned>(
156 &self,
157 ) -> Option<Result<T, ReadError>> {
158 self.as_ref_exn().try_decode()
159 }
160
161 #[inline]
163 #[cfg_attr(not(target_arch = "riscv32"), track_caller)]
164 pub fn re_throw(&self) -> ! {
165 #[cfg(all(not(target_arch = "riscv32"), feature = "std"))]
166 crate::contract_op::abort_breadcrumb("Re-throwing", "Location", self);
167
168 crate::contract_op::contract_abort(self.wire.as_ref())
169 }
170
171 #[cfg(feature = "alloc")]
173 pub fn new(selector: u64, payload: &[u8]) -> Self {
174 let mut wire = crate::internal::encode_xdr(&selector)
175 .expect("u64 CXS-encode cannot fail under alloc");
176 let payload_start = wire.len();
177 wire.extend_from_slice(payload);
178 Self {
179 selector,
180 wire,
181 payload_start,
182 }
183 }
184
185 #[cfg(feature = "alloc")]
188 pub fn from_abort_buf(bytes: &[u8]) -> Result<Self, LibraryExn> {
189 let exn = RawExceptionRef::parse(bytes).map_err(|e| {
190 LibraryExn::Calling(CallingError::DecodingException(
191 FaultString::from_display(e),
192 ))
193 })?;
194 Ok(exn.to_owned())
195 }
196}
197
198impl PartialEq for RawException {
199 fn eq(&self, other: &Self) -> bool {
200 self.selector == other.selector && self.payload() == other.payload()
201 }
202}
203
204impl core::fmt::Debug for RawException {
205 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
206 fmt_exn(f, "RawException", self.selector, self.payload())
207 }
208}
209
210impl crate::re_throw::ReThrow for RawException {
211 #[cfg_attr(not(target_arch = "riscv32"), track_caller)]
212 fn re_throw(self) -> ! {
213 RawException::re_throw(&self)
214 }
215}
216
217#[cfg(all(test, feature = "alloc"))]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn parse_extracts_selector_and_payload() {
223 let mut wire = crate::internal::encode_xdr(&0xABCDu64).unwrap();
225 wire.extend_from_slice(b"payload-bytes");
226
227 let exn = RawExceptionRef::parse(&wire).unwrap();
228
229 assert_eq!(exn.selector(), 0xABCD);
230 assert_eq!(exn.payload(), b"payload-bytes");
231 }
232
233 #[test]
234 fn parse_rejects_garbage() {
235 assert!(RawExceptionRef::parse(&[]).is_err());
237 }
238
239 #[test]
240 fn new_roundtrips_selector_and_payload() {
241 let c = RawException::new(0x1234, b"hello");
242 assert_eq!(c.selector(), 0x1234);
243 assert_eq!(c.payload(), b"hello");
244 }
245
246 #[test]
247 fn from_abort_buf_parses_wire_format() {
248 let mut wire = crate::internal::encode_xdr(&0xABCDu64).unwrap();
250 wire.extend_from_slice(b"payload-bytes");
251 let c = RawException::from_abort_buf(&wire).unwrap();
252 assert_eq!(c.selector(), 0xABCD);
253 assert_eq!(c.payload(), b"payload-bytes");
254 }
255
256 #[test]
257 fn holds_oversized_payload_without_truncation_under_alloc() {
258 let big = [0xABu8; crate::internal::SCRATCH_MAX + 1];
259 let c = RawException::new(0x1234, &big);
260 assert_eq!(c.payload(), &big[..]);
261 }
262
263 #[test]
264 fn eq_compares_selector_and_payload_bytes() {
265 assert_eq!(RawException::new(1, b"x"), RawException::new(1, b"x"));
266 assert_ne!(RawException::new(1, b"x"), RawException::new(2, b"x"));
267 assert_ne!(RawException::new(1, b"x"), RawException::new(1, b"y"));
268 }
269
270 #[test]
271 fn try_as_decodes_matching_selector_and_payload() {
272 let payload =
273 crate::internal::encode_xdr(&LibraryExn::NonNegativeIntOverflow)
274 .unwrap();
275 let c = RawException::new(LibraryExn::SELECTOR, &payload);
276
277 assert_eq!(
278 c.try_as::<LibraryExn>(),
279 Some(LibraryExn::NonNegativeIntOverflow)
280 );
281 }
282
283 #[test]
284 fn try_as_returns_none_for_mismatched_selector() {
285 let payload =
286 crate::internal::encode_xdr(&LibraryExn::NonNegativeIntOverflow)
287 .unwrap();
288 let c = RawException::new(!LibraryExn::SELECTOR, &payload);
290
291 assert_eq!(c.try_as::<LibraryExn>(), None);
292 }
293
294 #[test]
295 fn try_as_returns_none_for_undecodable_payload() {
296 let payload =
297 crate::internal::encode_xdr(&LibraryExn::NonNegativeIntOverflow)
298 .unwrap();
299 let truncated = &payload[..payload.len().saturating_sub(1)];
301 let c = RawException::new(LibraryExn::SELECTOR, truncated);
302
303 assert_eq!(c.try_as::<LibraryExn>(), None);
304 }
305}