From 670f1d5e6f6620c098802890366dc712c8ca48b0 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 23 Sep 2026 23:44:52 +0530 Subject: [PATCH] Note the escrow and contract host functions are not isolated The two groups share one flat ABI table in one `host_lib` namespace, and the only thing separating them is that the escrow host leaves the contract methods at their `Unimplemented` default. That separates them too late, and in one direction only. An escrow whose bytecode imports `emit_built_txn` passes screening, so `EscrowCreate` stores it and the escrow fails at finish with `tecINTERNAL`, because `Unimplemented` is a fatal fault rather than a code a contract reads. A contract calling `set_data` does not fail at all: the method is inherited, only `EscrowFinish::doApply` drains it, so the write returns a byte count and is discarded. Both need screening to know which subset of the ABI the host being built for actually serves. `docs/wasm-amendment-gating.md` already designs that transport for amendments; this wants the same one keyed on which host is running. --- include/xrpl/tx/wasm/ContractHostFuncImpl.h | 4 ++++ include/xrpl/tx/wasm/HostFunc.h | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/xrpl/tx/wasm/ContractHostFuncImpl.h b/include/xrpl/tx/wasm/ContractHostFuncImpl.h index 10a1ca5d2f..e77d51f7a4 100644 --- a/include/xrpl/tx/wasm/ContractHostFuncImpl.h +++ b/include/xrpl/tx/wasm/ContractHostFuncImpl.h @@ -22,6 +22,10 @@ public: // getFieldBytesFromSTData(xrpl::STData const& funcParam, std::uint32_t // stTypeId); + // TODO: `updateData` is inherited from the escrow host and is not refused here, so a + // contract calling `set_data` is told how many bytes it stored and then has them + // dropped: `ContractCall::doApply` never drains `getData()`. Refusing it needs an error + // a contract can read, which `Unimplemented` is not — see the note in HostFunc.h. std::expected instanceParam(std::uint32_t index, std::uint32_t stTypeId) override; diff --git a/include/xrpl/tx/wasm/HostFunc.h b/include/xrpl/tx/wasm/HostFunc.h index ee6daeffee..ee0174b08e 100644 --- a/include/xrpl/tx/wasm/HostFunc.h +++ b/include/xrpl/tx/wasm/HostFunc.h @@ -479,6 +479,25 @@ public: return std::unexpected(HostFunctionError::Unimplemented); } + // Smart-contract host functions. `WasmHostFunctionsImpl`, the escrow host, leaves every + // one of them at the `Unimplemented` default below, and `ContractHostFunctionsImpl` + // overrides them. + // + // TODO: that default is the only thing separating the two groups, and it separates them + // too late and in one direction only. The ABI is one flat table in one `host_lib` + // namespace, so screening accepts an escrow whose bytecode imports `emit_built_txn`: + // `EscrowCreate` stores it, and the escrow fails at finish with `tecINTERNAL`, because + // `Unimplemented` is a fatal fault rather than a code a contract reads. The other + // direction does not fail at all — `ContractHostFunctionsImpl` inherits `updateData`, + // and only `EscrowFinish::doApply` ever drains it, so a contract's write returns a byte + // count and is discarded. + // + // Both want the same thing: screening has to know which subset of + // `HostFunctionSpec::ALL` the host being built for actually serves, so a module + // importing the wrong group is refused at create with `temINVALID_BYTECODE`. + // `docs/wasm-amendment-gating.md` designs that transport for amendments (a gate + // bitfield threaded into `check()` and `register_host_functions`); this needs the same + // transport keyed on which host is running. virtual std::expected instanceParam(std::uint32_t index, std::uint32_t stTypeId) {