Skip to content

Add new float host functions and versioning rules - #504

Open
sappenin wants to merge 49 commits into
masterfrom
df/update-host-functions
Open

Add new float host functions and versioning rules#504
sappenin wants to merge 49 commits into
masterfrom
df/update-host-functions

Conversation

@sappenin

@sappenin sappenin commented Mar 23, 2026

Copy link
Copy Markdown
Collaborator
  • Updated §5.8 (Floats) preamble to describe a general-purpose 12-byte buffer (4-byte exponent + 8-byte mantissa), document the rounding_modes values.
  • Added new host functions to §5.8.
  • 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
  • Added FAQs to help explain design rationale.

- 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>
@sappenin
sappenin requested a review from mvadari March 23, 2026 23:54
Signed-off-by: David Fuelling <sappenin@gmail.com>
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
@pwang200

Copy link
Copy Markdown
Contributor

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(
 opaque_float_in_buf: i32,
 opaque_float_in_len: i32,
 mantissa_out_buf: i32,
 mantissa_out_len: i32,
 exponent_out_buf: i32,
 exponent_out_len: i32,
)

Does it need a rounding mode? Not, right?

Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
Comment thread XLS-0102-wasm-vm/README.md
Comment thread XLS-0102-wasm-vm/README.md Outdated
Signed-off-by: David Fuelling <sappenin@gmail.com>
@sappenin

Copy link
Copy Markdown
Collaborator Author

@pwang200 I addressed your comment above via e56c1b9

Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
@sappenin

Copy link
Copy Markdown
Collaborator Author

@pwang200 said:

Does it need a rounding mode? Not, right?

We decided that we don't, because the float is already rounded.

Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Signed-off-by: David Fuelling <sappenin@gmail.com>

# Conflicts:
#	XLS-0102-wasm-vm/README.md
@sappenin
sappenin requested a review from pwang200 July 24, 2026 21:10
Comment thread XLS-0102-wasm-vm/README.md
Comment thread XLS-0102-wasm-vm/README.md
Comment thread XLS-0102-wasm-vm/README.md
Comment thread XLS-0102-wasm-vm/README.md
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Comment thread XLS-0102-wasm-vm/README.md Outdated
Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
Comment thread XLS-0102-wasm-vm/README.md Outdated
Signed-off-by: David Fuelling <sappenin@gmail.com>
Signed-off-by: David Fuelling <sappenin@gmail.com>
| `float_div(`<br/>&emsp;`in_buf1: i32,`<br/>&emsp;`in_len1: i32,`<br/>&emsp;`in_buf2: i32,`<br/>&emsp;`in_len2: i32,`<br/>&emsp;`out_buf: i32,`<br/>&emsp;`out_len: i32,`<br/>&emsp;`rounding_modes: i32`<br />`)` | Divide two floats in rippled format. | 300 |
| `float_pow(`<br/>&emsp;`in_buf: i32,`<br/>&emsp;`in_len: i32,`<br/>&emsp;`pow: i32,`<br/>&emsp;`out_buf: i32,`<br/>&emsp;`out_len: i32,`<br/>&emsp;`rounding_modes: i32`<br />`)` | Compute the nth power of a float in rippled format. | 5500 |
| `float_root(`<br/>&emsp;`in_buf: i32,`<br/>&emsp;`in_len: i32,`<br/>&emsp;`root: i32,`<br/>&emsp;`out_buf: i32,`<br/>&emsp;`out_len: i32,`<br/>&emsp;`rounding_modes: i32`<br />`)` | Compute the nth root of a float in rippled format. | 5500 |
Helper functions for performing floating point arithmetic via rippled. These are used for any calculation requiring XRPL's decimal floating point format — including IOU amounts, lending protocol math, fee calculations, or arbitrary numeric operations within a smart contract.

@mvadari mvadari Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Helper functions for performing floating point arithmetic via rippled. These are used for any calculation requiring XRPL's decimal floating point format including IOU amounts, lending protocol math, fee calculations, or arbitrary numeric operations within a smart contract.
Helper functions for performing floating point arithmetic via xrpld. These are used for any calculation requiring XRPL's decimal floating point format, including IOU amounts, lending protocol math, fee calculations, or arbitrary numeric operations within a smart contract.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update all other instances of rippled to xrpld in this doc?


Smart contracts SHOULD NOT inspect, decode, or construct XFloat bytes directly. All operations should instead go through the host functions defined in [§5.8](#58-floats). Bypassing host functions is gas-inefficient — each host call is priced to amortize the cost of the operation, and hand-rolling arithmetic in WASM bytecode incurs more gas for less precision. A contract that reads or writes the individual bytes of an XFloat buffer is also relying on an implementation detail that may change, and could produce incorrect or undefined behavior if it does. The buffer should be allocated, passed to a host functions, and discarded — nothing else.

**Warning — do not persist XFloat bytes:** Contracts MUST NOT write XFloat buffers into contract storage (e.g., the `data` field of a smart feature). The 12-byte encoding is an in-memory convention tied to today's float host functions. Per [§5.11](#511-host-function-versioning-rules) rule 3, that encoding can never change for these functions — but a future need for a different layout would ship as a new host function under a new name, and bytes persisted under today's convention would not be readable by it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the Github warning block if you want here


#### 5.8.2. XFloat Serialization Format

This section documents the XFloat encoding for **rippled implementers and tooling authors**. Contracts must not use this information to construct or decode buffers — they must use the host functions in [§5.8](#58-floats) exclusively.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section seems unnecessary, I don't really think we need to assert literally everywhere that contracts shouldn't decode themselves


#### 5.8.3. XFloat Motivation

XRPL Smart Contracts running in WebAssembly need to perform correct decimal arithmetic. This need arises in many contexts: computing with fungible token amounts (IOUs), implementing lending protocols with interest and collateral ratios, calculating fees, and more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smart Contracts implies not Smart Escrows etc


The 12-byte XFloat format is strictly an in-memory buffer convention for passing values to and from WASM host functions. Values stored in ledger objects continue to use their existing serialization formats; the host function `float_from_iou_value` bridges the IOU amount format to WASM floating point numbers. `STNumber` values are decoded in Rust by `xrpl-wasm-stdlib` — for the IOU-precision case, the decoded 8-byte value is layout-identical to an `IouNumber` and is passed to `float_from_iou_value` directly, without a dedicated host function.

#### 5.8.3. XFloat Motivation

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section should be higher up


Implementing xrpld floating-point arithmetic correctly is genuinely hard. Correct rounding, normalization, overflow handling, and edge-case behavior require a carefully engineered implementation. Implementing this correctly in WASM from scratch is not a reasonable expectation for contract developers, and cannot be practically verified or guaranteed. By delegating all arithmetic to rippled's `Number` class via host functions, contracts get a battle-tested implementation that is known to be correct for XRPL's numeric domain, even across amendment changes.

Note that the XRPL WASM VM does not enable the WASM floating-point instruction set (i.e., `f32`/`f64` ops are unavailable to contracts). This means native IEEE 754 arithmetic is not an option regardless of determinism concerns. Contracts that need fixed-point arithmetic independent of the `XFloat` host functions — for example, to work with integer ratios or basis points — should consider crates like the [`fixed`](https://crates.io/crates/fixed) Rust crate, which performs fixed-point math entirely in integer instructions and is fully compatible with the `no_std`, `wasm32v1-none` build target.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
Note that the XRPL WASM VM does not enable the WASM floating-point instruction set (i.e., `f32`/`f64` ops are unavailable to contracts). This means native IEEE 754 arithmetic is not an option regardless of determinism concerns. Contracts that need fixed-point arithmetic independent of the `XFloat` host functions for example, to work with integer ratios or basis points should consider crates like the [`fixed`](https://crates.io/crates/fixed) Rust crate, which performs fixed-point math entirely in integer instructions and is fully compatible with the `no_std`, `wasm32v1-none` build target.
Note that the XRPL WASM VM does not enable the WASM floating-point instruction set (i.e., `f32`/`f64` ops are unavailable to contracts). This means native IEEE 754 arithmetic is not an option regardless of determinism concerns. Contracts that need fixed-point arithmetic independent of the `XFloat` host functions (for example, to work with integer ratios or basis points) should consider crates like the [`fixed`](https://crates.io/crates/fixed) Rust crate, which performs fixed-point math entirely in integer instructions and is fully compatible with the `no_std`, `wasm32v1-none` build target.


#### 5.8.5. XFloat Binary Format Reference

> **For implementers and tooling authors only.** Contracts must never decode or construct `XFloat` bytes directly. This section exists to support rippled development, debuggers, explorers, and spec verification — not contract authors.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here


1. **New host functions MAY be added** at any time without breaking existing contracts. Contracts that do not call a 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. Deprecation does not remove or change the function — it signals to new contract authors that a function is discouraged, so documentation and tooling (e.g. `xrpl-wasm-stdlib`) can steer new development away from it even though it remains available for existing contracts.
3. **Host functions MUST NOT ever be changed.** Once a host function is deployed — its name, parameter types, parameter order, and observable behavior are permanently immutable. This includes buffer sizes, since contracts hardcode allocation sizes (e.g., 20 bytes for an account ID, 12 bytes for an `XFloat`). If a buffer size changes, a new host function with a different name MUST be introduced.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
3. **Host functions MUST NOT ever be changed.** Once a host function is deployed — its name, parameter types, parameter order, and observable behavior are permanently immutable. This includes buffer sizes, since contracts hardcode allocation sizes (e.g., 20 bytes for an account ID, 12 bytes for an `XFloat`). If a buffer size changes, a new host function with a different name MUST be introduced.
3. **Host functions MUST be permanently backwards compatible.** Once a host function is deployed — its name, parameter types, parameter order, and observable behavior are permanently immutable. This includes buffer sizes, since contracts hardcode allocation sizes (e.g., 20 bytes for an account ID, 12 bytes for an `XFloat`). If a buffer size changes, a new host function with a different name MUST be introduced.

If a buffer size changes, a new host function with a different name MUST be introduced. - did we agree on this? I thought it was fine as long as you still support old behavior, but I might be misremembering.

3. **Host functions MUST NOT ever be changed.** Once a host function is deployed — its name, parameter types, parameter order, and observable behavior are permanently immutable. This includes buffer sizes, since contracts hardcode allocation sizes (e.g., 20 bytes for an account ID, 12 bytes for an `XFloat`). If a buffer size changes, a new host function with a different name MUST be introduced.

### 6.1. Consensus
These rules ensure that smart contracts compiled and deployed today will continue to execute correctly on future versions of the platform.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here


## 6. Rationale

This section summarizes the main design decisions in this specification and why they were made. The appendices contain the full analysis of the alternatives that were considered.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This para is unnecessary, that's the objective of the Rationale section


This section summarizes the main design decisions in this specification and why they were made. The appendices contain the full analysis of the alternatives that were considered.

- **Interpreted Wasmi runtime.** Different WASM runtimes meter gas differently, so the same code can produce different gas costs across implementations — a consensus hazard. Fixing the runtime (Wasmi), its version, and an interpreted compile mode guarantees identical, deterministic gas costs on every validator. Interpretation also avoids the larger attack surface and platform-dependent behavior of JIT/AOT compilation. See [Appendix A](#appendix-a-other-wasm-vms-considered) for the full comparison of runtimes and compilation modes.

@mvadari mvadari Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use : instead of . at the end of each bullet's heading (even better would be a subsection)


### C.5: Why not use native WASM floating point?

WebAssembly's native `f32` and `f64` types are IEEE 754 binary floating-point. One might ask whether those could be used directly for numeric operations in smart contracts, perhaps with NaN canonicalization to address the one known source of non-determinism in the WASM spec (NaN bit-payload variation when inputs are non-canonical). In practice this would be insufficient for two independent reasons.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
WebAssembly's native `f32` and `f64` types are IEEE 754 binary floating-point. One might ask whether those could be used directly for numeric operations in smart contracts, perhaps with NaN canonicalization to address the one known source of non-determinism in the WASM spec (NaN bit-payload variation when inputs are non-canonical). In practice this would be insufficient for two independent reasons.
WebAssembly's native `f32` and `f64` types are IEEE 754 binary floating-point. While they could be used directly for numeric operations in smart contracts, perhaps with NaN canonicalization to address the one known source of non-determinism in the WASM spec (NaN bit-payload variation when inputs are non-canonical), in practice this would be insufficient for two independent reasons:


### C.6: Why not provide a Rust implementation of Number arithmetic in `xrpl-wasm-stdlib`?

For reasons related to C.5, `xrpl-wasm-stdlib` deliberately does not ship a Rust implementation of `Number` arithmetic. Such an implementation would face the same amendment-drift problem defined in C.5: it would be frozen at the version of the logic that existed when it was written. The correct abstraction boundary is the host function interface — contracts call into rippled, rippled's `Number` class does the math, and the contract receives the result as an opaque 12-byte buffer. This keeps the arithmetic logic in exactly one place.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also mention that it would be more gas/performance-efficient to do it in C++

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants