Skip to main content

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