mirror of
https://github.com/XRPLF/rippled.git
synced 2026-01-25 00:55:23 +00:00
* add Vector256 nesting/length support * [WIP] add tests * fix tests * simplify with helper function * oops typo * remove static variable * respond to comments * STBaseOrUInt256->FieldValue * oops * add more tests for coverage * respond to comments
3209 lines
110 KiB
C++
3209 lines
110 KiB
C++
#include <test/jtx.h>
|
|
|
|
#include <xrpld/app/wasm/HostFuncImpl.h>
|
|
|
|
#include <xrpl/protocol/digest.h>
|
|
|
|
namespace xrpl {
|
|
namespace 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(Asset const& asset)
|
|
{
|
|
if (asset.holds<Issue>())
|
|
{
|
|
Serializer s;
|
|
auto const& issue = asset.get<Issue>();
|
|
s.addBitString(issue.currency);
|
|
if (!isXRP(issue.currency))
|
|
s.addBitString(issue.account);
|
|
auto const data = s.getData();
|
|
return data;
|
|
}
|
|
|
|
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 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);
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
auto const result = hfs.getLedgerSqn();
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == env.current()->header().seq);
|
|
}
|
|
|
|
void
|
|
testGetParentLedgerTime()
|
|
{
|
|
testcase("getParentLedgerTime");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this};
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
|
|
{
|
|
OpenView ov{*env.current()};
|
|
ApplyContext ac = createApplyContext(env, ov);
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
auto const result = hfs.getParentLedgerTime();
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(
|
|
result.value() ==
|
|
env.current()
|
|
->parentCloseTime()
|
|
.time_since_epoch()
|
|
.count());
|
|
}
|
|
|
|
env.close(
|
|
env.now() +
|
|
std::chrono::seconds(std::numeric_limits<int32_t>::max() - 1));
|
|
{
|
|
OpenView ov{*env.current()};
|
|
ApplyContext ac = createApplyContext(env, ov);
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
auto const result = hfs.getParentLedgerTime();
|
|
if (BEAST_EXPECTS(
|
|
!result.has_value(), std::to_string(result.value())))
|
|
BEAST_EXPECT(result.error() == HostFunctionError::INTERNAL);
|
|
}
|
|
}
|
|
|
|
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));
|
|
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
auto const result = hfs.getParentLedgerHash();
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == env.current()->header().parentHash);
|
|
}
|
|
|
|
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));
|
|
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
auto const result = hfs.getBaseFee();
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == env.current()->fees().base.drops());
|
|
|
|
{
|
|
Env env2(
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->FEES.reference_fee =
|
|
static_cast<int64_t>(
|
|
std::numeric_limits<int32_t>::max()) +
|
|
1;
|
|
return cfg;
|
|
}),
|
|
testable_amendments());
|
|
// Run past the flag ledger so that a Fee change vote occurs and
|
|
// updates FeeSettings. (It also activates all supported
|
|
// amendments.)
|
|
for (auto i = env.current()->seq(); i <= 257; ++i)
|
|
env.close();
|
|
|
|
OpenView ov2{*env2.current()};
|
|
ApplyContext ac2 = createApplyContext(env2, ov2);
|
|
WasmHostFunctionsImpl hfs2(ac2, dummyEscrow);
|
|
auto const result2 = hfs2.getBaseFee();
|
|
if (BEAST_EXPECT(!result2.has_value()))
|
|
BEAST_EXPECT(result2.error() == HostFunctionError::INTERNAL);
|
|
}
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
// Use featureTokenEscrow for testing
|
|
auto const amendmentId = featureTokenEscrow;
|
|
|
|
// Test by id
|
|
{
|
|
auto const result = hfs.isAmendmentEnabled(amendmentId);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 1);
|
|
}
|
|
|
|
// Test by name
|
|
std::string const amendmentName = "TokenEscrow";
|
|
{
|
|
auto const result = hfs.isAmendmentEnabled(amendmentName);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 1);
|
|
}
|
|
|
|
// Test with a fake amendment id (all zeros)
|
|
uint256 fakeId;
|
|
{
|
|
auto const result = hfs.isAmendmentEnabled(fakeId);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 0);
|
|
}
|
|
|
|
// Test with a fake amendment name
|
|
std::string fakeName = "FakeAmendment";
|
|
{
|
|
auto const result = hfs.isAmendmentEnabled(fakeName);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 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);
|
|
{
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
BEAST_EXPECT(
|
|
hfs.cacheLedgerObj(accountKeylet.key, -1).error() ==
|
|
HostFunctionError::SLOT_OUT_RANGE);
|
|
BEAST_EXPECT(
|
|
hfs.cacheLedgerObj(accountKeylet.key, 257).error() ==
|
|
HostFunctionError::SLOT_OUT_RANGE);
|
|
BEAST_EXPECT(
|
|
hfs.cacheLedgerObj(dummyEscrow.key, 0).error() ==
|
|
HostFunctionError::LEDGER_OBJ_NOT_FOUND);
|
|
BEAST_EXPECT(hfs.cacheLedgerObj(accountKeylet.key, 0).value() == 1);
|
|
|
|
for (int i = 1; i <= 256; ++i)
|
|
{
|
|
auto const result = hfs.cacheLedgerObj(accountKeylet.key, i);
|
|
BEAST_EXPECT(result.has_value() && result.value() == i);
|
|
}
|
|
BEAST_EXPECT(
|
|
hfs.cacheLedgerObj(accountKeylet.key, 0).error() ==
|
|
HostFunctionError::SLOTS_FULL);
|
|
}
|
|
|
|
{
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
for (int i = 1; i <= 256; ++i)
|
|
{
|
|
auto const result = hfs.cacheLedgerObj(accountKeylet.key, 0);
|
|
BEAST_EXPECT(result.has_value() && result.value() == i);
|
|
}
|
|
BEAST_EXPECT(
|
|
hfs.cacheLedgerObj(accountKeylet.key, 0).error() ==
|
|
HostFunctionError::SLOTS_FULL);
|
|
}
|
|
}
|
|
|
|
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.push_back(credId);
|
|
obj.setFieldV256(sfCredentialIDs, credIds);
|
|
});
|
|
ApplyContext ac = createApplyContext(env, ov, stx);
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
|
|
{
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
auto const account = hfs.getTxField(sfAccount);
|
|
BEAST_EXPECT(
|
|
account && std::ranges::equal(*account, env.master.id()));
|
|
|
|
auto const owner = hfs.getTxField(sfOwner);
|
|
BEAST_EXPECT(owner && std::ranges::equal(*owner, env.master.id()));
|
|
|
|
auto const txType = hfs.getTxField(sfTransactionType);
|
|
BEAST_EXPECT(txType && *txType == toBytes(ttESCROW_FINISH));
|
|
|
|
auto const offerSeq = hfs.getTxField(sfOfferSequence);
|
|
BEAST_EXPECT(offerSeq && *offerSeq == toBytes(env.seq(env.master)));
|
|
|
|
auto const notPresent = hfs.getTxField(sfDestination);
|
|
if (BEAST_EXPECT(!notPresent.has_value()))
|
|
BEAST_EXPECT(
|
|
notPresent.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
auto const memos = hfs.getTxField(sfMemos);
|
|
if (BEAST_EXPECT(!memos.has_value()))
|
|
BEAST_EXPECT(
|
|
memos.error() == HostFunctionError::NOT_LEAF_FIELD);
|
|
|
|
auto const credentialIds = hfs.getTxField(sfCredentialIDs);
|
|
if (BEAST_EXPECT(!credentialIds.has_value()))
|
|
BEAST_EXPECTS(
|
|
credentialIds.error() == HostFunctionError::NOT_LEAF_FIELD,
|
|
std::to_string(HfErrorToInt(credentialIds.error())));
|
|
|
|
auto const nonField = hfs.getTxField(sfInvalid);
|
|
if (BEAST_EXPECT(!nonField.has_value()))
|
|
BEAST_EXPECT(
|
|
nonField.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
auto const nonField2 = hfs.getTxField(sfGeneric);
|
|
if (BEAST_EXPECT(!nonField2.has_value()))
|
|
BEAST_EXPECT(
|
|
nonField2.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
}
|
|
|
|
{
|
|
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);
|
|
WasmHostFunctionsImpl hfs(ac2, dummyEscrow);
|
|
|
|
auto const asset = hfs.getTxField(sfAsset);
|
|
std::vector<std::uint8_t> expectedAsset(20, 0);
|
|
BEAST_EXPECT(asset && *asset == expectedAsset);
|
|
|
|
auto const asset2 = hfs.getTxField(sfAsset2);
|
|
BEAST_EXPECT(asset2 && *asset2 == 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);
|
|
WasmHostFunctionsImpl hfs(ac2, dummyEscrow);
|
|
|
|
auto const asset = hfs.getTxField(sfAsset);
|
|
if (BEAST_EXPECT(asset.has_value()))
|
|
{
|
|
BEAST_EXPECT(*asset == toBytes(Asset(iouAsset)));
|
|
}
|
|
|
|
auto const asset2 = hfs.getTxField(sfAsset2);
|
|
if (BEAST_EXPECT(asset2.has_value()))
|
|
{
|
|
BEAST_EXPECT(*asset2 == 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);
|
|
WasmHostFunctionsImpl hfs(ac2, dummyEscrow);
|
|
|
|
auto const actualScale = hfs.getTxField(sfAssetScale);
|
|
if (BEAST_EXPECT(actualScale.has_value()))
|
|
{
|
|
BEAST_EXPECT(
|
|
std::ranges::equal(*actualScale, 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::finish_time(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));
|
|
|
|
WasmHostFunctionsImpl hfs(ac, escrowKeylet);
|
|
|
|
// Should return the Account field from the escrow ledger object
|
|
auto const account = hfs.getCurrentLedgerObjField(sfAccount);
|
|
if (BEAST_EXPECTS(
|
|
account.has_value(),
|
|
std::to_string(static_cast<int>(account.error()))))
|
|
BEAST_EXPECT(std::ranges::equal(*account, env.master.id()));
|
|
|
|
// Should return the Amount field from the escrow ledger object
|
|
auto const amountField = hfs.getCurrentLedgerObjField(sfAmount);
|
|
if (BEAST_EXPECT(amountField.has_value()))
|
|
{
|
|
BEAST_EXPECT(*amountField == toBytes(XRP(100)));
|
|
}
|
|
|
|
// Should return the PreviousTxnID field from the escrow ledger object
|
|
auto const previousTxnId =
|
|
hfs.getCurrentLedgerObjField(sfPreviousTxnID);
|
|
if (BEAST_EXPECT(previousTxnId.has_value()))
|
|
{
|
|
BEAST_EXPECT(
|
|
*previousTxnId == toBytes(env.tx()->getTransactionID()));
|
|
}
|
|
|
|
// Should return nullopt for a field not present
|
|
auto const notPresent = hfs.getCurrentLedgerObjField(sfOwner);
|
|
BEAST_EXPECT(
|
|
!notPresent.has_value() &&
|
|
notPresent.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
{
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master) + 5);
|
|
WasmHostFunctionsImpl hfs2(ac, dummyEscrow);
|
|
auto const account = hfs2.getCurrentLedgerObjField(sfAccount);
|
|
if (BEAST_EXPECT(!account.has_value()))
|
|
{
|
|
BEAST_EXPECT(
|
|
account.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND);
|
|
}
|
|
}
|
|
}
|
|
|
|
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::finish_time(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);
|
|
WasmHostFunctionsImpl hfs(ac, escrowKeylet);
|
|
|
|
// Cache the escrow ledger object in slot 1
|
|
auto cacheResult = hfs.cacheLedgerObj(accountKeylet.key, 1);
|
|
BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1);
|
|
|
|
// Should return the Account field from the cached ledger object
|
|
auto const account = hfs.getLedgerObjField(1, sfAccount);
|
|
if (BEAST_EXPECTS(
|
|
account.has_value(),
|
|
std::to_string(static_cast<int>(account.error()))))
|
|
BEAST_EXPECT(std::ranges::equal(*account, env.master.id()));
|
|
|
|
// Should return the Balance field from the cached ledger object
|
|
auto const balanceField = hfs.getLedgerObjField(1, sfBalance);
|
|
if (BEAST_EXPECT(balanceField.has_value()))
|
|
{
|
|
BEAST_EXPECT(*balanceField == toBytes(env.balance(env.master)));
|
|
}
|
|
|
|
// Should return error for slot out of range
|
|
auto const outOfRange = hfs.getLedgerObjField(0, sfAccount);
|
|
BEAST_EXPECT(
|
|
!outOfRange.has_value() &&
|
|
outOfRange.error() == HostFunctionError::SLOT_OUT_RANGE);
|
|
|
|
auto const tooHigh = hfs.getLedgerObjField(257, sfAccount);
|
|
BEAST_EXPECT(
|
|
!tooHigh.has_value() &&
|
|
tooHigh.error() == HostFunctionError::SLOT_OUT_RANGE);
|
|
|
|
// Should return error for empty slot
|
|
auto const emptySlot = hfs.getLedgerObjField(2, sfAccount);
|
|
BEAST_EXPECT(
|
|
!emptySlot.has_value() &&
|
|
emptySlot.error() == HostFunctionError::EMPTY_SLOT);
|
|
|
|
// Should return error for field not present
|
|
auto const notPresent = hfs.getLedgerObjField(1, sfOwner);
|
|
BEAST_EXPECT(
|
|
!notPresent.has_value() &&
|
|
notPresent.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
}
|
|
|
|
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.push_back(credId);
|
|
obj.setFieldV256(sfCredentialIDs, credIds);
|
|
});
|
|
|
|
ApplyContext ac = createApplyContext(env, ov, stx);
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
// Locator for sfMemos[0].sfMemo.sfMemoData
|
|
// Locator is a sequence of int32_t codes:
|
|
// [sfMemos.fieldCode, 0, sfMemoData.fieldCode]
|
|
std::vector<int32_t> locatorVec = {
|
|
sfMemos.fieldCode, 0, sfMemoData.fieldCode};
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
|
|
auto const result = hfs.getTxNestedField(locator);
|
|
if (BEAST_EXPECTS(
|
|
result.has_value(),
|
|
std::to_string(static_cast<int>(result.error()))))
|
|
{
|
|
std::string memoData(
|
|
result.value().begin(), result.value().end());
|
|
BEAST_EXPECT(memoData == "hello");
|
|
}
|
|
}
|
|
|
|
{
|
|
// Locator for sfCredentialIDs[0]
|
|
std::vector<int32_t> locatorVec = {sfCredentialIDs.fieldCode, 0};
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
|
|
auto const result = hfs.getTxNestedField(locator);
|
|
if (BEAST_EXPECTS(
|
|
result.has_value(),
|
|
std::to_string(static_cast<int>(result.error()))))
|
|
{
|
|
std::string credIdResult(
|
|
result.value().begin(), result.value().end());
|
|
BEAST_EXPECT(strHex(credIdResult) == credIdHex);
|
|
}
|
|
}
|
|
|
|
{
|
|
// can use the nested locator for base fields too
|
|
std::vector<int32_t> locatorVec = {sfAccount.fieldCode};
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
|
|
auto const account = hfs.getTxNestedField(locator);
|
|
if (BEAST_EXPECTS(
|
|
account.has_value(),
|
|
std::to_string(static_cast<int>(account.error()))))
|
|
{
|
|
BEAST_EXPECT(std::ranges::equal(*account, env.master.id()));
|
|
}
|
|
}
|
|
|
|
{
|
|
// unaligned locator
|
|
std::vector<uint8_t> locatorVec(sizeof(int32_t) + 1);
|
|
memcpy(
|
|
locatorVec.data() + 1, &sfAccount.fieldCode, sizeof(int32_t));
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data() + 1),
|
|
sizeof(int32_t));
|
|
|
|
auto const account = hfs.getTxNestedField(locator);
|
|
if (BEAST_EXPECTS(
|
|
account.has_value(),
|
|
std::to_string(static_cast<int>(account.error()))))
|
|
{
|
|
BEAST_EXPECT(std::ranges::equal(*account, env.master.id()));
|
|
}
|
|
}
|
|
|
|
auto expectError = [&](std::vector<int32_t> const& locatorVec,
|
|
HostFunctionError expectedError) {
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result = hfs.getTxNestedField(locator);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECTS(
|
|
result.error() == expectedError,
|
|
std::to_string(static_cast<int>(result.error())));
|
|
};
|
|
// Locator for non-existent base field
|
|
expectError(
|
|
{sfSigners.fieldCode, // sfSigners does not exist
|
|
0,
|
|
sfAccount.fieldCode},
|
|
HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
// Locator for non-existent index
|
|
expectError(
|
|
{sfMemos.fieldCode,
|
|
1, // index 1 does not exist
|
|
sfMemoData.fieldCode},
|
|
HostFunctionError::INDEX_OUT_OF_BOUNDS);
|
|
|
|
// Locator for non-existent index
|
|
expectError(
|
|
{sfCredentialIDs.fieldCode, 1}, // index 1 does not exist
|
|
HostFunctionError::INDEX_OUT_OF_BOUNDS);
|
|
|
|
// Locator for non-existent nested field
|
|
expectError(
|
|
{sfMemos.fieldCode,
|
|
0,
|
|
sfURI.fieldCode}, // sfURI does not exist in the memo
|
|
HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
// Locator for non-existent base sfield
|
|
expectError(
|
|
{field_code(20000, 20000), // nonexistent SField code
|
|
0,
|
|
sfAccount.fieldCode},
|
|
HostFunctionError::INVALID_FIELD);
|
|
|
|
// Locator for non-existent nested sfield
|
|
expectError(
|
|
{sfMemos.fieldCode, // nonexistent SField code
|
|
0,
|
|
field_code(20000, 20000)},
|
|
HostFunctionError::INVALID_FIELD);
|
|
|
|
// Locator for STArray
|
|
expectError({sfMemos.fieldCode}, HostFunctionError::NOT_LEAF_FIELD);
|
|
|
|
// Locator for STVector256
|
|
expectError(
|
|
{sfCredentialIDs.fieldCode}, HostFunctionError::NOT_LEAF_FIELD);
|
|
|
|
// Locator for nesting into non-array/object field
|
|
expectError(
|
|
{sfAccount.fieldCode, // sfAccount is not an array or object
|
|
0,
|
|
sfAccount.fieldCode},
|
|
HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
// Locator for empty locator
|
|
expectError({}, HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
// Locator for malformed locator (not multiple of 4)
|
|
{
|
|
std::vector<int32_t> locatorVec = {sfMemos.fieldCode};
|
|
Slice malformedLocator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()), 3);
|
|
auto const malformedResult = hfs.getTxNestedField(malformedLocator);
|
|
BEAST_EXPECT(
|
|
!malformedResult.has_value() &&
|
|
malformedResult.error() ==
|
|
HostFunctionError::LOCATOR_MALFORMED);
|
|
}
|
|
}
|
|
|
|
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));
|
|
|
|
WasmHostFunctionsImpl hfs(ac, signerKeylet);
|
|
|
|
// Locator for base field
|
|
std::vector<int32_t> baseLocator = {sfSignerQuorum.fieldCode};
|
|
Slice baseLocatorSlice(
|
|
reinterpret_cast<uint8_t const*>(baseLocator.data()),
|
|
baseLocator.size() * sizeof(int32_t));
|
|
auto const signerQuorum =
|
|
hfs.getCurrentLedgerObjNestedField(baseLocatorSlice);
|
|
if (BEAST_EXPECTS(
|
|
signerQuorum.has_value(),
|
|
std::to_string(static_cast<int>(signerQuorum.error()))))
|
|
{
|
|
BEAST_EXPECT(*signerQuorum == toBytes(static_cast<uint32_t>(2)));
|
|
}
|
|
|
|
auto expectError = [&](std::vector<int32_t> const& locatorVec,
|
|
HostFunctionError expectedError) {
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result = hfs.getCurrentLedgerObjNestedField(locator);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECTS(
|
|
result.error() == expectedError,
|
|
std::to_string(static_cast<int>(result.error())));
|
|
};
|
|
// Locator for non-existent base field
|
|
expectError(
|
|
{sfSigners.fieldCode, // sfSigners does not exist
|
|
0,
|
|
sfAccount.fieldCode},
|
|
HostFunctionError::FIELD_NOT_FOUND);
|
|
// Locator for nesting into non-array/object field
|
|
expectError(
|
|
{sfSignerQuorum
|
|
.fieldCode, // sfSignerQuorum is not an array or object
|
|
0,
|
|
sfAccount.fieldCode},
|
|
HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
// Locator for empty locator
|
|
Slice emptyLocator(nullptr, 0);
|
|
auto const emptyResult =
|
|
hfs.getCurrentLedgerObjNestedField(emptyLocator);
|
|
BEAST_EXPECT(
|
|
!emptyResult.has_value() &&
|
|
emptyResult.error() == HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
// Locator for malformed locator (not multiple of 4)
|
|
std::vector<int32_t> malformedLocatorVec = {sfMemos.fieldCode};
|
|
Slice malformedLocator(
|
|
reinterpret_cast<uint8_t const*>(malformedLocatorVec.data()), 3);
|
|
auto const malformedResult =
|
|
hfs.getCurrentLedgerObjNestedField(malformedLocator);
|
|
BEAST_EXPECT(
|
|
!malformedResult.has_value() &&
|
|
malformedResult.error() == HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
{
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master) + 5);
|
|
WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow);
|
|
std::vector<int32_t> const locatorVec = {sfAccount.fieldCode};
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result =
|
|
dummyHfs.getCurrentLedgerObjNestedField(locator);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECTS(
|
|
result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND,
|
|
std::to_string(static_cast<int>(result.error())));
|
|
}
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
// Cache the SignerList ledger object in slot 1
|
|
auto const signerListKeylet = keylet::signers(env.master.id());
|
|
auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1);
|
|
BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1);
|
|
|
|
// Locator for sfSignerEntries[0].sfAccount
|
|
{
|
|
std::vector<int32_t> const locatorVec = {
|
|
sfSignerEntries.fieldCode, 0, sfAccount.fieldCode};
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
|
|
auto const result = hfs.getLedgerObjNestedField(1, locator);
|
|
if (BEAST_EXPECTS(
|
|
result.has_value(),
|
|
std::to_string(static_cast<int>(result.error()))))
|
|
{
|
|
BEAST_EXPECT(std::ranges::equal(*result, alice.id()));
|
|
}
|
|
}
|
|
|
|
// Locator for sfSignerEntries[1].sfAccount
|
|
{
|
|
std::vector<int32_t> const locatorVec = {
|
|
sfSignerEntries.fieldCode, 1, sfAccount.fieldCode};
|
|
Slice const locator = Slice(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result2 = hfs.getLedgerObjNestedField(1, locator);
|
|
if (BEAST_EXPECTS(
|
|
result2.has_value(),
|
|
std::to_string(static_cast<int>(result2.error()))))
|
|
{
|
|
BEAST_EXPECT(std::ranges::equal(*result2, becky.id()));
|
|
}
|
|
}
|
|
|
|
// Locator for sfSignerEntries[0].sfSignerWeight
|
|
{
|
|
std::vector<int32_t> const locatorVec = {
|
|
sfSignerEntries.fieldCode, 0, sfSignerWeight.fieldCode};
|
|
Slice const locator = Slice(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const weightResult = hfs.getLedgerObjNestedField(1, locator);
|
|
if (BEAST_EXPECTS(
|
|
weightResult.has_value(),
|
|
std::to_string(static_cast<int>(weightResult.error()))))
|
|
{
|
|
// Should be 1
|
|
auto const expected = toBytes(static_cast<std::uint16_t>(1));
|
|
BEAST_EXPECT(*weightResult == expected);
|
|
}
|
|
}
|
|
|
|
// Locator for base field sfSignerQuorum
|
|
{
|
|
std::vector<int32_t> const locatorVec = {sfSignerQuorum.fieldCode};
|
|
Slice const locator = Slice(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const quorumResult = hfs.getLedgerObjNestedField(1, locator);
|
|
if (BEAST_EXPECTS(
|
|
quorumResult.has_value(),
|
|
std::to_string(static_cast<int>(quorumResult.error()))))
|
|
{
|
|
auto const expected = toBytes(static_cast<std::uint32_t>(2));
|
|
BEAST_EXPECT(*quorumResult == expected);
|
|
}
|
|
}
|
|
|
|
// Helper for error checks
|
|
auto expectError = [&](std::vector<int32_t> const& locatorVec,
|
|
HostFunctionError expectedError,
|
|
int slot = 1) {
|
|
Slice const locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result = hfs.getLedgerObjNestedField(slot, locator);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECTS(
|
|
result.error() == expectedError,
|
|
std::to_string(static_cast<int>(result.error())));
|
|
};
|
|
|
|
// Error: base field not found
|
|
expectError(
|
|
{sfSigners.fieldCode, // sfSigners does not exist
|
|
0,
|
|
sfAccount.fieldCode},
|
|
HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
// Error: index out of bounds
|
|
expectError(
|
|
{sfSignerEntries.fieldCode,
|
|
2, // index 2 does not exist
|
|
sfAccount.fieldCode},
|
|
HostFunctionError::INDEX_OUT_OF_BOUNDS);
|
|
|
|
// Error: nested field not found
|
|
expectError(
|
|
{
|
|
sfSignerEntries.fieldCode,
|
|
0,
|
|
sfDestination.fieldCode // sfDestination does not exist
|
|
},
|
|
HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
// Error: invalid field code
|
|
expectError(
|
|
{field_code(99999, 99999), 0, sfAccount.fieldCode},
|
|
HostFunctionError::INVALID_FIELD);
|
|
|
|
// Error: invalid nested field code
|
|
expectError(
|
|
{sfSignerEntries.fieldCode, 0, field_code(99999, 99999)},
|
|
HostFunctionError::INVALID_FIELD);
|
|
|
|
// Error: slot out of range
|
|
expectError(
|
|
{sfSignerQuorum.fieldCode}, HostFunctionError::SLOT_OUT_RANGE, 0);
|
|
expectError(
|
|
{sfSignerQuorum.fieldCode}, HostFunctionError::SLOT_OUT_RANGE, 257);
|
|
|
|
// Error: empty slot
|
|
expectError(
|
|
{sfSignerQuorum.fieldCode}, HostFunctionError::EMPTY_SLOT, 2);
|
|
|
|
// Error: locator for STArray (not leaf field)
|
|
expectError(
|
|
{sfSignerEntries.fieldCode}, HostFunctionError::NOT_LEAF_FIELD);
|
|
|
|
// Error: nesting into non-array/object field
|
|
expectError(
|
|
{sfSignerQuorum.fieldCode, 0, sfAccount.fieldCode},
|
|
HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
// Error: empty locator
|
|
expectError({}, HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
// Error: locator malformed (not multiple of 4)
|
|
std::vector<int32_t> const locatorVec = {sfSignerEntries.fieldCode};
|
|
Slice const locator =
|
|
Slice(reinterpret_cast<uint8_t const*>(locatorVec.data()), 3);
|
|
auto const malformed = hfs.getLedgerObjNestedField(1, locator);
|
|
BEAST_EXPECT(
|
|
!malformed.has_value() &&
|
|
malformed.error() == HostFunctionError::LOCATOR_MALFORMED);
|
|
}
|
|
|
|
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 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.push_back(credId);
|
|
obj.setFieldV256(sfCredentialIDs, credIds);
|
|
});
|
|
|
|
ApplyContext ac = createApplyContext(env, ov, stx);
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
// Should return 1 for sfMemos
|
|
auto const memosLen = hfs.getTxArrayLen(sfMemos);
|
|
if (BEAST_EXPECT(memosLen.has_value()))
|
|
BEAST_EXPECT(memosLen.value() == 2);
|
|
|
|
// Should return error for non-array field
|
|
auto const notArray = hfs.getTxArrayLen(sfAccount);
|
|
if (BEAST_EXPECT(!notArray.has_value()))
|
|
BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY);
|
|
|
|
// Should return error for missing array field
|
|
auto const missingArray = hfs.getTxArrayLen(sfSigners);
|
|
if (BEAST_EXPECT(!missingArray.has_value()))
|
|
BEAST_EXPECT(
|
|
missingArray.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
// Should return 1 for sfCredentialIDs
|
|
auto const credIdsLen = hfs.getTxArrayLen(sfCredentialIDs);
|
|
if (BEAST_EXPECT(credIdsLen.has_value()))
|
|
BEAST_EXPECT(credIdsLen.value() == 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());
|
|
WasmHostFunctionsImpl hfs(ac, signerKeylet);
|
|
|
|
auto const entriesLen =
|
|
hfs.getCurrentLedgerObjArrayLen(sfSignerEntries);
|
|
if (BEAST_EXPECT(entriesLen.has_value()))
|
|
BEAST_EXPECT(entriesLen.value() == 2);
|
|
|
|
auto const arrLen = hfs.getCurrentLedgerObjArrayLen(sfMemos);
|
|
if (BEAST_EXPECT(!arrLen.has_value()))
|
|
BEAST_EXPECT(arrLen.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
// Should return NO_ARRAY for non-array field
|
|
auto const notArray = hfs.getCurrentLedgerObjArrayLen(sfAccount);
|
|
if (BEAST_EXPECT(!notArray.has_value()))
|
|
BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY);
|
|
|
|
{
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master) + 5);
|
|
WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow);
|
|
auto const len = dummyHfs.getCurrentLedgerObjArrayLen(sfMemos);
|
|
if (BEAST_EXPECT(!len.has_value()))
|
|
BEAST_EXPECT(
|
|
len.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND);
|
|
}
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
auto const signerListKeylet = keylet::signers(env.master.id());
|
|
auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1);
|
|
BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1);
|
|
|
|
{
|
|
auto const arrLen = hfs.getLedgerObjArrayLen(1, sfSignerEntries);
|
|
if (BEAST_EXPECT(arrLen.has_value()))
|
|
// Should return 2 for sfSignerEntries
|
|
BEAST_EXPECT(arrLen.value() == 2);
|
|
}
|
|
{
|
|
auto const arrLen = hfs.getLedgerObjArrayLen(0, sfSignerEntries);
|
|
if (BEAST_EXPECT(!arrLen.has_value()))
|
|
BEAST_EXPECT(
|
|
arrLen.error() == HostFunctionError::SLOT_OUT_RANGE);
|
|
}
|
|
|
|
{
|
|
// Should return error for non-array field
|
|
auto const notArray = hfs.getLedgerObjArrayLen(1, sfAccount);
|
|
if (BEAST_EXPECT(!notArray.has_value()))
|
|
BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY);
|
|
}
|
|
|
|
{
|
|
// Should return error for empty slot
|
|
auto const emptySlot = hfs.getLedgerObjArrayLen(2, sfSignerEntries);
|
|
if (BEAST_EXPECT(!emptySlot.has_value()))
|
|
BEAST_EXPECT(
|
|
emptySlot.error() == HostFunctionError::EMPTY_SLOT);
|
|
}
|
|
|
|
{
|
|
// Should return error for missing array field
|
|
auto const missingArray = hfs.getLedgerObjArrayLen(1, sfMemos);
|
|
if (BEAST_EXPECT(!missingArray.has_value()))
|
|
BEAST_EXPECT(
|
|
missingArray.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
}
|
|
}
|
|
|
|
void
|
|
testGetTxNestedArrayLen()
|
|
{
|
|
testcase("getTxNestedArrayLen");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this};
|
|
OpenView ov{*env.current()};
|
|
|
|
STTx 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
// Helper for error checks
|
|
auto expectError = [&](std::vector<int32_t> const& locatorVec,
|
|
HostFunctionError expectedError,
|
|
int slot = 1) {
|
|
Slice const locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result = hfs.getTxNestedArrayLen(locator);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECTS(
|
|
result.error() == expectedError,
|
|
std::to_string(static_cast<int>(result.error())));
|
|
};
|
|
|
|
// Locator for sfMemos
|
|
{
|
|
std::vector<int32_t> locatorVec = {sfMemos.fieldCode};
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const arrLen = hfs.getTxNestedArrayLen(locator);
|
|
BEAST_EXPECT(arrLen.has_value() && arrLen.value() == 1);
|
|
}
|
|
|
|
// Error: non-array field
|
|
expectError({sfAccount.fieldCode}, HostFunctionError::NO_ARRAY);
|
|
|
|
// Error: missing field
|
|
expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND);
|
|
}
|
|
|
|
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());
|
|
WasmHostFunctionsImpl hfs(ac, signerKeylet);
|
|
|
|
// Helper for error checks
|
|
auto expectError = [&](std::vector<int32_t> const& locatorVec,
|
|
HostFunctionError expectedError,
|
|
int slot = 1) {
|
|
Slice const locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result = hfs.getCurrentLedgerObjNestedArrayLen(locator);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECTS(
|
|
result.error() == expectedError,
|
|
std::to_string(static_cast<int>(result.error())));
|
|
};
|
|
|
|
// Locator for sfSignerEntries
|
|
{
|
|
std::vector<int32_t> locatorVec = {sfSignerEntries.fieldCode};
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const arrLen = hfs.getCurrentLedgerObjNestedArrayLen(locator);
|
|
BEAST_EXPECT(arrLen.has_value() && arrLen.value() == 2);
|
|
}
|
|
|
|
// Error: non-array field
|
|
expectError({sfSignerQuorum.fieldCode}, HostFunctionError::NO_ARRAY);
|
|
|
|
// Error: missing field
|
|
expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
{
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master) + 5);
|
|
WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow);
|
|
std::vector<int32_t> locatorVec = {sfAccount.fieldCode};
|
|
Slice const locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result =
|
|
dummyHfs.getCurrentLedgerObjNestedArrayLen(locator);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECTS(
|
|
result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND,
|
|
std::to_string(static_cast<int>(result.error())));
|
|
}
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
auto const signerListKeylet = keylet::signers(env.master.id());
|
|
auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1);
|
|
BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1);
|
|
|
|
// Locator for sfSignerEntries
|
|
std::vector<int32_t> locatorVec = {sfSignerEntries.fieldCode};
|
|
Slice locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const arrLen = hfs.getLedgerObjNestedArrayLen(1, locator);
|
|
if (BEAST_EXPECT(arrLen.has_value()))
|
|
BEAST_EXPECT(arrLen.value() == 2);
|
|
|
|
// Helper for error checks
|
|
auto expectError = [&](std::vector<int32_t> const& locatorVec,
|
|
HostFunctionError expectedError,
|
|
int slot = 1) {
|
|
Slice const locator(
|
|
reinterpret_cast<uint8_t const*>(locatorVec.data()),
|
|
locatorVec.size() * sizeof(int32_t));
|
|
auto const result = hfs.getLedgerObjNestedArrayLen(slot, locator);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECTS(
|
|
result.error() == expectedError,
|
|
std::to_string(static_cast<int>(result.error())));
|
|
};
|
|
|
|
// Error: non-array field
|
|
expectError({sfSignerQuorum.fieldCode}, HostFunctionError::NO_ARRAY);
|
|
|
|
// Error: missing field
|
|
expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND);
|
|
|
|
// Slot out of range
|
|
expectError(locatorVec, HostFunctionError::SLOT_OUT_RANGE, 0);
|
|
expectError(locatorVec, HostFunctionError::SLOT_OUT_RANGE, 257);
|
|
|
|
// Empty slot
|
|
expectError(locatorVec, HostFunctionError::EMPTY_SLOT, 2);
|
|
|
|
// Error: empty locator
|
|
expectError({}, HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
// Error: locator malformed (not multiple of 4)
|
|
Slice malformedLocator(
|
|
reinterpret_cast<uint8_t const*>(locator.data()), 3);
|
|
auto const malformed =
|
|
hfs.getLedgerObjNestedArrayLen(1, malformedLocator);
|
|
BEAST_EXPECT(
|
|
!malformed.has_value() &&
|
|
malformed.error() == HostFunctionError::LOCATOR_MALFORMED);
|
|
|
|
// Error: locator for non-STArray field
|
|
expectError(
|
|
{sfSignerQuorum.fieldCode, 0, sfAccount.fieldCode},
|
|
HostFunctionError::LOCATOR_MALFORMED);
|
|
}
|
|
|
|
void
|
|
testUpdateData()
|
|
{
|
|
testcase("updateData");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this};
|
|
env(escrow::create(env.master, env.master, XRP(100)),
|
|
escrow::finish_time(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);
|
|
WasmHostFunctionsImpl hfs(ac, escrowKeylet);
|
|
|
|
// Should succeed for small data
|
|
std::vector<uint8_t> data(10, 0x42);
|
|
auto const result = hfs.updateData(Slice(data.data(), data.size()));
|
|
BEAST_EXPECT(result.has_value() && result.value() == data.size());
|
|
|
|
// Should fail for too large data
|
|
std::vector<uint8_t> bigData(maxWasmDataLength + 1, 0x42);
|
|
auto const tooBig =
|
|
hfs.updateData(Slice(bigData.data(), bigData.size()));
|
|
if (BEAST_EXPECT(!tooBig.has_value()))
|
|
BEAST_EXPECT(
|
|
tooBig.error() == HostFunctionError::DATA_FIELD_TOO_LARGE);
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
// 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
|
|
{
|
|
auto const result = hfs.checkSignature(
|
|
Slice(message.data(), message.size()),
|
|
Slice(sig.data(), sig.size()),
|
|
Slice(pk.data(), pk.size()));
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 1);
|
|
}
|
|
|
|
// Should fail for invalid signature
|
|
{
|
|
std::string badSig(sig.size(), 0xFF);
|
|
auto const result = hfs.checkSignature(
|
|
Slice(message.data(), message.size()),
|
|
Slice(badSig.data(), badSig.size()),
|
|
Slice(pk.data(), pk.size()));
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 0);
|
|
}
|
|
|
|
// Should fail for invalid public key
|
|
{
|
|
std::string badPk(pk.size(), 0x00);
|
|
auto const result = hfs.checkSignature(
|
|
Slice(message.data(), message.size()),
|
|
Slice(sig.data(), sig.size()),
|
|
Slice(badPk.data(), badPk.size()));
|
|
BEAST_EXPECT(!result.has_value());
|
|
BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS);
|
|
}
|
|
|
|
// Should fail for empty public key
|
|
{
|
|
auto const result = hfs.checkSignature(
|
|
Slice(message.data(), message.size()),
|
|
Slice(sig.data(), sig.size()),
|
|
Slice(nullptr, 0));
|
|
BEAST_EXPECT(!result.has_value());
|
|
BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS);
|
|
}
|
|
|
|
// Should fail for empty signature
|
|
{
|
|
auto const result = hfs.checkSignature(
|
|
Slice(message.data(), message.size()),
|
|
Slice(nullptr, 0),
|
|
Slice(pk.data(), pk.size()));
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 0);
|
|
}
|
|
|
|
// Should fail for empty message
|
|
{
|
|
auto const result = hfs.checkSignature(
|
|
Slice(nullptr, 0),
|
|
Slice(sig.data(), sig.size()),
|
|
Slice(pk.data(), pk.size()));
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string data = "hello world";
|
|
auto const result =
|
|
hfs.computeSha512HalfHash(Slice(data.data(), data.size()));
|
|
BEAST_EXPECT(result.has_value());
|
|
|
|
// Should match direct call to sha512Half
|
|
auto expected = sha512Half(Slice(data.data(), data.size()));
|
|
BEAST_EXPECT(result.value() == 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);
|
|
|
|
auto compareKeylet = [](std::vector<uint8_t> const& bytes,
|
|
Keylet const& kl) {
|
|
return std::ranges::equal(bytes, kl.key);
|
|
};
|
|
// Lambda to compare a Bytes (std::vector<uint8_t>) to a keylet
|
|
#define COMPARE_KEYLET(hfsFunc, keyletFunc, ...) \
|
|
{ \
|
|
auto actual = hfs.hfsFunc(__VA_ARGS__); \
|
|
auto expected = keyletFunc(__VA_ARGS__); \
|
|
if (BEAST_EXPECT(actual.has_value())) \
|
|
{ \
|
|
BEAST_EXPECT(compareKeylet(actual.value(), expected)); \
|
|
} \
|
|
}
|
|
#define COMPARE_KEYLET_FAIL(hfsFunc, expected, ...) \
|
|
{ \
|
|
auto actual = hfs.hfsFunc(__VA_ARGS__); \
|
|
if (BEAST_EXPECT(!actual.has_value())) \
|
|
{ \
|
|
BEAST_EXPECTS( \
|
|
actual.error() == expected, \
|
|
std::to_string(HfErrorToInt(actual.error()))); \
|
|
} \
|
|
}
|
|
|
|
COMPARE_KEYLET(accountKeylet, keylet::account, env.master.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
accountKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount());
|
|
|
|
COMPARE_KEYLET(
|
|
ammKeylet, keylet::amm, xrpIssue(), env.master["USD"].issue());
|
|
COMPARE_KEYLET_FAIL(
|
|
ammKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
xrpIssue(),
|
|
xrpIssue());
|
|
COMPARE_KEYLET_FAIL(
|
|
ammKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
makeMptID(1, env.master.id()),
|
|
xrpIssue());
|
|
|
|
COMPARE_KEYLET(checkKeylet, keylet::check, env.master.id(), 1);
|
|
COMPARE_KEYLET_FAIL(
|
|
checkKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1);
|
|
|
|
std::string const credType = "test";
|
|
COMPARE_KEYLET(
|
|
credentialKeylet,
|
|
keylet::credential,
|
|
env.master.id(),
|
|
env.master.id(),
|
|
Slice(credType.data(), credType.size()));
|
|
|
|
Account const alice("alice");
|
|
constexpr std::string_view longCredType =
|
|
"abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
|
|
"asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p";
|
|
static_assert(longCredType.size() > maxCredentialTypeLength);
|
|
COMPARE_KEYLET_FAIL(
|
|
credentialKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
env.master.id(),
|
|
alice.id(),
|
|
Slice(longCredType.data(), longCredType.size()));
|
|
COMPARE_KEYLET_FAIL(
|
|
credentialKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
xrpAccount(),
|
|
alice.id(),
|
|
Slice(credType.data(), credType.size()));
|
|
COMPARE_KEYLET_FAIL(
|
|
credentialKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
env.master.id(),
|
|
xrpAccount(),
|
|
Slice(credType.data(), credType.size()));
|
|
|
|
COMPARE_KEYLET(didKeylet, keylet::did, env.master.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
didKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount());
|
|
|
|
COMPARE_KEYLET(
|
|
delegateKeylet, keylet::delegate, env.master.id(), alice.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
delegateKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
env.master.id(),
|
|
env.master.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
delegateKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
env.master.id(),
|
|
xrpAccount());
|
|
COMPARE_KEYLET_FAIL(
|
|
delegateKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
xrpAccount(),
|
|
env.master.id());
|
|
|
|
COMPARE_KEYLET(
|
|
depositPreauthKeylet,
|
|
keylet::depositPreauth,
|
|
env.master.id(),
|
|
alice.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
depositPreauthKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
env.master.id(),
|
|
env.master.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
depositPreauthKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
env.master.id(),
|
|
xrpAccount());
|
|
COMPARE_KEYLET_FAIL(
|
|
depositPreauthKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
xrpAccount(),
|
|
env.master.id());
|
|
|
|
COMPARE_KEYLET(escrowKeylet, keylet::escrow, env.master.id(), 1);
|
|
COMPARE_KEYLET_FAIL(
|
|
escrowKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1);
|
|
|
|
Currency usd = to_currency("USD");
|
|
COMPARE_KEYLET(
|
|
lineKeylet, keylet::line, env.master.id(), alice.id(), usd);
|
|
COMPARE_KEYLET_FAIL(
|
|
lineKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
env.master.id(),
|
|
env.master.id(),
|
|
usd);
|
|
COMPARE_KEYLET_FAIL(
|
|
lineKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
env.master.id(),
|
|
xrpAccount(),
|
|
usd);
|
|
COMPARE_KEYLET_FAIL(
|
|
lineKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
xrpAccount(),
|
|
env.master.id(),
|
|
usd);
|
|
COMPARE_KEYLET_FAIL(
|
|
lineKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
env.master.id(),
|
|
alice.id(),
|
|
to_currency(""));
|
|
|
|
{
|
|
auto actual = hfs.mptIssuanceKeylet(env.master.id(), 1);
|
|
auto expected = keylet::mptIssuance(1, env.master.id());
|
|
if (BEAST_EXPECT(actual.has_value()))
|
|
{
|
|
BEAST_EXPECT(compareKeylet(actual.value(), expected));
|
|
}
|
|
}
|
|
{
|
|
auto actual = hfs.mptIssuanceKeylet(xrpAccount(), 1);
|
|
if (BEAST_EXPECT(!actual.has_value()))
|
|
BEAST_EXPECT(
|
|
actual.error() == HostFunctionError::INVALID_ACCOUNT);
|
|
}
|
|
|
|
auto const sampleMPTID = makeMptID(1, env.master.id());
|
|
COMPARE_KEYLET(mptokenKeylet, keylet::mptoken, sampleMPTID, alice.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
mptokenKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
MPTID{},
|
|
alice.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
mptokenKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
sampleMPTID,
|
|
xrpAccount());
|
|
|
|
COMPARE_KEYLET(nftOfferKeylet, keylet::nftoffer, env.master.id(), 1);
|
|
COMPARE_KEYLET_FAIL(
|
|
nftOfferKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
xrpAccount(),
|
|
1);
|
|
|
|
COMPARE_KEYLET(offerKeylet, keylet::offer, env.master.id(), 1);
|
|
COMPARE_KEYLET_FAIL(
|
|
offerKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1);
|
|
|
|
COMPARE_KEYLET(oracleKeylet, keylet::oracle, env.master.id(), 1);
|
|
COMPARE_KEYLET_FAIL(
|
|
oracleKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1);
|
|
|
|
COMPARE_KEYLET(
|
|
paychanKeylet, keylet::payChan, env.master.id(), alice.id(), 1);
|
|
COMPARE_KEYLET_FAIL(
|
|
paychanKeylet,
|
|
HostFunctionError::INVALID_PARAMS,
|
|
env.master.id(),
|
|
env.master.id(),
|
|
1);
|
|
COMPARE_KEYLET_FAIL(
|
|
paychanKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
env.master.id(),
|
|
xrpAccount(),
|
|
1);
|
|
COMPARE_KEYLET_FAIL(
|
|
paychanKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
xrpAccount(),
|
|
env.master.id(),
|
|
1);
|
|
|
|
COMPARE_KEYLET(
|
|
permissionedDomainKeylet,
|
|
keylet::permissionedDomain,
|
|
env.master.id(),
|
|
1);
|
|
COMPARE_KEYLET_FAIL(
|
|
permissionedDomainKeylet,
|
|
HostFunctionError::INVALID_ACCOUNT,
|
|
xrpAccount(),
|
|
1);
|
|
|
|
COMPARE_KEYLET(signersKeylet, keylet::signers, env.master.id());
|
|
COMPARE_KEYLET_FAIL(
|
|
signersKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount());
|
|
|
|
COMPARE_KEYLET(ticketKeylet, keylet::ticket, env.master.id(), 1);
|
|
COMPARE_KEYLET_FAIL(
|
|
ticketKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1);
|
|
|
|
COMPARE_KEYLET(vaultKeylet, keylet::vault, env.master.id(), 1);
|
|
COMPARE_KEYLET_FAIL(
|
|
vaultKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1);
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
// Should succeed for valid NFT
|
|
{
|
|
auto const result = hfs.getNFT(alice.id(), nftId);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(std::ranges::equal(*result, uri));
|
|
}
|
|
|
|
// Should fail for invalid account
|
|
{
|
|
auto const result = hfs.getNFT(xrpAccount(), nftId);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::INVALID_ACCOUNT);
|
|
}
|
|
|
|
// Should fail for invalid nftId
|
|
{
|
|
auto const result = hfs.getNFT(alice.id(), uint256());
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::INVALID_PARAMS);
|
|
}
|
|
|
|
// Should fail for invalid nftId
|
|
{
|
|
auto const badId = token::getNextID(env, alice, 0u, 1u);
|
|
auto const result = hfs.getNFT(alice.id(), badId);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.getNFT(alice.id(), nftId2);
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FIELD_NOT_FOUND);
|
|
}
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
// Should succeed for valid NFT id
|
|
{
|
|
auto const result = hfs.getNFTIssuer(nftId);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(std::ranges::equal(*result, env.master.id()));
|
|
}
|
|
|
|
// Should fail for zero NFT id
|
|
{
|
|
auto const result = hfs.getNFTIssuer(uint256());
|
|
if (BEAST_EXPECT(!result.has_value()))
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::INVALID_PARAMS);
|
|
}
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
auto const result = hfs.getNFTTaxon(nftId);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.getNFTFlags(nftId);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == tfTransferable);
|
|
}
|
|
|
|
// Should return 0 for zero NFT id
|
|
{
|
|
auto const result = hfs.getNFTFlags(uint256());
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.getNFTTransferFee(nftId);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == transferFee);
|
|
}
|
|
|
|
// Should return 0 for zero NFT id
|
|
{
|
|
auto const result = hfs.getNFTTransferFee(uint256());
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.getNFTSerial(nftId);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == serial);
|
|
}
|
|
|
|
// Should return 0 for zero NFT id
|
|
{
|
|
auto const result = hfs.getNFTSerial(uint256());
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(result.value() == 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
testTrace()
|
|
{
|
|
testcase("trace");
|
|
using namespace test::jtx;
|
|
|
|
{
|
|
Env env(*this);
|
|
OpenView ov{*env.current()};
|
|
test::StreamSink sink{beast::severities::kTrace};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string msg = "test trace";
|
|
std::string data = "abc";
|
|
auto const slice = Slice(data.data(), data.size());
|
|
auto const result = hfs.trace(msg, slice, false);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
{
|
|
BEAST_EXPECT(result.value() == msg.size() + data.size());
|
|
auto const messages = sink.messages().str();
|
|
BEAST_EXPECT(messages.find(msg) != std::string::npos);
|
|
}
|
|
|
|
auto const resultHex = hfs.trace(msg, slice, true);
|
|
if (BEAST_EXPECT(resultHex.has_value()))
|
|
{
|
|
BEAST_EXPECT(resultHex.has_value());
|
|
BEAST_EXPECT(resultHex.value() == msg.size() + data.size() * 2);
|
|
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::severities::kError};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string msg = "test trace";
|
|
std::string data = "abc";
|
|
auto const slice = Slice(data.data(), data.size());
|
|
auto const result = hfs.trace(msg, slice, false);
|
|
BEAST_EXPECT(result && *result == msg.size() + data.size());
|
|
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::severities::kTrace};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string msg = "trace number";
|
|
int64_t num = 123456789;
|
|
auto const result = hfs.traceNum(msg, num);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
{
|
|
BEAST_EXPECT(result.value() == msg.size() + sizeof(num));
|
|
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::severities::kError};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string msg = "trace number";
|
|
int64_t num = 123456789;
|
|
auto const result = hfs.traceNum(msg, num);
|
|
BEAST_EXPECT(result && *result == msg.size() + sizeof(int64_t));
|
|
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::severities::kTrace};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string msg = "trace account";
|
|
auto const result = hfs.traceAccount(msg, env.master.id());
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
{
|
|
BEAST_EXPECT(
|
|
result.value() == msg.size() + env.master.id().size());
|
|
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::severities::kError};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
std::string msg = "trace account";
|
|
auto const result = hfs.traceAccount(msg, env.master.id());
|
|
BEAST_EXPECT(
|
|
result && *result == msg.size() + env.master.id().size());
|
|
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::severities::kTrace};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string msg = "trace amount";
|
|
STAmount amount = XRP(12345);
|
|
{
|
|
auto const result = hfs.traceAmount(msg, amount);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
{
|
|
BEAST_EXPECT(*result == msg.size());
|
|
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 iouAmount = env.master["USD"](100);
|
|
{
|
|
auto const result = hfs.traceAmount(msg, iouAmount);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(*result == msg.size());
|
|
}
|
|
|
|
// MPT amount
|
|
{
|
|
auto const mptId = makeMptID(42, env.master.id());
|
|
Asset mptAsset = Asset(mptId);
|
|
STAmount mptAmount(mptAsset, 123456);
|
|
auto const result = hfs.traceAmount(msg, mptAmount);
|
|
if (BEAST_EXPECT(result.has_value()))
|
|
BEAST_EXPECT(*result == msg.size());
|
|
}
|
|
}
|
|
|
|
{
|
|
// logs disabled
|
|
Env env(*this);
|
|
OpenView ov{*env.current()};
|
|
test::StreamSink sink{beast::severities::kError};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string msg = "trace amount";
|
|
STAmount amount = XRP(12345);
|
|
auto const result = hfs.traceAmount(msg, amount);
|
|
BEAST_EXPECT(result && *result == msg.size());
|
|
auto const messages = sink.messages().str();
|
|
BEAST_EXPECT(messages.empty());
|
|
}
|
|
}
|
|
|
|
// clang-format off
|
|
|
|
int const normalExp = 15;
|
|
|
|
Bytes const floatIntMin = {0x99, 0x20, 0xc4, 0x9b, 0xa5, 0xe3, 0x53, 0xf8}; // -2^63
|
|
Bytes const floatIntZero = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 0
|
|
Bytes const floatIntMax = {0xd9, 0x20, 0xc4, 0x9b, 0xa5, 0xe3, 0x53, 0xf8}; // 2^63-1
|
|
Bytes const floatUIntMax = {0xd9, 0x46, 0x8d, 0xb8, 0xba, 0xc7, 0x10, 0xcb}; // 2^64-1
|
|
Bytes const floatMaxExp = {0xEC, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e(80+15)
|
|
Bytes const floatPreMaxExp = {0xEC, 0x03, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e(79+15)
|
|
Bytes const floatMinusMaxExp = {0xAC, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // -1e(80+15)
|
|
Bytes const floatMaxIOU = {0xEC, 0x63, 0x86, 0xF2, 0x6F, 0xC0, 0xFF, 0xFF}; // 1e(81+15)-1
|
|
Bytes const floatMinExp = {0xC0, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e-96
|
|
Bytes const float1 = {0xD4, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1
|
|
Bytes const floatMinus1 = {0x94, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // -1
|
|
Bytes const float1More = {0xD4, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x01}; // 1.000 000 000 000 001
|
|
Bytes const float2 = {0xD4, 0x87, 0x1A, 0xFD, 0x49, 0x8D, 0x00, 0x00}; // 2
|
|
Bytes const float10 = {0xD4, 0xC3, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 10
|
|
Bytes const floatInvalidZero = {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // INVALID
|
|
Bytes const floatPi = {0xD4, 0x8B, 0x29, 0x43, 0x0A, 0x25, 0x6D, 0x21}; // 3.141592653589793
|
|
|
|
std::string const invalid = "invalid_data";
|
|
|
|
// clang-format on
|
|
|
|
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);
|
|
|
|
std::string msg = "trace float";
|
|
|
|
{
|
|
auto const result = hfs.traceFloat(msg, makeSlice(invalid));
|
|
BEAST_EXPECT(
|
|
result &&
|
|
*result == msg.size() + makeSlice(invalid).size());
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.traceFloat(msg, makeSlice(floatMaxExp));
|
|
BEAST_EXPECT(
|
|
result &&
|
|
*result == msg.size() + makeSlice(floatMaxExp).size());
|
|
}
|
|
}
|
|
|
|
{
|
|
// logs disabled
|
|
Env env(*this);
|
|
OpenView ov{*env.current()};
|
|
test::StreamSink sink{beast::severities::kError};
|
|
beast::Journal jlog{sink};
|
|
ApplyContext ac = createApplyContext(env, ov, jlog);
|
|
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
std::string msg = "trace float";
|
|
|
|
auto const result = hfs.traceFloat(msg, makeSlice(invalid));
|
|
BEAST_EXPECT(
|
|
result && *result == msg.size() + makeSlice(invalid).size());
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatFromInt(std::numeric_limits<int64_t>::min(), -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatFromInt(std::numeric_limits<int64_t>::min(), 4);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatFromInt(std::numeric_limits<int64_t>::min(), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatFromInt(0, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatFromInt(std::numeric_limits<int64_t>::max(), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatFromUint(std::numeric_limits<uint64_t>::min(), -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatFromUint(std::numeric_limits<uint64_t>::min(), 4);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatFromUint(0, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatFromUint(std::numeric_limits<uint64_t>::max(), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatUIntMax);
|
|
}
|
|
}
|
|
|
|
void
|
|
testFloatSet()
|
|
{
|
|
testcase("floatSet");
|
|
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);
|
|
|
|
{
|
|
auto const result = hfs.floatSet(1, 0, -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatSet(1, 0, 4);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSet(1, wasm_float::maxExponent + normalExp + 1, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSet(1, wasm_float::maxExponent + normalExp + 1, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSet(1, wasm_float::minExponent + normalExp - 1, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSet(1, wasm_float::maxExponent + normalExp, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxExp);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSet(-1, wasm_float::maxExponent + normalExp, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinusMaxExp);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSet(1, wasm_float::maxExponent + normalExp - 1, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatPreMaxExp);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSet(STAmount::cMaxValue, wasm_float::maxExponent, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSet(1, wasm_float::minExponent + normalExp, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinExp);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatSet(10, -1, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1);
|
|
}
|
|
}
|
|
|
|
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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.floatCompare(Slice(), Slice());
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatCompare(makeSlice(floatInvalidZero), Slice());
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatCompare(makeSlice(float1), makeSlice(invalid));
|
|
BEAST_EXPECT(
|
|
!result &&
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto x = floatMaxExp;
|
|
// exp = 81 + 97 = 178
|
|
x[1] |= 0x80;
|
|
x[1] &= 0xBF;
|
|
auto const result =
|
|
hfs.floatCompare(makeSlice(x), makeSlice(floatMaxExp));
|
|
BEAST_EXPECT(
|
|
!result &&
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatCompare(
|
|
makeSlice(floatIntMin), makeSlice(floatIntZero));
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == 2);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatCompare(
|
|
makeSlice(floatIntMax), makeSlice(floatIntZero));
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == 1);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatCompare(makeSlice(float1), makeSlice(float1));
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.floatAdd(Slice(), Slice(), -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatAdd(Slice(), Slice(), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatAdd(makeSlice(float1), makeSlice(invalid), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatAdd(makeSlice(floatMaxIOU), makeSlice(floatMaxExp), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatAdd(
|
|
makeSlice(floatIntMin), makeSlice(floatIntZero), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatAdd(makeSlice(floatIntMax), makeSlice(floatIntMin), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero);
|
|
}
|
|
}
|
|
|
|
void
|
|
testFloatSubtract()
|
|
{
|
|
testcase("floatSubtract");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this};
|
|
OpenView ov{*env.current()};
|
|
ApplyContext ac = createApplyContext(env, ov);
|
|
auto const dummyEscrow =
|
|
keylet::escrow(env.master, env.seq(env.master));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.floatSubtract(Slice(), Slice(), -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatSubtract(Slice(), Slice(), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatSubtract(makeSlice(float1), makeSlice(invalid), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatSubtract(
|
|
makeSlice(floatMaxIOU), makeSlice(floatMinusMaxExp), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatSubtract(
|
|
makeSlice(floatIntMin), makeSlice(floatIntZero), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatSubtract(
|
|
makeSlice(floatIntZero), makeSlice(float1), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.floatMultiply(Slice(), Slice(), -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatMultiply(Slice(), Slice(), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatMultiply(makeSlice(float1), makeSlice(invalid), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatMultiply(
|
|
makeSlice(floatMaxIOU), makeSlice(float1More), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatMultiply(makeSlice(float1), makeSlice(float1), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatMultiply(
|
|
makeSlice(floatIntZero), makeSlice(floatMaxIOU), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatMultiply(
|
|
makeSlice(float10), makeSlice(floatPreMaxExp), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.floatDivide(Slice(), Slice(), -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatDivide(Slice(), Slice(), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatDivide(makeSlice(float1), makeSlice(invalid), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatDivide(makeSlice(float1), makeSlice(floatIntZero), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
|
|
{
|
|
auto const y = hfs.floatSet(
|
|
STAmount::cMaxValue, -normalExp - 1, 0); // 0.9999999...
|
|
if (BEAST_EXPECT(y))
|
|
{
|
|
auto const result =
|
|
hfs.floatDivide(makeSlice(floatMaxIOU), makeSlice(*y), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatDivide(makeSlice(floatIntZero), makeSlice(float1), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatDivide(makeSlice(floatMaxExp), makeSlice(float10), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == 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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.floatRoot(Slice(), 2, -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatRoot(makeSlice(invalid), 3, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatRoot(makeSlice(float1), -2, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatRoot(makeSlice(floatIntZero), 2, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatRoot(makeSlice(floatMaxIOU), 1, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU);
|
|
}
|
|
|
|
{
|
|
auto const x = hfs.floatSet(100, 0, 0); // 100
|
|
if (BEAST_EXPECT(x))
|
|
{
|
|
auto const result = hfs.floatRoot(makeSlice(*x), 2, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == float10);
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const x = hfs.floatSet(1000, 0, 0); // 1000
|
|
if (BEAST_EXPECT(x))
|
|
{
|
|
auto const result = hfs.floatRoot(makeSlice(*x), 3, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == float10);
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const x = hfs.floatSet(1, -2, 0); // 0.01
|
|
auto const y = hfs.floatSet(1, -1, 0); // 0.1
|
|
if (BEAST_EXPECT(x && y))
|
|
{
|
|
auto const result = hfs.floatRoot(makeSlice(*x), 2, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == *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));
|
|
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
|
|
|
{
|
|
auto const result = hfs.floatPower(Slice(), 2, -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(invalid), 3, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(float1), -2, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 2, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 81, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 2, 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() ==
|
|
HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 0, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 1, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU);
|
|
}
|
|
|
|
{
|
|
auto const x = hfs.floatSet(100, 0, 0); // 100
|
|
if (BEAST_EXPECT(x))
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(float10), 2, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == *x);
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const x = hfs.floatSet(1, -1, 0); // 0.1
|
|
auto const y = hfs.floatSet(1, -2, 0); // 0.01
|
|
if (BEAST_EXPECT(x && y))
|
|
{
|
|
auto const result = hfs.floatPower(makeSlice(*x), 2, 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testFloatLog()
|
|
{
|
|
testcase("floatLog");
|
|
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);
|
|
|
|
{
|
|
auto const result = hfs.floatLog(Slice(), -1);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result = hfs.floatLog(makeSlice(invalid), 0);
|
|
BEAST_EXPECT(!result) &&
|
|
BEAST_EXPECT(
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
// perf test logs
|
|
// {
|
|
// auto const result = hfs.floatLog(makeSlice(floatPi), 0);
|
|
// if (BEAST_EXPECT(result))
|
|
// {
|
|
// std::cout << "lg(" << floatToString(makeSlice(floatPi))
|
|
// << ") = " << floatToString(makeSlice(*result))
|
|
// << std::endl;
|
|
// }
|
|
// }
|
|
// {
|
|
// auto const result = hfs.floatLog(makeSlice(floatIntMax), 0);
|
|
// if (BEAST_EXPECT(result))
|
|
// {
|
|
// std::cout << "lg(" << floatToString(makeSlice(floatIntMax))
|
|
// << ") = " << floatToString(makeSlice(*result))
|
|
// << std::endl;
|
|
// }
|
|
// }
|
|
|
|
// {
|
|
// auto const result = hfs.floatLog(makeSlice(floatMaxExp), 0);
|
|
// if (BEAST_EXPECT(result))
|
|
// {
|
|
// std::cout << "lg(" << floatToString(makeSlice(floatMaxExp))
|
|
// << ") = " << floatToString(makeSlice(*result))
|
|
// << std::endl;
|
|
// }
|
|
// }
|
|
|
|
// {
|
|
// auto const result = hfs.floatLog(makeSlice(floatMaxIOU), 0);
|
|
// if (BEAST_EXPECT(result))
|
|
// {
|
|
// std::cout << "lg(" << floatToString(makeSlice(floatMaxIOU))
|
|
// << ") = " << floatToString(makeSlice(*result))
|
|
// << std::endl;
|
|
// }
|
|
// }
|
|
|
|
{
|
|
auto const x =
|
|
hfs.floatSet(9'500'000'000'000'001, -14, 0); // almost 80+15
|
|
if (BEAST_EXPECT(x))
|
|
{
|
|
auto const result = hfs.floatLog(makeSlice(floatMaxExp), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == *x);
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const x = hfs.floatSet(100, 0, 0); // 100
|
|
if (BEAST_EXPECT(x))
|
|
{
|
|
auto const result = hfs.floatLog(makeSlice(*x), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == float2);
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const x = hfs.floatSet(1000, 0, 0); // 1000
|
|
auto const y = hfs.floatSet(3, 0, 0); // 0.1
|
|
if (BEAST_EXPECT(x && y))
|
|
{
|
|
auto const result = hfs.floatLog(makeSlice(*x), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y);
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const x = hfs.floatSet(1, -2, 0); // 0.01
|
|
auto const y =
|
|
hfs.floatSet(-1999999993734431, -15, 0); // almost -2
|
|
if (BEAST_EXPECT(x && y))
|
|
{
|
|
auto const result = hfs.floatLog(makeSlice(*x), 0);
|
|
BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testFloatNonIOU()
|
|
{
|
|
testcase("float Xrp+Mpt");
|
|
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);
|
|
|
|
auto const y = hfs.floatSet(20, 0, 0);
|
|
if (!BEAST_EXPECT(y))
|
|
return;
|
|
|
|
Bytes x(8);
|
|
|
|
// XRP
|
|
memset(x.data(), 0, x.size());
|
|
x[0] = 0x40;
|
|
x[7] = 10;
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatCompare(makeSlice(x), makeSlice(float10));
|
|
BEAST_EXPECT(
|
|
!result &&
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatAdd(makeSlice(float10), makeSlice(x), 0);
|
|
BEAST_EXPECT(
|
|
!result &&
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
// MPT
|
|
memset(x.data(), 0, x.size());
|
|
x[0] = 0x60;
|
|
x[7] = 10;
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatCompare(makeSlice(x), makeSlice(float10));
|
|
BEAST_EXPECT(
|
|
!result &&
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
|
|
{
|
|
auto const result =
|
|
hfs.floatAdd(makeSlice(float10), makeSlice(x), 0);
|
|
BEAST_EXPECT(
|
|
!result &&
|
|
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
|
|
}
|
|
}
|
|
|
|
void
|
|
testFloats()
|
|
{
|
|
testTraceFloat();
|
|
testFloatFromInt();
|
|
testFloatFromUint();
|
|
testFloatSet();
|
|
testFloatCompare();
|
|
testFloatAdd();
|
|
testFloatSubtract();
|
|
testFloatMultiply();
|
|
testFloatDivide();
|
|
testFloatRoot();
|
|
testFloatPower();
|
|
testFloatLog();
|
|
testFloatNonIOU();
|
|
}
|
|
|
|
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();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, xrpl);
|
|
|
|
} // namespace test
|
|
} // namespace xrpl
|