Add new float host functions and versioning rules#504
Add new float host functions and versioning rules#504
Conversation
sappenin
commented
Mar 23, 2026
- Updated §5.8 (Floats) preamble to describe OpaqueFloat as a general-purpose 12-byte buffer (4-byte exponent + 8-byte mantissa), document the rounding_modes values, and drop the IOU-specific framing
- Added six new host functions to §5.8 (all at 1000 gas, consistent with existing float operations)
- Added §5.11 Host Function Versioning Rules, covering the immutability of deployed host functions and the requirement to introduce new function names if buffer layouts change
- Updated §5.8 (Floats) preamble to describe OpaqueFloat as a general-purpose 12-byte buffer (4-byte exponent + 8-byte mantissa), document the rounding_modes values, and drop the IOU-specific framing - Added six new host functions to §5.8 (all at 1000 gas, consistent with existing float operations) - Added §5.11 Host Function Versioning Rules, covering the immutability of deployed host functions and the requirement to introduce new function names if buffer layouts change Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
| new function are unaffected. | ||
| 2. **Host functions MAY be deprecated** with appropriate notice, but deprecated functions MUST remain callable for | ||
| backward compatibility. Deployed contracts may rely on any host function that was available at deployment time. | ||
| 3. **Host functions MUST NOT ever be changed.** Once a host function is deployed — its name, parameter types, |
There was a problem hiding this comment.
Not sure what I think about this rule. E.g., an amendment might change the meaning of a host function, but then again... should that be allowed? Such a thing would likely break existing contracts assuming the previous functionality.
There was a problem hiding this comment.
Yeah, this is hard to think... I think these are the right requirements. I.e. it is essentially append-only. Unfortunately I don't think there is a gate to guard this. The amendment (at least the existing meaning I understand) is not for this. I think the current approach about the float is the right one, "only use exponent and mantissa if want to store the float".
There was a problem hiding this comment.
Brainstorming out loud, here are some possible approaches that might help us version host functions to better ensure correctness.
1. Explicit versioning per host function
Contract authors declare which version they target (e.g. float_set/v1). If rippled introduces float_set/v2, the contract keeps calling v1 until explicitly upgraded. Downside: rippled can never remove old host functions, and it adds overhead for contract authors.
2. Host-function fingerprint
Hash all host-function names+versions into a single fingerprint, stored in each contract (similar to Data). rippled computes and caches its own fingerprint at runtime; if they diverge, the contract enters a permanent-fail mode. This is blunt and could create pain for contract authors who must react to any host-function change — but it enforces correctness. Scoping the fingerprint strictly to host functions (not all amendments) keeps it manageable.
3. "Do nothing for now" is also an option, but feels risky: by the time we need a versioning system, older contracts will have no way to adopt it retroactively.
4. Stable core + versioned extensions: Define a small, frozen set of core host functions (arithmetic, ledger reads, etc.) that are guaranteed never to change. Everything experimental or evolving lives in a separate, explicitly versioned extension namespace. Contracts that only use the core never think about versioning; only contracts using extensions need to care.
5. Runtime capability query: Expose a host function like get_capabilities() -> u64 that returns a bitmask of supported features. Contracts can branch at runtime based on what's available — similar to CPU feature detection (SSE, AVX). This is flexible but pushes complexity into every contract that needs to handle multiple code paths.
|
Because a smart escrow should store the mantissa and exponent as separate integers in a contract-defined format if the smart escrow needs to persist the opaque float in the ledger. It needs a way to get the mantissa and exponent, through a new host function: float_to_mantissa_and_exponent( Does it need a rounding mode? Not, right? |
Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
| `float_from_stamount` and `float_from_stnumber` exist to load values from those on-ledger formats into `OpaqueFloat` | ||
| for in-contract computation, without touching how those values are stored or transmitted on the wire. | ||
|
|
||
| ### C.9: Why is host function immutability required? |
There was a problem hiding this comment.
I think this is probably what we want, but open to other solutions.
|
|
||
| The 4 extra bytes per value are negligible given the `no_std` stack-only model. | ||
|
|
||
| **Compared to STNumber (14 bytes):** OpaqueFloat is 2 bytes shorter because it omits the type prefix — the host |
There was a problem hiding this comment.
The current "12 byte" serialization does save two bytes as compared to just using STNumber (which is likely 14 bytes). However, if we did just use STNumber bytes, then we might be able to remove the need for float_from_stnumber since an stnumber would be a float.
Thoughts?
There was a problem hiding this comment.
I don't have a strong opinion. My vote is the "12 byte".
There was a problem hiding this comment.
I also lean towards "keep 12" but am noticing that -- in Rust/WASM -- anytime I grab an STNumber, I can't just use it. I need to convert it to an OpaqueFloat. It would instead be nice if the WASM layer just treated those as the same thing, so I can save a host function call.
Signed-off-by: David Fuelling <sappenin@gmail.com>
|
@pwang200 I addressed your comment above via e56c1b9 |
XLS-0102-wasm-vm/README.md
Outdated
| | `float_multiply(`<br/> `in_buf1: i32,`<br/> `in_len1: i32,`<br/> `in_buf2: i32,`<br/> `in_len2: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Multiply two floats in rippled format. | 1000 | | ||
| | `float_divide(`<br/> `in_buf1: i32,`<br/> `in_len1: i32,`<br/> `in_buf2: i32,`<br/> `in_len2: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Divide two floats in rippled format. | 1000 | | ||
| | `float_negate(`<br/> `in_buf: i32,`<br/> `in_len: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32`<br />`)` | Negate a float. | 1000 | | ||
| | `float_abs(`<br/> `in_buf: i32,`<br/> `in_len: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32`<br />`)` | Absolute value of a float. | 1000 | |
There was a problem hiding this comment.
Do we need float_abs? We can accomplish the same thing checking with float_sign and then float_negate.
There was a problem hiding this comment.
I don't have a strong opinion. My vote is keep it if it is there already. Same for other host functions.
There was a problem hiding this comment.
I think I feel the same (weakly-held opinion). @mvadari chime in here, and if you agree we can resolve this comment and the onle above about float_negate.
XLS-0102-wasm-vm/README.md
Outdated
| | `float_subtract(`<br/> `in_buf1: i32,`<br/> `in_len1: i32,`<br/> `in_buf2: i32,`<br/> `in_len2: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Subtract two floats in rippled format. | 1000 | | ||
| | `float_multiply(`<br/> `in_buf1: i32,`<br/> `in_len1: i32,`<br/> `in_buf2: i32,`<br/> `in_len2: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Multiply two floats in rippled format. | 1000 | | ||
| | `float_divide(`<br/> `in_buf1: i32,`<br/> `in_len1: i32,`<br/> `in_buf2: i32,`<br/> `in_len2: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Divide two floats in rippled format. | 1000 | | ||
| | `float_negate(`<br/> `in_buf: i32,`<br/> `in_len: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32`<br />`)` | Negate a float. | 1000 | |
There was a problem hiding this comment.
Do we need float_negate? We can accomplish the same things by declaring -1 and then using float_multiply.
XLS-0102-wasm-vm/README.md
Outdated
| | `float_sign(`<br/> `in_buf: i32,`<br/> `in_len: i32`<br />`)` | Sign of a float. Returns `-1` (negative), `0` (zero), or `1` (positive). | 1000 | | ||
| | `float_pow(`<br/> `in_buf: i32,`<br/> `in_len: i32,`<br/> `pow: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Compute the nth power of a float in rippled format. | 1000 | | ||
| | `float_root(`<br/> `in_buf: i32,`<br/> `in_len: i32,`<br/> `root: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Compute the nth root of a float in rippled format. | 1000 | | ||
| | `float_log(`<br/> `in_buf: i32,`<br/> `in_len: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Compute the 10 based log of a float in rippled format. | 1000 | |
There was a problem hiding this comment.
Is float_log necessary?
XLS-0102-wasm-vm/README.md
Outdated
| | Function Signature | Description | Gas Cost | | ||
| |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------|:---------| | ||
| | `float_from_int(`<br/> `in_int: i64,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Create a float in rippled format from a 64-bit integer. | 1000 | | ||
| | `float_from_uint(`<br/> `in_uint_ptr: i32,`<br/> `in_uint_len: i32,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Create a float in rippled format from a 64-bit unsigned integer. | 1000 | |
There was a problem hiding this comment.
the gas costs of the float host functions mismatches with the code:
WASM_IMPORT_FUNC2(i, floatFromInt, "float_from_int", hfs, 100);
WASM_IMPORT_FUNC2(i, floatFromUint, "float_from_uint", hfs, 130);
WASM_IMPORT_FUNC2(i, floatFromSTAmount, "float_from_stamount", hfs, 150);
WASM_IMPORT_FUNC2(i, floatFromSTNumber, "float_from_stnumber", hfs, 150);
WASM_IMPORT_FUNC2(i, floatToInt, "float_to_int", hfs, 130);
WASM_IMPORT_FUNC2(i, floatToMantissaAndExponent, "float_to_mantissa_and_exponent", hfs, 130);
WASM_IMPORT_FUNC2(i, floatNegate, "float_negate", hfs, 150);
WASM_IMPORT_FUNC2(i, floatAbs, "float_abs", hfs, 150);
WASM_IMPORT_FUNC2(i, floatSet, "float_set", hfs, 100);
WASM_IMPORT_FUNC2(i, floatCompare, "float_compare", hfs, 80);
WASM_IMPORT_FUNC2(i, floatAdd, "float_add", hfs, 160);
WASM_IMPORT_FUNC2(i, floatSubtract, "float_subtract", hfs, 160);
WASM_IMPORT_FUNC2(i, floatMultiply, "float_multiply", hfs, 300);
WASM_IMPORT_FUNC2(i, floatDivide, "float_divide", hfs, 300);
WASM_IMPORT_FUNC2(i, floatRoot, "float_root", hfs, 5'500);
WASM_IMPORT_FUNC2(i, floatPower, "float_pow", hfs, 5'500);
WASM_IMPORT_FUNC2(i, floatLog, "float_log", hfs, 12'000);
XLS-0102-wasm-vm/README.md
Outdated
| - **Exponent** (bytes 0–3): Signed 32-bit integer (`i32`), big-endian. Represents the power of 10 applied to the | ||
| mantissa. | ||
| - **Mantissa** (bytes 4–11): Signed 64-bit integer (`i64`), big-endian. Represents the significant digits of the value. | ||
| When normalized: 10^15 ≤ |mantissa| < 10^16 (i.e., 1,000,000,000,000,000 to 9,999,999,999,999,999), except for zero. |
Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
|
|
||
| | Function Signature | Description | Gas Cost | | ||
| | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :------- | | ||
| | `float_from_int(`<br/> `in_int: i64,`<br/> `out_buf: i32,`<br/> `out_len: i32,`<br/> `rounding_modes: i32`<br />`)` | Create a float in rippled format from a 64-bit integer. | 100 | |
There was a problem hiding this comment.
Hope you don't mind a comment from the community. I was wondering why out_buf and out_len are required? I thought lengths would remain the same?
From a contract writer perspective, something like fn xfloat_from_int(i64) with default rounding more nearest and other function for fn xfloat_from_int_up(i64)... or at least use an enum for the rounding mode? And xfloat stands for xrpl float.
My 0.02 RLUSD cents