1use crate::host::Error::PointerOutOfBounds;
2use crate::host::trace::trace_num;
3use crate::host::{Error, Result, Result::Err, Result::Ok};
4
5pub const UNIMPLEMENTED: i32 = -1;
7pub const FIELD_NOT_FOUND: i32 = -2;
9pub const BUFFER_TOO_SMALL: i32 = -3;
11pub const NO_ARRAY: i32 = -4;
13pub const NOT_LEAF_FIELD: i32 = -5;
15pub const LOCATOR_MALFORMED: i32 = -6;
17pub const SLOT_OUT_RANGE: i32 = -7;
19pub const SLOTS_FULL: i32 = -8;
21pub const EMPTY_SLOT: i32 = -9;
23pub const LEDGER_OBJ_NOT_FOUND: i32 = -10;
25pub const OUT_OF_TRANSFER_LIMIT: i32 = -11;
27pub const DATA_FIELD_TOO_LARGE: i32 = -12;
29pub const POINTER_OUT_OF_BOUNDS: i32 = -13;
31pub const NO_MEM_EXPORTED: i32 = -14;
33pub const INVALID_PARAMS: i32 = -15;
35pub const INVALID_ACCOUNT: i32 = -16;
37pub const INVALID_FIELD: i32 = -17;
39pub const INDEX_OUT_OF_BOUNDS: i32 = -18;
41pub const INVALID_FLOAT_INPUT: i32 = -19;
43pub const INVALID_FLOAT_COMPUTATION: i32 = -20;
45
46pub const INVALID_DECODING: i32 = i32::MIN;
51
52#[cfg(any(test, feature = "test-host-bindings"))]
58pub const SOME_ERROR: i32 = NO_MEM_EXPORTED;
59
60#[inline(always)]
83pub fn match_result_code<F, T>(result_code: i32, on_success: F) -> Result<T>
84where
85 F: FnOnce() -> T,
86{
87 match result_code {
88 code if code >= 0 => Ok(on_success()),
89 code => Err(Error::from_code(code)),
90 }
91}
92
93#[inline(always)]
121pub fn match_result_code_optional<F, T>(result_code: i32, on_success: F) -> Result<Option<T>>
122where
123 F: FnOnce() -> Option<T>,
124{
125 match result_code {
126 code if code >= 0 => Ok(on_success()),
127 code => Err(Error::from_code(code)),
128 }
129}
130
131#[inline]
161pub fn match_result_code_with_expected_bytes<F, T>(
162 result_code: i32,
163 expected_num_bytes: usize,
164 on_success: F,
165) -> Result<T>
166where
167 F: FnOnce() -> T,
168{
169 match result_code {
170 code if code as usize == expected_num_bytes => Ok(on_success()),
171 code if code >= 0 => {
173 panic!(
174 "internal invariant violated: host wrote {code} bytes but {expected_num_bytes} were expected"
175 );
176 }
177 code => Err(Error::from_code(code)),
178 }
179}
180
181#[inline]
212pub fn match_result_code_with_expected_bytes_optional<F, T>(
213 result_code: i32,
214 expected_num_bytes: usize,
215 on_success: F,
216) -> Result<Option<T>>
217where
218 F: FnOnce() -> Option<T>,
219{
220 match result_code {
221 code if code as usize == expected_num_bytes => Ok(on_success()),
222 code if code == FIELD_NOT_FOUND => Ok(None),
223 code if code >= 0 => {
225 trace_num(
226 "Byte array was expected to have this many bytes: ",
227 expected_num_bytes as i64,
228 );
229 trace_num("Byte array had this many bytes: ", code as i64);
230 Err(PointerOutOfBounds)
231 }
232 code => {
234 trace_num("Encountered error_code:", code as i64);
235 Err(Error::from_code(code))
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::host::Error;
244 use crate::host::host_bindings_trait::MockHostBindings;
245 use crate::host::setup_mock;
246 use mockall::predicate::always;
247
248 #[test]
249 fn test_match_result_code_success_positive() {
250 let result = match_result_code(5, || "success");
251 assert!(result.is_ok());
252 assert_eq!(result.unwrap(), "success");
253 }
254
255 #[test]
256 fn test_match_result_code_success_zero() {
257 let result = match_result_code(0, || "zero_success");
258 assert!(result.is_ok());
259 assert_eq!(result.unwrap(), "zero_success");
260 }
261
262 #[test]
263 fn test_match_result_code_error_negative() {
264 let result = match_result_code(SOME_ERROR, || "should_not_execute");
265 assert_eq!(result.err().unwrap().code(), SOME_ERROR);
266 }
267
268 #[test]
269 fn test_match_result_code_error_field_not_found() {
270 let result = match_result_code(FIELD_NOT_FOUND, || "should_not_execute");
271 assert!(result.is_err());
272 assert_eq!(result.err().unwrap().code(), FIELD_NOT_FOUND);
273 }
274
275 #[test]
276 fn test_match_result_code_closure_not_called_on_error() {
277 let mut called = false;
278 let _result = match_result_code(BUFFER_TOO_SMALL, || {
279 called = true;
280 "should_not_execute"
281 });
282 assert!(!called);
283 }
284
285 #[test]
286 fn test_match_result_code_optional_success_some() {
287 let result = match_result_code_optional(10, || Some("data"));
288 assert!(result.is_ok());
289 assert_eq!(result.unwrap(), Some("data"));
290 }
291
292 #[test]
293 fn test_match_result_code_optional_success_none() {
294 let result = match_result_code_optional(0, || None::<&str>);
295 assert!(result.is_ok());
296 assert_eq!(result.unwrap(), None);
297 }
298
299 #[test]
300 fn test_match_result_code_optional_error() {
301 let result = match_result_code_optional(NO_ARRAY, || Some("should_not_execute"));
302 assert!(result.is_err());
303 assert_eq!(result.err().unwrap().code(), NO_ARRAY);
304 }
305
306 #[test]
307 fn test_match_result_code_with_expected_bytes_exact_match() {
308 let expected_bytes = 32;
309 let result = match_result_code_with_expected_bytes(32, expected_bytes, || "exact_match");
310 assert!(result.is_ok());
311 assert_eq!(result.unwrap(), "exact_match");
312 }
313
314 #[test]
315 #[should_panic]
316 fn test_match_result_code_with_expected_bytes_mismatch() {
317 let expected_bytes = 32;
318 let _ = match_result_code_with_expected_bytes(16, expected_bytes, || "should_not_execute");
321 }
322
323 #[test]
324 fn test_match_result_code_with_expected_bytes_negative_error() {
325 let expected_bytes = 32;
326 let result = match_result_code_with_expected_bytes(
327 INVALID_PARAMS,
328 expected_bytes,
329 || "should_not_execute",
330 );
331 assert!(result.is_err());
332 assert_eq!(result.err().unwrap().code(), INVALID_PARAMS);
333 }
334
335 #[test]
336 fn test_match_result_code_with_expected_bytes_zero_bytes() {
337 let expected_bytes = 0;
338 let result = match_result_code_with_expected_bytes(0, expected_bytes, || "zero_bytes");
339 assert!(result.is_ok());
340 assert_eq!(result.unwrap(), "zero_bytes");
341 }
342
343 #[test]
344 fn test_match_result_code_with_expected_bytes_optional_exact_match_some() {
345 let expected_bytes = 20;
346 let result =
347 match_result_code_with_expected_bytes_optional(20, expected_bytes, || Some("data"));
348 assert!(result.is_ok());
349 assert_eq!(result.unwrap(), Some("data"));
350 }
351
352 #[test]
353 fn test_match_result_code_with_expected_bytes_optional_exact_match_none() {
354 let expected_bytes = 20;
355 let result =
356 match_result_code_with_expected_bytes_optional(20, expected_bytes, || None::<&str>);
357 assert!(result.is_ok());
358 assert_eq!(result.unwrap(), None);
359 }
360
361 #[test]
362 fn test_match_result_code_with_expected_bytes_optional_field_not_found() {
363 let expected_bytes = 20;
364 let result =
365 match_result_code_with_expected_bytes_optional(FIELD_NOT_FOUND, expected_bytes, || {
366 Some("should_not_execute")
367 });
368 assert!(result.is_ok());
369 assert_eq!(result.unwrap(), None);
370 }
371
372 #[test]
373 fn test_match_result_code_with_expected_bytes_optional_byte_mismatch() {
374 let mut mock = MockHostBindings::new();
375
376 mock.expect_trace()
378 .with(always(), always(), always(), always(), always())
379 .returning(|_, _, _, _, _| ())
380 .times(2);
381
382 let _guard = setup_mock(mock);
383
384 let expected_bytes = 20;
385 let result = match_result_code_with_expected_bytes_optional(15, expected_bytes, || {
386 Some("should_not_execute")
387 });
388 assert!(result.is_err());
389 assert_eq!(result.err().unwrap().code(), POINTER_OUT_OF_BOUNDS);
390 }
391
392 #[test]
393 fn test_match_result_code_with_expected_bytes_optional_other_error() {
394 let mut mock = MockHostBindings::new();
395
396 mock.expect_trace()
398 .with(always(), always(), always(), always(), always())
399 .returning(|_, _, _, _, _| ());
400
401 let _guard = setup_mock(mock);
402
403 let expected_bytes = 20;
404 let result =
405 match_result_code_with_expected_bytes_optional(INVALID_ACCOUNT, expected_bytes, || {
406 Some("should_not_execute")
407 });
408 assert!(result.is_err());
409 assert_eq!(result.err().unwrap().code(), INVALID_ACCOUNT);
410 }
411
412 #[test]
413 fn test_match_result_code_with_expected_bytes_optional_zero_bytes() {
414 let expected_bytes = 0;
415 let result =
416 match_result_code_with_expected_bytes_optional(0, expected_bytes, || Some("zero_data"));
417 assert!(result.is_ok());
418 assert_eq!(result.unwrap(), Some("zero_data"));
419 }
420
421 #[test]
422 fn test_all_error_constants_are_negative() {
423 let error_codes = [
424 UNIMPLEMENTED,
425 FIELD_NOT_FOUND,
426 BUFFER_TOO_SMALL,
427 NO_ARRAY,
428 NOT_LEAF_FIELD,
429 LOCATOR_MALFORMED,
430 SLOT_OUT_RANGE,
431 SLOTS_FULL,
432 EMPTY_SLOT,
433 LEDGER_OBJ_NOT_FOUND,
434 OUT_OF_TRANSFER_LIMIT,
435 DATA_FIELD_TOO_LARGE,
436 POINTER_OUT_OF_BOUNDS,
437 NO_MEM_EXPORTED,
438 INVALID_PARAMS,
439 INVALID_ACCOUNT,
440 INVALID_FIELD,
441 INDEX_OUT_OF_BOUNDS,
442 INVALID_FLOAT_INPUT,
443 INVALID_FLOAT_COMPUTATION,
444 INVALID_DECODING,
445 ];
446
447 for &code in &error_codes {
448 assert!(code < 0, "Error code {} should be negative", code);
449 }
450 }
451
452 #[test]
453 fn test_error_constants_are_unique() {
454 let error_codes = [
455 UNIMPLEMENTED,
456 FIELD_NOT_FOUND,
457 BUFFER_TOO_SMALL,
458 NO_ARRAY,
459 NOT_LEAF_FIELD,
460 LOCATOR_MALFORMED,
461 SLOT_OUT_RANGE,
462 SLOTS_FULL,
463 EMPTY_SLOT,
464 LEDGER_OBJ_NOT_FOUND,
465 OUT_OF_TRANSFER_LIMIT,
466 DATA_FIELD_TOO_LARGE,
467 POINTER_OUT_OF_BOUNDS,
468 NO_MEM_EXPORTED,
469 INVALID_PARAMS,
470 INVALID_ACCOUNT,
471 INVALID_FIELD,
472 INDEX_OUT_OF_BOUNDS,
473 INVALID_FLOAT_INPUT,
474 INVALID_FLOAT_COMPUTATION,
475 INVALID_DECODING,
476 ];
477
478 for (i, &code1) in error_codes.iter().enumerate() {
480 for (j, &code2) in error_codes.iter().enumerate() {
481 if i != j {
482 assert_ne!(
483 code1, code2,
484 "Error codes at indices {} and {} are not unique: {} == {}",
485 i, j, code1, code2
486 );
487 }
488 }
489 }
490 }
491
492 #[test]
493 fn test_error_from_code_roundtrip() {
494 let test_codes = [
495 UNIMPLEMENTED,
496 FIELD_NOT_FOUND,
497 BUFFER_TOO_SMALL,
498 NO_ARRAY,
499 NOT_LEAF_FIELD,
500 LOCATOR_MALFORMED,
501 SLOT_OUT_RANGE,
502 SLOTS_FULL,
503 EMPTY_SLOT,
504 LEDGER_OBJ_NOT_FOUND,
505 OUT_OF_TRANSFER_LIMIT,
506 DATA_FIELD_TOO_LARGE,
507 POINTER_OUT_OF_BOUNDS,
508 NO_MEM_EXPORTED,
509 INVALID_PARAMS,
510 INVALID_ACCOUNT,
511 INVALID_FIELD,
512 INDEX_OUT_OF_BOUNDS,
513 INVALID_FLOAT_INPUT,
514 INVALID_FLOAT_COMPUTATION,
515 INVALID_DECODING,
516 ];
517
518 for &code in &test_codes {
519 let error = Error::from_code(code);
520 assert_eq!(
521 error.code(),
522 code,
523 "Error code roundtrip failed for code {}",
524 code
525 );
526 }
527 }
528
529 #[test]
530 fn test_closure_execution_count() {
531 let mut execution_count = 0;
532 let closure = || {
533 execution_count += 1;
534 "executed"
535 };
536
537 let _result = match_result_code(1, closure);
539 assert_eq!(execution_count, 1);
540
541 execution_count = 0;
543 let closure = || {
544 execution_count += 1;
545 "should_not_execute"
546 };
547 let _result = match_result_code(SOME_ERROR, closure);
548 assert_eq!(execution_count, 0);
549 }
550
551 #[test]
552 fn test_large_positive_result_codes() {
553 let large_positive = 1024;
555 let result = match_result_code(large_positive, || "large_success");
556 assert!(result.is_ok());
557 assert_eq!(result.unwrap(), "large_success");
558
559 let result = match_result_code_with_expected_bytes(
561 large_positive,
562 large_positive as usize,
563 || "exact_large",
564 );
565 assert!(result.is_ok());
566 assert_eq!(result.unwrap(), "exact_large");
567 }
568
569 #[test]
570 fn test_edge_case_usize_conversion() {
571 let result_code = 255i32;
573 let expected_bytes = 255usize;
574 let result =
575 match_result_code_with_expected_bytes(result_code, expected_bytes, || "converted");
576 assert!(result.is_ok());
577 assert_eq!(result.unwrap(), "converted");
578 }
579}