Files
rippled/src/test/app/HostFuncImpl_test.cpp
Peng Wang abfa572370 fix: Adapt wasm keylet calls to SeqProxy API (#7890)
develop changed every sequence-based `keylet::` factory to take
`SeqProxy const&` instead of `std::uint32_t`, and removed the
two-argument `mptokenIssuance(seq, issuer)` overload. The wasm host
functions and their tests still passed raw sequences, so the branch
merged cleanly but did not compile.

Wrap the raw sequences at the call sites, matching the idiom develop
adopted in its own tests:

- `SeqProxy::rawSequence` for check, escrow, nftokenOffer, offer,
  payChannel, permissionedDomain and vault
- `SeqProxy::rawTicket` for ticket
- `keylet::mptokenIssuance(makeMptID(seq, issuer))` for the removed
  overload

No computed keylet changes: the factories only read `seq.value()`, and
the removed overload was itself defined as `mptokenIssuance(makeMptID(
seq, issuer))`.
2026-08-09 15:52:10 -04:00

6439 lines
237 KiB
C++

#include <test/jtx/Env.h>
#include <test/jtx/amount.h>
#include <test/jtx/escrow.h>
#include <test/jtx/multisign.h>
#include <test/jtx/token.h>
#include <test/jtx/txflags.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Seed.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/HostFuncImpl.h>
#include <xrpl/tx/wasm/HostFuncWrapper.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <xrpl/tx/wasm/WasmImportsHelper.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <xrpl/tx/wasm/WasmiVM.h>
#include <boost/algorithm/hex.hpp>
#include <wasm.h>
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <exception>
#include <iomanip>
#include <ios>
#include <iostream>
#include <iterator>
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace xrpl::test {
static Bytes
toBytes(std::uint8_t value)
{
return {value};
}
static Bytes
toBytes(std::uint16_t value)
{
auto const* b = reinterpret_cast<uint8_t const*>(&value);
auto const* e = reinterpret_cast<uint8_t const*>(&value + 1);
return Bytes{b, e};
}
static Bytes
toBytes(std::uint32_t value)
{
auto const* b = reinterpret_cast<uint8_t const*>(&value);
auto const* e = reinterpret_cast<uint8_t const*>(&value + 1);
return Bytes{b, e};
}
static Bytes
toBytes(uint256 const& value)
{
return Bytes{value.begin(), value.end()};
}
static Bytes
toBytes(Issue const& issue)
{
Serializer s;
s.addBitString(issue.currency);
if (!isXRP(issue.currency))
s.addBitString(issue.account);
auto const data = s.getData();
return data;
}
static Bytes
toBytes(Asset const& asset)
{
if (asset.holds<Issue>())
return toBytes(asset.get<Issue>());
auto const& mptIssue = asset.get<MPTIssue>();
auto const& mptID = mptIssue.getMptID();
return Bytes{mptID.cbegin(), mptID.cend()};
}
static Bytes
toBytes(STAmount const& amount)
{
Serializer msg;
amount.add(msg);
auto const data = msg.getData();
return data;
}
static Bytes
toBytes(STNumber const& number)
{
Serializer msg;
number.add(msg);
auto const data = msg.getData();
return data;
}
static ApplyContext
createApplyContext(
test::jtx::Env& env,
OpenView& ov,
beast::Journal j,
STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {}))
{
ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, j};
return ac;
}
static ApplyContext
createApplyContext(
test::jtx::Env& env,
OpenView& ov,
STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {}))
{
return createApplyContext(env, ov, env.journal, tx);
}
class VirtualRuntime : public WasmRuntimeWrapper
{
Bytes buffer_;
std::int64_t gas_ = 1'000'000;
std::int64_t transferLimit_ = kWasmTransferLimit;
public:
static constexpr std::int64_t transferDiff = 1024;
VirtualRuntime() : buffer_(1024 * 1024)
{
}
Wmem
getMem() override
{
return Wmem(buffer_.data(), buffer_.size());
}
std::int64_t
getGas() override
{
gas_ -= 100;
return gas_;
}
std::int64_t
setGas(std::int64_t gas) override
{
if (gas == -2)
return -1;
if (gas < 0)
{
gas_ = std::numeric_limits<decltype(gas)>::max();
}
else
{
gas_ = gas;
}
return gas_;
}
std::int64_t
getTransferLimit() override
{
transferLimit_ -= transferDiff;
return transferLimit_;
}
[[nodiscard]] std::int64_t
getTestTransferLimit() const
{
return transferLimit_;
}
std::int64_t
setTransferLimit(std::int64_t x) override
{
if (x == -2)
return -1;
if (x < 0)
{
transferLimit_ = std::numeric_limits<decltype(transferLimit_)>::max();
}
else
{
transferLimit_ = x;
}
return transferLimit_;
}
void
checkIdx(WasmValVec const& params, size_t i) const
{
if (i + 1 >= params.size())
Throw<std::out_of_range>("Out of bounds");
if (params[i].kind != WASM_I32 || params[i + 1].kind != WASM_I32)
Throw<std::runtime_error>("Invalid params");
std::int32_t const ptr = params[i].of.i32;
std::int32_t const size = params[i + 1].of.i32;
std::int64_t const offset = (std::int64_t)ptr + size;
if (ptr < 0 || size < 0 || std::cmp_greater_equal(offset, buffer_.size()))
Throw<std::out_of_range>("Out of bounds");
}
[[nodiscard]] Slice
getBuffer(WasmValVec const& params, size_t i) const
{
checkIdx(params, i);
std::int32_t const ptr = params[i].of.i32;
std::int32_t const size = params[i + 1].of.i32;
return {&buffer_[ptr], static_cast<size_t>(size)};
}
[[nodiscard]] Bytes
getBytes(WasmValVec const& params, size_t i) const
{
checkIdx(params, i);
std::int32_t const ptr = params[i].of.i32;
std::int32_t const size = params[i + 1].of.i32;
return {&buffer_[ptr], &buffer_[ptr + size]};
}
void
setBytes(size_t ptr, void const* bytes, size_t size)
{
if (ptr + size >= buffer_.size())
Throw<std::runtime_error>("Out of bounds");
memcpy(&buffer_[ptr], bytes, size);
}
template <class T>
[[nodiscard]] [[nodiscard]] [[nodiscard]] [[nodiscard]] T
getInt(WasmValVec const& params, size_t i) const
{
checkIdx(params, i);
std::int32_t const ptr = params[i].of.i32;
std::int32_t const size = params[i + 1].of.i32;
if (size != sizeof(T))
Throw<std::runtime_error>("Invalid size");
return *reinterpret_cast<T const*>(&buffer_[ptr]);
}
[[nodiscard]] std::int32_t
getInt32(WasmValVec const& params, size_t i) const
{
return getInt<std::int32_t>(params, i);
}
[[nodiscard]] std::uint32_t
getUint32(WasmValVec const& params, size_t i) const
{
return getInt<std::uint32_t>(params, i);
}
[[nodiscard]] std::int64_t
getInt64(WasmValVec const& params, size_t i) const
{
return getInt<std::int64_t>(params, i);
}
[[nodiscard]] std::uint64_t
getUint64(WasmValVec const& params, size_t i) const
{
return getInt<std::uint64_t>(params, i);
}
};
template <class P, class E, typename Arg>
void
ww_hlp(size_t& idx, E&& e, P&& params, Arg&& arg)
{
if constexpr (std::is_integral_v<Arg>)
{
params[idx++] = std::is_same_v<Arg, int64_t> || std::is_same_v<Arg, long long>
? wasm_val_t WASM_I64_VAL(static_cast<int64_t>(arg))
: wasm_val_t WASM_I32_VAL(static_cast<int32_t>(arg));
}
else if constexpr (std::is_same_v<Arg, Issue>)
{
auto const* udata = reinterpret_cast<WasmUserData*>(e);
HostFunctions const& hf = udata->first;
auto& vrt = reinterpret_cast<VirtualRuntime&>(hf.getRT());
auto const data = toBytes(std::forward<Arg>(arg));
size_t const ptr = (idx << 10);
vrt.setBytes(ptr, data.data(), data.size());
params[idx++] = wasm_val_t WASM_I32_VAL(static_cast<int32_t>(ptr));
params[idx++] = wasm_val_t WASM_I32_VAL(static_cast<int32_t>(data.size()));
}
else
{
auto const* udata = reinterpret_cast<WasmUserData*>(e);
HostFunctions const& hf = udata->first;
auto& vrt = reinterpret_cast<VirtualRuntime&>(hf.getRT());
size_t const ptr = (idx << 10);
vrt.setBytes(ptr, arg.data(), arg.size());
params[idx++] = wasm_val_t WASM_I32_VAL(static_cast<int32_t>(ptr));
params[idx++] = wasm_val_t WASM_I32_VAL(static_cast<int32_t>(arg.size()));
}
}
// Helper wrapper to call WASM wrapper functions with automatic parameter packing
template <class E, class P, typename... Args>
wasm_trap_t*
ww(E&& e, P&& params, P&& result, Args... args)
{
size_t idx = 0;
(ww_hlp(idx, e, params, std::forward<Args>(args)), ...); // NOLINT
return HostFuncMain_wrap(std::forward<E>(e), params.get(), result.get()); // NOLINT
}
// ww() packs only integral args as wasm params, so the scoped enum needs widening.
constexpr int32_t
traceDataTypeToInt(TraceDataType t)
{
return static_cast<int32_t>(t);
}
constexpr int64_t min64 = std::numeric_limits<int64_t>::min();
constexpr int64_t max64 = std::numeric_limits<int64_t>::max();
constexpr int32_t floatSize = 12;
struct HostFuncImpl_test : public beast::unit_test::Suite
{
void
testGetLedgerSqn()
{
testcase("getLedgerSqn");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.getLedgerSqn();
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) &&
BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->header().seq);
}
}
void
testGetParentLedgerTime()
{
testcase("getParentLedgerTime");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.getParentLedgerTime();
WasmValVec params(2), result(1);
auto* trap =
ww(&import.at("parent_ldgr_time"), params, result, 0, sizeof(std::uint32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) &&
BEAST_EXPECT(
vrt.getUint32(params, 0) ==
env.current()->parentCloseTime().time_since_epoch().count());
}
}
void
testGetParentLedgerHash()
{
testcase("getParentLedgerHash");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.getParentLedgerHash();
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("parent_ldgr_hash"), params, result, 0, uint256::size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == uint256::size());
auto const resultBytes = vrt.getBytes(params, 0);
auto const expectedHash = env.current()->header().parentHash;
BEAST_EXPECT(
resultBytes.size() == uint256::size() &&
std::memcmp(resultBytes.data(), expectedHash.data(), uint256::size()) == 0);
}
}
void
testGetBaseFee()
{
testcase("getBaseFee");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getBaseFee();
{
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("base_fee"), params, result, 0, sizeof(std::uint32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) &&
BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->fees().base.drops());
}
}
void
testIsAmendmentEnabled()
{
testcase("isAmendmentEnabled");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Use featureTokenEscrow for testing
auto const amendmentId = featureTokenEscrow;
// hfs.isAmendmentEnabled(amendmentId);
{
WasmValVec params(2), result(1);
vrt.setBytes(0, amendmentId.data(), uint256::size());
auto* trap = ww(&import.at("amendment_enabled"), params, result, 0, uint256::size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
std::string const amendmentName = "TokenEscrow";
// hfs.isAmendmentEnabled(amendmentName);
{
WasmValVec params(2), result(1);
vrt.setBytes(0, amendmentName.data(), amendmentName.size());
auto* trap =
ww(&import.at("amendment_enabled"), params, result, 0, amendmentName.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
uint256 const fakeId;
// hfs.isAmendmentEnabled(fakeId);
{
WasmValVec params(2), result(1);
vrt.setBytes(0, fakeId.data(), uint256::size());
auto* trap = ww(&import.at("amendment_enabled"), params, result, 0, uint256::size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
std::string const fakeName = "FakeAmendment";
// hfs.isAmendmentEnabled(fakeName);
{
WasmValVec params(2), result(1);
vrt.setBytes(0, fakeName.data(), fakeName.size());
auto* trap = ww(&import.at("amendment_enabled"), params, result, 0, fakeName.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
}
void
testCacheLedgerObj()
{
testcase("cacheLedgerObj");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow = keylet::escrow(env.master, SeqProxy::rawSequence(2));
auto const accountKeylet = keylet::account(env.master);
{
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.cacheLedgerObj(accountKeylet.key, -1);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, accountKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::SlotOutRange));
}
// hfs.cacheLedgerObj(accountKeylet.key, 257);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, accountKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 257);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::SlotOutRange));
}
// hfs.cacheLedgerObj(dummyEscrow.key, 0);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, dummyEscrow.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::LedgerObjNotFound));
}
// hfs.cacheLedgerObj(accountKeylet.key, 0);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, accountKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
vrt.setGas(2'000'000);
for (int i = 1; i <= 256; ++i)
{
// hfs.cacheLedgerObj(accountKeylet.key, i);
WasmValVec params(3), result(1);
vrt.setBytes(0, accountKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), i);
if (!(BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECTS(
result[0].of.i32 == i,
"result: " + std::to_string(result[0].of.i32) +
", expected: " + std::to_string(i))))
break;
}
// hfs.cacheLedgerObj(accountKeylet.key, 0);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, accountKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::SlotsFull));
}
}
{
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
vrt.setGas(2'000'000);
for (int i = 1; i <= 256; ++i)
{
// hfs.cacheLedgerObj(accountKeylet.key, 0);
WasmValVec params(3), result(1);
vrt.setBytes(0, accountKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0);
if (!(BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECTS(
result[0].of.i32 == i,
"result: " + std::to_string(result[0].of.i32) +
", expected: " + std::to_string(i))))
break;
}
// hfs.cacheLedgerObj(accountKeylet.key, 0);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, accountKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::SlotsFull));
}
}
}
void
testGetTxField()
{
testcase("getTxField");
using namespace test::jtx;
std::string const credIdHex =
"0011223344556677889900112233445566778899001122334455667788990011";
uint256 credId;
BEAST_EXPECT(credId.parseHex(credIdHex));
Env env{*this};
OpenView ov{*env.current()};
STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) {
obj.setAccountID(sfAccount, env.master.id());
obj.setAccountID(sfOwner, env.master.id());
obj.setFieldU32(sfOfferSequence, env.seq(env.master));
obj.setFieldArray(sfMemos, STArray{});
STVector256 credIds;
credIds.pushBack(credId);
obj.setFieldV256(sfCredentialIDs, credIds);
});
ApplyContext ac = createApplyContext(env, ov, stx);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
{
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getTxField(sfAccount);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"),
params,
result,
sfAccount.getCode(),
0,
AccountID::size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == AccountID::size());
auto const accountBytes = vrt.getBytes(params, 1);
BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id()));
}
// hfs.getTxField(sfOwner);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"),
params,
result,
sfOwner.getCode(),
0,
AccountID::size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == AccountID::size());
auto const ownerBytes = vrt.getBytes(params, 1);
BEAST_EXPECT(std::ranges::equal(ownerBytes, env.master.id()));
}
// hfs.getTxField(sfTransactionType);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"), params, result, sfTransactionType.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 > 0);
auto txTypeBytes = vrt.getBytes(params, 1);
txTypeBytes.resize(result[0].of.i32);
BEAST_EXPECT(txTypeBytes == toBytes(ttESCROW_FINISH));
}
// hfs.getTxField(sfOfferSequence);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"), params, result, sfOfferSequence.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 > 0);
auto offerSeqBytes = vrt.getBytes(params, 1);
offerSeqBytes.resize(result[0].of.i32);
BEAST_EXPECT(offerSeqBytes == toBytes(env.seq(env.master)));
}
// hfs.getTxField(sfDestination);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"), params, result, sfDestination.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::FieldNotFound));
}
// hfs.getTxField(sfMemos);
{
WasmValVec params(3), result(1);
auto* trap = ww(&import.at("tx_field"), params, result, sfMemos.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::NotLeafField));
}
// hfs.getTxField(sfCredentialIDs);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"), params, result, sfCredentialIDs.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::NotLeafField),
std::to_string(result[0].of.i32));
}
// hfs.getTxField(sfInvalid);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"), params, result, sfInvalid.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::FieldNotFound));
}
// hfs.getTxField(sfGeneric);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"), params, result, sfGeneric.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::FieldNotFound));
}
}
{
auto const iouAsset = env.master["USD"];
STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) {
obj.setAccountID(sfAccount, env.master.id());
obj.setFieldIssue(sfAsset, STIssue{sfAsset, xrpIssue()});
obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, iouAsset.issue()});
});
ApplyContext ac2 = createApplyContext(env, ov, stx2);
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac2, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getTxField(sfAsset);
{
WasmValVec params(3), result(1);
auto* trap = ww(&import.at("tx_field"), params, result, sfAsset.getCode(), 0, 256);
std::vector<std::uint8_t> const expectedAsset(20, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 > 0);
auto assetBytes = vrt.getBytes(params, 1);
assetBytes.resize(result[0].of.i32);
BEAST_EXPECT(assetBytes == expectedAsset);
}
// hfs.getTxField(sfAsset2);
{
WasmValVec params(3), result(1);
auto* trap = ww(&import.at("tx_field"), params, result, sfAsset2.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 > 0);
auto asset2Bytes = vrt.getBytes(params, 1);
asset2Bytes.resize(result[0].of.i32);
BEAST_EXPECT(asset2Bytes == toBytes(Asset(iouAsset)));
}
}
{
auto const iouAsset = env.master["GBP"];
auto const mptId = makeMptID(1, env.master);
STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) {
obj.setAccountID(sfAccount, env.master.id());
obj.setFieldIssue(sfAsset, STIssue{sfAsset, iouAsset.issue()});
obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, MPTIssue{mptId}});
});
ApplyContext ac2 = createApplyContext(env, ov, stx2);
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac2, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getTxField(sfAsset);
{
WasmValVec params(3), result(1);
auto* trap = ww(&import.at("tx_field"), params, result, sfAsset.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
{
auto assetBytes = vrt.getBytes(params, 1);
assetBytes.resize(result[0].of.i32);
BEAST_EXPECT(assetBytes == toBytes(Asset(iouAsset)));
}
}
// hfs.getTxField(sfAsset2);
{
WasmValVec params(3), result(1);
auto* trap = ww(&import.at("tx_field"), params, result, sfAsset2.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
{
auto assetBytes = vrt.getBytes(params, 1);
assetBytes.resize(result[0].of.i32);
BEAST_EXPECT(assetBytes == toBytes(Asset(mptId)));
}
}
}
{
std::uint8_t const expectedScale = 8;
STTx const stx2 = STTx(ttMPTOKEN_ISSUANCE_CREATE, [&](auto& obj) {
obj.setAccountID(sfAccount, env.master.id());
obj.setFieldU8(sfAssetScale, expectedScale);
});
ApplyContext ac2 = createApplyContext(env, ov, stx2);
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac2, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getTxField(sfAssetScale);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("tx_field"), params, result, sfAssetScale.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
{
auto assetBytes = vrt.getBytes(params, 1);
assetBytes.resize(result[0].of.i32);
BEAST_EXPECT(std::ranges::equal(assetBytes, toBytes(expectedScale)));
}
}
}
}
void
testGetCurrentLedgerObjField()
{
testcase("getCurrentLedgerObjField");
using namespace test::jtx;
using namespace std::chrono;
Env env{*this};
// Fund the account and create an escrow so the ledger object exists
env(escrow::create(env.master, env.master, XRP(100)), escrow::kFinishTime(env.now() + 1s));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
// Find the escrow ledger object
auto const escrowKeylet =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) - 1));
BEAST_EXPECT(env.le(escrowKeylet));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, escrowKeylet);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getCurrentLedgerObjField(sfAccount);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("home_le_field"), params, result, sfAccount.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto accountBytes = vrt.getBytes(params, 1);
accountBytes.resize(result[0].of.i32);
BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id()));
}
}
// hfs.getCurrentLedgerObjField(sfAmount);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("home_le_field"), params, result, sfAmount.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
{
auto amountBytes = vrt.getBytes(params, 1);
amountBytes.resize(result[0].of.i32);
BEAST_EXPECT(amountBytes == toBytes(XRP(100)));
}
}
// hfs.getCurrentLedgerObjField(sfPreviousTxnID);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("home_le_field"), params, result, sfPreviousTxnID.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
{
auto previousTxnIdBytes = vrt.getBytes(params, 1);
previousTxnIdBytes.resize(result[0].of.i32);
BEAST_EXPECT(previousTxnIdBytes == toBytes(env.tx()->getTransactionID()));
}
}
// hfs.getCurrentLedgerObjField(sfOwner);
{
WasmValVec params(3), result(1);
auto* trap = ww(&import.at("home_le_field"), params, result, sfOwner.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::FieldNotFound));
}
{
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) + 5));
VirtualRuntime vrt2;
WasmHostFunctionsImpl hfs2(ac, dummyEscrow);
auto import2 = xrpl::createWasmImport(hfs2);
hfs2.setRT(vrt2);
// hfs2.getCurrentLedgerObjField(sfAccount);
{
WasmValVec params(3), result(1);
auto* trap =
ww(&import2.at("home_le_field"), params, result, sfAccount.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::LedgerObjNotFound));
}
}
}
void
testGetLedgerObjField()
{
testcase("getLedgerObjField");
using namespace test::jtx;
using namespace std::chrono;
Env env{*this};
// Fund the account and create an escrow so the ledger object exists
env(escrow::create(env.master, env.master, XRP(100)), escrow::kFinishTime(env.now() + 1s));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const accountKeylet = keylet::account(env.master.id());
auto const escrowKeylet =
keylet::escrow(env.master.id(), SeqProxy::rawSequence(env.seq(env.master) - 1));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, escrowKeylet);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.cacheLedgerObj(accountKeylet.key, 1);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, accountKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
// hfs.getLedgerObjField(1, sfAccount);
{
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("le_field"), params, result, 1, sfAccount.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto accountBytes = vrt.getBytes(params, 2);
accountBytes.resize(result[0].of.i32);
BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id()));
}
}
// hfs.getLedgerObjField(1, sfBalance);
{
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("le_field"), params, result, 1, sfBalance.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
{
auto balanceBytes = vrt.getBytes(params, 2);
balanceBytes.resize(result[0].of.i32);
BEAST_EXPECT(balanceBytes == toBytes(env.balance(env.master)));
}
}
// hfs.getLedgerObjField(0, sfAccount);
{
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("le_field"), params, result, 0, sfAccount.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::SlotOutRange));
}
// hfs.getLedgerObjField(257, sfAccount);
{
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("le_field"), params, result, 257, sfAccount.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::SlotOutRange));
}
// hfs.getLedgerObjField(2, sfAccount);
{
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("le_field"), params, result, 2, sfAccount.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::EmptySlot));
}
// hfs.getLedgerObjField(1, sfOwner);
{
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("le_field"), params, result, 1, sfOwner.getCode(), 0, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::FieldNotFound));
}
}
void
testGetTxNestedField()
{
testcase("getTxNestedField");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
std::string const credIdHex =
"0011223344556677889900112233445566778899001122334455667788990011";
uint256 credId;
BEAST_EXPECT(credId.parseHex(credIdHex));
// Create a transaction with a nested array field
STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) {
obj.setAccountID(sfAccount, env.master.id());
STArray memos;
STObject memoObj(sfMemo);
memoObj.setFieldVL(sfMemoData, Slice("hello", 5));
memos.push_back(memoObj);
obj.setFieldArray(sfMemos, memos);
STVector256 credIds;
credIds.pushBack(credId);
obj.setFieldV256(sfCredentialIDs, credIds);
});
ApplyContext ac = createApplyContext(env, ov, stx);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getTxNestedField(locator);
{
// Locator for sfMemos[0].sfMemo.sfMemoData
// Locator is a sequence of int32_t codes:
// [sfMemos.getCode(), 0, sfMemoData.getCode()]
std::vector<int32_t> const locatorVec = {sfMemos.getCode(), 0, sfMemoData.getCode()};
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("tx_inner"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto memoDataBytes = vrt.getBytes(params, 2);
memoDataBytes.resize(result[0].of.i32);
std::string const memoData(memoDataBytes.begin(), memoDataBytes.end());
BEAST_EXPECT(memoData == "hello");
}
}
// hfs.getTxNestedField(locator);
{
// Locator for sfCredentialIDs[0]
std::vector<int32_t> locatorVec = {sfCredentialIDs.getCode(), 0};
vrt.setBytes(
0,
reinterpret_cast<uint8_t const*>(locatorVec.data()),
locatorVec.size() * sizeof(int32_t));
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("tx_inner"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto credIdBytes = vrt.getBytes(params, 2);
credIdBytes.resize(result[0].of.i32);
std::string const credIdResult(credIdBytes.begin(), credIdBytes.end());
BEAST_EXPECT(strHex(credIdResult) == credIdHex);
}
}
// hfs.getTxNestedField(locator);
{
// can use the nested locator for base fields too
std::vector<int32_t> locatorVec = {sfAccount.getCode()};
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("tx_inner"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto accountBytes = vrt.getBytes(params, 2);
accountBytes.resize(result[0].of.i32);
BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id()));
}
}
// hfs.getTxNestedField(locator);
{
// unaligned locator
std::vector<uint8_t> locatorVec(sizeof(int32_t) + 1);
auto const accountFieldCode = sfAccount.getCode();
memcpy(locatorVec.data() + 1, &accountFieldCode, sizeof(int32_t));
vrt.setBytes(0, locatorVec.data(), sizeof(int32_t) + 1);
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("tx_inner"), params, result, 1, sizeof(int32_t), 256, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto accountBytes = vrt.getBytes(params, 2);
accountBytes.resize(result[0].of.i32);
BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id()));
}
}
auto expectError = [&](std::vector<int32_t> const& locatorVec,
HostFunctionError expectedError) {
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(4), result(1);
// hfs.getTxNestedField(locator);
auto* trap =
ww(&import.at("tx_inner"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32));
};
// hfs.getTxNestedField(locator);
// Locator for non-existent base field
expectError(
{sfSigners.getCode(), // sfSigners does not exist
0,
sfAccount.getCode()},
HostFunctionError::FieldNotFound);
// hfs.getTxNestedField(locator);
// Locator for non-existent index
expectError(
{sfMemos.getCode(),
1, // index 1 does not exist
sfMemoData.getCode()},
HostFunctionError::IndexOutOfBounds);
// hfs.getTxNestedField(locator);
// Locator for non-existent index
expectError(
{sfCredentialIDs.getCode(), 1}, // index 1 does not exist
HostFunctionError::IndexOutOfBounds);
// hfs.getTxNestedField(locator);
// Locator for negative index (STArray)
expectError(
{sfMemos.getCode(),
-1, // negative index
sfMemoData.getCode()},
HostFunctionError::IndexOutOfBounds);
// hfs.getTxNestedField(locator);
// Locator for negative index (STVector256)
expectError(
{sfCredentialIDs.getCode(), -1}, // negative index
HostFunctionError::IndexOutOfBounds);
// hfs.getTxNestedField(locator);
// Locator for non-existent nested field
expectError(
{sfMemos.getCode(), 0, sfURI.getCode()}, // sfURI does not exist in the memo
HostFunctionError::FieldNotFound);
// hfs.getTxNestedField(locator);
// Locator for non-existent base sfield
expectError(
{fieldCode(20000, 20000), // nonexistent SField code
0,
sfAccount.getCode()},
HostFunctionError::InvalidField);
// hfs.getTxNestedField(locator);
// Locator for non-existent nested sfield
expectError(
{sfMemos.getCode(), // nonexistent SField code
0,
fieldCode(20000, 20000)},
HostFunctionError::InvalidField);
// hfs.getTxNestedField(locator);
// Locator for negative base sfield code (-1 = sfInvalid, exists in map but not in tx)
expectError(
{-1, // sfInvalid's field code
0,
sfAccount.getCode()},
HostFunctionError::FieldNotFound);
// hfs.getTxNestedField(locator);
// Locator for zero base sfield code (0 = sfGeneric, exists in map but not in tx)
expectError(
{0, // sfGeneric's field code
0,
sfAccount.getCode()},
HostFunctionError::FieldNotFound);
// hfs.getTxNestedField(locator);
// Locator for very negative base sfield code (not in knownCodeToField map)
expectError(
{std::numeric_limits<int32_t>::min(), 0, sfAccount.getCode()},
HostFunctionError::InvalidField);
// hfs.getTxNestedField(locator);
// Locator for negative nested sfield code in STObject context
// (sfMemos[0] is an STObject, then -1 is looked up as SField)
expectError(
{sfMemos.getCode(), 0, -1}, // -1 = sfInvalid, exists in map but not in memo object
HostFunctionError::FieldNotFound);
// hfs.getTxNestedField(locator);
// Locator for STArray
expectError({sfMemos.getCode()}, HostFunctionError::NotLeafField);
// hfs.getTxNestedField(locator);
// Locator for STVector256
expectError({sfCredentialIDs.getCode()}, HostFunctionError::NotLeafField);
// hfs.getTxNestedField(locator);
// Locator for nesting into non-array/object field
expectError(
{sfAccount.getCode(), // sfAccount is not an array or object
0,
sfAccount.getCode()},
HostFunctionError::LocatorMalformed);
// hfs.getTxNestedField(locator);
// Locator for empty locator
expectError({}, HostFunctionError::LocatorMalformed);
// hfs.getTxNestedField(locator);
// Locator for malformed locator (not multiple of 4)
{
std::vector<int32_t> locatorVec = {sfMemos.getCode()};
vrt.setBytes(0, locatorVec.data(), 3);
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("tx_inner"), params, result, 0, 3, 256, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::LocatorMalformed));
}
}
void
testGetCurrentLedgerObjNestedField()
{
testcase("getCurrentLedgerObjNestedField");
using namespace test::jtx;
Env env{*this};
Account const alice("alice");
Account const becky("becky");
// Create a SignerList for env.master
env(signers(env.master, 2, {{alice, 1}, {becky, 1}}));
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
// Find the signer ledger object
auto const signerKeylet = keylet::signerList(env.master.id());
BEAST_EXPECT(env.le(signerKeylet));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, signerKeylet);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getCurrentLedgerObjNestedField(baseLocatorSlice);
// Locator for base field
{
std::vector<int32_t> baseLocator = {sfSignerQuorum.getCode()};
vrt.setBytes(0, baseLocator.data(), baseLocator.size() * sizeof(int32_t));
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("home_le_inner"),
params,
result,
0,
baseLocator.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto signerQuorumBytes = vrt.getBytes(params, 2);
signerQuorumBytes.resize(result[0].of.i32);
BEAST_EXPECT(signerQuorumBytes == toBytes(static_cast<uint32_t>(2)));
}
}
auto expectError = [&](std::vector<int32_t> const& locatorVec,
HostFunctionError expectedError) {
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(4), result(1);
// hfs.getCurrentLedgerObjNestedField(locator);
auto* trap =
ww(&import.at("home_le_inner"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32));
};
// hfs.getCurrentLedgerObjNestedField(locator);
// Locator for non-existent base field
expectError(
{sfSigners.getCode(), // sfSigners does not exist
0,
sfAccount.getCode()},
HostFunctionError::FieldNotFound);
// hfs.getCurrentLedgerObjNestedField(locator);
// Locator for nesting into non-array/object field
expectError(
{sfSignerQuorum.getCode(), // sfSignerQuorum is not an array or object
0,
sfAccount.getCode()},
HostFunctionError::LocatorMalformed);
// hfs.getCurrentLedgerObjNestedField(emptyLocator);
// Locator for empty locator
{
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("home_le_inner"), params, result, 0, 0, 256, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::LocatorMalformed));
}
// hfs.getCurrentLedgerObjNestedField(malformedLocator);
// Locator for malformed locator (not multiple of 4)
{
std::vector<int32_t> malformedLocatorVec = {sfMemos.getCode()};
vrt.setBytes(0, malformedLocatorVec.data(), 3);
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("home_le_inner"), params, result, 0, 3, 256, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::LocatorMalformed));
}
// hfs.getCurrentLedgerObjNestedField(locator);
{
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) + 5));
VirtualRuntime vrt2;
WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow);
auto import2 = xrpl::createWasmImport(dummyHfs);
dummyHfs.setRT(vrt2);
std::vector<int32_t> const locatorVec = {sfAccount.getCode()};
vrt2.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(4), result(1);
auto* trap =
ww(&import2.at("home_le_inner"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::LedgerObjNotFound),
std::to_string(result[0].of.i32));
}
}
void
testGetLedgerObjNestedField()
{
testcase("getLedgerObjNestedField");
using namespace test::jtx;
Env env{*this};
Account const alice("alice");
Account const becky("becky");
// Create a SignerList for env.master
env(signers(env.master, 2, {{alice, 1}, {becky, 1}}));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Cache the SignerList ledger object in slot 1
auto const signerListKeylet = keylet::signerList(env.master.id());
// hfs.cacheLedgerObj(signerListKeylet.key, 1);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, signerListKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
// Locator for sfSignerEntries[0].sfAccount
{
std::vector<int32_t> const locatorVec = {
sfSignerEntries.getCode(), 0, sfAccount.getCode()};
// hfs.getLedgerObjNestedField(1, locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("le_inner"),
params,
result,
1,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto aliceIdBytes = vrt.getBytes(params, 3);
aliceIdBytes.resize(result[0].of.i32);
BEAST_EXPECT(std::ranges::equal(aliceIdBytes, alice.id()));
}
}
// Locator for sfSignerEntries[1].sfAccount
{
std::vector<int32_t> const locatorVec = {
sfSignerEntries.getCode(), 1, sfAccount.getCode()};
// hfs.getLedgerObjNestedField(1, locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("le_inner"),
params,
result,
1,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto beckyIdBytes = vrt.getBytes(params, 3);
beckyIdBytes.resize(result[0].of.i32);
BEAST_EXPECT(std::ranges::equal(beckyIdBytes, becky.id()));
}
}
// Locator for sfSignerEntries[0].sfSignerWeight
{
std::vector<int32_t> const locatorVec = {
sfSignerEntries.getCode(), 0, sfSignerWeight.getCode()};
// hfs.getLedgerObjNestedField(1, locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("le_inner"),
params,
result,
1,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
// Should be 1
auto const expected = toBytes(static_cast<std::uint16_t>(1));
auto weightBytes = vrt.getBytes(params, 3);
weightBytes.resize(result[0].of.i32);
BEAST_EXPECT(weightBytes == expected);
}
}
// Locator for base field sfSignerQuorum
{
std::vector<int32_t> const locatorVec = {sfSignerQuorum.getCode()};
// hfs.getLedgerObjNestedField(1, locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("le_inner"),
params,
result,
1,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32)))
{
auto const expected = toBytes(static_cast<std::uint32_t>(2));
auto quorumBytes = vrt.getBytes(params, 3);
quorumBytes.resize(result[0].of.i32);
BEAST_EXPECT(quorumBytes == expected);
}
}
// Helper for error checks
auto expectError = [&](std::vector<int32_t> const& locatorVec,
HostFunctionError expectedError,
int slot = 1) {
// hfs.getLedgerObjNestedField(slot, locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("le_inner"),
params,
result,
slot,
0,
locatorVec.size() * sizeof(int32_t),
256,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32));
};
// Error: base field not found
expectError(
{sfSigners.getCode(), // sfSigners does not exist
0,
sfAccount.getCode()},
HostFunctionError::FieldNotFound);
// Error: index out of bounds
expectError(
{sfSignerEntries.getCode(),
2, // index 2 does not exist
sfAccount.getCode()},
HostFunctionError::IndexOutOfBounds);
// Error: nested field not found
expectError(
{
sfSignerEntries.getCode(),
0,
sfDestination.getCode() // sfDestination does not exist
},
HostFunctionError::FieldNotFound);
// Error: invalid field code
expectError(
{fieldCode(99999, 99999), 0, sfAccount.getCode()}, HostFunctionError::InvalidField);
// Error: invalid nested field code
expectError(
{sfSignerEntries.getCode(), 0, fieldCode(99999, 99999)},
HostFunctionError::InvalidField);
// Error: slot out of range
expectError({sfSignerQuorum.getCode()}, HostFunctionError::SlotOutRange, 0);
expectError({sfSignerQuorum.getCode()}, HostFunctionError::SlotOutRange, 257);
// Error: empty slot
expectError({sfSignerQuorum.getCode()}, HostFunctionError::EmptySlot, 2);
// Error: locator for STArray (not leaf field)
expectError({sfSignerEntries.getCode()}, HostFunctionError::NotLeafField);
// Error: nesting into non-array/object field
expectError(
{sfSignerQuorum.getCode(), 0, sfAccount.getCode()},
HostFunctionError::LocatorMalformed);
// Error: empty locator
expectError({}, HostFunctionError::LocatorMalformed);
// Error: locator malformed (not multiple of 4)
{
std::vector<int32_t> const locatorVec = {sfSignerEntries.getCode()};
// hfs.getLedgerObjNestedField(1, locator);
vrt.setBytes(0, locatorVec.data(), 3);
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("le_inner"), params, result, 1, 0, 3, 256, 256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::LocatorMalformed));
}
}
void
testGetTxArrayLen()
{
testcase("getTxArrayLen");
using namespace test::jtx;
std::string const credIdHex =
"0011223344556677889900112233445566778899001122334455667788990011";
uint256 credId;
BEAST_EXPECT(credId.parseHex(credIdHex));
Env env{*this};
OpenView ov{*env.current()};
// Transaction with an array field
STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) {
obj.setAccountID(sfAccount, env.master.id());
STArray memos;
{
STObject memoObj(sfMemo);
memoObj.setFieldVL(sfMemoData, Slice("hello", 5));
memos.push_back(memoObj);
}
{
STObject memoObj(sfMemo);
memoObj.setFieldVL(sfMemoData, Slice("world", 5));
memos.push_back(memoObj);
}
obj.setFieldArray(sfMemos, memos);
STVector256 credIds;
credIds.pushBack(credId);
obj.setFieldV256(sfCredentialIDs, credIds);
});
ApplyContext ac = createApplyContext(env, ov, stx);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Should return 2 for sfMemos
// hfs.getTxArrayLen(sfMemos);
{
WasmValVec params(1), result(1);
auto* trap = ww(&import.at("tx_arr_len"), params, result, sfMemos.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
BEAST_EXPECT(result[0].of.i32 == 2);
}
// Should return error for non-array field
// hfs.getTxArrayLen(sfAccount);
{
WasmValVec params(1), result(1);
auto* trap = ww(&import.at("tx_arr_len"), params, result, sfAccount.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(result[0].of.i32 == static_cast<int32_t>(HostFunctionError::NoArray));
}
// Should return error for missing array field
// hfs.getTxArrayLen(sfSigners);
{
WasmValVec params(1), result(1);
auto* trap = ww(&import.at("tx_arr_len"), params, result, sfSigners.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::FieldNotFound));
}
// Should return 1 for sfCredentialIDs
// hfs.getTxArrayLen(sfCredentialIDs);
{
WasmValVec params(1), result(1);
auto* trap = ww(&import.at("tx_arr_len"), params, result, sfCredentialIDs.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
BEAST_EXPECT(result[0].of.i32 == 1);
}
}
void
testGetCurrentLedgerObjArrayLen()
{
testcase("getCurrentLedgerObjArrayLen");
using namespace test::jtx;
Env env{*this};
Account const alice("alice");
Account const becky("becky");
// Create a SignerList for env.master
env(signers(env.master, 2, {{alice, 1}, {becky, 1}}));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const signerKeylet = keylet::signerList(env.master.id());
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, signerKeylet);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getCurrentLedgerObjArrayLen(sfSignerEntries);
{
WasmValVec params(1), result(1);
auto* trap =
ww(&import.at("home_le_arr_len"), params, result, sfSignerEntries.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
BEAST_EXPECT(result[0].of.i32 == 2);
}
// hfs.getCurrentLedgerObjArrayLen(sfMemos);
{
WasmValVec params(1), result(1);
auto* trap = ww(&import.at("home_le_arr_len"), params, result, sfMemos.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::FieldNotFound));
}
// Should return NO_ARRAY for non-array field
// hfs.getCurrentLedgerObjArrayLen(sfAccount);
{
WasmValVec params(1), result(1);
auto* trap = ww(&import.at("home_le_arr_len"), params, result, sfAccount.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(result[0].of.i32 == static_cast<int32_t>(HostFunctionError::NoArray));
}
{
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) + 5));
VirtualRuntime vrt2;
WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow);
auto import2 = xrpl::createWasmImport(dummyHfs);
dummyHfs.setRT(vrt2);
// auto const len = dummyHfs.getCurrentLedgerObjArrayLen(sfMemos);
WasmValVec params(1), result(1);
auto* trap = ww(&import2.at("home_le_arr_len"), params, result, sfMemos.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::LedgerObjNotFound));
}
}
void
testGetLedgerObjArrayLen()
{
testcase("getLedgerObjArrayLen");
using namespace test::jtx;
Env env{*this};
Account const alice("alice");
Account const becky("becky");
// Create a SignerList for env.master
env(signers(env.master, 2, {{alice, 1}, {becky, 1}}));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
auto const signerListKeylet = keylet::signerList(env.master.id());
// hfs.cacheLedgerObj(signerListKeylet.key, 1);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, signerListKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
{
// hfs.getLedgerObjArrayLen(1, sfSignerEntries);
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("le_arr_len"), params, result, 1, sfSignerEntries.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
{
// Should return 2 for sfSignerEntries
BEAST_EXPECT(result[0].of.i32 == 2);
}
}
{
// hfs.getLedgerObjArrayLen(0, sfSignerEntries);
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("le_arr_len"), params, result, 0, sfSignerEntries.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(result[0].of.i32 == static_cast<int32_t>(HostFunctionError::SlotOutRange));
}
{
// Should return error for non-array field
// hfs.getLedgerObjArrayLen(1, sfAccount);
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("le_arr_len"), params, result, 1, sfAccount.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(result[0].of.i32 == static_cast<int32_t>(HostFunctionError::NoArray));
}
{
// Should return error for empty slot
// hfs.getLedgerObjArrayLen(2, sfSignerEntries);
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("le_arr_len"), params, result, 2, sfSignerEntries.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(result[0].of.i32 == static_cast<int32_t>(HostFunctionError::EmptySlot));
}
{
// Should return error for missing array field
// hfs.getLedgerObjArrayLen(1, sfMemos);
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("le_arr_len"), params, result, 1, sfMemos.getCode());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::FieldNotFound));
}
}
void
testGetTxNestedArrayLen()
{
testcase("getTxNestedArrayLen");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) {
STArray memos;
STObject memoObj(sfMemo);
memoObj.setFieldVL(sfMemoData, Slice("hello", 5));
memos.push_back(memoObj);
obj.setFieldArray(sfMemos, memos);
});
ApplyContext ac = createApplyContext(env, ov, stx);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Helper for error checks
auto expectError = [&](std::vector<int32_t> const& locatorVec,
HostFunctionError expectedError) {
// hfs.getTxNestedArrayLen(locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(2), result(1);
auto* trap =
ww(&import.at("tx_inner_arr_len"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32));
};
// Locator for sfMemos
{
std::vector<int32_t> locatorVec = {sfMemos.getCode()};
// hfs.getTxNestedArrayLen(locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(2), result(1);
auto* trap =
ww(&import.at("tx_inner_arr_len"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(result[0].of.i32 == 1);
}
// Error: non-array field
expectError({sfAccount.getCode()}, HostFunctionError::NoArray);
// Error: missing field
expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound);
}
void
testGetCurrentLedgerObjNestedArrayLen()
{
testcase("getCurrentLedgerObjNestedArrayLen");
using namespace test::jtx;
Env env{*this};
Account const alice("alice");
Account const becky("becky");
// Create a SignerList for env.master
env(signers(env.master, 2, {{alice, 1}, {becky, 1}}));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const signerKeylet = keylet::signerList(env.master.id());
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, signerKeylet);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Helper for error checks
auto expectError = [&](std::vector<int32_t> const& locatorVec,
HostFunctionError expectedError) {
// hfs.getCurrentLedgerObjNestedArrayLen(locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(2), result(1);
auto* trap =
ww(&import.at("home_le_inner_arr_len"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32));
};
// Locator for sfSignerEntries
{
std::vector<int32_t> locatorVec = {sfSignerEntries.getCode()};
// hfs.getCurrentLedgerObjNestedArrayLen(locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(2), result(1);
auto* trap =
ww(&import.at("home_le_inner_arr_len"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECT(result[0].of.i32 == 2);
}
// Error: non-array field
expectError({sfSignerQuorum.getCode()}, HostFunctionError::NoArray);
// Error: missing field
expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound);
{
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) + 5));
VirtualRuntime vrt2;
WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow);
auto import2 = xrpl::createWasmImport(dummyHfs);
dummyHfs.setRT(vrt2);
std::vector<int32_t> locatorVec = {sfAccount.getCode()};
// auto const result = dummyHfs.getCurrentLedgerObjNestedArrayLen(locator);
vrt2.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(2), result(1);
auto* trap =
ww(&import2.at("home_le_inner_arr_len"),
params,
result,
0,
locatorVec.size() * sizeof(int32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::LedgerObjNotFound),
std::to_string(result[0].of.i32));
}
}
void
testGetLedgerObjNestedArrayLen()
{
testcase("getLedgerObjNestedArrayLen");
using namespace test::jtx;
Env env{*this};
Account const alice("alice");
Account const becky("becky");
env(signers(env.master, 2, {{alice, 1}, {becky, 1}}));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
auto const signerListKeylet = keylet::signerList(env.master.id());
// hfs.cacheLedgerObj(signerListKeylet.key, 1);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, signerListKeylet.key.data(), uint256::size());
auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
// Locator for sfSignerEntries
std::vector<int32_t> locatorVec = {sfSignerEntries.getCode()};
// hfs.getLedgerObjNestedArrayLen(1, locator);
{
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("le_inner_arr_len"),
params,
result,
1,
0,
locatorVec.size() * sizeof(int32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
if (BEAST_EXPECT(result[0].of.i32 > 0))
BEAST_EXPECT(result[0].of.i32 == 2);
}
// Helper for error checks
auto expectError = [&](std::vector<int32_t> const& locatorVec,
HostFunctionError expectedError,
int slot = 1) {
// hfs.getLedgerObjNestedArrayLen(slot, locator);
vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t));
WasmValVec params(3), result(1);
auto* trap =
ww(&import.at("le_inner_arr_len"),
params,
result,
slot,
0,
locatorVec.size() * sizeof(int32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32);
BEAST_EXPECTS(
result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32));
};
// Error: non-array field
expectError({sfSignerQuorum.getCode()}, HostFunctionError::NoArray);
// Error: missing field
expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound);
// Slot out of range
expectError(locatorVec, HostFunctionError::SlotOutRange, 0);
expectError(locatorVec, HostFunctionError::SlotOutRange, 257);
// Empty slot
expectError(locatorVec, HostFunctionError::EmptySlot, 2);
// Error: empty locator
expectError({}, HostFunctionError::LocatorMalformed);
// Error: locator malformed (not multiple of 4)
{
// hfs.getLedgerObjNestedArrayLen(1, malformedLocator);
vrt.setBytes(0, locatorVec.data(), 3);
WasmValVec params(3), result(1);
auto* trap = ww(&import.at("le_inner_arr_len"), params, result, 1, 0, 3);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::LocatorMalformed));
}
// Error: locator for non-STArray field
expectError(
{sfSignerQuorum.getCode(), 0, sfAccount.getCode()},
HostFunctionError::LocatorMalformed);
}
void
testUpdateData()
{
testcase("updateData");
using namespace test::jtx;
Env env{*this};
env(escrow::create(env.master, env.master, XRP(100)),
escrow::kFinishTime(env.now() + std::chrono::seconds(1)));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const escrowKeylet =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) - 1));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, escrowKeylet);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Should succeed for small data
Bytes data(10, 0x42);
// hfs.updateData(Slice(data.data(), data.size()));
{
vrt.setBytes(0, data.data(), data.size());
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("set_data"), params, result, 0, data.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == data.size());
BEAST_EXPECT(hfs.getData() && *hfs.getData() == data);
}
// Should fail for too large data
Bytes bigData(kMaxWasmDataLength + 1, 0x42);
// hfs.updateData(Slice(bigData.data(), bigData.size()));
{
vrt.setBytes(0, bigData.data(), bigData.size());
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("set_data"), params, result, 0, bigData.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == hfErrorToInt(HostFunctionError::DataFieldTooLarge));
}
}
void
testCheckSignature()
{
testcase("checkSignature");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Generate a keypair and sign a message
auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed());
PublicKey const& pk = kp.first;
SecretKey const& sk = kp.second;
std::string const& message = "hello signature";
auto const sig = sign(pk, sk, Slice(message.data(), message.size()));
// Should succeed for valid signature
{
// hfs.checkSignature(
// Slice(message.data(), message.size()),
// Slice(sig.data(), sig.size()),
// Slice(pk.data(), pk.size()));
vrt.setBytes(0, message.data(), message.size());
vrt.setBytes(256, sig.data(), sig.size());
vrt.setBytes(512, pk.data(), pk.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("check_sig"),
params,
result,
0,
message.size(),
256,
sig.size(),
512,
pk.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
// Should fail for invalid signature
{
std::string badSig(sig.size(), 0xFF);
// hfs.checkSignature(
// Slice(message.data(), message.size()),
// Slice(badSig.data(), badSig.size()),
// Slice(pk.data(), pk.size()));
vrt.setBytes(0, message.data(), message.size());
vrt.setBytes(256, badSig.data(), badSig.size());
vrt.setBytes(512, pk.data(), pk.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("check_sig"),
params,
result,
0,
message.size(),
256,
badSig.size(),
512,
pk.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
// Should fail for invalid public key
{
std::string badPk(pk.size(), 0x00);
// hfs.checkSignature(
// Slice(message.data(), message.size()),
// Slice(sig.data(), sig.size()),
// Slice(badPk.data(), badPk.size()));
vrt.setBytes(0, message.data(), message.size());
vrt.setBytes(256, sig.data(), sig.size());
vrt.setBytes(512, badPk.data(), badPk.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("check_sig"),
params,
result,
0,
message.size(),
256,
sig.size(),
512,
badPk.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidParams));
}
// Should fail for empty public key
{
// hfs.checkSignature(
// Slice(message.data(), message.size()),
// Slice(sig.data(), sig.size()),
// Slice(nullptr, 0));
vrt.setBytes(0, message.data(), message.size());
vrt.setBytes(256, sig.data(), sig.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("check_sig"),
params,
result,
0,
message.size(),
256,
sig.size(),
512,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidParams));
}
// Should fail for empty signature
{
// hfs.checkSignature(
// Slice(message.data(), message.size()),
// Slice(nullptr, 0),
// Slice(pk.data(), pk.size()));
vrt.setBytes(0, message.data(), message.size());
vrt.setBytes(512, pk.data(), pk.size());
WasmValVec params(6), result(1);
auto* trap = ww(
&import.at("check_sig"), params, result, 0, message.size(), 256, 0, 512, pk.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
// Should fail for empty message
{
// hfs.checkSignature(
// Slice(nullptr, 0), Slice(sig.data(), sig.size()), Slice(pk.data(), pk.size()));
vrt.setBytes(256, sig.data(), sig.size());
vrt.setBytes(512, pk.data(), pk.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("check_sig"), params, result, 0, 0, 256, sig.size(), 512, pk.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
}
void
testComputeSha512HalfHash()
{
testcase("computeSha512HalfHash");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string data = "hello world";
// hfs.computeSha512HalfHash(Slice(data.data(), data.size()));
{
vrt.setBytes(0, data.data(), data.size());
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("sha512_half"), params, result, 0, data.size(), 256, uint256::size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == uint256::size());
// Should match direct call to sha512Half
auto expected = sha512Half(Slice(data.data(), data.size()));
auto hashBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(std::ranges::equal(hashBytes, expected));
}
}
void
testKeyletFunctions()
{
testcase("keylet functions");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto const usdIssue = env.master["USD"].issue();
auto const masterID = env.master.id();
auto const baseMpt = makeMptID(1, masterID);
auto imp = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Lambda to compare a Bytes (std::vector<uint8_t>) to a keylet
auto compareKeylet = [](std::vector<uint8_t> const& bytes, Keylet const& kl) {
return std::ranges::equal(bytes, kl.key);
};
{
auto const expected = keylet::account(masterID);
WasmValVec params(4), result(1);
auto* trap = ww(&imp.at("accountroot_id"), params, result, masterID, 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 2);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 = ww(&imp.at("accountroot_id"), params, result, xrpAccount(), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::amm(xrpIssue(), usdIssue);
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("amm_id"), params, result, xrpIssue(), usdIssue, 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 = ww(&imp.at("amm_id"), params, result, xrpIssue(), xrpIssue(), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
auto* trap3 = ww(&imp.at("amm_id"), params, result, baseMpt, xrpIssue(), 1024, 32);
BEAST_EXPECT(
!trap3 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
}
{
auto const expected = keylet::check(masterID, SeqProxy::rawSequence(1u));
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("check_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("check_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
std::string const credTypeStr = "test";
Slice const credType(credTypeStr.data(), credTypeStr.size());
Account const alice("alice");
{
auto const expected = keylet::credential(masterID, masterID, credType);
WasmValVec params(8), result(1);
auto* trap = ww(
&imp.at("credential_id"), params, result, masterID, masterID, credType, 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 6);
BEAST_EXPECT(compareKeylet(actual, expected));
}
std::string_view constexpr longCredTypeStr =
"abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
"asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p";
Slice const longCredType(longCredTypeStr.data(), longCredTypeStr.size());
static_assert(longCredTypeStr.size() > kMaxCredentialTypeLength);
auto* trap2 =
ww(&imp.at("credential_id"),
params,
result,
masterID,
alice.id(),
longCredType,
1024,
32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
auto* trap3 =
ww(&imp.at("credential_id"),
params,
result,
xrpAccount(),
alice.id(),
credType,
1024,
32);
BEAST_EXPECT(
!trap3 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
auto* trap4 =
ww(&imp.at("credential_id"),
params,
result,
masterID,
xrpAccount(),
credType,
1024,
32);
BEAST_EXPECT(
!trap4 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::did(masterID);
WasmValVec params(4), result(1);
auto* trap = ww(&imp.at("did_id"), params, result, masterID, 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 2);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 = ww(&imp.at("did_id"), params, result, xrpAccount(), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::delegate(masterID, alice.id());
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("delegate_id"), params, result, masterID, alice.id(), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 = ww(&imp.at("delegate_id"), params, result, masterID, masterID, 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
auto* trap3 =
ww(&imp.at("delegate_id"), params, result, masterID, xrpAccount(), 1024, 32);
BEAST_EXPECT(
!trap3 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
auto* trap4 =
ww(&imp.at("delegate_id"), params, result, xrpAccount(), masterID, 1024, 32);
BEAST_EXPECT(
!trap4 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::depositPreauth(masterID, alice.id());
WasmValVec params(6), result(1);
auto* trap =
ww(&imp.at("deposit_preauth_id"), params, result, masterID, alice.id(), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("deposit_preauth_id"), params, result, masterID, masterID, 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
auto* trap3 =
ww(&imp.at("deposit_preauth_id"), params, result, masterID, xrpAccount(), 1024, 32);
BEAST_EXPECT(
!trap3 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
auto* trap4 =
ww(&imp.at("deposit_preauth_id"), params, result, xrpAccount(), masterID, 1024, 32);
BEAST_EXPECT(
!trap4 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::escrow(masterID, SeqProxy::rawSequence(1u));
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("escrow_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("escrow_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
Currency const usd = toCurrency("USD");
{
auto const expected = keylet::trustLine(masterID, alice.id(), usd);
WasmValVec params(8), result(1);
auto* trap =
ww(&imp.at("trustline_id"), params, result, masterID, alice.id(), usd, 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 6);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("trustline_id"), params, result, masterID, masterID, usd, 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
auto* trap3 =
ww(&imp.at("trustline_id"), params, result, masterID, xrpAccount(), usd, 1024, 32);
BEAST_EXPECT(
!trap3 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
auto* trap4 =
ww(&imp.at("trustline_id"), params, result, xrpAccount(), masterID, usd, 1024, 32);
BEAST_EXPECT(
!trap4 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
auto* trap5 =
ww(&imp.at("trustline_id"),
params,
result,
masterID,
alice.id(),
toCurrency(""),
1024,
32);
BEAST_EXPECT(
!trap5 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
}
{
auto const expected = keylet::mptokenIssuance(makeMptID(1u, masterID));
WasmValVec params(6), result(1);
auto* trap =
ww(&imp.at("mpt_issuance_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("mpt_issuance_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::mptoken(baseMpt, alice.id());
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("mptoken_id"), params, result, baseMpt, alice.id(), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 = ww(&imp.at("mptoken_id"), params, result, MPTID{}, alice.id(), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
auto* trap3 =
ww(&imp.at("mptoken_id"), params, result, baseMpt, xrpAccount(), 1024, 32);
BEAST_EXPECT(
!trap3 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::nftokenOffer(masterID, SeqProxy::rawSequence(1u));
WasmValVec params(6), result(1);
auto* trap =
ww(&imp.at("nft_offer_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("nft_offer_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::offer(masterID, SeqProxy::rawSequence(1u));
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("offer_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("offer_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::oracle(masterID, 1u);
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("oracle_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("oracle_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected =
keylet::payChannel(masterID, alice.id(), SeqProxy::rawSequence(1u));
WasmValVec params(8), result(1);
auto* trap = ww(
&imp.at("paychan_id"), params, result, masterID, alice.id(), toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 6);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 = ww(
&imp.at("paychan_id"), params, result, masterID, masterID, toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidParams));
auto* trap3 =
ww(&imp.at("paychan_id"),
params,
result,
masterID,
xrpAccount(),
toBytes(1u),
1024,
32);
BEAST_EXPECT(
!trap3 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
auto* trap4 =
ww(&imp.at("paychan_id"),
params,
result,
xrpAccount(),
masterID,
toBytes(1u),
1024,
32);
BEAST_EXPECT(
!trap4 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::permissionedDomain(masterID, SeqProxy::rawSequence(1u));
WasmValVec params(6), result(1);
auto* trap = ww(
&imp.at("permissioned_domain_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("permissioned_domain_id"),
params,
result,
xrpAccount(),
toBytes(1u),
1024,
32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::signerList(masterID);
WasmValVec params(4), result(1);
auto* trap = ww(&imp.at("signers_id"), params, result, masterID, 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 2);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 = ww(&imp.at("signers_id"), params, result, xrpAccount(), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::ticket(masterID, SeqProxy::rawTicket(1u));
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("ticket_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("ticket_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
{
auto const expected = keylet::vault(masterID, SeqProxy::rawSequence(1u));
WasmValVec params(6), result(1);
auto* trap = ww(&imp.at("vault_id"), params, result, masterID, toBytes(1u), 1024, 32);
if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32))
{
auto const actual = vrt.getBytes(params, 4);
BEAST_EXPECT(compareKeylet(actual, expected));
}
auto* trap2 =
ww(&imp.at("vault_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32);
BEAST_EXPECT(
!trap2 && result[0].kind == WASM_I32 &&
result[0].of.i32 == static_cast<int32_t>(HostFunctionError::InvalidAccount));
}
}
void
testGetNFT()
{
testcase("getNFT");
using namespace test::jtx;
Env env{*this};
Account const alice("alice");
env.fund(XRP(1000), alice);
env.close();
// Mint NFT for alice
uint256 const nftId = token::getNextID(env, alice, 0u, 0u);
std::string const uri = "https://example.com/nft";
env(token::mint(alice), token::Uri(uri));
env.close();
uint256 const nftId2 = token::getNextID(env, alice, 0u, 0u);
env(token::mint(alice));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow = keylet::escrow(alice, SeqProxy::rawSequence(env.seq(alice)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Should succeed for valid NFT
{
// hfs.getNFT(alice.id(), nftId);
vrt.setBytes(0, alice.id().data(), AccountID::size());
vrt.setBytes(256, nftId.data(), uint256::size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("nft_uri"),
params,
result,
0,
AccountID::size(),
256,
uint256::size(),
512,
256);
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 > 0))
{
auto uriBytes = vrt.getBytes(params, 4);
uriBytes.resize(result[0].of.i32);
BEAST_EXPECT(std::ranges::equal(uriBytes, uri));
}
}
// Should fail for invalid account
{
// hfs.getNFT(xrpAccount(), nftId);
vrt.setBytes(0, xrpAccount().data(), AccountID::size());
vrt.setBytes(256, nftId.data(), uint256::size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("nft_uri"),
params,
result,
0,
AccountID::size(),
256,
uint256::size(),
512,
256);
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32))
BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidAccount));
}
// Should fail for invalid nftId
{
// hfs.getNFT(alice.id(), uint256());
uint256 zeroId;
vrt.setBytes(0, alice.id().data(), AccountID::size());
vrt.setBytes(256, zeroId.data(), uint256::size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("nft_uri"),
params,
result,
0,
AccountID::size(),
256,
uint256::size(),
512,
256);
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32))
BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidParams));
}
// Should fail for invalid nftId
{
auto const badId = token::getNextID(env, alice, 0u, 1u);
// hfs.getNFT(alice.id(), badId);
vrt.setBytes(0, alice.id().data(), AccountID::size());
vrt.setBytes(256, badId.data(), uint256::size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("nft_uri"),
params,
result,
0,
AccountID::size(),
256,
uint256::size(),
512,
256);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == hfErrorToInt(HostFunctionError::LedgerObjNotFound));
}
{
// hfs.getNFT(alice.id(), nftId2);
vrt.setBytes(0, alice.id().data(), AccountID::size());
vrt.setBytes(256, nftId2.data(), uint256::size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("nft_uri"),
params,
result,
0,
AccountID::size(),
256,
uint256::size(),
512,
256);
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32))
BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::FieldNotFound));
}
}
void
testGetNFTIssuer()
{
testcase("getNFTIssuer");
using namespace test::jtx;
Env env{*this};
// Mint NFT for env.master
uint32_t const taxon = 12345;
uint256 const nftId = token::getNextID(env, env.master, taxon);
env(token::mint(env.master, taxon));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Should succeed for valid NFT id
{
// hfs.getNFTIssuer(nftId);
vrt.setBytes(0, nftId.data(), uint256::size());
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("nft_issuer"),
params,
result,
0,
uint256::size(),
256,
AccountID::size());
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == AccountID::size()))
{
auto issuerBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(std::ranges::equal(issuerBytes, env.master.id()));
}
}
// Should fail for zero NFT id
{
// hfs.getNFTIssuer(uint256());
uint256 zeroId;
vrt.setBytes(0, zeroId.data(), uint256::size());
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("nft_issuer"),
params,
result,
0,
uint256::size(),
256,
AccountID::size());
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32))
BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidParams));
}
}
void
testGetNFTTaxon()
{
testcase("getNFTTaxon");
using namespace test::jtx;
Env env{*this};
uint32_t const taxon = 54321;
uint256 const nftId = token::getNextID(env, env.master, taxon);
env(token::mint(env.master, taxon));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// hfs.getNFTTaxon(nftId);
vrt.setBytes(0, nftId.data(), uint256::size());
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("nft_taxon"), params, result, 0, uint256::size(), 256, sizeof(uint32_t));
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t)))
{
BEAST_EXPECT(vrt.getUint32(params, 2) == taxon);
}
}
void
testGetNFTFlags()
{
testcase("getNFTFlags");
using namespace test::jtx;
Env env{*this};
// Mint NFT with default flags
uint256 const nftId = token::getNextID(env, env.master, 0u, tfTransferable);
env(token::mint(env.master, 0), Txflags(tfTransferable));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.getNFTFlags(nftId);
vrt.setBytes(0, nftId.data(), uint256::size());
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("nft_flags"), params, result, 0, uint256::size());
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32))
BEAST_EXPECT(result[0].of.i32 == tfTransferable);
}
// Should return 0 for zero NFT id
{
// hfs.getNFTFlags(uint256());
uint256 zeroId;
vrt.setBytes(0, zeroId.data(), uint256::size());
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("nft_flags"), params, result, 0, uint256::size());
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32))
BEAST_EXPECT(result[0].of.i32 == 0);
}
}
void
testGetNFTTransferFee()
{
testcase("getNFTTransferFee");
using namespace test::jtx;
Env env{*this};
uint16_t const transferFee = 250;
uint256 const nftId = token::getNextID(env, env.master, 0u, tfTransferable, transferFee);
env(token::mint(env.master, 0), token::XferFee(transferFee), Txflags(tfTransferable));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.getNFTTransferFee(nftId);
vrt.setBytes(0, nftId.data(), uint256::size());
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("nft_xfer_fee"), params, result, 0, uint256::size());
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32))
BEAST_EXPECT(result[0].of.i32 == transferFee);
}
// Should return 0 for zero NFT id
{
// hfs.getNFTTransferFee(uint256());
uint256 zeroId;
vrt.setBytes(0, zeroId.data(), uint256::size());
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("nft_xfer_fee"), params, result, 0, uint256::size());
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32))
BEAST_EXPECT(result[0].of.i32 == 0);
}
}
void
testGetNFTSerial()
{
testcase("getNFTSequence");
using namespace test::jtx;
Env env{*this};
// Mint NFT with serial 0
uint256 const nftId = token::getNextID(env, env.master, 0u);
auto const serial = env.seq(env.master);
env(token::mint(env.master));
env.close();
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.getNFTSequence(nftId);
vrt.setBytes(0, nftId.data(), uint256::size());
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("nft_serial"),
params,
result,
0,
uint256::size(),
256,
sizeof(uint32_t));
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t)))
{
BEAST_EXPECT(vrt.getUint32(params, 2) == serial);
}
}
// Should return 0 for zero NFT id
{
// hfs.getNFTSequence(uint256());
uint256 zeroId;
vrt.setBytes(0, zeroId.data(), uint256::size());
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("nft_serial"),
params,
result,
0,
uint256::size(),
256,
sizeof(uint32_t));
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t)))
{
BEAST_EXPECT(vrt.getUint32(params, 2) == 0);
}
}
}
void
testTrace()
{
testcase("trace");
using namespace test::jtx;
{
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Trace};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "test trace";
std::string data = "abc";
auto const slice = Slice(data.data(), data.size());
// AsText: data printed verbatim (was trace with as_hex = 0)
{
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, slice.data(), slice.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::AsText),
256,
slice.size());
if (BEAST_EXPECT(!trap))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.contains(msg));
BEAST_EXPECT(messages.contains(data));
}
}
// AsHex: host hex-encodes data (was trace with as_hex = 1)
{
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, slice.data(), slice.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::AsHex),
256,
slice.size());
if (BEAST_EXPECT(!trap))
{
auto const messages = sink.messages().str();
std::string hex;
hex.reserve(data.size() * 2);
boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex));
BEAST_EXPECT(messages.contains(msg));
BEAST_EXPECT(messages.contains(hex));
}
}
// Unknown data_type: logged as invalid, never a trap
{
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, slice.data(), slice.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"), params, result, 0, msg.size(), 9999, 256, slice.size());
BEAST_EXPECT(!trap);
}
// msg and data each fit, but their combined size exceeds
// kMaxWasmDataLength, so nothing is logged
{
std::string const longMsg(kMaxWasmDataLength, 'x');
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(longMsg.data()), longMsg.size());
vrt.setBytes(2048, slice.data(), slice.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
longMsg.size(),
traceDataTypeToInt(TraceDataType::AsText),
2048,
slice.size());
if (BEAST_EXPECT(!trap))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.contains("message and data too long"));
BEAST_EXPECT(!messages.contains(longMsg));
}
}
}
{
// logs disabled (trace < error)
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Error};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "test trace";
std::string data = "abc";
auto const slice = Slice(data.data(), data.size());
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, slice.data(), slice.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::AsText),
256,
slice.size());
BEAST_EXPECT(!trap);
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.empty());
}
}
void
testTraceNum()
{
testcase("traceNum");
using namespace test::jtx;
{
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Trace};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "trace number";
// adjustWasmEndianess is its own inverse, so writing the adjusted value
// lets the wrapper's adjustment recover it on either endianness.
auto const traceNum = [&](TraceDataType type, auto value) {
auto const wire = adjustWasmEndianess(value);
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, reinterpret_cast<uint8_t const*>(&wire), sizeof(wire));
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(type),
256,
sizeof(wire));
if (BEAST_EXPECT(!trap))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.contains(msg));
BEAST_EXPECT(messages.contains(std::to_string(value)));
}
};
traceNum(TraceDataType::Int64, int64_t{123456789});
traceNum(TraceDataType::Int64, int64_t{-42});
// Above int64 max -- unreachable through the old trace_num
traceNum(TraceDataType::Uint64, std::numeric_limits<std::uint64_t>::max());
// Wrong buffer length for the type: logged as invalid, no trap
{
std::int32_t const tooShort = 7;
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, reinterpret_cast<uint8_t const*>(&tooShort), sizeof(tooShort));
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Int64),
256,
sizeof(tooShort));
BEAST_EXPECT(!trap);
}
}
{
// logs disabled
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Error};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "trace number";
auto const wire = adjustWasmEndianess(int64_t{123456789});
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, reinterpret_cast<uint8_t const*>(&wire), sizeof(wire));
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Int64),
256,
sizeof(wire));
BEAST_EXPECT(!trap);
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.empty());
}
}
void
testTraceAccount()
{
testcase("traceAccount");
using namespace test::jtx;
{
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Trace};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "trace account";
auto const& accountId = env.master.id();
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, accountId.data(), accountId.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Account),
256,
accountId.size());
if (BEAST_EXPECT(!trap))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.contains(msg));
BEAST_EXPECT(messages.contains(env.master.human()));
}
}
{
// logs disabled
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Error};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string msg = "trace account";
auto const& accountId = env.master.id();
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, accountId.data(), accountId.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Account),
256,
accountId.size());
BEAST_EXPECT(!trap);
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.empty());
}
}
void
testTraceAmount()
{
testcase("traceAmount");
using namespace test::jtx;
{
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Trace};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "trace amount";
STAmount const amount = XRP(12345);
{
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Amount),
256,
amountBytes.size());
if (BEAST_EXPECT(!trap))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.contains(msg));
BEAST_EXPECT(messages.contains(amount.getFullText()));
}
}
// IOU amount
Account const alice("alice");
env.fund(XRP(1000), alice);
env.close();
STAmount const iouAmount = env.master["USD"](100);
{
Bytes amountBytes = toBytes(iouAmount);
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Amount),
256,
amountBytes.size());
BEAST_EXPECT(!trap);
}
// MPT amount
{
auto const mptId = makeMptID(42, env.master.id());
Asset const mptAsset = Asset(mptId);
STAmount const mptAmount(mptAsset, 123456);
Bytes amountBytes = toBytes(mptAmount);
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Amount),
256,
amountBytes.size());
BEAST_EXPECT(!trap);
}
}
{
// logs disabled
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Error};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "trace amount";
STAmount const amount = XRP(12345);
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Amount),
256,
amountBytes.size());
BEAST_EXPECT(!trap);
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.empty());
}
}
// clang-format off
int const normalExp = 18;
Bytes const floatIntMin = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}; // -2^63 (rounds to nearest: -(2^63-1))
Bytes const floatIntZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 0
Bytes const floatIntMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}; // 2^63-1
Bytes const floatUIntMax = {0x19, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x00, 0x00, 0x00, 0x01}; // 2^64-1
Bytes const floatMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00}; // 1e(Number::kMaxExponent + normalExp)
Bytes const floatPreMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF}; // 1e(Number::kMaxExponent + normalExp - 1)
Bytes const floatMinusMaxExp = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00}; // -1e(Number::kMaxExponent + normalExp)
Bytes const floatMinExp = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 1e(Number::kMinExponent - normalExp)
Bytes const floatMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x80, 0x00}; // Number::kMaxRep e(Number::kMaxExponent - normalExp)
Bytes const floatMaxIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x63, 0xFF, 0x9C, 0x00, 0x00, 0x00, 0x4E}; // 9999999999999999e(96)
Bytes const floatMinIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x9D}; // 1e(-96 - 3 + normalExp = -81)
Bytes const float1 = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 1
Bytes const floatMinus1 = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -1
Bytes const float1More = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x03, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 1.000 000 000 000 001
Bytes const float2 = {0x1B, 0xC1, 0x6D, 0x67, 0x4E, 0xC8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 2
Bytes const float10 = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEF}; // 10
Bytes const floatPi = {0x2B, 0x99, 0x2D, 0xDF, 0xA2, 0x32, 0x48, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 3.141592653589793
Bytes const floatInvalidZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00}; // INVALID
Bytes const floatMinus3 = {0xD6, 0x5D, 0xDB, 0xE5, 0x09, 0xD4, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -3
std::string const invalid = "invalid_data";
// clang-format on
template <class T>
void
printFloats(std::string_view descr, T m, int e)
{
Serializer msg;
Number n;
if constexpr (std::is_signed_v<T>)
{
n = Number(static_cast<int64_t>(m), e);
}
else
{
n = Number(static_cast<uint64_t>(m), e, Number::Normalized{});
}
STNumber(sfNumber, n).add(msg);
auto const& data = msg.modData();
std::cout << std::setw(24) << descr << " m: " << std::setw(20) << n.mantissa()
<< ", e: " << std::setw(8) << n.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;
}
void
printNumbersBin()
{
printFloats("int64.min", std::numeric_limits<int64_t>::min(), 0);
printFloats("zero", 0, 0);
printFloats("int64.max", std::numeric_limits<int64_t>::max(), 0);
printFloats("uint64.max", std::numeric_limits<uint64_t>::max(), 0);
printFloats("Number 1 max exp", 1, Number::kMaxExponent + normalExp);
printFloats("Number (max exp - 1)", 1, Number::kMaxExponent + normalExp - 1);
printFloats("Number -1 max exp", -1, Number::kMaxExponent + normalExp);
printFloats("Number.max", Number::kMaxRep, Number::kMaxExponent);
printFloats("Number min positive", 1, Number::kMinExponent + normalExp);
printFloats(
"Number.min", std::numeric_limits<int64_t>::min(), Number::kMaxExponent - normalExp);
printFloats("STAmount.max", STAmount::kMaxValue, STAmount::kMaxOffset);
printFloats("STAmount min positive", STAmount::kMinValue, STAmount::kMinOffset);
printFloats("one", 1, 0);
printFloats("-one", -1, 0);
printFloats("1,00...01", 1'000'000'000'000'001, -15);
printFloats("two", 2, 0);
printFloats("ten", 10, 0);
printFloats("pi", 3141592653589793, -15);
printFloats("-three", -3, 0);
}
void
testTraceFloat()
{
testcase("traceFloat");
using namespace test::jtx;
{
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "trace float";
{
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, reinterpret_cast<uint8_t const*>(invalid.data()), invalid.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Xfloat),
256,
invalid.size());
BEAST_EXPECT(!trap);
}
{
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, floatMaxExp.data(), floatMaxExp.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Xfloat),
256,
floatMaxExp.size());
BEAST_EXPECT(!trap);
}
}
{
// logs disabled
Env env(*this);
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Error};
beast::Journal const jlog{sink};
ApplyContext ac = createApplyContext(env, ov, jlog);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
std::string const msg = "trace float";
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, reinterpret_cast<uint8_t const*>(invalid.data()), invalid.size());
WasmValVec params(5), result(0);
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
msg.size(),
traceDataTypeToInt(TraceDataType::Xfloat),
256,
invalid.size());
BEAST_EXPECT(!trap);
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.empty());
}
}
void
testFloatFromInt()
{
testcase("floatFromInt");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatFromInt(min64, -1);
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("float_from_int"), params, result, min64, 0, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromInt(min64, 4);
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("float_from_int"), params, result, min64, 0, floatSize, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromInt(min64, 0);
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("float_from_int"), params, result, min64, 0, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 1);
BEAST_EXPECT(resultBytes == floatIntMin);
}
{
// hfs.floatFromInt(0, 0);
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("float_from_int"), params, result, 0ll, 0, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 1);
BEAST_EXPECT(resultBytes == floatIntZero);
}
{
// hfs.floatFromInt(max64, 0);
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("float_from_int"), params, result, max64, 0, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 1);
BEAST_EXPECT(resultBytes == floatIntMax);
}
}
void
testFloatFromUint()
{
testcase("floatFromUint");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatFromUint(std::numeric_limits<uint64_t>::min(), -1);
WasmValVec params(5), result(1);
uint64_t val = std::numeric_limits<uint64_t>::min();
vrt.setBytes(0, &val, sizeof(val));
auto* trap = ww(&import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromUint(std::numeric_limits<uint64_t>::min(), 4);
WasmValVec params(5), result(1);
uint64_t val = std::numeric_limits<uint64_t>::min();
vrt.setBytes(0, &val, sizeof(val));
auto* trap = ww(&import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromUint(0, 0);
WasmValVec params(5), result(1);
uint64_t val = 0;
vrt.setBytes(0, &val, sizeof(val));
auto* trap = ww(&import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatIntZero);
}
{
// hfs.floatFromUint(std::numeric_limits<uint64_t>::max(), 0);
WasmValVec params(5), result(1);
uint64_t val = std::numeric_limits<uint64_t>::max();
vrt.setBytes(0, &val, sizeof(val));
auto* trap = ww(&import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatUIntMax);
}
}
void
testfloatFromMantExp()
{
testcase("floatFromMantExp");
using namespace test::jtx;
using namespace wasm_float;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatFromMantExp(1, 0, -1);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"), params, result, 1ll, 0, 0, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromMantExp(1, 0, 4);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"), params, result, 1ll, 0, 0, floatSize, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp + 1, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"),
params,
result,
1ll,
Number::kMaxExponent + normalExp + 1,
0,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromMantExp(1, Number::kMinExponent + normalExp - 1, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"),
params,
result,
1ll,
Number::kMinExponent + normalExp - 1,
0,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatIntZero);
}
{
// hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"),
params,
result,
1ll,
Number::kMaxExponent + normalExp,
0,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatMaxExp);
}
{
// hfs.floatFromMantExp(-1, Number::kMaxExponent + normalExp, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"),
params,
result,
-1ll,
Number::kMaxExponent + normalExp,
0,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatMinusMaxExp);
}
{
// hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp - 1, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"),
params,
result,
1ll,
Number::kMaxExponent + normalExp - 1,
0,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatPreMaxExp);
}
{
// hfs.floatFromMantExp(STAmount::kMaxValue, STAmount::kMaxOffset, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"),
params,
result,
static_cast<int64_t>(STAmount::kMaxValue),
STAmount::kMaxOffset,
0,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatMaxIOU);
}
{
// hfs.floatFromMantExp(1, Number::kMinExponent + normalExp, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"),
params,
result,
1ll,
Number::kMinExponent - normalExp,
0,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatMinExp);
}
{
// hfs.floatFromMantExp(10, -1, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"), params, result, 10ll, -1, 0, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == float1);
}
{
// hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp + 1, 0);
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_mant_exp"),
params,
result,
1ll,
Number::kMaxExponent + normalExp + 1,
0,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
}
void
testFloatCompare()
{
testcase("floatCompare");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatCompare(Slice(), Slice());
WasmValVec params(4), result(1);
auto* trap = ww(&import.at("float_cmp"), params, result, 0, 0, 0, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatCompare(makeSlice(floatInvalidZero), Slice());
WasmValVec params(4), result(1);
vrt.setBytes(0, floatInvalidZero.data(), floatInvalidZero.size());
auto* trap = ww(&import.at("float_cmp"), params, result, 0, floatSize, 0, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatCompare(makeSlice(float1), makeSlice(invalid));
WasmValVec params(4), result(1);
vrt.setBytes(0, float1.data(), float1.size());
vrt.setBytes(floatSize, invalid.data(), invalid.size());
auto* trap = ww(
&import.at("float_cmp"), params, result, 0, floatSize, floatSize, invalid.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatCompare(makeSlice(floatIntMin), makeSlice(floatIntZero));
WasmValVec params(4), result(1);
vrt.setBytes(0, floatIntMin.data(), floatIntMin.size());
vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size());
auto* trap =
ww(&import.at("float_cmp"), params, result, 0, floatSize, floatSize, floatSize);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 2);
}
{
// hfs.floatCompare(makeSlice(floatIntMax), makeSlice(floatIntZero));
WasmValVec params(4), result(1);
vrt.setBytes(0, floatIntMax.data(), floatIntMax.size());
vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size());
auto* trap =
ww(&import.at("float_cmp"), params, result, 0, floatSize, floatSize, floatSize);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 1);
}
{
// hfs.floatCompare(makeSlice(float1), makeSlice(float1));
WasmValVec params(4), result(1);
vrt.setBytes(0, float1.data(), float1.size());
vrt.setBytes(floatSize, float1.data(), float1.size());
auto* trap =
ww(&import.at("float_cmp"), params, result, 0, floatSize, floatSize, floatSize);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
}
void
testFloatAdd()
{
testcase("floatAdd");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatAdd(Slice(), Slice(), -1);
WasmValVec params(7), result(1);
auto* trap = ww(&import.at("float_add"), params, result, 0, 0, 0, 0, 0, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatAdd(Slice(), Slice(), 0);
WasmValVec params(7), result(1);
auto* trap = ww(&import.at("float_add"), params, result, 0, 0, 0, 0, 0, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatAdd(makeSlice(float1), makeSlice(invalid), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, float1.data(), float1.size());
vrt.setBytes(floatSize, invalid.data(), invalid.size());
auto* trap =
ww(&import.at("float_add"),
params,
result,
0,
floatSize,
floatSize,
invalid.size(),
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatAdd(makeSlice(floatMaxIOU), makeSlice(floatMaxExp), 0);
// max IOU is too small to make any change
WasmValVec params(7), result(1);
vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size());
vrt.setBytes(floatSize, floatMaxExp.data(), floatMaxExp.size());
auto* trap =
ww(&import.at("float_add"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatMaxExp);
}
{
// hfs.floatAdd(makeSlice(floatIntMin), makeSlice(floatIntZero), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, floatIntMin.data(), floatIntMin.size());
vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size());
auto* trap =
ww(&import.at("float_add"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatIntMin);
}
{
// hfs.floatAdd(makeSlice(floatIntMax), makeSlice(floatIntMin), 0);//
// int64.min is rounded to nearest: -(2^63-1), so max + min == 0
WasmValVec params(7), result(1);
vrt.setBytes(0, floatIntMax.data(), floatIntMax.size());
vrt.setBytes(floatSize, floatIntMin.data(), floatIntMin.size());
auto* trap =
ww(&import.at("float_add"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatIntZero);
}
}
void
testFloatSubtract()
{
testcase("floatSubtract");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatSubtract(Slice(), Slice(), -1);
WasmValVec params(7), result(1);
auto* trap = ww(&import.at("float_sub"), params, result, 0, 0, 0, 0, 0, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatSubtract(Slice(), Slice(), 0);
WasmValVec params(7), result(1);
auto* trap = ww(&import.at("float_sub"), params, result, 0, 0, 0, 0, 0, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatSubtract(makeSlice(float1), makeSlice(invalid), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, float1.data(), float1.size());
vrt.setBytes(floatSize, invalid.data(), invalid.size());
auto* trap =
ww(&import.at("float_sub"),
params,
result,
0,
floatSize,
floatSize,
invalid.size(),
floatSize * 2,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatSubtract(makeSlice(floatMinusMaxExp), makeSlice(floatMaxIOU), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, floatMinusMaxExp.data(), floatMinusMaxExp.size());
vrt.setBytes(floatSize, floatMaxIOU.data(), floatMaxIOU.size());
auto* trap =
ww(&import.at("float_sub"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatMinusMaxExp);
}
{
// hfs.floatSubtract(makeSlice(floatIntMin), makeSlice(floatIntZero), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, floatIntMin.data(), floatIntMin.size());
vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size());
auto* trap =
ww(&import.at("float_sub"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatIntMin);
}
{
// hfs.floatSubtract(makeSlice(floatIntZero), makeSlice(float1), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, floatIntZero.data(), floatIntZero.size());
vrt.setBytes(floatSize, float1.data(), float1.size());
auto* trap =
ww(&import.at("float_sub"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatMinus1);
}
}
void
testFloatMultiply()
{
testcase("floatMultiply");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatMultiply(Slice(), Slice(), -1);
WasmValVec params(7), result(1);
auto* trap = ww(&import.at("float_mult"), params, result, 0, 0, 0, 0, 0, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatMultiply(Slice(), Slice(), 0);
WasmValVec params(7), result(1);
auto* trap = ww(&import.at("float_mult"), params, result, 0, 0, 0, 0, 0, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatMultiply(makeSlice(float1), makeSlice(invalid), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, float1.data(), float1.size());
vrt.setBytes(floatSize, invalid.data(), invalid.size());
auto* trap =
ww(&import.at("float_mult"),
params,
result,
0,
floatSize,
floatSize,
invalid.size(),
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatMultiply(makeSlice(floatMax), makeSlice(float1More), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, floatMax.data(), floatMax.size());
vrt.setBytes(floatSize, float1More.data(), float1More.size());
auto* trap =
ww(&import.at("float_mult"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatComputationError));
}
{
// hfs.floatMultiply(makeSlice(float1), makeSlice(float1), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, float1.data(), float1.size());
vrt.setBytes(floatSize, float1.data(), float1.size());
auto* trap =
ww(&import.at("float_mult"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == float1);
}
{
// hfs.floatMultiply(makeSlice(floatIntZero), makeSlice(floatMaxIOU), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, floatIntZero.data(), floatIntZero.size());
vrt.setBytes(floatSize, floatMaxIOU.data(), floatMaxIOU.size());
auto* trap =
ww(&import.at("float_mult"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatIntZero);
}
{
// hfs.floatMultiply(makeSlice(float10), makeSlice(floatPreMaxExp), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, float10.data(), float10.size());
vrt.setBytes(floatSize, floatPreMaxExp.data(), floatPreMaxExp.size());
auto* trap =
ww(&import.at("float_mult"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatMaxExp);
}
}
void
testFloatDivide()
{
testcase("floatDivide");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatDivide(Slice(), Slice(), -1);
WasmValVec params(7), result(1);
auto* trap = ww(&import.at("float_div"), params, result, 0, 0, 0, 0, 0, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatDivide(Slice(), Slice(), 0);
WasmValVec params(7), result(1);
auto* trap = ww(&import.at("float_div"), params, result, 0, 0, 0, 0, 0, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{ // hfs.floatDivide(makeSlice(float1), makeSlice(invalid), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, float1.data(), float1.size());
vrt.setBytes(floatSize, invalid.data(), invalid.size());
auto* trap =
ww(&import.at("float_div"),
params,
result,
0,
floatSize,
floatSize,
invalid.size(),
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{ // hfs.floatDivide(makeSlice(float1), makeSlice(floatIntZero), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, float1.data(), float1.size());
vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size());
auto* trap =
ww(&import.at("float_div"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatComputationError));
}
{ // hfs.floatDivide(makeSlice(floatMax), makeSlice(*y), 0);
auto const y =
hfs.floatFromMantExp(STAmount::kMaxValue, -normalExp - 1, 0); // 0.9999999...
if (BEAST_EXPECT(y))
{
WasmValVec params(7), result(1);
vrt.setBytes(0, floatMax.data(), floatMax.size());
vrt.setBytes(floatSize, y->data(), y->size());
auto* trap =
ww(&import.at("float_div"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatComputationError));
}
}
{ // hfs.floatDivide(makeSlice(floatIntZero), makeSlice(float1), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, floatIntZero.data(), floatIntZero.size());
vrt.setBytes(floatSize, float1.data(), float1.size());
auto* trap =
ww(&import.at("float_div"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatIntZero);
}
{ // hfs.floatDivide(makeSlice(floatMaxExp), makeSlice(float10), 0);
WasmValVec params(7), result(1);
vrt.setBytes(0, floatMaxExp.data(), floatMaxExp.size());
vrt.setBytes(floatSize, float10.data(), float10.size());
auto* trap =
ww(&import.at("float_div"),
params,
result,
0,
floatSize,
floatSize,
floatSize,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 4);
BEAST_EXPECT(resultBytes == floatPreMaxExp);
}
}
void
testFloatRoot()
{
testcase("floatRoot");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{ // hfs.floatRoot(Slice(), 2, -1);
WasmValVec params(6), result(1);
auto* trap = ww(&import.at("float_root"), params, result, 0, 0, 2, 0, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{ // hfs.floatRoot(makeSlice(invalid), 3, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, invalid.data(), invalid.size());
auto* trap =
ww(&import.at("float_root"),
params,
result,
0,
invalid.size(),
3,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{ // hfs.floatRoot(makeSlice(float1), -2, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, float1.data(), float1.size());
auto* trap =
ww(&import.at("float_root"),
params,
result,
0,
floatSize,
-2,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{ // hfs.floatRoot(makeSlice(floatIntZero), 2, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, floatIntZero.data(), floatIntZero.size());
auto* trap =
ww(&import.at("float_root"),
params,
result,
0,
floatSize,
2,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == floatIntZero);
}
{ // hfs.floatRoot(makeSlice(floatMaxIOU), 1, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size());
auto* trap =
ww(&import.at("float_root"),
params,
result,
0,
floatSize,
1,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == floatMaxIOU);
}
{
// hfs.floatRoot(makeSlice(*x), 2, 0);
auto const x = hfs.floatFromMantExp(100, 0, 0); // 100
if (BEAST_EXPECT(x))
{
WasmValVec params(6), result(1);
vrt.setBytes(0, x->data(), x->size());
auto* trap =
ww(&import.at("float_root"),
params,
result,
0,
floatSize,
2,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == float10);
}
}
{
// hfs.floatRoot(makeSlice(*x), 3, 0);
auto const x = hfs.floatFromMantExp(1000, 0, 0); // 1000
if (BEAST_EXPECT(x))
{
WasmValVec params(6), result(1);
vrt.setBytes(0, x->data(), x->size());
auto* trap =
ww(&import.at("float_root"),
params,
result,
0,
floatSize,
3,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == float10);
}
}
{
// hfs.floatRoot(makeSlice(*x), 2, 0);
auto const x = hfs.floatFromMantExp(1, -2, 0); // 0.01
auto const y = hfs.floatFromMantExp(1, -1, 0); // 0.1
if (BEAST_EXPECT(x && y))
{
WasmValVec params(6), result(1);
vrt.setBytes(0, x->data(), x->size());
auto* trap =
ww(&import.at("float_root"),
params,
result,
0,
floatSize,
2,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == *y);
}
}
}
void
testFloatPower()
{
testcase("floatPower");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{ // hfs.floatPower(Slice(), 2, -1);
WasmValVec params(6), result(1);
auto* trap = ww(&import.at("float_pow"), params, result, 0, 0, 2, 0, floatSize, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{ // hfs.floatPower(makeSlice(invalid), 3, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, invalid.data(), invalid.size());
auto* trap =
ww(&import.at("float_pow"),
params,
result,
0,
invalid.size(),
3,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{ // hfs.floatPower(makeSlice(float1), -2, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, float1.data(), float1.size());
auto* trap =
ww(&import.at("float_pow"),
params,
result,
0,
floatSize,
-2,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatPower(makeSlice(floatMax), 2, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, floatMax.data(), floatMax.size());
auto* trap = ww(
&import.at("float_pow"), params, result, 0, floatSize, 2, floatSize, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatComputationError));
}
{
// hfs.floatPower(makeSlice(floatMax), Number::kMaxExponent + 1, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, floatMax.data(), floatMax.size());
auto* trap =
ww(&import.at("float_pow"),
params,
result,
0,
floatSize,
Number::kMaxExponent + 1,
floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatPower(makeSlice(floatMaxIOU), 0, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size());
auto* trap = ww(
&import.at("float_pow"), params, result, 0, floatSize, 0, floatSize, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == float1);
}
{ // hfs.floatPower(makeSlice(floatMaxIOU), 1, 0);
WasmValVec params(6), result(1);
vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size());
auto* trap = ww(
&import.at("float_pow"), params, result, 0, floatSize, 1, floatSize, floatSize, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == floatMaxIOU);
}
{
// hfs.floatPower(makeSlice(float10), 2, 0);
auto const x = hfs.floatFromMantExp(100, 0, 0); // 100
if (BEAST_EXPECT(x))
{
WasmValVec params(6), result(1);
vrt.setBytes(0, float10.data(), float10.size());
auto* trap =
ww(&import.at("float_pow"),
params,
result,
0,
floatSize,
2,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == *x);
}
}
{
// hfs.floatPower(makeSlice(*x), 2, 0);
auto const x = hfs.floatFromMantExp(1, -1, 0); // 0.1
auto const y = hfs.floatFromMantExp(1, -2, 0); // 0.01
if (BEAST_EXPECT(x && y))
{
WasmValVec params(6), result(1);
vrt.setBytes(0, x->data(), x->size());
auto* trap =
ww(&import.at("float_pow"),
params,
result,
0,
floatSize,
2,
2 * floatSize,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 3);
BEAST_EXPECT(resultBytes == *y);
}
}
}
void
testFloatSpecialCases()
{
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
WasmHostFunctionsImpl const hfs(ac, dummyEscrow);
testcase("float non-canonical");
{ // non-canonical mantissa 100000e-4
Bytes const y = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xA0, 0xFF, 0xFF, 0xFF, 0xFC};
auto const result = hfs.floatCompare(makeSlice(y), makeSlice(float10));
BEAST_EXPECT(result && *result == 0);
}
}
void
testFloatFromSTAmount()
{
testcase("floatFromSTAmount");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatFromSTAmount(amount, -1);
STAmount const amount = XRP(100);
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stamount"),
params,
result,
0,
amountBytes.size(),
256,
floatSize,
-1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromSTAmount(amount, 4);
STAmount const amount = XRP(100);
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stamount"),
params,
result,
0,
amountBytes.size(),
256,
floatSize,
4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromSTAmount(amount, 0);
STAmount const amount = XRP(0);
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stamount"),
params,
result,
0,
amountBytes.size(),
256,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatIntZero);
}
{
// hfs.floatFromSTAmount(amount, 0);
STAmount const amount = XRP(-1);
auto const y = hfs.floatFromMantExp(-1 * 1'000'000, 0, 0);
if (BEAST_EXPECT(y))
{
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stamount"),
params,
result,
0,
amountBytes.size(),
256,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == *y);
}
}
{
// hfs.floatFromSTAmount(amount, 0);
auto const y = hfs.floatFromMantExp(9223372036854776, 3, 0);
STAmount const amount(noIssue(), std::numeric_limits<int64_t>::max());
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stamount"),
params,
result,
0,
amountBytes.size(),
256,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == *y);
}
{
bool ex = false;
try
{
STAmount const amount(noIssue(), -1, Number::kMaxExponent + normalExp);
[[maybe_unused]] Bytes const amountBytes = toBytes(amount);
}
catch (...)
{
ex = true;
}
BEAST_EXPECT(ex);
}
auto const usd = env.master["USD"];
{
// hfs.floatFromSTAmount(amount, 0);
STAmount const amount(
IOUAmount(STAmount::kMinValue, STAmount::kMinOffset), usd.issue());
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stamount"),
params,
result,
0,
amountBytes.size(),
256,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatMinIOU);
}
{
// hfs.floatFromSTAmount(amount, 0);
STAmount const amount(
IOUAmount(STAmount::kMaxValue, STAmount::kMaxOffset), usd.issue());
Bytes amountBytes = toBytes(amount);
vrt.setBytes(0, amountBytes.data(), amountBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stamount"),
params,
result,
0,
amountBytes.size(),
256,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatMaxIOU);
}
}
void
testFloatFromSTNumber()
{
testcase("floatFromSTNumber");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Test with invalid rounding mode
{
// hfs.floatFromSTNumber(num, -1);
STNumber const num(sfNumber, Number(123, 0));
Bytes numBytes = toBytes(num);
vrt.setBytes(0, numBytes.data(), numBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stnumber"),
params,
result,
0,
numBytes.size(),
256,
floatSize,
-1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromSTNumber(num, 4);
STNumber const num(sfNumber, Number(123, 0));
Bytes numBytes = toBytes(num);
vrt.setBytes(0, numBytes.data(), numBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stnumber"),
params,
result,
0,
numBytes.size(),
256,
floatSize,
4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatFromSTNumber(num, 0);
STNumber const num(
sfNumber, Number(std::numeric_limits<uint64_t>::max(), 0, Number::Normalized{}));
Bytes numBytes = toBytes(num);
vrt.setBytes(0, numBytes.data(), numBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stnumber"),
params,
result,
0,
numBytes.size(),
256,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatUIntMax);
}
{
// hfs.floatFromSTNumber(num, 0);
STNumber const num(sfNumber, Number(-1, Number::kMaxExponent + normalExp));
Bytes numBytes = toBytes(num);
vrt.setBytes(0, numBytes.data(), numBytes.size());
WasmValVec params(5), result(1);
auto* trap =
ww(&import.at("float_from_stnumber"),
params,
result,
0,
numBytes.size(),
256,
floatSize,
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const resultBytes = vrt.getBytes(params, 2);
BEAST_EXPECT(resultBytes == floatMinusMaxExp);
}
}
void
testFloatToInt()
{
testcase("floatToInt");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatToInt(makeSlice(float1), -1);
vrt.setBytes(0, float1.data(), float1.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, -1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatToInt(makeSlice(float1), 4);
vrt.setBytes(0, float1.data(), float1.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatToInt(Slice(), 0);
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, 0, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatToInt(makeSlice(invalid), 0);
vrt.setBytes(0, invalid.data(), invalid.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatToInt(makeSlice(floatIntZero), 0);
vrt.setBytes(0, floatIntZero.data(), floatIntZero.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == 0);
// roundtrip
auto const result2 = hfs.floatFromInt(resultVal, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntZero);
}
{
// hfs.floatToInt(makeSlice(float1), 0);
vrt.setBytes(0, float1.data(), float1.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == 1);
// roundtrip
auto const result2 = hfs.floatFromInt(resultVal, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float1);
}
{
// hfs.floatToInt(makeSlice(floatMinus1), 0);
vrt.setBytes(0, floatMinus1.data(), floatMinus1.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == -1);
// roundtrip
auto const result2 = hfs.floatFromInt(resultVal, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMinus1);
}
{
// hfs.floatToInt(makeSlice(floatIntMax), 0);
vrt.setBytes(0, floatIntMax.data(), floatIntMax.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == std::numeric_limits<int64_t>::max());
// roundtrip
auto const result2 = hfs.floatFromInt(resultVal, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMax);
}
{
// int64.min is rounded to nearest: -(2^63-1), which fits into int64
// hfs.floatToInt(makeSlice(floatIntMin), 0);
vrt.setBytes(0, floatIntMin.data(), floatIntMin.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == -std::numeric_limits<int64_t>::max());
// roundtrip
auto const result2 = hfs.floatFromInt(resultVal, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMin);
}
{
// hfs.floatToInt(makeSlice(floatUIntMax), 0);
vrt.setBytes(0, floatUIntMax.data(), floatUIntMax.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatComputationError));
}
// Test rounding modes with pi (3.141592653589793)
{
// to_nearest (mode 0): should round to 3
// hfs.floatToInt(makeSlice(floatPi), 0);
vrt.setBytes(0, floatPi.data(), floatPi.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == 3);
}
{
// towards_zero (mode 1): should truncate to 3
// hfs.floatToInt(makeSlice(floatPi), 1);
vrt.setBytes(0, floatPi.data(), floatPi.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == 3);
}
{
// downward (mode 2): should round down to 3
// hfs.floatToInt(makeSlice(floatPi), 2);
vrt.setBytes(0, floatPi.data(), floatPi.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 2);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == 3);
}
{
// upward (mode 3): should round up to 4
// hfs.floatToInt(makeSlice(floatPi), 3);
vrt.setBytes(0, floatPi.data(), floatPi.size());
WasmValVec params(5), result(1);
auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 3);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 8);
auto const resultVal = vrt.getInt64(params, 2);
BEAST_EXPECT(resultVal == 4);
}
}
void
testFloatToMantExp()
{
testcase("floatToMantExp");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
{
// hfs.floatToMantExp(makeSlice(invalid));
vrt.setBytes(0, invalid.data(), invalid.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 ==
static_cast<int32_t>(HostFunctionError::FloatInputMalformed));
}
{
// hfs.floatToMantExp(makeSlice(floatIntZero));
vrt.setBytes(0, floatIntZero.data(), floatIntZero.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const mantissa = vrt.getInt64(params, 2);
auto const exponent = vrt.getInt32(params, 4);
BEAST_EXPECT(mantissa == 0) &&
BEAST_EXPECT(exponent == std::numeric_limits<int32_t>::min());
// roundtrip
auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntZero);
}
{
// hfs.floatToMantExp(makeSlice(float1));
vrt.setBytes(0, float1.data(), float1.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const mantissa = vrt.getInt64(params, 2);
auto const exponent = vrt.getInt32(params, 4);
BEAST_EXPECT(mantissa == 1000000000000000000) && BEAST_EXPECT(exponent == -normalExp);
// roundtrip
auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float1);
}
{
// hfs.floatToMantExp(makeSlice(floatMinus1));
vrt.setBytes(0, floatMinus1.data(), floatMinus1.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const mantissa = vrt.getInt64(params, 2);
auto const exponent = vrt.getInt32(params, 4);
BEAST_EXPECT(mantissa == -1000000000000000000) && BEAST_EXPECT(exponent == -normalExp);
// roundtrip
auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMinus1);
}
{
// hfs.floatToMantExp(makeSlice(float10));
vrt.setBytes(0, float10.data(), float10.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const mantissa = vrt.getInt64(params, 2);
auto const exponent = vrt.getInt32(params, 4);
BEAST_EXPECT(mantissa == 1000000000000000000) &&
BEAST_EXPECT(exponent == -normalExp + 1);
// roundtrip
auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float10);
}
{
// hfs.floatToMantExp(makeSlice(floatPi));
vrt.setBytes(0, floatPi.data(), floatPi.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const mantissa = vrt.getInt64(params, 2);
auto const exponent = vrt.getInt32(params, 4);
BEAST_EXPECT(mantissa == 3141592653589793000) && BEAST_EXPECT(exponent == -normalExp);
// roundtrip
auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatPi);
}
{
// hfs.floatToMantExp(makeSlice(floatIntMax));
vrt.setBytes(0, floatIntMax.data(), floatIntMax.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const mantissa = vrt.getInt64(params, 2);
auto const exponent = vrt.getInt32(params, 4);
BEAST_EXPECT(mantissa == std::numeric_limits<int64_t>::max()) &&
BEAST_EXPECT(exponent == 0);
// roundtrip
auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMax);
}
{
// hfs.floatToMantExp(makeSlice(floatIntMin));
vrt.setBytes(0, floatIntMin.data(), floatIntMin.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const mantissa = vrt.getInt64(params, 2);
auto const exponent = vrt.getInt32(params, 4);
BEAST_EXPECT(mantissa == -std::numeric_limits<int64_t>::max()) &&
BEAST_EXPECT(exponent == 0);
// roundtrip
auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMin);
}
{
// hfs.floatToMantExp(makeSlice(floatMax));
vrt.setBytes(0, floatMax.data(), floatMax.size());
WasmValVec params(6), result(1);
auto* trap =
ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == floatSize);
auto const mantissa = vrt.getInt64(params, 2);
auto const exponent = vrt.getInt32(params, 4);
BEAST_EXPECT(mantissa == Number::kMaxRep) &&
BEAST_EXPECT(exponent == Number::kMaxExponent);
// roundtrip
auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0);
BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMax);
}
}
void
testFloats()
{
// for checking binary formats manually
// printNumbersBin();
testTraceFloat();
testFloatFromInt();
testFloatFromUint();
testFloatFromSTAmount();
testFloatFromSTNumber();
testFloatToInt();
testFloatToMantExp();
testfloatFromMantExp();
testFloatCompare();
testFloatAdd();
testFloatSubtract();
testFloatMultiply();
testFloatDivide();
testFloatRoot();
testFloatPower();
testFloatSpecialCases();
}
void
testVectorIndexes()
{
testcase("WasmValVec indicies");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
bool ex = false;
try
{
// hfs.getLedgerSqn();
WasmValVec params(2), result(1);
// 3 parameters instead of 2
auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t), 1);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) &&
BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->header().seq);
}
catch (std::exception const& e)
{
BEAST_EXPECTS(e.what() == std::string("Out of bound"), e.what());
ex = true;
}
// const version
ex = false;
try
{
WasmValVec params(2);
[[maybe_unused]] auto const x = params[2];
}
catch (std::exception const& e)
{
BEAST_EXPECTS(e.what() == std::string("Out of bound"), e.what());
ex = true;
}
BEAST_EXPECT(ex);
}
void
testTransferLimit()
{
testcase("transferLimit");
using namespace test::jtx;
Env env{*this};
OpenView ov{*env.current()};
ApplyContext ac = createApplyContext(env, ov);
auto const dummyEscrow =
keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master)));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(vrt);
// Test 1: Test setData() - copying FROM host TO wasm
// Multiple calls to getLedgerSqn() which uses setData() to write result to WASM memory
vrt.setTransferLimit(kWasmTransferLimit + 1024);
// hfs.getLedgerSqn();
for (int i = 0; i < (kWasmTransferLimit / vrt.transferDiff) - 3; ++i)
{
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) &&
BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->header().seq);
}
BEAST_EXPECT((vrt.getTestTransferLimit() >= 0) && (vrt.getTestTransferLimit() < 1024));
// Next call should hit OutOfTransferLimit
{
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit));
}
// After limit exhausted, all next call return OutOfTransferLimit
{
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t));
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit));
}
// Reset transfer limit to a small value that can accommodate overhead but not AccountID
// copy
vrt.setTransferLimit(vrt.transferDiff + 10);
Account const alice("alice");
auto const aliceID = env.master.id();
vrt.setBytes(0, aliceID.data(), AccountID::size());
// This should fail because getDataAccountID() needs to copy AccountID (20 bytes)
// After getTransferLimit() overhead (1024), we only have 10 bytes left, not enough for 20
{
WasmValVec params(4), result(1);
auto* trap =
ww(&import.at("accountroot_id"), params, result, 0, AccountID::size(), 100, 32);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit));
}
// Verify that reading slices (without copying) does NOT consume transfer limit
vrt.setTransferLimit(vrt.transferDiff + 10);
// trace() uses getDataString() -> getDataSlice() which does NOT check transfer limit
std::string testMsg = "This message is longer than 10 bytes to prove slices don't count";
vrt.setBytes(0, testMsg.data(), testMsg.size());
vrt.setBytes(
100,
reinterpret_cast<uint8_t const*>("dummy"),
5); // Empty data slice for trace
{
WasmValVec params(5), result(0);
// trace(msg_ptr, msg_len, data_type, data_ptr, data_len) -- returns nothing
auto* trap =
ww(&import.at("trace"),
params,
result,
0,
testMsg.size(),
traceDataTypeToInt(TraceDataType::AsText),
100,
5);
// Should not trap even though the message is >10 bytes, because trace only reads
// slices (no transfer limit check in getDataSlice) and never charges the limit.
BEAST_EXPECT(!trap);
}
// setData should return OutOfTransferLimit when the transfer limit is exhausted.
// trace left the limit untouched, so the next getTransferLimit() overhead (1024)
// takes 1034 down to 10 -- not enough for the 32-byte hash copy.
{
WasmValVec params(2), result(1);
auto* trap = ww(&import.at("parent_ldgr_hash"), params, result, 500, 32);
// the transfer limit went negative
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(
result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit));
}
}
void
run() override
{
testGetLedgerSqn();
testGetParentLedgerTime();
testGetParentLedgerHash();
testGetBaseFee();
testIsAmendmentEnabled();
testCacheLedgerObj();
testGetTxField();
testGetCurrentLedgerObjField();
testGetLedgerObjField();
testGetTxNestedField();
testGetCurrentLedgerObjNestedField();
testGetLedgerObjNestedField();
testGetTxArrayLen();
testGetCurrentLedgerObjArrayLen();
testGetLedgerObjArrayLen();
testGetTxNestedArrayLen();
testGetCurrentLedgerObjNestedArrayLen();
testGetLedgerObjNestedArrayLen();
testUpdateData();
testCheckSignature();
testComputeSha512HalfHash();
testKeyletFunctions();
testGetNFT();
testGetNFTIssuer();
testGetNFTTaxon();
testGetNFTFlags();
testGetNFTTransferFee();
testGetNFTSerial();
testTrace();
testTraceNum();
testTraceAccount();
testTraceAmount();
testFloats();
testVectorIndexes();
testTransferLimit();
}
};
BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, xrpl);
} // namespace xrpl::test