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 test` or with `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`
50include!("host_bindings_empty.rs");
51
52// TODO: UPDATE when `host_bindings_for_test.rs` is introduced.
53#[cfg(all(any(test, feature = "test-host-bindings"), not(target_arch = "wasm32")))] // <-- e.g., `cargo test` or coverage
54include!("host_bindings_for_testing.rs");
55
56// host functions defined by the host.
57#[cfg(target_arch = "wasm32")] // <-- e.g., `cargo build --target wasm32v1-none`
58include!("host_bindings_wasm.rs");
59
60/// `Result` is a type that represents either a success ([`Ok`]) or failure ([`Err`]) result from the host.
61#[must_use]
62pub enum Result<T> {
63 /// Contains the success value
64 Ok(T),
65 /// Contains the error value
66 Err(Error), // TODO: Test if the WASM size is expanded if we use an enum here instead of i32
67}
68
69impl<T> Result<T> {
70 /// Returns `true` if the result is [`Ok`].
71 #[inline]
72 pub fn is_ok(&self) -> bool {
73 matches!(*self, Result::Ok(_))
74 }
75
76 /// Returns `true` if the result is [`Err`].
77 #[inline]
78 pub fn is_err(&self) -> bool {
79 !self.is_ok()
80 }
81
82 /// Converts from `Result<T>` to `Option<T>`.
83 ///
84 /// Converts `self` into an `Option<T>`, consuming `self`,
85 /// and discarding the error, if any.
86 #[inline]
87 pub fn ok(self) -> Option<T> {
88 match self {
89 Result::Ok(x) => Some(x),
90 Result::Err(_) => None,
91 }
92 }
93
94 /// Converts from `Result<T>` to `Option<Error>`.
95 ///
96 /// Converts `self` into an `Option<Error>`, consuming `self`,
97 /// and discarding the success value, if any.
98 #[inline]
99 pub fn err(self) -> Option<Error> {
100 match self {
101 Result::Ok(_) => None,
102 Result::Err(x) => Some(x),
103 }
104 }
105
106 /// Returns the contained [`Ok`] value, consuming the `self` value.
107 ///
108 /// # Panics
109 ///
110 /// Panics if the value is an [`Err`], with a panic message provided by the
111 /// [`Err`]'s value.
112 #[inline]
113 pub fn unwrap(self) -> T {
114 match self {
115 Result::Ok(t) => t,
116 Result::Err(error) => {
117 let _ = trace::trace_num("error_code=", error.code() as i64);
118 panic!(
119 "called `Result::unwrap()` on an `Err` with code: {}",
120 error.code()
121 )
122 }
123 }
124 }
125
126 /// Returns the contained [`Ok`] value or a provided default.
127 #[inline]
128 pub fn unwrap_or(self, default: T) -> T {
129 match self {
130 Result::Ok(t) => t,
131 Result::Err(_) => default,
132 }
133 }
134
135 /// Returns the contained [`Ok`] value or computes it from a closure.
136 #[inline]
137 pub fn unwrap_or_else<F: FnOnce(Error) -> T>(self, op: F) -> T {
138 match self {
139 Result::Ok(t) => t,
140 Result::Err(e) => op(e),
141 }
142 }
143
144 #[inline]
145 pub fn unwrap_or_panic(self) -> T {
146 self.unwrap_or_else(|error| {
147 let _ = trace::trace_num("error_code=", error.code() as i64);
148 core::panic!(
149 "Failed in {}: error_code={}",
150 core::panic::Location::caller(),
151 error.code()
152 );
153 })
154 }
155}
156
157impl From<i64> for Result<u64> {
158 #[inline(always)] // <-- Inline because this function is very small
159 fn from(value: i64) -> Self {
160 match value {
161 res if res >= 0 => Result::Ok(value as _),
162 _ => Result::Err(Error::from_code(value as _)),
163 }
164 }
165}
166
167/// Possible errors returned by XRPL Programmability APIs.
168///
169/// Errors are global across all Programmability APIs.
170#[derive(Clone, Copy, Debug)]
171#[repr(i32)]
172pub enum Error {
173 /// Reserved for internal invariant trips, generally unrelated to inputs.
174 /// These should be reported with an issue.
175 InternalError = error_codes::INTERNAL_ERROR,
176
177 /// The requested serialized field could not be found in the specified object.
178 /// This error is returned when attempting to access a field that doesn't exist
179 /// in the current transaction or ledger object.
180 FieldNotFound = error_codes::FIELD_NOT_FOUND,
181
182 /// The provided buffer is too small to hold the requested data.
183 /// Increase the buffer size and retry the operation.
184 BufferTooSmall = error_codes::BUFFER_TOO_SMALL,
185
186 /// The API was asked to assume the object under analysis is an STArray but it was not.
187 /// This error occurs when trying to perform array operations on non-array objects.
188 NoArray = error_codes::NO_ARRAY,
189
190 /// The specified field is not a leaf field and cannot be accessed directly.
191 /// Leaf fields are primitive types that contain actual data values.
192 NotLeafField = error_codes::NOT_LEAF_FIELD,
193
194 /// The provided locator string is malformed or invalid.
195 /// Locators must follow the proper format for field identification.
196 LocatorMalformed = error_codes::LOCATOR_MALFORMED,
197
198 /// The specified slot number is outside the valid range.
199 /// Slot numbers must be within the allowed bounds for the current context.
200 SlotOutRange = error_codes::SLOT_OUT_RANGE,
201
202 /// No free slots are available for allocation.
203 /// All available slots are currently in use. Consider reusing existing slots.
204 SlotsFull = error_codes::SLOTS_FULL,
205
206 /// The specified slot did not contain any slotted data (i.e., is empty).
207 /// This error occurs when trying to access a slot that hasn't been allocated
208 /// or has been freed.
209 EmptySlot = error_codes::EMPTY_SLOT,
210
211 /// The requested ledger object could not be found.
212 /// This may occur if the object doesn't exist or the keylet is invalid.
213 LedgerObjNotFound = error_codes::LEDGER_OBJ_NOT_FOUND,
214
215 /// An error occurred while decoding serialized data.
216 /// This typically indicates corrupted or invalidly formatted data.
217 InvalidDecoding = error_codes::INVALID_DECODING,
218
219 /// The data field is too large to be processed.
220 /// Consider reducing the size of the data or splitting it into smaller chunks.
221 DataFieldTooLarge = error_codes::DATA_FIELD_TOO_LARGE,
222
223 /// A pointer or buffer length provided as a parameter described memory outside the allowed memory region.
224 /// This error indicates a memory access violation.
225 PointerOutOfBounds = error_codes::POINTER_OUT_OF_BOUNDS,
226
227 /// No memory has been exported by the WebAssembly module.
228 /// The module must export its memory for host functions to access it.
229 NoMemoryExported = error_codes::NO_MEM_EXPORTED,
230
231 /// One or more of the parameters provided to the API are invalid.
232 /// Check the API documentation for valid parameter ranges and formats.
233 InvalidParams = error_codes::INVALID_PARAMS,
234
235 /// The provided account identifier is invalid.
236 /// Account IDs must be valid 20-byte addresses in the proper format.
237 InvalidAccount = error_codes::INVALID_ACCOUNT,
238
239 /// The specified field identifier is invalid or not recognized.
240 /// Field IDs must correspond to valid XRPL serialization fields.
241 InvalidField = error_codes::INVALID_FIELD,
242
243 /// The specified index is outside the valid bounds of the array or collection.
244 /// Ensure the index is within the valid range for the target object.
245 IndexOutOfBounds = error_codes::INDEX_OUT_OF_BOUNDS,
246
247 /// The input provided for floating-point parsing is malformed.
248 /// Floating-point values must be in the correct format for XFL operations.
249 InvalidFloatInput = error_codes::INVALID_FLOAT_INPUT,
250
251 /// An error occurred during floating-point computation.
252 /// This may indicate overflow, underflow, or other arithmetic errors.
253 InvalidFloatComputation = error_codes::INVALID_FLOAT_COMPUTATION,
254}
255
256impl Error {
257 // TODO: Use Trait instead?
258 #[inline(always)] // <-- Inline because this function is very small
259 pub fn from_code(code: i32) -> Self {
260 unsafe { core::mem::transmute(code) }
261 }
262
263 /// Error code
264 #[inline(always)] // <-- Inline because this function is very small
265 pub fn code(self) -> i32 {
266 self as _
267 }
268}
269
270impl From<Error> for i64 {
271 fn from(val: Error) -> Self {
272 val as i64
273 }
274}