xrpl_wasm_stdlib/host/
mod.rs

1//! Host bindings and utilities exposed to WASM smart contracts.
2//!
3//! This module exposes the low-level host ABI plus typed primitives (Result, Error, helpers).
4//! Most users should prefer the safe, high-level APIs in [`crate::core`], which wrap these bindings.
5//!
6//! ## Float Operations for Fungible Tokens (IOUs)
7//!
8//! The host provides float arithmetic functions for XRPL's fungible token amounts.
9//! These operations use rippled's Number class via FFI to ensure exact consensus compatibility:
10//!
11//! - `float_from_int` / `float_from_uint` - Convert integers to float format
12//! - `float_set` - Create float from exponent and mantissa
13//! - `float_add` / `float_subtract` / `float_multiply` / `float_divide` - Arithmetic
14//! - `float_pow` / `float_root` / `float_log` - Mathematical functions
15//! - `float_compare` - Comparison operations
16//!
17//! All operations support explicit rounding modes (0=ToNearest, 1=TowardsZero, 2=Downward, 3=Upward).
18//!
19//! See the host_bindings documentation for detailed function signatures.
20
21pub mod error_codes;
22pub mod field_helpers;
23pub mod trace;
24
25// Float rounding mode constants (same as in host_bindings.rs)
26#[allow(unused)]
27pub const FLOAT_ROUNDING_MODES_TO_NEAREST: i32 = 0;
28#[allow(unused)]
29pub const FLOAT_ROUNDING_MODES_TOWARDS_ZERO: i32 = 1;
30#[allow(unused)]
31pub const FLOAT_ROUNDING_MODES_DOWNWARD: i32 = 2;
32#[allow(unused)]
33pub const FLOAT_ROUNDING_MODES_UPWARD: i32 = 3;
34
35// This setup allows us to keep all host functions in the `host::` namespace, but vary the implementation based on
36// target and build profiles.
37// 1) `host_bindings_trait.rs` defines the trait that specifies the host functions available to WASM smart contracts.
38// 2a) When cargo is executed with `test` or with the `test-host-bindings` feature, `host_bindings_test.rs` is included,
39//     which provides stub implementations for coverage testing.
40// 2b) When `cargo build` is executed, then `host_bindings_empty.rs` is included, which provides a no-op implementation
41//     that simply allows the build to pass when the target is not Wasm32.
42// 2c) When `cargo build --target wasm32v1-none` (or any Wasm target) is executed, then `host_bindings_wasm.rs` is
43//     included, which provides the actual host function implementations.
44pub mod host_bindings_trait;
45
46#[cfg(all(
47    not(any(test, feature = "test-host-bindings")),
48    not(target_arch = "wasm32")
49))] // <-- e.g., `cargo build` or `... --features xrpl-wasm-stdlib/test-host-bindings`
50include!("host_bindings_empty.rs");
51
52#[cfg(all(any(test, feature = "test-host-bindings"), not(target_arch = "wasm32")))] // <-- e.g., `cargo test` or cov
53include!("host_bindings_test.rs");
54
55// host functions defined by the host.
56#[cfg(target_arch = "wasm32")] // <-- e.g., `cargo build --target wasm32v1-none`
57include!("host_bindings_wasm.rs");
58
59/// `Result` is a type that represents either a success ([`Ok`]) or failure ([`Err`]) result from the host.
60#[must_use]
61pub enum Result<T> {
62    /// Contains the success value
63    Ok(T),
64    /// Contains the error value
65    Err(Error), // TODO: Test if the WASM size is expanded if we use an enum here instead of i32
66}
67
68impl<T> Result<T> {
69    /// Returns `true` if the result is [`Ok`].
70    #[inline]
71    pub fn is_ok(&self) -> bool {
72        matches!(*self, Result::Ok(_))
73    }
74
75    /// Returns `true` if the result is [`Err`].
76    #[inline]
77    pub fn is_err(&self) -> bool {
78        !self.is_ok()
79    }
80
81    /// Converts from `Result<T>` to `Option<T>`.
82    ///
83    /// Converts `self` into an `Option<T>`, consuming `self`,
84    /// and discarding the error, if any.
85    #[inline]
86    pub fn ok(self) -> Option<T> {
87        match self {
88            Result::Ok(x) => Some(x),
89            Result::Err(_) => None,
90        }
91    }
92
93    /// Converts from `Result<T>` to `Option<Error>`.
94    ///
95    /// Converts `self` into an `Option<Error>`, consuming `self`,
96    /// and discarding the success value, if any.
97    #[inline]
98    pub fn err(self) -> Option<Error> {
99        match self {
100            Result::Ok(_) => None,
101            Result::Err(x) => Some(x),
102        }
103    }
104
105    /// Returns the contained [`Ok`] value, consuming the `self` value.
106    ///
107    /// # Panics
108    ///
109    /// Panics if the value is an [`Err`], with a panic message provided by the
110    /// [`Err`]'s value.
111    #[inline]
112    #[track_caller]
113    pub fn unwrap(self) -> T {
114        match self {
115            Result::Ok(t) => t,
116            Result::Err(error) => {
117                #[cfg(target_arch = "wasm32")]
118                {
119                    let _ = trace::trace_num("error_code=", error.code() as i64);
120                }
121                #[cfg(not(target_arch = "wasm32"))]
122                {
123                    let location = core::panic::Location::caller();
124                    eprintln!(
125                        "Result::unwrap() failed at {}:{}:{} with error_code={}",
126                        location.file(),
127                        location.line(),
128                        location.column(),
129                        error.code()
130                    );
131                }
132                panic!(
133                    "called `Result::unwrap()` on an `Err` with code: {}",
134                    error.code()
135                )
136            }
137        }
138    }
139
140    /// Returns the contained [`Ok`] value or a provided default.
141    #[inline]
142    pub fn unwrap_or(self, default: T) -> T {
143        match self {
144            Result::Ok(t) => t,
145            Result::Err(_) => default,
146        }
147    }
148
149    /// Returns the contained [`Ok`] value or computes it from a closure.
150    #[inline]
151    pub fn unwrap_or_else<F: FnOnce(Error) -> T>(self, op: F) -> T {
152        match self {
153            Result::Ok(t) => t,
154            Result::Err(e) => op(e),
155        }
156    }
157
158    #[inline]
159    #[track_caller]
160    pub fn unwrap_or_panic(self) -> T {
161        self.unwrap_or_else(|error| {
162            let location = core::panic::Location::caller();
163            #[cfg(target_arch = "wasm32")]
164            {
165                let _ = trace::trace_num("error_code=", error.code() as i64);
166            }
167            #[cfg(not(target_arch = "wasm32"))]
168            {
169                eprintln!(
170                    "unwrap_or_panic() failed at {}:{}:{} with error_code={}",
171                    location.file(),
172                    location.line(),
173                    location.column(),
174                    error.code()
175                );
176            }
177            core::panic!("Failed in {}: error_code={}", location, error.code());
178        })
179    }
180
181    #[inline]
182    pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U> {
183        match self {
184            Result::Ok(t) => Result::Ok(op(t)),
185            Result::Err(e) => Result::Err(e),
186        }
187    }
188
189    /// Calls `op` if the result is [`Ok`], otherwise returns the [`Err`] value of `self`.
190    #[inline]
191    pub fn and_then<U, F: FnOnce(T) -> Result<U>>(self, op: F) -> Result<U> {
192        match self {
193            Result::Ok(t) => op(t),
194            Result::Err(e) => Result::Err(e),
195        }
196    }
197}
198
199impl From<i64> for Result<u64> {
200    #[inline(always)] // <-- Inline because this function is very small
201    fn from(value: i64) -> Self {
202        match value {
203            res if res >= 0 => Result::Ok(value as _),
204            _ => Result::Err(Error::from_code(value as _)),
205        }
206    }
207}
208
209/// Transposes an `Option` of a `Result` into a `Result` of an `Option`.
210///
211/// `None` will be mapped to `Ok(None)`.
212/// `Some(Ok(_))` and `Some(Err(_))` will be mapped to `Ok(Some(_))` and `Err(_)`.
213///
214/// # Examples
215///
216/// ```ignore
217/// let x: Option<Result<i32>> = Some(Ok(5));
218/// let y: Result<Option<i32>> = transpose_option(x);
219/// assert_eq!(y, Ok(Some(5)));
220/// ```
221#[inline]
222pub fn transpose_option<T>(opt: Option<Result<T>>) -> Result<Option<T>> {
223    match opt {
224        Some(Result::Ok(x)) => Result::Ok(Some(x)),
225        Some(Result::Err(e)) => Result::Err(e),
226        None => Result::Ok(None),
227    }
228}
229
230/// Possible errors returned by XRPL Programmability APIs.
231///
232/// Errors are global across all Programmability APIs.
233#[derive(Clone, Copy, Debug)]
234#[repr(i32)]
235pub enum Error {
236    /// Reserved for internal invariant trips, generally unrelated to inputs.
237    /// These should be reported with an issue.
238    InternalError = error_codes::INTERNAL_ERROR,
239
240    /// The requested serialized field could not be found in the specified object.
241    /// This error is returned when attempting to access a field that doesn't exist
242    /// in the current transaction or ledger object.
243    FieldNotFound = error_codes::FIELD_NOT_FOUND,
244
245    /// The provided buffer is too small to hold the requested data.
246    /// Increase the buffer size and retry the operation.
247    BufferTooSmall = error_codes::BUFFER_TOO_SMALL,
248
249    /// The API was asked to assume the object under analysis is an STArray but it was not.
250    /// This error occurs when trying to perform array operations on non-array objects.
251    NoArray = error_codes::NO_ARRAY,
252
253    /// The specified field is not a leaf field and cannot be accessed directly.
254    /// Leaf fields are primitive types that contain actual data values.
255    NotLeafField = error_codes::NOT_LEAF_FIELD,
256
257    /// The provided locator string is malformed or invalid.
258    /// Locators must follow the proper format for field identification.
259    LocatorMalformed = error_codes::LOCATOR_MALFORMED,
260
261    /// The specified slot number is outside the valid range.
262    /// Slot numbers must be within the allowed bounds for the current context.
263    SlotOutRange = error_codes::SLOT_OUT_RANGE,
264
265    /// No free slots are available for allocation.
266    /// All available slots are currently in use. Consider reusing existing slots.
267    SlotsFull = error_codes::SLOTS_FULL,
268
269    /// The specified slot did not contain any slotted data (i.e., is empty).
270    /// This error occurs when trying to access a slot that hasn't been allocated
271    /// or has been freed.
272    EmptySlot = error_codes::EMPTY_SLOT,
273
274    /// The requested ledger object could not be found.
275    /// This may occur if the object doesn't exist or the keylet is invalid.
276    LedgerObjNotFound = error_codes::LEDGER_OBJ_NOT_FOUND,
277
278    /// An error occurred while decoding serialized data.
279    /// This typically indicates corrupted or invalidly formatted data.
280    InvalidDecoding = error_codes::INVALID_DECODING,
281
282    /// The data field is too large to be processed.
283    /// Consider reducing the size of the data or splitting it into smaller chunks.
284    DataFieldTooLarge = error_codes::DATA_FIELD_TOO_LARGE,
285
286    /// A pointer or buffer length provided as a parameter described memory outside the allowed memory region.
287    /// This error indicates a memory access violation.
288    PointerOutOfBounds = error_codes::POINTER_OUT_OF_BOUNDS,
289
290    /// No memory has been exported by the WebAssembly module.
291    /// The module must export its memory for host functions to access it.
292    NoMemoryExported = error_codes::NO_MEM_EXPORTED,
293
294    /// One or more of the parameters provided to the API are invalid.
295    /// Check the API documentation for valid parameter ranges and formats.
296    InvalidParams = error_codes::INVALID_PARAMS,
297
298    /// The provided account identifier is invalid.
299    /// Account IDs must be valid 20-byte addresses in the proper format.
300    InvalidAccount = error_codes::INVALID_ACCOUNT,
301
302    /// The specified field identifier is invalid or not recognized.
303    /// Field IDs must correspond to valid XRPL serialization fields.
304    InvalidField = error_codes::INVALID_FIELD,
305
306    /// The specified index is outside the valid bounds of the array or collection.
307    /// Ensure the index is within the valid range for the target object.
308    IndexOutOfBounds = error_codes::INDEX_OUT_OF_BOUNDS,
309
310    /// The input provided for floating-point parsing is malformed.
311    /// Floating-point values must be in the correct format for XFL operations.
312    InvalidFloatInput = error_codes::INVALID_FLOAT_INPUT,
313
314    /// An error occurred during floating-point computation.
315    /// This may indicate overflow, underflow, or other arithmetic errors.
316    InvalidFloatComputation = error_codes::INVALID_FLOAT_COMPUTATION,
317}
318
319impl Error {
320    // TODO: Use Trait instead?
321    #[inline(always)] // <-- Inline because this function is very small
322    pub fn from_code(code: i32) -> Self {
323        unsafe { core::mem::transmute(code) }
324    }
325
326    /// Error code
327    #[inline(always)] // <-- Inline because this function is very small
328    pub fn code(self) -> i32 {
329        self as _
330    }
331}
332
333impl From<Error> for i64 {
334    fn from(val: Error) -> Self {
335        val as i64
336    }
337}