The 15 smart-contract host functions were registered through `HostFuncWrapper.cpp` and `createWasmImport`, both of which upstream deleted. They are now declared where every other host function is: one `host_functions!` entry each in `crates/xrpl-host-functions`, which generates the trait, the spec table and the wasmi registration, with a body in the engine, a crossing in the cxx bridge, and a `HostContext` method lowering it onto the wire. They share the `host_lib` namespace with the escrow functions, whose host leaves them `Unimplemented`. Wire order and gas are unchanged, so a guest built against the old ABI still links, with two exceptions. `emit_built_txn` and `emit_txn` gain an output region and write the TER there as four little-endian bytes, returning the length. Their old signature returned the TER as the call's result, which the engine reads as a `HostError` when negative: every `tem`, `tef`, `ter` and `tel` code would have stopped the run instead of reaching the contract. `HostContext` decodes what the old wrappers decoded: the account region, the typed value a `set_data_*` call carries, the serialized transaction and the serialized event. A guest that writes those bytes wrongly gets `InvalidParams`, so the deserializers' exceptions are caught at the call rather than by `guarded`, which would report a node fault. Every Rust site that enumerates the ABI is extended: the two literal tables in `generated_abi.rs`, the import list and arity check in `preflight.rs`, the per-function wasm in `budgets.rs`, and the three fake hosts. `MockHostFunctions` gains the 15 methods and the matchers a contract expectation needs. Two `host_calls` tests cover the shapes that are new here: a typed value decoded on its way to the host, and a TER crossing as bytes, including the negative code that motivated the change.
Rust crates
This directory holds the WebAssembly engine that runs Smart Escrow contracts,
bridged into C++ via cxxbridge/the cxx crate.
The workspace is built unconditionally — add_subdirectory(crates) in the
top-level CMakeLists.txt is not behind an option, and
xrpl_wasm_vm_ffi_cxxbridge is a PUBLIC dependency of
xrpl.libxrpl.ledger (see cmake/XrplCore.cmake). The Rust toolchain pinned in
rust-toolchain.toml is therefore required to build
libxrpl at all; the Nix devshell provides it automatically.
The crates
Dependencies run in one direction: the ABI crate at the bottom, the engine on top of it, and the two bridges at the edge.
xrpl-host-functions
The wasm host ABI, declared exactly once. A host_functions! block at the
bottom of src/lib.rs generates the HostFunctions trait a host implements and
the HostFunctionSpec table a wasm engine registers from. Only the vocabulary
the declarations are written in — HostError, HostResult, TraceDataType,
HASH_LEN — is hand-written.
Add or change a host function here, never in the engine or the bridge: the expansion names nothing this file does not, so neither side of the FFI boundary gets to restate a signature.
no_std, because this crate is also what a guest contract links against.
xrpl-host-functions-macros
The proc macro behind that block, plus the wasmi_glue! marshalling it
generates. An implementation detail of the crate above — nothing else should
depend on it.
The dev-dependency back on xrpl-host-functions is a deliberate cycle: the
doctests declare host functions returning HostResult, which the facade crate
hand-writes. Cargo allows it because dev-dependencies sit outside the library
build graph.
xrpl-wasm-vm
The engine itself, on wasmi: preflight validation (preflight/), gas
metering and execution (vm.rs), and host-call dispatch (abi.rs, args.rs,
register.rs).
Two lint decisions are load-bearing, both because this is a consensus path:
forbid(unsafe_code), so "every guest access reaches linear memory only through wasmi's bounds-checked slice operations" is a property rather than a claim.- The truncating, wrapping and sign-losing cast lints are
denyand each remaining cast is argued for at its site — a bad cast here changes what a contract is charged or told.
It pins wasmi with default-features = false deliberately. wasmi's wat
feature is on by default and makes Module::new accept text as readily as
binary, which would turn a transaction's validity into a build flag.
Not bridged to C++ directly; it reaches xrpld through xrpl-wasm-vm-ffi.
xrpl-wasm-vm-ffi
The cxx bridge into xrpld. Three crossings:
- In: C++ calls
run_escrow, once per escrow finish. - Back out: that run's host calls leave through the C++
HostContext, whichCxxHostpresents to the engine as an ordinaryHostFunctionsimplementor. - In only: C++ screens a module with
check_escrow. Screening needs no host, so nothing comes back out.
The C++ side is src/libxrpl/tx/wasm/WasmVM.cpp and
src/libxrpl/tx/wasm/HostContext.cpp.
Neither language may unwind into the other, and the two halves are not symmetric:
- A Rust panic is caught here, by
guarded. Letting one reach C++ is undefined behaviour, and[profile.release]enables overflow checks, so this is a live path rather than a formality. - A C++ exception is stopped on the C++ side: every
HostContextmethod isnoexceptand catches its own. That is what makesguardedsufficient.
Everything hand-written here is private, so cargo doc needs
--document-private-items to show any of it. That is also why this crate,
unlike xrpl-wasm-vm, does not deny(unreachable_pub) — cxx's expansion is
pub throughout by necessity.
xrpl-wasm-testkit
Test-only. Assembles WebAssembly text for the C++ test suite, and exposes the gas price of each host function by its guest import name for the C++ gas benchmarks (read through the bridge rather than transcribed into C++, so the numbers cannot drift silently).
A crate of its own rather than an entry on xrpl-wasm-vm-ffi, and the
separation is the point: putting compile_wat on the production bridge would
link wat into xrpld even if nothing called it. Linked only into
xrpl_tests, never into libxrpl or xrpld, so "no text assembler in the
shipped node" holds by the link graph rather than by a flag someone can flip.
Testing
cargo test --manifest-path crates/Cargo.toml --workspace
CI uses cargo nextest. This is independent of the CMake build.
One gap that command does not cover: it never compiles xrpl-host-functions
with its wasmi_glue feature off, because xrpl-wasm-vm enables the
feature and Cargo unifies features across a workspace build. The feature-off
configuration is the one a guest contract sees, so after touching that crate
also run:
cargo check -p xrpl-host-functions --manifest-path crates/Cargo.toml