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}