Files
rippled/src/test/app/HostFuncImpl_test.cpp
2026-05-20 13:44:18 -04:00

7492 lines
259 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/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/ParamsHelper.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;
public:
VirtualRuntime() : buffer_(1024 * 1024)
{
}
Wmem
getMem() override
{
return {.p = buffer_.data(), .s = 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_;
}
void
checkIdx(WasmValVec const& params, size_t i) const
{
if (i + 1 >= params.size())
Throw<std::runtime_error>("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::runtime_error>("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 = reinterpret_cast<HostFunctions*>(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 = reinterpret_cast<HostFunctions*>(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 F, class E, class P, typename... Args>
wasm_trap_t*
ww(F&& f, E&& e, P&& params, P&& result, Args... args)
{
size_t idx = 0;
(ww_hlp(idx, e, params, std::forward<Args>(args)), ...); // NOLINT
return f(std::forward<E>(e), params.get(), result.get()); // NOLINT
}
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, 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(getLedgerSqn_wrap,
&import.at("get_ledger_sqn"),
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, 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(getParentLedgerTime_wrap,
&import.at("get_parent_ledger_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, 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(getParentLedgerHash_wrap,
&import.at("get_parent_ledger_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, 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(getBaseFee_wrap,
&import.at("get_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, 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(isAmendmentEnabled_wrap,
&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(isAmendmentEnabled_wrap,
&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(isAmendmentEnabled_wrap,
&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(isAmendmentEnabled_wrap,
&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, 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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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, 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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(kSfInvalid);
{
WasmValVec params(3), result(1);
auto* trap =
ww(getTxField_wrap,
&import.at("get_tx_field"),
params,
result,
kSfInvalid.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(kSfGeneric);
{
WasmValVec params(3), result(1);
auto* trap =
ww(getTxField_wrap,
&import.at("get_tx_field"),
params,
result,
kSfGeneric.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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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(getTxField_wrap,
&import.at("get_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, 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(getCurrentLedgerObjField_wrap,
&import.at("get_current_ledger_obj_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(getCurrentLedgerObjField_wrap,
&import.at("get_current_ledger_obj_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(getCurrentLedgerObjField_wrap,
&import.at("get_current_ledger_obj_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(getCurrentLedgerObjField_wrap,
&import.at("get_current_ledger_obj_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, 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(getCurrentLedgerObjField_wrap,
&import2.at("get_current_ledger_obj_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(), 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(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(getLedgerObjField_wrap,
&import.at("get_ledger_obj_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(getLedgerObjField_wrap,
&import.at("get_ledger_obj_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(getLedgerObjField_wrap,
&import.at("get_ledger_obj_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(getLedgerObjField_wrap,
&import.at("get_ledger_obj_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(getLedgerObjField_wrap,
&import.at("get_ledger_obj_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(getLedgerObjField_wrap,
&import.at("get_ledger_obj_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, 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(getTxNestedField_wrap,
&import.at("get_tx_nested_field"),
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(getTxNestedField_wrap,
&import.at("get_tx_nested_field"),
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(getTxNestedField_wrap,
&import.at("get_tx_nested_field"),
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() + 1, sizeof(int32_t));
WasmValVec params(4), result(1);
auto* trap =
ww(getTxNestedField_wrap,
&import.at("get_tx_nested_field"),
params,
result,
0,
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(getTxNestedField_wrap,
&import.at("get_tx_nested_field"),
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>(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 = kSfInvalid, exists in map but not in tx)
expectError(
{-1, // kSfInvalid's field code
0,
sfAccount.getCode()},
HostFunctionError::FieldNotFound);
// hfs.getTxNestedField(locator);
// Locator for zero base sfield code (0 = kSfGeneric, exists in map but not in tx)
expectError(
{0, // kSfGeneric'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 = kSfInvalid, 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(getTxNestedField_wrap,
&import.at("get_tx_nested_field"),
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::signers(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(getCurrentLedgerObjNestedField_wrap,
&import.at("get_current_ledger_obj_nested_field"),
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(getCurrentLedgerObjNestedField_wrap,
&import.at("get_current_ledger_obj_nested_field"),
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>(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(getCurrentLedgerObjNestedField_wrap,
&import.at("get_current_ledger_obj_nested_field"),
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(getCurrentLedgerObjNestedField_wrap,
&import.at("get_current_ledger_obj_nested_field"),
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, 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(getCurrentLedgerObjNestedField_wrap,
&import2.at("get_current_ledger_obj_nested_field"),
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, 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::signers(env.master.id());
// hfs.cacheLedgerObj(signerListKeylet.key, 1);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, signerListKeylet.key.data(), uint256::size());
auto* trap =
ww(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(getLedgerObjNestedField_wrap,
&import.at("get_ledger_obj_nested_field"),
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(getLedgerObjNestedField_wrap,
&import.at("get_ledger_obj_nested_field"),
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(getLedgerObjNestedField_wrap,
&import.at("get_ledger_obj_nested_field"),
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(getLedgerObjNestedField_wrap,
&import.at("get_ledger_obj_nested_field"),
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(getLedgerObjNestedField_wrap,
&import.at("get_ledger_obj_nested_field"),
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 == static_cast<int32_t>(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(getLedgerObjNestedField_wrap,
&import.at("get_ledger_obj_nested_field"),
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, 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(getTxArrayLen_wrap,
&import.at("get_tx_array_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(getTxArrayLen_wrap,
&import.at("get_tx_array_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(getTxArrayLen_wrap,
&import.at("get_tx_array_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(getTxArrayLen_wrap,
&import.at("get_tx_array_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::signers(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(getCurrentLedgerObjArrayLen_wrap,
&import.at("get_current_ledger_obj_array_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(getCurrentLedgerObjArrayLen_wrap,
&import.at("get_current_ledger_obj_array_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(getCurrentLedgerObjArrayLen_wrap,
&import.at("get_current_ledger_obj_array_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, 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(getCurrentLedgerObjArrayLen_wrap,
&import2.at("get_current_ledger_obj_array_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, env.seq(env.master));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(&vrt);
auto const signerListKeylet = keylet::signers(env.master.id());
// hfs.cacheLedgerObj(signerListKeylet.key, 1);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, signerListKeylet.key.data(), uint256::size());
auto* trap =
ww(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(getLedgerObjArrayLen_wrap,
&import.at("get_ledger_obj_array_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(getLedgerObjArrayLen_wrap,
&import.at("get_ledger_obj_array_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(getLedgerObjArrayLen_wrap,
&import.at("get_ledger_obj_array_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(getLedgerObjArrayLen_wrap,
&import.at("get_ledger_obj_array_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(getLedgerObjArrayLen_wrap,
&import.at("get_ledger_obj_array_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, 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(getTxNestedArrayLen_wrap,
&import.at("get_tx_nested_array_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>(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(getTxNestedArrayLen_wrap,
&import.at("get_tx_nested_array_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::signers(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(getCurrentLedgerObjNestedArrayLen_wrap,
&import.at("get_current_ledger_obj_nested_array_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>(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(getCurrentLedgerObjNestedArrayLen_wrap,
&import.at("get_current_ledger_obj_nested_array_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, 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(getCurrentLedgerObjNestedArrayLen_wrap,
&import2.at("get_current_ledger_obj_nested_array_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, env.seq(env.master));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(&vrt);
auto const signerListKeylet = keylet::signers(env.master.id());
// hfs.cacheLedgerObj(signerListKeylet.key, 1);
{
WasmValVec params(3), result(1);
vrt.setBytes(0, signerListKeylet.key.data(), uint256::size());
auto* trap =
ww(cacheLedgerObj_wrap,
&import.at("cache_ledger_obj"),
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(getLedgerObjNestedArrayLen_wrap,
&import.at("get_ledger_obj_nested_array_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(getLedgerObjNestedArrayLen_wrap,
&import.at("get_ledger_obj_nested_array_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 == static_cast<int32_t>(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(getLedgerObjNestedArrayLen_wrap,
&import.at("get_ledger_obj_nested_array_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, 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(updateData_wrap, &import.at("update_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(maxWasmDataLength + 1, 0x42);
// hfs.updateData(Slice(bigData.data(), bigData.size()));
{
vrt.setBytes(0, bigData.data(), bigData.size());
WasmValVec params(2), result(1);
auto* trap =
ww(updateData_wrap, &import.at("update_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, 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(checkSignature_wrap,
&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(checkSignature_wrap,
&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(checkSignature_wrap,
&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(checkSignature_wrap,
&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(checkSignature_wrap,
&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(checkSignature_wrap,
&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, 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(computeSha512HalfHash_wrap,
&import.at("compute_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, 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(
accountKeylet_wrap, &imp.at("account_keylet"), 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(accountKeylet_wrap,
&imp.at("account_keylet"),
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(ammKeylet_wrap,
&imp.at("amm_keylet"),
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(ammKeylet_wrap,
&imp.at("amm_keylet"),
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(ammKeylet_wrap,
&imp.at("amm_keylet"),
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, 1u);
WasmValVec params(6), result(1);
auto* trap =
ww(checkKeylet_wrap,
&imp.at("check_keylet"),
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(checkKeylet_wrap,
&imp.at("check_keylet"),
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(credentialKeylet_wrap,
&imp.at("credential_keylet"),
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(credentialKeylet_wrap,
&imp.at("credential_keylet"),
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(credentialKeylet_wrap,
&imp.at("credential_keylet"),
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(credentialKeylet_wrap,
&imp.at("credential_keylet"),
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(didKeylet_wrap, &imp.at("did_keylet"), 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(didKeylet_wrap, &imp.at("did_keylet"), 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(delegateKeylet_wrap,
&imp.at("delegate_keylet"),
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(delegateKeylet_wrap,
&imp.at("delegate_keylet"),
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(delegateKeylet_wrap,
&imp.at("delegate_keylet"),
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(delegateKeylet_wrap,
&imp.at("delegate_keylet"),
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(depositPreauthKeylet_wrap,
&imp.at("deposit_preauth_keylet"),
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(depositPreauthKeylet_wrap,
&imp.at("deposit_preauth_keylet"),
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(depositPreauthKeylet_wrap,
&imp.at("deposit_preauth_keylet"),
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(depositPreauthKeylet_wrap,
&imp.at("deposit_preauth_keylet"),
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, 1u);
WasmValVec params(6), result(1);
auto* trap =
ww(escrowKeylet_wrap,
&imp.at("escrow_keylet"),
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(escrowKeylet_wrap,
&imp.at("escrow_keylet"),
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::line(masterID, alice.id(), usd);
WasmValVec params(8), result(1);
auto* trap =
ww(lineKeylet_wrap,
&imp.at("line_keylet"),
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(lineKeylet_wrap,
&imp.at("line_keylet"),
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(lineKeylet_wrap,
&imp.at("line_keylet"),
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(lineKeylet_wrap,
&imp.at("line_keylet"),
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(lineKeylet_wrap,
&imp.at("line_keylet"),
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::mptIssuance(1u, masterID);
WasmValVec params(6), result(1);
auto* trap =
ww(mptIssuanceKeylet_wrap,
&imp.at("mpt_issuance_keylet"),
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(mptIssuanceKeylet_wrap,
&imp.at("mpt_issuance_keylet"),
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(mptokenKeylet_wrap,
&imp.at("mptoken_keylet"),
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(mptokenKeylet_wrap,
&imp.at("mptoken_keylet"),
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(mptokenKeylet_wrap,
&imp.at("mptoken_keylet"),
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::nftoffer(masterID, 1u);
WasmValVec params(6), result(1);
auto* trap =
ww(nftOfferKeylet_wrap,
&imp.at("nft_offer_keylet"),
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(nftOfferKeylet_wrap,
&imp.at("nft_offer_keylet"),
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, 1u);
WasmValVec params(6), result(1);
auto* trap =
ww(offerKeylet_wrap,
&imp.at("offer_keylet"),
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(offerKeylet_wrap,
&imp.at("offer_keylet"),
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(oracleKeylet_wrap,
&imp.at("oracle_keylet"),
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(oracleKeylet_wrap,
&imp.at("oracle_keylet"),
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::payChan(masterID, alice.id(), 1u);
WasmValVec params(8), result(1);
auto* trap =
ww(paychanKeylet_wrap,
&imp.at("paychan_keylet"),
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(paychanKeylet_wrap,
&imp.at("paychan_keylet"),
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(paychanKeylet_wrap,
&imp.at("paychan_keylet"),
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(paychanKeylet_wrap,
&imp.at("paychan_keylet"),
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, 1u);
WasmValVec params(6), result(1);
auto* trap =
ww(permissionedDomainKeylet_wrap,
&imp.at("permissioned_domain_keylet"),
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(permissionedDomainKeylet_wrap,
&imp.at("permissioned_domain_keylet"),
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::signers(masterID);
WasmValVec params(4), result(1);
auto* trap = ww(
signersKeylet_wrap, &imp.at("signers_keylet"), 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(signersKeylet_wrap,
&imp.at("signers_keylet"),
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::kTicket(masterID, 1u);
WasmValVec params(6), result(1);
auto* trap =
ww(ticketKeylet_wrap,
&imp.at("ticket_keylet"),
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(ticketKeylet_wrap,
&imp.at("ticket_keylet"),
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, 1u);
WasmValVec params(6), result(1);
auto* trap =
ww(vaultKeylet_wrap,
&imp.at("vault_keylet"),
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(vaultKeylet_wrap,
&imp.at("vault_keylet"),
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, 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(getNFT_wrap,
&import.at("get_nft"),
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(getNFT_wrap,
&import.at("get_nft"),
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(getNFT_wrap,
&import.at("get_nft"),
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(getNFT_wrap,
&import.at("get_nft"),
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(getNFT_wrap,
&import.at("get_nft"),
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, 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(getNFTIssuer_wrap,
&import.at("get_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(getNFTIssuer_wrap,
&import.at("get_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, 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(getNFTTaxon_wrap,
&import.at("get_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, 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(
getNFTFlags_wrap, &import.at("get_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(
getNFTFlags_wrap, &import.at("get_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, 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(getNFTTransferFee_wrap,
&import.at("get_nft_transfer_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(getNFTTransferFee_wrap,
&import.at("get_nft_transfer_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("getNFTSerial");
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, env.seq(env.master));
VirtualRuntime vrt;
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(&vrt);
{
// hfs.getNFTSerial(nftId);
vrt.setBytes(0, nftId.data(), uint256::size());
WasmValVec params(4), result(1);
auto* trap =
ww(getNFTSerial_wrap,
&import.at("get_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.getNFTSerial(uint256());
uint256 zeroId;
vrt.setBytes(0, zeroId.data(), uint256::size());
WasmValVec params(4), result(1);
auto* trap =
ww(getNFTSerial_wrap,
&import.at("get_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, 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());
// hfs.trace(msg, slice, false);
{
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, slice.data(), slice.size());
WasmValVec params(5), result(1);
auto* trap =
ww(trace_wrap,
&import.at("trace"),
params,
result,
0,
msg.size(),
256,
slice.size(),
0);
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.find(msg) != std::string::npos);
}
}
// hfs.trace(msg, slice, true);
{
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, slice.data(), slice.size());
WasmValVec params(5), result(1);
auto* trap =
ww(trace_wrap,
&import.at("trace"),
params,
result,
0,
msg.size(),
256,
slice.size(),
1);
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0))
{
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.find(msg) != std::string::npos);
BEAST_EXPECT(messages.find(hex) != std::string::npos);
}
}
}
{
// 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, 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());
// hfs.trace(msg, slice, false);
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, slice.data(), slice.size());
WasmValVec params(5), result(1);
auto* trap =
ww(trace_wrap,
&import.at("trace"),
params,
result,
0,
msg.size(),
256,
slice.size(),
0);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
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, env.seq(env.master));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(&vrt);
std::string const msg = "trace number";
int64_t const num = 123456789;
// hfs.traceNum(msg, num);
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
WasmValVec params(3), result(1);
auto* trap =
ww(traceNum_wrap, &import.at("trace_num"), params, result, 0, msg.size(), num);
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.find(msg) != std::string::npos);
BEAST_EXPECT(messages.find(std::to_string(num)) != std::string::npos);
}
}
{
// 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, env.seq(env.master));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(&vrt);
std::string const msg = "trace number";
int64_t const num = 123456789;
// hfs.traceNum(msg, num);
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
WasmValVec params(3), result(1);
auto* trap =
ww(traceNum_wrap, &import.at("trace_num"), params, result, 0, msg.size(), num);
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
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, 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();
// hfs.traceAccount(msg, env.master.id());
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, accountId.data(), accountId.size());
WasmValVec params(4), result(1);
auto* trap =
ww(traceAccount_wrap,
&import.at("trace_account"),
params,
result,
0,
msg.size(),
256,
accountId.size());
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.find(msg) != std::string::npos);
BEAST_EXPECT(messages.find(env.master.human()) != std::string::npos);
}
}
{
// 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, 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();
// hfs.traceAccount(msg, env.master.id());
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, accountId.data(), accountId.size());
WasmValVec params(4), result(1);
auto* trap =
ww(traceAccount_wrap,
&import.at("trace_account"),
params,
result,
0,
msg.size(),
256,
accountId.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
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, 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);
{
// hfs.traceAmount(msg, amount);
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(4), result(1);
auto* trap =
ww(traceAmount_wrap,
&import.at("trace_amount"),
params,
result,
0,
msg.size(),
256,
amountBytes.size());
if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0))
{
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.find(msg) != std::string::npos);
BEAST_EXPECT(messages.find(amount.getFullText()) != std::string::npos);
}
}
// IOU amount
Account const alice("alice");
env.fund(XRP(1000), alice);
env.close();
STAmount const iouAmount = env.master["USD"](100);
{
// hfs.traceAmount(msg, iouAmount);
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(4), result(1);
auto* trap =
ww(traceAmount_wrap,
&import.at("trace_amount"),
params,
result,
0,
msg.size(),
256,
amountBytes.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
// MPT amount
{
auto const mptId = makeMptID(42, env.master.id());
Asset const mptAsset = Asset(mptId);
STAmount const mptAmount(mptAsset, 123456);
// hfs.traceAmount(msg, mptAmount);
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(4), result(1);
auto* trap =
ww(traceAmount_wrap,
&import.at("trace_amount"),
params,
result,
0,
msg.size(),
256,
amountBytes.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
}
{
// 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, 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);
// hfs.traceAmount(msg, amount);
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(4), result(1);
auto* trap =
ww(traceAmount_wrap,
&import.at("trace_amount"),
params,
result,
0,
msg.size(),
256,
amountBytes.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
auto const messages = sink.messages().str();
BEAST_EXPECT(messages.empty());
}
}
// clang-format off
int const normalExp = 18;
Bytes const floatIntMin = {0xF3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x01}; // -2^63
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);
return;
}
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, env.seq(env.master));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(&vrt);
std::string const msg = "trace float";
{
// hfs.traceFloat(msg, makeSlice(invalid));
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(4), result(1);
auto* trap =
ww(traceFloat_wrap,
&import.at("trace_opaque_float"),
params,
result,
0,
msg.size(),
256,
invalid.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
{
// hfs.traceFloat(msg, makeSlice(floatMaxExp));
vrt.setBytes(0, reinterpret_cast<uint8_t const*>(msg.data()), msg.size());
vrt.setBytes(256, floatMaxExp.data(), floatMaxExp.size());
WasmValVec params(4), result(1);
auto* trap =
ww(traceFloat_wrap,
&import.at("trace_opaque_float"),
params,
result,
0,
msg.size(),
256,
floatMaxExp.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
}
}
{
// 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, env.seq(env.master));
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
VirtualRuntime vrt;
auto import = xrpl::createWasmImport(hfs);
hfs.setRT(&vrt);
std::string const msg = "trace float";
// hfs.traceFloat(msg, makeSlice(invalid));
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(4), result(1);
auto* trap =
ww(traceFloat_wrap,
&import.at("trace_opaque_float"),
params,
result,
0,
msg.size(),
256,
invalid.size());
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
BEAST_EXPECT(result[0].of.i32 == 0);
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, 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(floatFromInt_wrap,
&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(floatFromInt_wrap,
&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(floatFromInt_wrap,
&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(floatFromInt_wrap,
&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(floatFromInt_wrap,
&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, 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(floatFromUint_wrap,
&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(floatFromUint_wrap,
&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(floatFromUint_wrap,
&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(floatFromUint_wrap,
&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, 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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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(floatFromMantExp_wrap,
&import.at("float_compare"),
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, 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(floatCompare_wrap, &import.at("float_add"), 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(floatCompare_wrap, &import.at("float_add"), 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(floatCompare_wrap,
&import.at("float_add"),
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(floatCompare_wrap,
&import.at("float_add"),
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(floatCompare_wrap,
&import.at("float_add"),
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(floatCompare_wrap,
&import.at("float_add"),
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, 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(floatAdd_wrap,
&import.at("float_subtract"),
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(floatAdd_wrap,
&import.at("float_subtract"),
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(floatAdd_wrap,
&import.at("float_subtract"),
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(floatAdd_wrap,
&import.at("float_subtract"),
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(floatAdd_wrap,
&import.at("float_subtract"),
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);//
// Number can't hold int64.min, it is rounded and we get -3, not -1
WasmValVec params(7), result(1);
vrt.setBytes(0, floatIntMax.data(), floatIntMax.size());
vrt.setBytes(floatSize, floatIntMin.data(), floatIntMin.size());
auto* trap =
ww(floatAdd_wrap,
&import.at("float_subtract"),
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 == floatMinus3);
}
}
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, 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(floatSubtract_wrap,
&import.at("float_from_mant_exp"),
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(floatSubtract_wrap,
&import.at("float_from_mant_exp"),
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(floatSubtract_wrap,
&import.at("float_from_mant_exp"),
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(floatSubtract_wrap,
&import.at("float_from_mant_exp"),
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(floatSubtract_wrap,
&import.at("float_from_mant_exp"),
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(floatSubtract_wrap,
&import.at("float_from_mant_exp"),
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, 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(floatMultiply_wrap,
&import.at("float_multiply"),
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(floatMultiply_wrap,
&import.at("float_multiply"),
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(floatMultiply_wrap,
&import.at("float_multiply"),
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(floatMultiply_wrap,
&import.at("float_multiply"),
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(floatMultiply_wrap,
&import.at("float_multiply"),
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(floatMultiply_wrap,
&import.at("float_multiply"),
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(floatMultiply_wrap,
&import.at("float_multiply"),
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, 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(floatDivide_wrap,
&import.at("float_divide"),
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(floatDivide_wrap,
&import.at("float_divide"),
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(floatDivide_wrap,
&import.at("float_divide"),
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(floatDivide_wrap,
&import.at("float_divide"),
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(floatDivide_wrap,
&import.at("float_divide"),
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(floatDivide_wrap,
&import.at("float_divide"),
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(floatDivide_wrap,
&import.at("float_divide"),
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, 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(floatRoot_wrap,
&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(floatRoot_wrap,
&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(floatRoot_wrap,
&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(floatRoot_wrap,
&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(floatRoot_wrap,
&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(floatRoot_wrap,
&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(floatRoot_wrap,
&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(floatRoot_wrap,
&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, 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(floatPower_wrap,
&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(floatPower_wrap,
&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(floatPower_wrap,
&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(floatPower_wrap,
&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(floatPower_wrap,
&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(floatPower_wrap,
&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(floatPower_wrap,
&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(floatPower_wrap,
&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(floatPower_wrap,
&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, 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, 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(floatFromSTAmount_wrap,
&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(floatFromSTAmount_wrap,
&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(floatFromSTAmount_wrap,
&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(floatFromSTAmount_wrap,
&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(floatFromSTAmount_wrap,
&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(floatFromSTAmount_wrap,
&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(floatFromSTAmount_wrap,
&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, 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(floatFromSTNumber_wrap,
&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(floatFromSTNumber_wrap,
&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(floatFromSTNumber_wrap,
&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(floatFromSTNumber_wrap,
&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, 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(floatToInt_wrap,
&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(floatToInt_wrap,
&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(floatToInt_wrap, &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(floatToInt_wrap,
&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(floatToInt_wrap,
&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(floatToInt_wrap,
&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(floatToInt_wrap,
&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(floatToInt_wrap,
&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);
}
{
// Number can't hold int64.min, it is rounded and we get int64_t.min - 3, which doesn't
// fit into int64
// hfs.floatToInt(makeSlice(floatIntMin), 0);
vrt.setBytes(0, floatIntMin.data(), floatIntMin.size());
WasmValVec params(5), result(1);
auto* trap =
ww(floatToInt_wrap,
&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));
}
{
// hfs.floatToInt(makeSlice(floatUIntMax), 0);
vrt.setBytes(0, floatUIntMax.data(), floatUIntMax.size());
WasmValVec params(5), result(1);
auto* trap =
ww(floatToInt_wrap,
&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(floatToInt_wrap,
&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(floatToInt_wrap,
&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(floatToInt_wrap,
&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(floatToInt_wrap,
&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, 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(floatToMantExp_wrap,
&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(floatToMantExp_wrap,
&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(floatToMantExp_wrap,
&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(floatToMantExp_wrap,
&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(floatToMantExp_wrap,
&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(floatToMantExp_wrap,
&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(floatToMantExp_wrap,
&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(floatToMantExp_wrap,
&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>::min() / 10) - 1) &&
BEAST_EXPECT(exponent == 1);
// 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(floatToMantExp_wrap,
&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, 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(getLedgerSqn_wrap,
&import.at("get_ledger_sqn"),
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
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();
}
};
BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, xrpl);
} // namespace xrpl::test