xrpl_escrow_stdlib/ctx/finish_result.rs
1/// Return type for Smart Feature entry points.
2///
3/// Wraps the `i32` the host inspects after a Smart Feature's WASM function
4/// returns:
5/// - positive (`> 0`) → the Smart Feature allows the native transaction to proceed
6/// - `0` → the Smart Feature blocks the transaction (no specific error)
7/// - negative (`< 0`) → the Smart Feature blocks and propagates an error code
8///
9/// Both `0` and negative values are rejections (per XLS-100: a return value
10/// greater than zero allows the operation, otherwise it is blocked). The
11/// distinction is purely for diagnostic purposes: `0` is a clean rejection, while a
12/// negative value carries an error code whose meaning is defined by the contract author.
13/// For example, `-6` might represent the result of a failed host call, but the same
14/// value could carry a different contract-defined meaning.
15///
16/// Construct with [`FinishResult::succeed`] / [`FinishResult::reject`] for the
17/// common cases, or [`FinishResult::succeed_with`] / [`FinishResult::reject_with`]
18/// for a custom code. `From<i32>` is also implemented so error codes from host
19/// calls (e.g. `e.code()`) can be propagated directly:
20/// `return e.code().into();`
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct FinishResult(i32);
23
24impl FinishResult {
25 /// Allow the Smart Feature to proceed (maps to `1`).
26 pub const fn succeed() -> Self {
27 Self(1)
28 }
29
30 /// Block the Smart Feature with no specific error code (maps to `0`).
31 pub const fn reject() -> Self {
32 Self(0)
33 }
34
35 /// Allow the Smart Feature with a custom positive code.
36 ///
37 /// `N` must be a positive compile-time constant; passing a non-positive value
38 /// is a compile-time error. The code is recorded in the `WasmReturnCode`
39 /// ledger metadata field and can be used for diagnostics.
40 ///
41 /// ```
42 /// # use xrpl_escrow_stdlib::FinishResult;
43 /// let result = FinishResult::succeed_with::<42>();
44 /// assert_eq!(i32::from(result), 42);
45 /// ```
46 pub fn succeed_with<const N: i32>() -> Self {
47 const { assert!(N > 0, "succeed_with requires a positive code") };
48 Self(N)
49 }
50
51 /// Block the Smart Feature with a custom non-positive error code.
52 ///
53 /// `N` must be non-positive (≤ 0) at compile time; passing a positive value
54 /// is a compile-time error. The code is recorded in the `WasmReturnCode`
55 /// ledger metadata field and can be used for diagnostics.
56 ///
57 /// ```
58 /// # use xrpl_escrow_stdlib::FinishResult;
59 /// let result = FinishResult::reject_with::<-5>();
60 /// assert_eq!(i32::from(result), -5);
61 /// ```
62 pub fn reject_with<const N: i32>() -> Self {
63 const { assert!(N <= 0, "reject_with requires a non-positive code") };
64 Self(N)
65 }
66}
67
68impl From<FinishResult> for i32 {
69 fn from(result: FinishResult) -> i32 {
70 result.0
71 }
72}
73
74impl From<i32> for FinishResult {
75 fn from(code: i32) -> Self {
76 Self(code)
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 // --- constructors ---
85
86 #[test]
87 fn succeed_maps_to_one() {
88 assert_eq!(i32::from(FinishResult::succeed()), 1);
89 }
90
91 #[test]
92 fn reject_maps_to_zero() {
93 assert_eq!(i32::from(FinishResult::reject()), 0);
94 }
95
96 // --- succeed_with / reject_with ---
97
98 #[test]
99 fn succeed_with_positive_uses_code() {
100 assert_eq!(i32::from(FinishResult::succeed_with::<42>()), 42);
101 }
102
103 #[test]
104 fn succeed_with_i32_max_uses_code() {
105 assert_eq!(
106 i32::from(FinishResult::succeed_with::<{ i32::MAX }>()),
107 i32::MAX
108 );
109 }
110
111 #[test]
112 fn reject_with_negative_uses_code() {
113 assert_eq!(i32::from(FinishResult::reject_with::<-5>()), -5);
114 }
115
116 #[test]
117 fn reject_with_i32_min_uses_code() {
118 assert_eq!(
119 i32::from(FinishResult::reject_with::<{ i32::MIN }>()),
120 i32::MIN
121 );
122 }
123
124 #[test]
125 fn reject_with_zero_uses_code() {
126 assert_eq!(i32::from(FinishResult::reject_with::<0>()), 0);
127 }
128
129 // --- From<i32> (used for error-code propagation: `e.code().into()`) ---
130
131 #[test]
132 fn from_positive_i32_roundtrips() {
133 assert_eq!(i32::from(FinishResult::from(7)), 7);
134 }
135
136 #[test]
137 fn from_zero_roundtrips() {
138 assert_eq!(i32::from(FinishResult::from(0)), 0);
139 }
140
141 #[test]
142 fn from_negative_i32_roundtrips() {
143 assert_eq!(i32::from(FinishResult::from(-3)), -3);
144 }
145
146 #[test]
147 fn from_i32_preserves_error_code_exactly() {
148 // Simulates: `return e.code().into();` where e.code() is a negative error code.
149 let host_error_code: i32 = -15; // INVALID_PARAMS
150 assert_eq!(
151 i32::from(FinishResult::from(host_error_code)),
152 host_error_code
153 );
154 }
155}