Skip to content

Conversation

@mvadari
Copy link
Collaborator

@mvadari mvadari commented Nov 24, 2025

High Level Overview of Change

This PR adds all the Wasmi integration code and the host functions specified in XLS-102. It also adds tests for all of this.

Note for reviewers: 10k lines of this PR are just WASM-compiled test fixtures, and another 4k lines are WASM test fixture source code. So while this PR is still quite large, it's not as large as it seems on the surface.

Context of Change

This PR depends on #5999 and replaces #5791

It is part of an effort to split up the Smart Escrow work into smaller, more managable-to-review PRs.

XLS-102: XRPLF/XRPL-Standards#303

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Performance (increase or change in throughput and/or latency)
  • Tests (you added tests for code that already exists, or your new feature included in this PR)
  • Documentation update
  • Chore (no impact to binary, e.g. .gitignore, formatting, dropping support for older tooling)
  • Release

API Impact

N/A

Test Plan

Several tests are added to this PR.

@mvadari mvadari added the DraftRunCI Normally CI does not run on draft PRs. This opts in. label Nov 24, 2025
@mvadari mvadari changed the title add WAMR dependency feat: add WASM host functions (Wasmi version) Nov 24, 2025
@codecov
Copy link

codecov bot commented Nov 24, 2025

Codecov Report

❌ Patch coverage is 95.87178% with 85 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.6%. Comparing base (bbc28b3) to head (9ed60b4).

Files with missing lines Patch % Lines
src/xrpld/app/wasm/detail/HostFuncWrapper.cpp 94.0% 56 Missing ⚠️
src/xrpld/app/wasm/detail/WasmiVM.cpp 93.1% 22 Missing ⚠️
src/xrpld/app/wasm/HostFuncImpl.h 80.0% 4 Missing ⚠️
src/xrpld/app/wasm/WasmiVM.h 93.9% 2 Missing ⚠️
src/libxrpl/basics/Number.cpp 95.2% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff               @@
##           ripple/wasmi   #6075     +/-   ##
==============================================
+ Coverage          79.1%   79.6%   +0.5%     
==============================================
  Files               836     845      +9     
  Lines             71261   73319   +2058     
  Branches           8303    8363     +60     
==============================================
+ Hits              56393   58367   +1974     
- Misses            14868   14952     +84     
Files with missing lines Coverage Δ
include/xrpl/basics/Number.h 100.0% <ø> (ø)
include/xrpl/protocol/IOUAmount.h 100.0% <ø> (ø)
include/xrpl/protocol/Protocol.h 100.0% <ø> (ø)
include/xrpl/protocol/TER.h 100.0% <ø> (ø)
include/xrpl/protocol/detail/ledger_entries.macro 100.0% <ø> (ø)
src/libxrpl/protocol/IOUAmount.cpp 90.1% <100.0%> (+0.1%) ⬆️
src/libxrpl/protocol/TER.cpp 100.0% <ø> (ø)
src/xrpld/app/wasm/HostFunc.h 100.0% <100.0%> (ø)
src/xrpld/app/wasm/ParamsHelper.h 100.0% <100.0%> (ø)
src/xrpld/app/wasm/WasmVM.h 100.0% <100.0%> (ø)
... and 7 more

... and 3 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mvadari mvadari marked this pull request as ready for review December 2, 2025 16:08
@mvadari mvadari requested a review from a team as a code owner December 2, 2025 16:08
@mvadari mvadari removed the DraftRunCI Normally CI does not run on draft PRs. This opts in. label Dec 2, 2025
Comment on lines +875 to +994
class Number2 : public Number
{
protected:
static Bytes const FLOAT_NULL;

bool good_;

public:
Number2(Slice const& data) : Number(), good_(false)
{
if (data.size() != 8)
return;

if (std::ranges::equal(FLOAT_NULL, data))
{
good_ = true;
return;
}

uint64_t const v = SerialIter(data).get64();
if (!(v & STAmount::cIssuedCurrency))
return;

int64_t const neg = (v & STAmount::cPositive) ? 1 : -1;
int32_t const e = static_cast<uint8_t>((v >> (64 - 10)) & 0xFFull);
if (e < 1 || e > 177)
return;

int64_t const m = neg * static_cast<int64_t>(v & ((1ull << 54) - 1));
if (!m)
return;

Number x(m, e + IOUAmount::minExponent - 1);
*static_cast<Number*>(this) = x;
good_ = true;
}

Number2() : Number(), good_(true)
{
}

Number2(int64_t x) : Number(x), good_(true)
{
}

Number2(uint64_t x) : Number(0), good_(false)
{
using mtype = std::invoke_result_t<decltype(&Number::mantissa), Number>;
if (x <= std::numeric_limits<mtype>::max())
*this = Number(x);
else
*this = Number(x / 10, 1) + Number(x % 10);

good_ = true;
}

Number2(int64_t mantissa, int32_t exponent)
: Number(mantissa, exponent), good_(true)
{
}

Number2(Number const& n) : Number(n), good_(true)
{
}

operator bool() const
{
return good_;
}

Expected<Bytes, HostFunctionError>
toBytes() const
{
uint64_t v = mantissa() >= 0 ? STAmount::cPositive : 0;
v |= STAmount::cIssuedCurrency;

uint64_t const absM = mantissa() >= 0 ? mantissa() : -mantissa();
if (!absM)
{
using etype =
std::invoke_result_t<decltype(&Number::exponent), Number>;
if (exponent() != std::numeric_limits<etype>::lowest())
{
return Unexpected(
HostFunctionError::
FLOAT_COMPUTATION_ERROR); // LCOV_EXCL_LINE
}
return FLOAT_NULL;
}
else if (absM > ((1ull << 54) - 1))
{
return Unexpected(
HostFunctionError::FLOAT_COMPUTATION_ERROR); // LCOV_EXCL_LINE
}
else if (exponent() > IOUAmount::maxExponent)
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
else if (exponent() < IOUAmount::minExponent)
return FLOAT_NULL;

int const e = exponent() - IOUAmount::minExponent + 1; //+97
v |= absM;
v |= ((uint64_t)e) << 54;

Serializer msg;
msg.add64(v);
auto const data = msg.getData();

#ifdef DEBUG_OUTPUT
std::cout << "m: " << std::setw(20) << mantissa()
<< ", e: " << std::setw(12) << exponent() << ", hex: ";
std::cout << std::hex << std::uppercase << std::setfill('0');
for (auto const& c : data)
std::cout << std::setw(2) << (unsigned)c << " ";
std::cout << std::dec << std::setfill(' ') << std::endl;
#endif

return data;
}
};

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm missing the point of having this class.

Most of it's functionality could be a part of base Number. The only part which isn't, toBytes function, could be a free function in this file, accepting number as an argument.

This also could mean that most of this could be a separate independent PR to develop, but I'm not forcing this in case you don't dant to do that

Copy link
Collaborator

@oleks-rip oleks-rip Jan 7, 2026

Choose a reason for hiding this comment

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

Conversion from Slice shouldn't be part of the Number, uint64_t shouldn't be the part of the Number (as it can lead to silent conversion, and I don't want to check every usage of the numbers). toBytes must be private (static to this file) and its appearance in the class just because class is present here, and not to conflict with other possible toBytes.
It is good helper class only for wasm "float" functions and will not interfere with the other modules

Comment on lines +634 to +675
// Continued fraction approximation of ln(x)
static Number
ln(Number const& x, unsigned iterations = 50)
{
if (x <= 0)
throw std::runtime_error("Not positive value");

Number const z = (x - 1) / (x + 1);
Number const zz = z * z;
Number denom = Number(1, -10);

// Construct the fraction from the bottom up
for (int i = iterations; i > 0; --i)
{
Number k(2 * i - 1);
denom = k - (i * i * zz / denom);
}

auto const r = 2 * z / denom;
return r;
}

Number
lg(Number const& x)
{
static Number const ln10 = ln(Number(10));

if (x <= Number(10))
{
auto const r = ln(x) / ln10;
return r;
}

// ln(x) = ln(normX * 10^norm) = ln(normX) + norm * ln(10)
int diffExp = 15 + x.exponent();
Number const normalX = x / Number(1, diffExp); // (1 <= normalX < 10)
auto const lnX = ln(normalX) + diffExp * ln10;

auto const r = lnX / ln10;
return r;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see test covering these functions are in TestHostFunctions.h. Since you've added this to libxrpl, those tests should be here as well

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also could be a separate small PR to develop ideally, but not forcing this

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.

6 participants