Skip to main content

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}