//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2025 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #include #include namespace ripple { namespace test { static Bytes toBytes(std::uint16_t value) { auto const* b = reinterpret_cast(&value); auto const* e = reinterpret_cast(&value + 1); return Bytes{b, e}; } static Bytes toBytes(std::uint32_t value) { auto const* b = reinterpret_cast(&value); auto const* e = reinterpret_cast(&value + 1); return Bytes{b, e}; } static Bytes toBytes(Asset const& asset) { if (asset.holds()) { Serializer s; auto const& issue = asset.get(); s.addBitString(issue.currency); if (!isXRP(issue.currency)) s.addBitString(issue.account); auto const data = s.getData(); return data; } auto const& mptIssue = asset.get(); 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, STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) { ApplyContext ac{ env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, tapNONE, env.journal}; return ac; } 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()->info().seq); } void testGetParentLedgerTime() { testcase("getParentLedgerTime"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); 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()); } 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()->info().parentHash); } void testGetLedgerAccountHash() { testcase("getLedgerAccountHash"); 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.getLedgerAccountHash(); if (BEAST_EXPECT(result.has_value())) BEAST_EXPECT(result.value() == env.current()->info().accountHash); } void testGetLedgerTransactionHash() { testcase("getLedgerTransactionHash"); 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.getLedgerTransactionHash(); if (BEAST_EXPECT(result.has_value())) BEAST_EXPECT(result.value() == env.current()->info().txHash); } 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()); } 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 featureSmartEscrow for testing auto const amendmentId = featureSmartEscrow; // 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 = "SmartEscrow"; { 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; 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.setFieldU32(sfComputationAllowance, 1000); obj.setFieldArray(sfMemos, STArray{}); }); 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 compAllowance = hfs.getTxField(sfComputationAllowance); std::uint32_t const expectedAllowance = 1000; BEAST_EXPECT( compAllowance && *compAllowance == toBytes(expectedAllowance)); 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 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 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))); } } } 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(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 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(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()}; // 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); }); 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 locatorVec = { sfMemos.fieldCode, 0, sfMemoData.fieldCode}; Slice locator( reinterpret_cast(locatorVec.data()), locatorVec.size() * sizeof(int32_t)); auto const result = hfs.getTxNestedField(locator); if (BEAST_EXPECTS( result.has_value(), std::to_string(static_cast(result.error())))) { std::string memoData( result.value().begin(), result.value().end()); BEAST_EXPECT(memoData == "hello"); } } { // can use the nested locator for base fields too std::vector locatorVec = {sfAccount.fieldCode}; Slice locator( reinterpret_cast(locatorVec.data()), locatorVec.size() * sizeof(int32_t)); auto const account = hfs.getTxNestedField(locator); if (BEAST_EXPECTS( account.has_value(), std::to_string(static_cast(account.error())))) { BEAST_EXPECT(std::ranges::equal(*account, env.master.id())); } } auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError) { Slice locator( reinterpret_cast(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(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 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 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 locatorVec = {sfMemos.fieldCode}; Slice malformedLocator( reinterpret_cast(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 baseLocator = {sfSignerQuorum.fieldCode}; Slice baseLocatorSlice( reinterpret_cast(baseLocator.data()), baseLocator.size() * sizeof(int32_t)); auto const signerQuorum = hfs.getCurrentLedgerObjNestedField(baseLocatorSlice); if (BEAST_EXPECTS( signerQuorum.has_value(), std::to_string(static_cast(signerQuorum.error())))) { BEAST_EXPECT(*signerQuorum == toBytes(static_cast(2))); } auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError) { Slice locator( reinterpret_cast(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(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 malformedLocatorVec = {sfMemos.fieldCode}; Slice malformedLocator( reinterpret_cast(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 const locatorVec = {sfAccount.fieldCode}; Slice locator( reinterpret_cast(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(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 const locatorVec = { sfSignerEntries.fieldCode, 0, sfAccount.fieldCode}; Slice locator( reinterpret_cast(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(result.error())))) { BEAST_EXPECT(std::ranges::equal(*result, alice.id())); } } // Locator for sfSignerEntries[1].sfAccount { std::vector const locatorVec = { sfSignerEntries.fieldCode, 1, sfAccount.fieldCode}; Slice const locator = Slice( reinterpret_cast(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(result2.error())))) { BEAST_EXPECT(std::ranges::equal(*result2, becky.id())); } } // Locator for sfSignerEntries[0].sfSignerWeight { std::vector const locatorVec = { sfSignerEntries.fieldCode, 0, sfSignerWeight.fieldCode}; Slice const locator = Slice( reinterpret_cast(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(weightResult.error())))) { // Should be 1 auto const expected = toBytes(static_cast(1)); BEAST_EXPECT(*weightResult == expected); } } // Locator for base field sfSignerQuorum { std::vector const locatorVec = {sfSignerQuorum.fieldCode}; Slice const locator = Slice( reinterpret_cast(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(quorumResult.error())))) { auto const expected = toBytes(static_cast(2)); BEAST_EXPECT(*quorumResult == expected); } } // Helper for error checks auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError, int slot = 1) { Slice const locator( reinterpret_cast(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(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 const locatorVec = {sfSignerEntries.fieldCode}; Slice const locator = Slice(reinterpret_cast(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; 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); }); 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); } 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 const& locatorVec, HostFunctionError expectedError, int slot = 1) { Slice const locator( reinterpret_cast(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(result.error()))); }; // Locator for sfMemos { std::vector locatorVec = {sfMemos.fieldCode}; Slice locator( reinterpret_cast(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 const& locatorVec, HostFunctionError expectedError, int slot = 1) { Slice const locator( reinterpret_cast(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(result.error()))); }; // Locator for sfSignerEntries { std::vector locatorVec = {sfSignerEntries.fieldCode}; Slice locator( reinterpret_cast(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 locatorVec = {sfAccount.fieldCode}; Slice const locator( reinterpret_cast(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(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 locatorVec = {sfSignerEntries.fieldCode}; Slice locator( reinterpret_cast(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 const& locatorVec, HostFunctionError expectedError, int slot = 1) { Slice const locator( reinterpret_cast(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(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(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 data(10, 0x42); auto const result = hfs.updateData(Slice(data.data(), data.size())); BEAST_EXPECT(result.has_value() && result.value() == 0); // Should fail for too large data std::vector bigData( 1024 * 1024 + 1, 0x42); // > maxWasmDataLength 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 const& bytes, Keylet const& kl) { return std::ranges::equal(bytes, kl.key); }; // Lambda to compare a Bytes (std::vector) 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, keyletFunc, expected, ...) \ { \ auto actual = hfs.hfsFunc(__VA_ARGS__); \ if (BEAST_EXPECT(!actual.has_value())) \ { \ BEAST_EXPECTS( \ actual.error() == expected, \ std::to_string(static_cast(actual.error()))); \ } \ } // accountKeylet COMPARE_KEYLET(accountKeylet, keylet::account, env.master.id()); COMPARE_KEYLET_FAIL( accountKeylet, keylet::account, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); COMPARE_KEYLET(checkKeylet, keylet::check, env.master.id(), 1); COMPARE_KEYLET_FAIL( checkKeylet, keylet::check, 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, keylet::credential, HostFunctionError::INVALID_PARAMS, env.master.id(), alice.id(), Slice(longCredType.data(), longCredType.size())); COMPARE_KEYLET_FAIL( credentialKeylet, keylet::credential, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), alice.id(), Slice(credType.data(), credType.size())); COMPARE_KEYLET_FAIL( credentialKeylet, keylet::credential, HostFunctionError::INVALID_ACCOUNT, env.master.id(), xrpAccount(), Slice(credType.data(), credType.size())); COMPARE_KEYLET(didKeylet, keylet::did, env.master.id()); COMPARE_KEYLET_FAIL( didKeylet, keylet::did, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); COMPARE_KEYLET( delegateKeylet, keylet::delegate, env.master.id(), alice.id()); COMPARE_KEYLET_FAIL( delegateKeylet, keylet::delegate, HostFunctionError::INVALID_PARAMS, env.master.id(), env.master.id()); COMPARE_KEYLET_FAIL( delegateKeylet, keylet::delegate, HostFunctionError::INVALID_ACCOUNT, env.master.id(), xrpAccount()); COMPARE_KEYLET_FAIL( delegateKeylet, keylet::delegate, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), env.master.id()); COMPARE_KEYLET( depositPreauthKeylet, keylet::depositPreauth, env.master.id(), alice.id()); COMPARE_KEYLET_FAIL( depositPreauthKeylet, keylet::depositPreauth, HostFunctionError::INVALID_PARAMS, env.master.id(), env.master.id()); COMPARE_KEYLET_FAIL( depositPreauthKeylet, keylet::depositPreauth, HostFunctionError::INVALID_ACCOUNT, env.master.id(), xrpAccount()); COMPARE_KEYLET_FAIL( depositPreauthKeylet, keylet::depositPreauth, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), env.master.id()); // escrowKeylet COMPARE_KEYLET(escrowKeylet, keylet::escrow, env.master.id(), 1); COMPARE_KEYLET_FAIL( escrowKeylet, keylet::escrow, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); // lineKeylet Currency usd = to_currency("USD"); COMPARE_KEYLET( lineKeylet, keylet::line, env.master.id(), alice.id(), usd); COMPARE_KEYLET_FAIL( lineKeylet, keylet::line, HostFunctionError::INVALID_PARAMS, env.master.id(), env.master.id(), usd); COMPARE_KEYLET_FAIL( lineKeylet, keylet::line, HostFunctionError::INVALID_ACCOUNT, env.master.id(), xrpAccount(), usd); COMPARE_KEYLET_FAIL( lineKeylet, keylet::line, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), env.master.id(), usd); COMPARE_KEYLET_FAIL( lineKeylet, keylet::line, HostFunctionError::INVALID_PARAMS, env.master.id(), alice.id(), to_currency("")); COMPARE_KEYLET(nftOfferKeylet, keylet::nftoffer, env.master.id(), 1); COMPARE_KEYLET_FAIL( nftOfferKeylet, keylet::nftoffer, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); COMPARE_KEYLET(offerKeylet, keylet::offer, env.master.id(), 1); COMPARE_KEYLET_FAIL( offerKeylet, keylet::offer, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); COMPARE_KEYLET(oracleKeylet, keylet::oracle, env.master.id(), 1); COMPARE_KEYLET_FAIL( oracleKeylet, keylet::oracle, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); COMPARE_KEYLET( paychanKeylet, keylet::payChan, env.master.id(), alice.id(), 1); COMPARE_KEYLET_FAIL( paychanKeylet, keylet::payChan, HostFunctionError::INVALID_PARAMS, env.master.id(), env.master.id(), 1); COMPARE_KEYLET_FAIL( paychanKeylet, keylet::payChan, HostFunctionError::INVALID_ACCOUNT, env.master.id(), xrpAccount(), 1); COMPARE_KEYLET_FAIL( paychanKeylet, keylet::payChan, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), env.master.id(), 1); COMPARE_KEYLET(signersKeylet, keylet::signers, env.master.id()); COMPARE_KEYLET_FAIL( signersKeylet, keylet::signers, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); COMPARE_KEYLET(ticketKeylet, keylet::ticket, env.master.id(), 1); COMPARE_KEYLET_FAIL( ticketKeylet, keylet::ticket, 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()}; ApplyContext ac = createApplyContext(env, ov); 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.has_value()); BEAST_EXPECT(result.value() == msg.size() + data.size()); auto const resultHex = hfs.trace(msg, slice, true); BEAST_EXPECT(resultHex.has_value()); BEAST_EXPECT(resultHex.value() == msg.size() + data.size() * 2); } void testTraceNum() { testcase("traceNum"); 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 number"; int64_t num = 123456789; auto const result = hfs.traceNum(msg, num); BEAST_EXPECT(result.has_value()); BEAST_EXPECT(result.value() == msg.size() + sizeof(num)); } // clang-format off int const normalExp = 15; Bytes const floatIntMin = {0x99, 0x20, 0xc4, 0x9b, 0xa5, 0xe3, 0x53, 0xf8}; // -2^63 Bytes const floatIntZero = {0xd8, 0x40, 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 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 floatMaxXRP = {0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // 2^62-1 Bytes const floatMaxMPT = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // 2^62-1 std::string const invalid = "invalid_data"; // clang-format on void testFloatTrace() { testcase("FloatTrace"); 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() + 14 /* error msg size*/ + invalid.size() * 2); } { auto const result = hfs.traceFloat(msg, makeSlice(floatMaxExp)); BEAST_EXPECT( result && *result == msg.size() + 19 /* string represenation*/); } } 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::min(), -1); BEAST_EXPECT(!result) && BEAST_EXPECT( result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); } { auto const result = hfs.floatFromInt(std::numeric_limits::min(), 4); BEAST_EXPECT(!result) && BEAST_EXPECT( result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); } { auto const result = hfs.floatFromInt(std::numeric_limits::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::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::min(), -1); BEAST_EXPECT(!result) && BEAST_EXPECT( result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); } { auto const result = hfs.floatFromUint(std::numeric_limits::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::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, Number::maxExponent + normalExp + 1, 0); BEAST_EXPECT(!result) && BEAST_EXPECT( result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); } { auto const result = hfs.floatSet(1, IOUAmount::maxExponent + normalExp + 1, 0); BEAST_EXPECT(!result) && BEAST_EXPECT( result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); } { auto const result = hfs.floatSet(1, IOUAmount::minExponent + normalExp - 1, 0); BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); } { auto const result = hfs.floatSet(1, IOUAmount::maxExponent + normalExp, 0); BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxExp); } { auto const result = hfs.floatSet(-1, IOUAmount::maxExponent + normalExp, 0); BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinusMaxExp); } { auto const result = hfs.floatSet(1, IOUAmount::maxExponent + normalExp - 1, 0); BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatPreMaxExp); } { auto const result = hfs.floatSet(IOUAmount::maxMantissa, IOUAmount::maxExponent, 0); BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU); } { auto const result = hfs.floatSet(1, IOUAmount::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(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( IOUAmount::maxMantissa, -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), 40000, 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); } { 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 == 0); } { auto const result = hfs.floatAdd(makeSlice(x), makeSlice(float10), 0); if (BEAST_EXPECT(result)) { auto const result2 = hfs.floatCompare(makeSlice(*result), makeSlice(*y)); BEAST_EXPECT(result2 && *result2 == 0); } } // underflow x[7] = 1; { auto const result = hfs.floatDivide(makeSlice(x), makeSlice(float1More), 0); BEAST_EXPECT( !result && result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); } { auto const result = hfs.floatMultiply( makeSlice(floatMaxXRP), makeSlice(floatIntZero), 0); if (BEAST_EXPECT(result)) { auto const result2 = hfs.floatCompare( makeSlice(*result), makeSlice(floatIntZero)); BEAST_EXPECT(result2 && *result2 == 0); } } // overflow { auto const result = hfs.floatAdd(makeSlice(floatMaxXRP), makeSlice(float1), 0); BEAST_EXPECT( !result && result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); } // 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 == 0); } { auto const result = hfs.floatAdd(makeSlice(x), makeSlice(float10), 0); if (BEAST_EXPECT(result)) { auto const result2 = hfs.floatCompare(makeSlice(*result), makeSlice(*y)); BEAST_EXPECT(result2 && *result2 == 0); } } { auto const result = hfs.floatAdd(makeSlice(floatMaxMPT), makeSlice(float1), 0); BEAST_EXPECT( !result && result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); } } void testFloats() { testFloatFromInt(); testFloatFromUint(); testFloatSet(); testFloatCompare(); testFloatAdd(); testFloatSubtract(); testFloatMultiply(); testFloatDivide(); testFloatRoot(); testFloatPower(); testFloatLog(); testFloatNonIOU(); testFloatTrace(); } void run() override { testGetLedgerSqn(); testGetParentLedgerTime(); testGetParentLedgerHash(); testGetLedgerAccountHash(); testGetLedgerTransactionHash(); 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(); testFloats(); } }; BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, ripple); } // namespace test } // namespace ripple